initial commit

This commit is contained in:
jacob 2024-02-29 16:01:51 -06:00
commit 8284923173
149 changed files with 58632 additions and 0 deletions

27
.gitattributes vendored Normal file
View File

@ -0,0 +1,27 @@
.gitattributes text eol=lf
.gitignore text eol=lf
*.build text eol=lf
*.c text eol=lf
*.hlsl text eol=lf
*.cmake text eol=lf
*.cpp text eol=lf
*.csv text eol=lf
*.f text eol=lf
*.f90 text eol=lf
*.for text eol=lf
*.grc text eol=lf
*.h text eol=lf
*.ipynb text eol=lf
*.m text eol=lf
*.md text eol=lf
*.pas text eol=lf
*.py text eol=lf
*.rst text eol=lf
*.sh text eol=lf
*.txt text eol=lf
*.yml text eol=lf
Makefile text eol=lf
*.tga filter=lfs diff=lfs merge=lfs -text
*.ase filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
.*.swp
*.lnk
*.rdbg
*.10x
*.cap
*.tracy
.vs/*
# Build / output directories
build/
out/

38
.natvis Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name = "string">
<DisplayString Condition="text != 0">{text, [len] s} ({len})</DisplayString>
<DisplayString Condition="text == 0">&lt;NULL&gt;</DisplayString>
<StringView>text, [len]</StringView>
</Type>
<Type Name = "arena">
<DisplayString>pos: {pos}, capacity: [{capacity} / {reserved}]</DisplayString>
<Expand>
<Item Name="Data">base, [pos] s</Item>
<Item Name="Data (Extended)">base, [pos+100] s</Item>
</Expand>
</Type>
<Type Name = "temp_arena">
<DisplayString>start: {start_pos}, arena: {{{*arena}}}</DisplayString>
<Expand>
<Item Name="Data">(arena->base + start_pos), [arena->pos - start_pos] s</Item>
<Item Name="Data (Extended)">(arena->base + start_pos), [arena->pos - start_pos + 100] s</Item>
<Item Name="Size">arena->pos - start_pos</Item>
</Expand>
</Type>
<Type Name = "bitbuf">
<DisplayString>bits: [{cur_bit} / {data_nbits}], bytes: [{cur_bit / 8} / {data_nbits / 8}]</DisplayString>
<Expand>
<ArrayItems>
<Size>(cur_bit + 7) / 8</Size>
<ValuePointer>data, bb</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>

226
CMakeLists.txt Normal file
View File

@ -0,0 +1,226 @@
cmake_minimum_required(VERSION 3.25.1)
project(powerplay)
# Options below are laid out so that running cmake with all "OFF" results in the "release" / "user" build
option(RTC "Should the build compile with runtime checks enabled (asserts, asan, etc.) - REQUIRES CRTLIB ON" OFF)
option(CRTLIB "Should the build link with the CRTLIB" OFF)
option(DEBINFO "Should the build compile with debug info" OFF)
option(DEVELOPER "Should the build compile with developer mode enabled" OFF)
option(PROFILING "Should the build compile with profiling enabled - REQUIRES CRTLIB ON" OFF)
option(UNOPTIMIZED "Should the build compile with optimization disabled" OFF)
################################################################################
# Source files
################################################################################
# Glob source files
file(GLOB_RECURSE sources CONFIGURE_DEPENDS src/*.c src/*.cpp src/*.h)
# Filter platform specific
list(FILTER sources EXCLUDE REGEX "sys_|renderer_|playback_|mp3_|ttf_")
if(PLATFORM_WIN32)
set(sources ${sources} src/sys_win32.c)
set(sources ${sources} src/renderer_d3d11.c)
set(sources ${sources} src/playback_wasapi.c)
set(sources ${sources} src/mp3_mmf.c)
set(sources ${sources} src/ttf_dwrite.cpp)
set(sources ${sources} .natvis)
endif()
# Add tracy
list(FILTER sources EXCLUDE REGEX "third_party")
if(PROFILING)
set(sources ${sources} src/third_party/tracy/TracyClient.cpp)
endif()
list(FILTER sources EXCLUDE REGEX "WIP")
################################################################################
# Resource embedding
################################################################################
set(inc_dependencies "")
# Only archive & embed "res" dir if not compiling a developer build
if(NOT DEVELOPER)
# Generate resource archive
set(resource_archive_path "${CMAKE_BINARY_DIR}/res.tar")
file(GLOB_RECURSE resource_sources CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/res/*)
add_custom_command(
OUTPUT ${resource_archive_path}
COMMAND cmake -E tar cvf ${resource_archive_path} .
DEPENDS ${resource_sources}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res
)
list(APPEND inc_dependencies ${resource_archive_path})
endif()
# Generate shaders archive
set(shaders_archive_path "${CMAKE_BINARY_DIR}/shaders.tar")
file(GLOB_RECURSE shader_sources CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/shaders/*)
add_custom_command(
OUTPUT ${shaders_archive_path}
COMMAND cmake -E tar cvf ${shaders_archive_path} .
DEPENDS ${shader_sources}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/shaders
)
list(APPEND inc_dependencies ${shaders_archive_path})
set_source_files_properties(src/inc.c OBJECT_DEPENDS "${inc_dependencies}")
################################################################################
# RC file (windows)
################################################################################
set(rc_res_sources "")
if(PLATFORM_WIN32)
set(RC_PATH "${CMAKE_BINARY_DIR}/rc.rc")
set(RC_RES_PATH "${CMAKE_BINARY_DIR}/rc.res")
# Add icon resource to .rc
set(ICON_SOURCE_PATH "${CMAKE_SOURCE_DIR}/icon.ico")
set(ICON_COPY_PATH "${CMAKE_BINARY_DIR}/icon.ico")
if(EXISTS ${ICON_SOURCE_PATH})
# Copy icon to output dir
add_custom_command(
OUTPUT ${ICON_COPY_PATH}
COMMAND ${CMAKE_COMMAND} -E copy ${ICON_SOURCE_PATH} ${ICON_COPY_PATH}
DEPENDS ${ICON_SOURCE_PATH}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Insert icon into .rc
add_custom_command(
OUTPUT ${RC_PATH}
COMMAND break > ${RC_PATH}
COMMAND echo IDI_ICON ICON DISCARDABLE icon.ico >> ${RC_PATH}
DEPENDS ${ICON_COPY_PATH}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Compile rc.rc -> rc.res
add_custom_command(
OUTPUT ${RC_RES_PATH}
COMMAND llvm-rc ${RC_PATH}
DEPENDS ${ICON_COPY_PATH} ${RC_PATH}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(build_rc_res ALL DEPENDS ${RC_RES_PATH})
# Add to list of res_sources
set(rc_res_sources ${rc_res_sources} ${RC_RES_PATH})
endif()
endif()
################################################################################
# Executable
################################################################################
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Add executable
add_executable(powerplay_exe ${sources})
set_target_properties(
powerplay_exe PROPERTIES
C_STANDARD 99
OUTPUT_NAME "${PROJECT_NAME}.exe"
)
# Add precompiled header
target_precompile_headers(powerplay_exe PRIVATE src/common.h)
################################################################################
# Compiler flags
################################################################################
# TODO:
# Enable -
# -Wconversion \
# -Wno-sign-conversion \
# Common flags
set(COMPILER_FLAGS "
-fno-strict-aliasing \
-msse4.2 \
")
set(COMPILER_WARNINGS " \
-Weverything -Werror \
-Wno-unused-macros -Wno-gnu-zero-variadic-macro-arguments -Wno-documentation \
-Wno-old-style-cast -Wno-reserved-identifier -Wno-reserved-macro-identifier \
-Wno-conversion -Wno-sign-conversion -Wno-declaration-after-statement -Wno-extra-semi \
-Wno-extra-semi-stmt -Wno-bad-function-cast -Wno-class-varargs \
-Wno-unreachable-code-break -Wno-cast-align -Wno-float-equal \
-Wno-zero-as-null-pointer-constant -Wno-cast-qual -Wno-missing-noreturn \
-Wno-missing-field-initializers -Wno-missing-braces -Wno-initializer-overrides \
-Wno-c99-extensions -Wno-c++98-compat-pedantic -Wno-c++98-compat \
-Wno-switch-enum -Wno-unsafe-buffer-usage \
")
# -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-parameter
set(LINKER_FLAGS " \
-fuse-ld=lld-link \
${rc_res_sources} \
-luser32.lib -lkernel32.lib -lgdi32.lib -lshell32.lib -ldwmapi.lib -lole32.lib -ld3d11.lib -ld3dcompiler.lib -ldxgi.lib -ldxguid.lib -ldwrite.lib -lwinmm.lib -ladvapi32.lib -lmfplat.lib -lmfreadwrite.lib -lavrt.lib -lshlwapi.lib \
")
# RTC (Runtime checks)
if (RTC)
if (NOT CRTLIB)
message(FATAL_ERROR "CRTLIB (C runtime library) Must be enabled when compiling with RTC (runtime checks)")
endif()
# NOTE: Adress sanitizer is disable for now because for some reason it's hiding local variables in debug mode.
# set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fno-sanitize=alignment -DRTC=1")
set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fsanitize-trap=undefined -fno-sanitize=alignment -DRTC=1")
# set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -DRTC=1")
endif()
# CRTLIB (C runtime library)
if (CRTLIB)
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DCRTLIB=1")
else()
set(COMPILER_FLAGS "${COMPILER_FLAGS} -mno-stack-arg-probe -fno-builtin")
set(LINKER_FLAGS "${LINKER_FLAGS} -nostdlib")
endif()
# Optimization
if (UNOPTIMIZED)
set(COMPILER_FLAGS "${COMPILER_FLAGS} -O0 -DUNOPTIMIZED=1")
set(LINKER_FLAGS "${LINKER_FLAGS} -O0 -DUNOPTIMIZED=1")
else()
set(COMPILER_FLAGS "${COMPILER_FLAGS} -O3 -flto")
set(LINKER_FLAGS "${LINKER_FLAGS} -O3 -flto -fwhole-program")
endif()
# Debug info
if (DEBINFO)
set(COMPILER_FLAGS "${COMPILER_FLAGS} -g -DDEBINFO=1")
endif()
# Developer mode
if(DEVELOPER)
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DDEVELOPER=1")
endif()
# Profiling
if(PROFILING)
if (NOT CRTLIB)
message(FATAL_ERROR "CRTLIB (C runtime library) Must be enabled when compiling with PROFILING")
endif()
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DPROFILING=1")
# Tracy flags
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DTRACY_ENABLE=1")
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DTRACY_NO_SAMPLING=1")
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DTRACY_NO_SYSTEM_TRACING=1")
set(COMPILER_FLAGS "${COMPILER_FLAGS} -DTRACY_NO_CALLSTACK=1")
# Disable warnings when compiling tracy client
set(COMPILER_WARNINGS "-Wno-everything")
endif()
set(CMAKE_C_FLAGS "${COMPILER_FLAGS} ${COMPILER_WARNINGS} -std=c99")
set(CMAKE_CXX_FLAGS "${COMPILER_FLAGS} ${COMPILER_WARNINGS} -std=c++14")
set(CMAKE_EXE_LINKER_FLAGS "${LINKER_FLAGS}")

111
build.bat Normal file
View File

@ -0,0 +1,111 @@
@echo off
setlocal
:: Description of build options:
::
:: - Configuration
:: 1. debug: The target is intended to run in a debugger
:: 3. profiling: The target is compiled with optimizations, debug info, and profiler timing info
:: 2. release: The target is compiled with optimizations and no debug info
::
:: - Platform
:: 1. developer: The target will include all developer tooling
:: 2. user: The target will not include any developer tooling
where /q cmake || (
echo ERROR: "cmake" not found - please install it and add the executable to your path
exit /b 1
)
where /q clang.exe || (
echo ERROR: "clang.exe" not found - please run this from the MSVC x64 native tools command prompt.
exit /b 1
)
if "%Platform%" neq "x64" (
echo ERROR: Platform is not "x64" - please run this from the MSVC x64 native tools command prompt.
exit /b 1
)
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Configuration
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set config_default=-DRTC=0 -DCRTLIB=0 -DDEBINFO=0 -DDEVELOPER=0 -DPROFILING=0 -DUNOPTIMIZED=0
:: Arg1 -> compiler options mappings
set config1_debug=-DRTC=1 -DCRTLIB=1 -DDEBINFO=1 -DUNOPTIMIZED=1
set config1_profiling=-DPROFILING=1 -DCRTLIB=1 -DDEBINFO=1
set config1_release=
:: Arg2 -> compiler options mappings
set config2_developer=-DDEVELOPER=1
set config2_user=
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Check args
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set argerror=false
:: Check arg 1
set arg_config=%1%
if /I "%arg_config%" neq "debug" (
if /I "%arg_config%" neq "profiling" (
if /I "%arg_config%" neq "release" (
echo ERROR: must specify either 'debug', 'release', or 'profiling' as first argument
set argerror=true
)
)
)
:: Check arg 2
set arg_platform=%2%
if /I "%arg_platform%" neq "developer" (
if /I "%arg_platform%" neq "user" (
echo ERROR: must specify either 'developer' or 'user' as second argument
set argerror=true
)
)
:: Exit if error
if %argerror% neq false (
exit /b 1
)
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Choose configuration from args
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set opt_config1=
if "%arg_config%" equ "debug" set opt_config1=%config1_debug%
if "%arg_config%" equ "profiling" set opt_config1=%config1_profiling%
if "%arg_config%" equ "release" set opt_config1=%config1_release%
set opt_config2=
if "%arg_platform%" equ "developer" set opt_config2=%config2_developer%
if "%arg_platform%" equ "user" set opt_config2=%config2_user%
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Build
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
if not exist "build" mkdir build
echo Calling cmake with options: %opt_config1% %opt_config2%
:: https://stackoverflow.com/a/46593308
cmake -H. -G Ninja -B build %config_default% %opt_config1% %opt_config2%^
-DCMAKE_C_COMPILER:PATH="clang.exe"^
-DCMAKE_CXX_COMPILER:PATH="clang.exe"^
-DCMAKE_C_COMPILER_ID="Clang"^
-DCMAKE_CXX_COMPILER_ID="Clang"^
-DCMAKE_SYSTEM_NAME="Generic"^
-DPLATFORM_WIN32=1
if NOT %errorlevel% == 0 exit /b 1
cmake --build build
@REM cmake --build build -v
exit /b %errorlevel%

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

24
profile.bat Normal file
View File

@ -0,0 +1,24 @@
@echo off
:: `ping` is being used in place of `TIMEOUT`
:: https://www.ibm.com/support/pages/timeout-command-run-batch-job-exits-immediately-and-returns-error-input-redirection-not-supported-exiting-process-immediately
taskkill /im tracy.exe /f 2> nul
start %UserProfile%\Home\apps\tracy\capture.exe -o .tracy -f
echo Launching app...
build\bin\powerplay.exe
if NOT %errorlevel% == 0 (
echo.
echo Program failed
pause
exit /b 1
)
:: Give time for trace file to finish before opening tracy
ping -n 2 127.0.0.1 >NUL
echo Launching tracy...
start "" "%UserProfile%\Home\apps\tracy\Tracy.exe" ".tracy"

BIN
res/fonts/fixedsys.ttf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
res/graphics/timmy.ase (Stored with Git LFS) Normal file

Binary file not shown.

156
src/app.c Normal file
View File

@ -0,0 +1,156 @@
#include "app.h"
#include "arena.h"
#include "string.h"
#include "scratch.h"
#include "sys.h"
#include "work.h"
#include "user.h"
#include "game.h"
#include "playback.h"
#include "log.h"
#include "console.h"
#include "resource.h"
#include "asset_cache.h"
#include "font.h"
#include "texture.h"
#include "ttf.h"
#include "sheet.h"
#include "mixer.h"
#include "sound.h"
#include "util.h"
#include "settings.h"
#include "draw.h"
#define WRITE_DIR "power_play"
GLOBAL struct {
struct arena arena;
struct string write_path;
struct sync_flag quit_sf;
} L = { 0 } DEBUG_LVAR(L_app);
/* ========================== *
* Write directory
* ========================== */
INTERNAL struct string initialize_write_directory(struct arena *arena, struct string write_dir)
{
struct temp_arena scratch = scratch_begin(arena);
/* Create write path */
struct string base_write_dir = sys_get_write_path(scratch.arena);
struct string write_path_fmt = base_write_dir.len > 0 ? STR("%F/%F/") : STR("%F%F/");
struct string write_path = string_format(
arena,
write_path_fmt,
FMT_STR(base_write_dir),
FMT_STR(write_dir)
);
/* Create write dir if not present */
if (!sys_is_dir(write_path)) {
sys_mkdir(write_path);
/* TODO: handle failure */
}
scratch_end(scratch);
return write_path;
}
struct string app_write_path_cat(struct arena *arena, struct string filename)
{
return string_cat(arena, L.write_path, filename);
}
/* ========================== *
* Entry point
* ========================== */
void app_entry_point(void)
{
L.quit_sf = sync_flag_alloc();
u32 worker_count = 4;
{
/* FIXME: Switch this on to utilize all cores. Only decreasing worker count for testing purposes. */
#if !PROFILING && !RTC
/* 1. User thread, Input thread
* 2. Game thread
* 3. Playback thread
*/
u32 num_reserved_cores = 3;
i32 min_worker_count = 2;
i32 max_worker_count = 512;
i32 target_worker_count = (i32)sys_num_logical_processors() - num_reserved_cores;
worker_count = (u32)clamp_i32(target_worker_count, min_worker_count, max_worker_count);
#endif
}
L.arena = arena_alloc(GIGABYTE(64));
L.write_path = initialize_write_directory(&L.arena, STR(WRITE_DIR));
/* Startup logging */
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct string logfile_path = app_write_path_cat(scratch.arena, STR("log.txt"));
log_startup(logfile_path);
scratch_end(scratch);
}
logf_info("Startup");
console_startup();
/* Startup window & renderer */
struct sys_window window = sys_window_alloc();
/* Read window settings from file */
struct sys_window_settings window_settings = settings_default_window_settings(&window);
settings_read_from_file(&window_settings);
sys_window_update_settings(&window, &window_settings);
renderer_startup(&window);
/* Startup subsystems */
resource_startup();
asset_cache_startup();
ttf_startup();
font_startup();
texture_startup();
sheet_startup();
mixer_startup();
sound_startup();
draw_startup();
/* Startup threaded systems */
user_startup(&window);
work_startup(worker_count);
game_startup();
playback_startup();
sys_window_show(&window);
/* Wait for app_quit() */
sync_flag_wait(&L.quit_sf);
/* Shutdown threaded systems */
/* FIXME: Only wait on threads for a certain period of time before
* forcing process exit (to prevent process hanging in the background
* when a thread gets stuck) */
playback_shutdown();
game_shutdown();
work_shutdown();
user_shutdown();
/* Write window settings to file */
struct sys_window_settings settings = sys_window_get_settings(&window);
settings_write_to_file(&settings);
logf_info("Program exited normally");
}
void app_quit(void)
{
sync_flag_set(&L.quit_sf);
}

9
src/app.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef APP_H
#define APP_H
struct string app_write_path_cat(struct arena *arena, struct string filename);
void app_entry_point(void);
void app_quit(void);
#endif

103
src/arena.c Normal file
View File

@ -0,0 +1,103 @@
#include "arena.h"
#include "sys.h"
#include "memory.h"
#include "string.h"
/* Arbitrary block size */
#define ARENA_BLOCK_SIZE 4096
/* NOTE: Application will exit if arena fails to reserve or commit initial
* memory. */
struct arena arena_alloc(u64 reserve)
{
struct arena arena = { 0 };
/* Round up to nearest block size */
u64 block_remainder = reserve % ARENA_BLOCK_SIZE;
if (block_remainder > 0) {
reserve += ARENA_BLOCK_SIZE - block_remainder;
}
arena.base = sys_memory_reserve(reserve);
if (!arena.base) {
/* Hard fail on memory reserve failure for now */
sys_panic_raw("Arena initialization error: Failed to reserve arena memory");
}
arena.reserved = reserve;
/* Commit one block to start with */
arena.base = sys_memory_commit(arena.base, ARENA_BLOCK_SIZE);
__profalloc(arena.base, ARENA_BLOCK_SIZE);
ASAN_POISON(arena.base, ARENA_BLOCK_SIZE);
if (!arena.base) {
/* Hard fail on commit failure */
sys_panic_raw("Arena initialization error: Failed to commit initial arena memory");
}
arena.committed = ARENA_BLOCK_SIZE;
/* Arena should be 64k aligned */
ASSERT(((u64)arena.base & 0xFFFF) == 0);
return arena;
}
void arena_release(struct arena *arena)
{
sys_memory_decommit(arena->base, arena->committed);
sys_memory_release(arena->base);
}
/* NOTE: Application will exit if arena fails to commit memory */
void *_arena_push_bytes(struct arena *arena, u64 size, u64 align)
{
__prof;
ASSERT(align > 0);
void *start = NULL;
/* TODO: Remove this check? (Only here to avoid aligning when size = 0) */
if (size > 0) {
u64 aligned_start_pos = (arena->pos + (align - 1));
aligned_start_pos -= aligned_start_pos % align;
u64 new_pos = aligned_start_pos + size;
if (new_pos > arena->committed) {
/* Commit new block(s) */
u64 blocks_needed = (new_pos - arena->committed + ARENA_BLOCK_SIZE - 1) / ARENA_BLOCK_SIZE;
u64 commit_bytes = blocks_needed * ARENA_BLOCK_SIZE;
u64 new_capacity = arena->committed + commit_bytes;
if (new_capacity > arena->reserved) {
/* Hard fail if we overflow reserved memory for now */
sys_panic_raw("Failed to commit new arena memory (Overflow of reserved memory)");
}
void *commit_address = arena->base + arena->committed;
if (!sys_memory_commit(commit_address, commit_bytes)) {
/* Hard fail on memory allocation failure for now */
sys_panic_raw("Failed to commit new arena memory (System out of memory?)");
}
__proffree(arena->base);
__profalloc(arena->base, arena->committed + commit_bytes);
ASAN_POISON(commit_address, commit_bytes);
arena->committed += commit_bytes;
}
start = arena->base + aligned_start_pos;
arena->pos = new_pos;
ASAN_UNPOISON(start, (arena->base + arena->pos) - (u8 *)start);
} else {
start = arena->base + arena->pos;
}
return start;
}
void arena_decommit_unused_blocks(struct arena *arena)
{
__prof;
u64 next_block_pos = ARENA_BLOCK_SIZE * ((arena->pos + (ARENA_BLOCK_SIZE - 1)) / ARENA_BLOCK_SIZE);
if (arena->committed > next_block_pos) {
u8 *decommit_start = arena->base + next_block_pos;
u64 decommit_size = (arena->base + arena->committed) - decommit_start;
sys_memory_decommit(decommit_start, decommit_size);
arena->committed = next_block_pos;
}
}

106
src/arena.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef ARENA_H
#define ARENA_H
#include "memory.h"
// #define arena_clear(a) (a->pos = 0)
#define arena_push(a, type) ((type *)_arena_push_bytes((a), sizeof(type), ALIGNOF(type)))
#define arena_push_zero(a, type) ((type *)_arena_push_bytes_zero((a), sizeof(type), ALIGNOF(type)))
#define arena_push_array(a, type, n) ((type *)_arena_push_bytes((a), (sizeof(type) * (n)), ALIGNOF(type)))
#define arena_push_array_zero(a, type, n) ((type *)_arena_push_bytes_zero((a), (sizeof(type) * (n)), ALIGNOF(type)))
/* Returns a pointer to where the next allocation would be (at alignment of type).
* Equivalent arena_push but without actually allocating anything. */
#define arena_dry_push(a, type) (type *)(_arena_dry_push((a), ALIGNOF(type)))
#define arena_align(a, align) (void *)(_arena_align((a), align))
struct temp_arena {
struct arena *arena;
u64 start_pos;
#if RTC
u64 scratch_id;
#endif
};
struct arena arena_alloc(u64 reserve);
void arena_release(struct arena *arena);
void *_arena_push_bytes(struct arena *arena, u64 size, u64 align);
void arena_decommit_unused_blocks(struct arena *arena);
INLINE void *_arena_push_bytes_zero(struct arena *arena, u64 size, u64 align)
{
__prof;
void *p = _arena_push_bytes(arena, size, align);
{
__profscope(_arena_push_bytes_zero_MEMZERO);
MEMZERO(p, size);
}
return p;
}
INLINE void arena_pop_to(struct arena *arena, u64 pos)
{
__prof;
ASSERT(arena->pos >= pos);
ASAN_POISON(arena->base + pos, arena->pos - pos);
arena->pos = pos;
}
INLINE struct temp_arena arena_push_temp(struct arena *arena)
{
__prof;
struct temp_arena t;
t.arena = arena;
t.start_pos = arena->pos;
return t;
}
INLINE void arena_pop_temp(struct temp_arena temp)
{
arena_pop_to(temp.arena, temp.start_pos);
}
INLINE void arena_reset(struct arena *arena)
{
arena_pop_to(arena, 0);
}
INLINE struct buffer arena_to_buffer(struct arena *arena)
{
struct buffer b;
b.data = arena->base;
b.size = arena->pos;
return b;
}
INLINE void *_arena_dry_push(struct arena *arena, u64 align)
{
u64 aligned_start_pos = (arena->pos + (align - 1));
aligned_start_pos -= aligned_start_pos % align;
void *ptr = arena->base + aligned_start_pos;
return ptr;
}
INLINE void *_arena_align(struct arena *arena, u64 align)
{
if (align > 0) {
u64 aligned_start_pos = (arena->pos + (align - 1));
aligned_start_pos -= aligned_start_pos % align;
u64 align_bytes = aligned_start_pos - (u64)arena->pos;
if (align_bytes > 0) {
return (void *)arena_push_array(arena, u8, align_bytes);
} else {
return (void *)arena->pos;
}
} else {
/* 0 alignment */
ASSERT(false);
return (void *)arena->pos;
}
}
#endif

887
src/ase.c Normal file
View File

@ -0,0 +1,887 @@
/* ========================== *
* Aseprite (.ase) file parser
*
* DEFLATE decoder based on Handmade Hero's png parser
* ========================== */
#include "ase.h"
#include "arena.h"
#include "scratch.h"
#include "byteio.h"
#include "string.h"
#include "log.h"
/* ========================== *
* Bitbuf
* ========================== */
struct bitbuf {
u8 *data;
u64 cur_bit;
};
INTERNAL u32 peek_bits(struct bitbuf *bb, u32 nbits)
{
ASSERT(nbits <= 32);
u64 cur_byte = bb->cur_bit >> 3;
u8 bit_index = bb->cur_bit % 8;
u64 val64 = *(u64 *)&bb->data[cur_byte];
u32 val32 = (u32)(val64 >> bit_index);
val32 &= U32_MAX >> (32 - nbits);
return val32;
}
INTERNAL u32 consume_bits(struct bitbuf *bb, u32 nbits)
{
u32 val = peek_bits(bb, nbits);
bb->cur_bit += nbits;
return val;
}
INTERNAL void skip_bits(struct bitbuf *bb, u32 nbits)
{
bb->cur_bit += nbits;
}
/* ========================== *
* Inflate
* ========================== */
#define HUFFMAN_BIT_COUNT 16
enum block_type {
BLOCK_TYPE_UNCOMPRESSED = 0,
BLOCK_TYPE_COMPRESSED_FIXED = 1,
BLOCK_TYPE_COMPRESSED_DYNAMIC = 2,
BLOCK_TYPE_RESERVED = 3
};
struct huffman_entry {
u16 symbol;
u16 bits_used;
};
struct huffman {
u32 max_code_bits;
u32 entries_count;
struct huffman_entry *entries;
};
GLOBAL const u32 g_hclen_order[] = {
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
};
GLOBAL const struct huffman_entry g_length_table[] = {
{3, 0}, /* 257 */
{4, 0}, /* 258 */
{5, 0}, /* 259 */
{6, 0}, /* 260 */
{7, 0}, /* 261 */
{8, 0}, /* 262 */
{9, 0}, /* 263 */
{10, 0}, /* 264 */
{11, 1}, /* 265 */
{13, 1}, /* 266 */
{15, 1}, /* 267 */
{17, 1}, /* 268 */
{19, 2}, /* 269 */
{23, 2}, /* 270 */
{27, 2}, /* 271 */
{31, 2}, /* 272 */
{35, 3}, /* 273 */
{43, 3}, /* 274 */
{51, 3}, /* 275 */
{59, 3}, /* 276 */
{67, 4}, /* 277 */
{83, 4}, /* 278 */
{99, 4}, /* 279 */
{115, 4}, /* 280 */
{131, 5}, /* 281 */
{163, 5}, /* 282 */
{195, 5}, /* 283 */
{227, 5}, /* 284 */
{258, 0}, /* 285 */
};
GLOBAL const struct huffman_entry g_dist_table[] = {
{1, 0}, /* 0 */
{2, 0}, /* 1 */
{3, 0}, /* 2 */
{4, 0}, /* 3 */
{5, 1}, /* 4 */
{7, 1}, /* 5 */
{9, 2}, /* 6 */
{13, 2}, /* 7 */
{17, 3}, /* 8 */
{25, 3}, /* 9 */
{33, 4}, /* 10 */
{49, 4}, /* 11 */
{65, 5}, /* 12 */
{97, 5}, /* 13 */
{129, 6}, /* 14 */
{193, 6}, /* 15 */
{257, 7}, /* 16 */
{385, 7}, /* 17 */
{513, 8}, /* 18 */
{769, 8}, /* 19 */
{1025, 9}, /* 20 */
{1537, 9}, /* 21 */
{2049, 10}, /* 22 */
{3073, 10}, /* 23 */
{4097, 11}, /* 24 */
{6145, 11}, /* 25 */
{8193, 12}, /* 26 */
{12289, 12}, /* 27 */
{16385, 13}, /* 28 */
{24577, 13}, /* 29 */
};
GLOBAL const u32 g_fixed_bl_counts[][2] = {
{143, 8},
{255, 9},
{279, 7},
{287, 8},
{319, 5},
};
INTERNAL u32 reverse_bits(u32 v, u32 bit_count)
{
/* 7 & 15 seem to be the most common bit_counts, so a
* more optimal path is layed out for them. */
if (bit_count == 15) {
u32 b1 = v & 0xFF;
b1 = (b1 & 0xF0) >> 4 | (b1 & 0x0F) << 4;
b1 = (b1 & 0xCC) >> 2 | (b1 & 0x33) << 2;
b1 = (b1 & 0xAA) >> 1 | (b1 & 0x55) << 1;
u32 b2 = (v & 0xFF00) >> 8;
b2 = (b2 & 0xF0) >> 4 | (b2 & 0x0F) << 4;
b2 = (b2 & 0xCC) >> 2 | (b2 & 0x33) << 2;
b2 = (b2 & 0xAA) >> 1 | (b2 & 0x55) << 1;
b2 >>= 1;
return (b1 << 7) | b2;;
} else if (bit_count == 7) {
v = (v & 0xF0) >> 4 | (v & 0x0F) << 4;
v = (v & 0xCC) >> 2 | (v & 0x33) << 2;
v = (v & 0xAA) >> 1 | (v & 0x55) << 1;
return v >> 1;
} else {
u32 res = 0;
for (u32 i = 0; i <= (bit_count / 2); ++i) {
u32 inv = (bit_count - (i + 1));
res |= ((v >> i) & 0x1) << inv;
res |= ((v >> inv) & 0x1) << i;
}
return res;
}
}
INTERNAL struct huffman huffman_init(struct arena *arena, u32 max_code_bits, u32 *bl_counts, u32 bl_counts_count)
{
__prof;
struct huffman res = { 0 };
res.max_code_bits = max_code_bits;
res.entries_count = (1 << max_code_bits);
res.entries = arena_push_array(arena, struct huffman_entry, res.entries_count);
u32 code_length_hist[HUFFMAN_BIT_COUNT] = { 0 };
for (u32 i = 0; i < bl_counts_count; ++i) {
u32 count = bl_counts[i];
ASSERT(count <= ARRAY_COUNT(code_length_hist));
++code_length_hist[count];
}
u32 next_code[HUFFMAN_BIT_COUNT] = { 0 };
next_code[0] = 0;
code_length_hist[0] = 0;
for (u32 i = 1; i < ARRAY_COUNT(next_code); ++i) {
next_code[i] = ((next_code[i - 1] + code_length_hist[i - 1]) << 1);
}
for (u32 i = 0; i < bl_counts_count; ++i) {
u32 code_bits = bl_counts[i];
if (code_bits) {
ASSERT(code_bits < ARRAY_COUNT(next_code));
u32 code = next_code[code_bits]++;
u32 arbitrary_bits = res.max_code_bits - code_bits;
u32 entry_count = (1 << arbitrary_bits);
for (u32 entry_index = 0; entry_index < entry_count; ++entry_index) {
/* TODO: Optimize this. It's bloating up the loading times. */
u32 base_index = (code << arbitrary_bits) | entry_index;
u32 index = reverse_bits(base_index, res.max_code_bits);
struct huffman_entry *entry = &res.entries[index];
entry->symbol = (u16)i;
entry->bits_used = (u16)code_bits;
}
}
}
return res;
}
INTERNAL u16 huffman_decode(struct huffman *huffman, struct bitbuf *bb)
{
u32 index = peek_bits(bb, huffman->max_code_bits);
ASSERT(index < huffman->entries_count);
struct huffman_entry *entry = &huffman->entries[index];
u16 res = entry->symbol;
skip_bits(bb, entry->bits_used);
ASSERT(entry->bits_used > 0);
return res;
}
INTERNAL void inflate(struct arena *arena, u8 *dest, u8 *encoded)
{
__prof;
struct bitbuf bb = { encoded };
/* ZLIB header */
u32 cm = consume_bits(&bb, 4);
u32 cinfo = consume_bits(&bb, 4);
ASSERT(cm == 8);
ASSERT(cinfo = 7);
u32 fcheck = consume_bits(&bb, 5);
u32 fdict = consume_bits(&bb, 1);
u32 flevl = consume_bits(&bb, 2);
ASSERT(fdict == 0);
u8 cmf = (u8)(cm | (cinfo << 4));
u8 flg = fcheck | (fdict << 5) | (flevl << 6);
(UNUSED)cmf;
(UNUSED)flg;
ASSERT(((cmf * 256) + flg) % 31 == 0);
u8 bfinal = 0;
while (!bfinal) {
bfinal = consume_bits(&bb, 1);
u8 btype = consume_bits(&bb, 2);
switch (btype) {
case BLOCK_TYPE_UNCOMPRESSED: {
sys_panic(STR("Unsupported block type while inflating ase: BLOCK_TYPE_UNCOMPRESSED"));
} break;
case BLOCK_TYPE_COMPRESSED_FIXED:
case BLOCK_TYPE_COMPRESSED_DYNAMIC: {
struct temp_arena scratch = scratch_begin(arena);
u32 lit_len_dist_table[512] = { 0 };
u32 hlit;
u32 hdist;
if (btype == BLOCK_TYPE_COMPRESSED_DYNAMIC) {
/* Dynamic table */
/* Read huffman table */
hlit = consume_bits(&bb, 5) + 257;
hdist = consume_bits(&bb, 5) + 1;
u32 hclen = consume_bits(&bb, 4) + 4;
/* Init dict huffman (hclen) */
u32 hclen_bl_counts[19] = { 0 };
for (u32 i = 0; i < hclen; ++i) {
u32 code = g_hclen_order[i];
hclen_bl_counts[code] = consume_bits(&bb, 3);
}
struct huffman dict_huffman = huffman_init(scratch.arena, 7, hclen_bl_counts, ARRAY_COUNT(hclen_bl_counts));
/* Decode dict huffman */
u32 lit_len_count = 0;
u32 len_count = hlit + hdist;
ASSERT(len_count <= ARRAY_COUNT(lit_len_dist_table));
while (lit_len_count < len_count) {
u32 rep_count = 1;
u32 rep_val = 0;
u32 encoded_len = huffman_decode(&dict_huffman, &bb);
if (encoded_len <= 15) {
rep_val = encoded_len;
} else if (encoded_len == 16) {
rep_count = 3 + consume_bits(&bb, 2);
ASSERT(lit_len_count > 0);
rep_val = lit_len_dist_table[lit_len_count - 1];
} else if (encoded_len == 17) {
rep_count = 3 + consume_bits(&bb, 3);
} else if (encoded_len == 18) {
rep_count = 11 + consume_bits(&bb, 7);
} else {
/* Invalid len */
ASSERT(false);
}
while (rep_count--) {
lit_len_dist_table[lit_len_count++] = rep_val;
}
}
ASSERT(lit_len_count == len_count);
} else {
/* Fixed table */
hlit = 288;
hdist = 32;
u32 index = 0;
for (u32 i = 0; i < ARRAY_COUNT(g_fixed_bl_counts); ++i) {
u32 bit_count = g_fixed_bl_counts[i][1];
u32 last_valuie = g_fixed_bl_counts[i][0];
while (index <= last_valuie) {
lit_len_dist_table[index++] = bit_count;
}
}
}
/* Decode */
struct huffman lit_len_huffman = huffman_init(scratch.arena, 15, lit_len_dist_table, hlit);
struct huffman dist_huffman = huffman_init(scratch.arena, 15, lit_len_dist_table + hlit, hdist);
while (true) {
u32 lit_len = huffman_decode(&lit_len_huffman, &bb);
if (lit_len <= 255) {
*dest++ = lit_len & 0xFF;
} else if (lit_len >= 257) {
u32 length_index = (lit_len - 257);
struct huffman_entry length_entry = g_length_table[length_index];
u32 length = length_entry.symbol;
if (length_entry.bits_used > 0) {
u32 extra_bits = consume_bits(&bb, length_entry.bits_used);
length += extra_bits;
}
u32 dist_index = huffman_decode(&dist_huffman, &bb);
struct huffman_entry dist_entry = g_dist_table[dist_index];
u32 distance = dist_entry.symbol;
if (dist_entry.bits_used > 0) {
u32 extra_bits = consume_bits(&bb, dist_entry.bits_used);
distance += extra_bits;
}
u8 *source = dest - distance;
while (length--) {
*dest++ = *source++;
}
} else {
break;
}
}
scratch_end(scratch);
} break;
case BLOCK_TYPE_RESERVED: {
/* TODO */
ASSERT(false);
} break;
}
}
}
/* ========================== *
* Decode image
* ========================== */
#define LAYER_FLAG_NONE 0x0
#define LAYER_FLAG_VISIBLE 0x1
enum chunk_type {
CHUNK_TYPE_OLD_PALETTE1 = 0x0004,
CHUNK_TYPE_OLD_PALETTE2 = 0x0011,
CHUNK_TYPE_LAYER = 0x2004,
CHUNK_TYPE_CEL = 0x2005,
CHUNK_TYPE_CEL_EXTRA = 0x2006,
CHUNK_TYPE_COLOR_PROFILE = 0x2007,
CHUNK_TYPE_EXTERNAL_FILES = 0x2008,
CHUNK_TYPE_MASK = 0x2016,
CHUNK_TYPE_PATH = 0x2017,
CHUNK_TYPE_TAGS = 0x2018,
CHUNK_TYPE_PALETTE = 0x2019,
CHUNK_TYPE_USER_DATA = 0x2020,
CHUNK_TYPE_SLICE = 0x2022,
CHUNK_TYPE_TILESET = 0x2023
};
enum cel_type {
CEL_TYPE_RAW_IMAGE = 0,
CEL_TYPE_LINKED = 1,
CEL_TYPE_COMPRESSED_IMAGE = 2,
CEL_TYPE_COMPRESSED_TILEMAP = 3
};
struct ase_header {
u32 file_size;
u16 magic;
u16 frames;
u16 width;
u16 height;
u16 color_depth;
u32 flags;
u16 speed;
u32 _1;
u32 _2;
u8 palette_entry;
u8 _3[3];
u16 num_colors;
u8 pixel_width;
u8 pixel_height;
i16 grid_x;
i16 grid_y;
u16 grid_width;
u16 grid_height;
u8 _4[84];
} PACKED;
struct frame_header {
u32 bytes;
u16 magic;
u16 chunks_old;
u16 frame_duration_ms;
u8 _[2];
u32 chunks_new;
} PACKED;
/* ========================== *
* Image decoder
* ========================== */
struct layer {
u16 flags;
u16 type;
u16 child_level;
u16 blend_mode;
u8 opacity;
struct string name;
u32 tileset_index;
u32 index;
struct layer *next;
};
struct cel {
u16 layer_index;
i16 x_pos;
i16 y_pos;
u8 opacity;
enum cel_type type;
i16 z_index;
/* Linked cel */
u16 frame_pos;
/* Compressed image */
u32 width;
u32 height;
u32 *pixels;
u16 frame_index;
struct cel *next;
};
/* Taken from
* https://github.com/RandyGaul/cute_headers/blob/master/cute_aseprite.h#L870 */
INTERNAL u32 mul_u8(u32 a, u32 b)
{
u32 t = (a * b) + 0x80;
return ((t >> 8) + t) >> 8;
}
INTERNAL u32 blend(u32 src, u32 dest, u8 opacity)
{
u32 dest_r = (dest & 0xff);
u32 dest_g = (dest >> 8) & 0xff;
u32 dest_b = (dest >> 16) & 0xff;
u32 dest_a = (dest >> 24) & 0xff;
u32 src_r = (src & 0xff);
u32 src_g = (src >> 8) & 0xff;
u32 src_b = (src >> 16) & 0xff;
u32 src_a = (src >> 24) & 0xff;
src_a = (u8)mul_u8(src_a, opacity);
u32 a = src_a + dest_a - mul_u8(src_a, dest_a);
u32 r, g, b;
if (a == 0) {
r = g = b = 0;
} else {
r = dest_r + (src_r - dest_r) * src_a / a;
g = dest_g + (src_g - dest_g) * src_a / a;
b = dest_b + (src_b - dest_b) * src_a / a;
}
return r | (g << 8) | (b << 16) | (a << 24);
}
INTERNAL void make_image_dimensions_squareish(struct ase_header *header, u32 *frames_x, u32 *frames_y, u64 *image_width, u64 *image_height)
{
/* Try and get image resolution into as much of a square as possible by
* separating frames into multiple rows. */
while (*frames_x > 1) {
u64 new_frames_x = *frames_x - 1;
u64 new_frames_y = ((header->frames - 1) / new_frames_x) + 1;
u64 new_image_width = header->width * new_frames_x;
u64 new_image_height = header->height * new_frames_y;
if (new_image_width >= new_image_height) {
*frames_x = new_frames_x;
*frames_y = new_frames_y;
*image_width = new_image_width;
*image_height = new_image_height;
} else {
break;
}
}
}
struct ase_decode_image_result ase_decode_image(struct arena *arena, struct buffer encoded)
{
__prof;
struct temp_arena scratch = scratch_begin(arena);
struct ase_decode_image_result res = { 0 };
struct byte_reader br = br_create_from_buffer(encoded);
struct ase_header ase_header;
br_read_to_struct(&br, &ase_header);
if (ase_header.magic != 0xA5E0) {
res.valid = false;
res.error_msg = STR("Not a valid aseprite file");
goto abort;
}
if (ase_header.color_depth != 32) {
res.valid = false;
res.error_msg = string_format(arena,
STR("Only 32 bit rgba color mode is supported (got %F)"),
FMT_UINT(ase_header.color_depth));
goto abort;
}
u32 frames_x = ase_header.frames;
u32 frames_y = 1;
u64 image_width = ase_header.width * frames_x;
u64 image_height = ase_header.height * frames_y;
make_image_dimensions_squareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height);
res.image.width = image_width;
res.image.height = image_height;
/* TODO: Optimize this. Naive memset(0) is bloating the decode time for large images. */
res.image.pixels = arena_push_array_zero(arena, u32, image_width * image_height);
u32 num_layers = 0;
struct layer *layer_head = NULL;
struct cel *cel_head = NULL;
struct cel *cel_tail = NULL;
/* Iterate frames */
u32 num_frames = 0;
for (u16 i = 0; i < ase_header.frames; ++i) {
struct frame_header frame_header;
br_read_to_struct(&br, &frame_header);
u32 num_chunks = frame_header.chunks_new;
if (num_chunks == 0) {
ASSERT(frame_header.chunks_old != 0xFFFF);
num_chunks = frame_header.chunks_old;
}
/* Iterate chunks in frame */
for (u32 j = 0; j < num_chunks; ++j) {
u32 chunk_size = br_read_u32(&br);
enum chunk_type chunk_type = br_read_u16(&br);
/* Chunk size includes size & type */
ASSERT(chunk_size >= 6);
chunk_size -= 6;
u64 chunk_end_pos = br_pos(&br) + chunk_size;
switch (chunk_type) {
case CHUNK_TYPE_LAYER: {
struct layer *layer = arena_push_zero(scratch.arena, struct layer);
layer->next = layer_head;
layer_head = layer;
layer->flags = br_read_u16(&br);
layer->type = br_read_u16(&br);
layer->child_level = br_read_u16(&br);
/* Ignoring layer default width & height */
br_seek(&br, sizeof(u16) * 2);
layer->blend_mode = br_read_u16(&br);
if (layer->blend_mode != 0) {
res.valid = false;
res.error_msg = STR("Layer has unsupported blend mode (only 'Normal' mode is supported). Tip: Try using 'merge down' to create a normal layer as a workaround");
goto abort;
}
layer->opacity = br_read_u8(&br);
if (!(ase_header.flags & 1)) {
layer->opacity = 255;
}
br_seek(&br, sizeof(u8) * 3);
/* TODO: Decode utf-8 */
u16 str_len = br_read_u16(&br);
u8 *str_bytes = br_read_raw(&br, str_len);
layer->name = (struct string) {
str_len,
arena_push_array(scratch.arena, u8, str_len)
};
MEMCPY(layer->name.text, str_bytes, str_len);
if (layer->type == 2) {
layer->tileset_index = br_read_u32(&br);
}
layer->index = num_layers++;
} break;
case CHUNK_TYPE_CEL: {
struct cel *cel = arena_push_zero(scratch.arena, struct cel);
if (cel_tail) {
cel_tail->next = cel;
} else {
cel_head = cel;
}
cel_tail = cel;
cel->layer_index = br_read_u16(&br);
cel->x_pos = br_read_i16(&br);
cel->y_pos = br_read_i16(&br);
cel->opacity = br_read_u8(&br);
cel->type = br_read_u16(&br);
cel->z_index = br_read_i16(&br);
br_seek(&br, sizeof(u8) * 5);
cel->frame_index = num_frames;
switch (cel->type) {
case CEL_TYPE_RAW_IMAGE: {
/* Unsupported */
br_seek_to(&br, chunk_end_pos);
} break;
case CEL_TYPE_LINKED: {
cel->frame_pos = br_read_u16(&br);
/* Actual linking happens later after iteration */
} break;
case CEL_TYPE_COMPRESSED_IMAGE: {
cel->width = br_read_u16(&br);
cel->height = br_read_u16(&br);
cel->pixels = arena_push_array(scratch.arena, u32, cel->width * cel->height);
inflate(scratch.arena, (u8 *)cel->pixels, br.at);
br_seek_to(&br, chunk_end_pos);
} break;
case CEL_TYPE_COMPRESSED_TILEMAP: {
/* Unsupported */
res.valid = false;
res.error_msg = STR("Tilemaps are not supported");
goto abort;
} break;
}
} break;
default: {
br_seek(&br, chunk_size);
} break;
}
}
++num_frames;
}
/* Create ordered layers array */
struct layer **layers_ordered = arena_push_array(scratch.arena, struct layer *, num_layers);
for (struct layer *layer = layer_head; layer; layer = layer->next) {
layers_ordered[layer->index] = layer;
}
/* Link cels */
struct cel **cels_ordered = arena_push_array(scratch.arena, struct cel *, num_frames * num_layers);
for (struct cel *cel = cel_head; cel; cel = cel->next) {
cels_ordered[(cel->frame_index * num_layers) + cel->layer_index] = cel;
if (cel->type == CEL_TYPE_LINKED) {
struct cel *ref_cel = cels_ordered[(cel->frame_pos * num_layers) + cel->layer_index];
cel->width = ref_cel->width;
cel->height = ref_cel->height;
cel->pixels = ref_cel->pixels;
}
}
{
__profscope(assemble_image);
/* Assemble image from cels */
for (struct cel *cel = cel_head; cel; cel = cel->next) {
struct layer *layer = layers_ordered[cel->layer_index];
/* Only draw visible layers */
if (layer->flags & 1) {
u8 opacity = (cel->opacity / 255.0f) * (layer->opacity / 255.0f) * 255.0f;
i32 cel_width = cel->width;
i32 cel_height = cel->height;
u32 frame_x = cel->frame_index % frames_x;
u32 frame_y = cel->frame_index / frames_x;
i32 cel_x_pos = cel->x_pos + (frame_x * ase_header.width);
i32 cel_y_pos = cel->y_pos + (frame_y * ase_header.height);
for (i32 cel_y = 0; cel_y < cel_height; ++cel_y) {
i32 image_y = cel_y_pos + cel_y;
if (image_y >= 0) { /* TODO: remove this check */
i32 cel_stride = cel_y * cel_width;
i32 image_stride = image_y * image_width;
for (i32 cel_x = 0; cel_x < cel_width; ++cel_x) {
i32 image_x = cel_x_pos + cel_x;
if (image_x >= 0) { /* TODO: remove this check */
u32 cel_pixel = cel->pixels[cel_x + cel_stride];
u32 *image_pixel = &res.image.pixels[image_x + image_stride];
*image_pixel = blend(cel_pixel, *image_pixel, opacity);
}
}
}
}
}
}
}
/* ASSERT all data was read */
ASSERT(br_bytes_left(&br) == 0);
res.valid = true;
abort:
scratch_end(scratch);
return res;
}
/* ========================== *
* Decode sheet
* ========================== */
struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buffer encoded)
{
__prof;
struct temp_arena scratch = scratch_begin(arena);
struct ase_decode_sheet_result res = { 0 };
struct byte_reader br = br_create_from_buffer(encoded);
struct ase_header ase_header;
br_read_to_struct(&br, &ase_header);
u32 frames_x = ase_header.frames;
u32 frames_y = 1;
u64 image_width = ase_header.width * frames_x;
u64 image_height = ase_header.height * frames_y;
make_image_dimensions_squareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height);
u32 num_tags = 0;
struct ase_tag *tag_head = NULL;
u32 num_frames = 0;
struct ase_frame *frame_head = NULL;
/* Iterate frames */
for (u16 i = 0; i < ase_header.frames; ++i) {
struct frame_header frame_header;
br_read_to_struct(&br, &frame_header);
u32 num_chunks = frame_header.chunks_new;
if (num_chunks == 0) {
ASSERT(frame_header.chunks_old != 0xFFFF);
num_chunks = frame_header.chunks_old;
}
struct ase_frame *frame = arena_push_zero(arena, struct ase_frame);
frame->next = frame_head;
frame_head = frame;
u32 frame_tile_x = i % frames_x;
u32 frame_tile_y = i / frames_x;
u32 frame_x1 = frame_tile_x * ase_header.width;
u32 frame_y1 = frame_tile_y * ase_header.height;
u32 frame_x2 = frame_x1 + ase_header.width;
u32 frame_y2 = frame_y1 + ase_header.height;
struct v2 clip_p1 = { (f32)frame_x1 / (f32)image_width, (f32)frame_y1 / (f32)image_height };
struct v2 clip_p2 = { (f32)frame_x2 / (f32)image_width, (f32)frame_y2 / (f32)image_height };
frame->index = i;
frame->duration = frame_header.frame_duration_ms / 1000.0;
frame->clip = (struct clip_rect) { clip_p1, clip_p2 };
/* Iterate chunks in frame */
for (u32 j = 0; j < num_chunks; ++j) {
u32 chunk_size = br_read_u32(&br);
enum chunk_type chunk_type = br_read_u16(&br);
/* Chunk size includes size & type */
ASSERT(chunk_size >= 6);
chunk_size -= 6;
u64 chunk_end_pos = br_pos(&br) + chunk_size;
switch (chunk_type) {
case CHUNK_TYPE_TAGS: {
u16 frame_tag_count = br_read_u16(&br);
br_seek(&br, 8);
for (u16 k = 0; k < frame_tag_count; ++k) {
struct ase_tag *tag = arena_push_zero(arena, struct ase_tag);
tag->next = tag_head;
tag_head = tag;
tag->start = br_read_u16(&br);
tag->end = br_read_u16(&br);
br_seek(&br, 13);
/* TODO: Decode utf-8 */
u16 str_len = br_read_u16(&br);
u8 *str_bytes = br_read_raw(&br, str_len);
tag->name = (struct string) {
str_len,
arena_push_array(scratch.arena, u8, str_len)
};
MEMCPY(tag->name.text, str_bytes, str_len);
++num_tags;
}
} break;
/* TODO */
//case CHUNK_TYPE_USER_DATA
//case CHUNK_TYPE_SLICE
default: {
br_seek_to(&br, chunk_end_pos);
} break;
}
}
++num_frames;
}
/* ASSERT all data was read */
ASSERT(br_bytes_left(&br) == 0);
res.valid = true;
scratch_end(scratch);
res.image_size = V2(image_width, image_height);
res.num_frames = num_frames;
res.num_tags = num_tags;
res.frame_head = frame_head;
res.tag_head = tag_head;
return res;
}

37
src/ase.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef ASE_H
#define ASE_H
struct ase_tag {
struct string name;
u32 start;
u32 end;
struct ase_tag *next;
};
struct ase_frame {
u32 index;
f64 duration;
struct clip_rect clip;
struct ase_frame *next;
};
struct ase_decode_image_result {
struct image_rgba image;
b32 valid;
struct string error_msg;
};
struct ase_decode_sheet_result {
struct v2 image_size;
u32 num_frames;
u32 num_tags;
struct ase_frame *frame_head;
struct ase_tag *tag_head;
b32 valid;
struct string error_msg;
};
struct ase_decode_image_result ase_decode_image(struct arena *arena, struct buffer encoded);
struct ase_decode_sheet_result ase_decode_sheet(struct arena *arena, struct buffer encoded);
#endif

244
src/asset_cache.c Normal file
View File

@ -0,0 +1,244 @@
#include "asset_cache.h"
#include "sys.h"
#include "string.h"
#include "memory.h"
#include "arena.h"
#include "scratch.h"
#include "util.h"
#include "work.h"
#include "log.h"
/* ========================== *
* Global state
* ========================== */
#define MAX_ASSETS 1024
#define ASSET_LOOKUP_TABLE_CAPACITY (MAX_ASSETS * 4)
GLOBAL struct {
struct sys_rw_mutex lookup_rw_mutex;
struct asset lookup[ASSET_LOOKUP_TABLE_CAPACITY];
u64 num_assets;
struct sys_rw_mutex store_rw_mutex;
struct arena store_arena;
#if RTC
/* Array of len `num_assets` pointing into populated entries of `lookup`. */
struct asset *dbg_table[ASSET_LOOKUP_TABLE_CAPACITY];
u64 dbg_table_count;
struct sys_mutex dbg_table_mutex;
#endif
} L = { 0 } DEBUG_LVAR(L_asset_cache);
/* ========================== *
* Startup
* ========================== */
void asset_cache_startup(void)
{
/* Init lookup */
L.lookup_rw_mutex = sys_rw_mutex_alloc();
/* Init store */
L.store_rw_mutex = sys_rw_mutex_alloc();
L.store_arena = arena_alloc(GIGABYTE(64));
#if RTC
/* Init debug */
L.dbg_table_mutex = sys_mutex_alloc();
#endif
}
/* ========================== *
* Lookup
* ========================== */
INTERNAL void refresh_dbg_table(void)
{
#if RTC
sys_mutex_lock(&L.dbg_table_mutex);
MEMZERO_ARRAY(L.dbg_table);
L.dbg_table_count = 0;
for (u64 i = 0; i < ARRAY_COUNT(L.lookup); ++i) {
struct asset *asset = &L.lookup[i];
if (asset->hash != 0) {
L.dbg_table[L.dbg_table_count++] = asset;
}
}
sys_mutex_unlock(&L.dbg_table_mutex);
#endif
}
/* Returns first matching slot or first empty slot if not found.
* Check returned slot->hash != 0 for presence. */
INTERNAL struct asset *asset_cache_get_slot_assume_locked(struct string key, u64 hash)
{
u64 index = hash % ARRAY_COUNT(L.lookup);
while (true) {
struct asset *slot = &L.lookup[index];
if (slot->hash) {
/* Occupied */
if (hash == slot->hash && string_eq(key, slot->key)) {
/* Matched slot */
return slot;
} else {
++index;
if (index >= ARRAY_COUNT(L.lookup)) {
index = 0;
}
}
} else {
/* Empty slot */
return slot;
}
}
}
u64 asset_cache_hash(struct string key)
{
/* TODO: Better hash */
return hash_fnv64(BUFFER_FROM_STRING(key));
}
/* `key` text is copied by this function
*
* Returns existing asset entry or inserts a new one.
*
* If is_first_touch (out parameter) is set to true, then the caller has
* inserted the asset into the cache.
*
* */
struct asset *asset_cache_touch(struct string key, u64 hash, b32 *is_first_touch)
{
__prof;
struct asset *asset = NULL;
if (is_first_touch) {
*is_first_touch = false;
}
/* Lookup */
{
sys_rw_mutex_lock_shared(&L.lookup_rw_mutex);
asset = asset_cache_get_slot_assume_locked(key, hash);
sys_rw_mutex_unlock_shared(&L.lookup_rw_mutex);
}
/* Insert if not found */
if (!asset->hash) {
sys_rw_mutex_lock_exclusive(&L.lookup_rw_mutex);
/* Re-check asset presence in case it was inserted since lock */
asset = asset_cache_get_slot_assume_locked(key, hash);
if (!asset->hash) {
if (L.num_assets >= MAX_ASSETS) {
sys_panic(STR("Max assets reached"));
}
struct string key_stored = { 0 };
{
/* Copy key to store */
struct asset_cache_store store = asset_cache_store_open();
key_stored = string_cpy(store.arena, key);
asset_cache_store_close(&store);
}
/* Initialize asset data */
logf_info("Inserting asset cache entry for \"%F\"", FMT_STR(key));
*asset = (struct asset) {
.status = ASSET_STATUS_UNINITIALIZED,
.hash = hash,
.key = key_stored,
.work_ready_sf = sync_flag_alloc(),
.asset_ready_sf = sync_flag_alloc()
};
if (is_first_touch) {
*is_first_touch = true;
}
++L.num_assets;
refresh_dbg_table();
}
sys_rw_mutex_unlock_exclusive(&L.lookup_rw_mutex);
}
return asset;
}
/* ========================== *
* Marking
* ========================== */
/* Call this once asset work has been created */
void asset_cache_mark_loading(struct asset *asset)
{
asset->status = ASSET_STATUS_LOADING;
}
/* Call this once asset work has finished */
void asset_cache_mark_ready(struct asset *asset, void *store_data)
{
asset->store_data = store_data;
asset->status = ASSET_STATUS_READY;
WRITE_BARRIER();
sync_flag_set(&asset->asset_ready_sf);
}
/* ========================== *
* Work
* ========================== */
/* NOTE: If an asset doesn't have any load work then call this function with `NULL` */
void asset_cache_set_work(struct asset *asset, struct work_handle *handle)
{
asset->work = handle ? *handle : (struct work_handle) { 0 };
sync_flag_set(&asset->work_ready_sf);
}
void asset_cache_wait(struct asset *asset)
{
__prof;
if (asset->status != ASSET_STATUS_READY) {
/* Wait for work to be set */
sync_flag_wait(&asset->work_ready_sf);
/* Help with work */
if (asset->work.gen != 0) {
work_help(asset->work);
}
/* Wait for asset to be ready */
sync_flag_wait(&asset->asset_ready_sf);
}
}
/* ========================== *
* Store
* ========================== */
/* NOTE: At the moment only one global asset store exists, however in the
* future there could be more based on asset lifetime. */
void *asset_cache_get_store_data(struct asset *asset)
{
if (asset->status == ASSET_STATUS_READY) {
return asset->store_data;
} else {
return NULL;
}
}
/* Asset store should be opened to allocate memory to the store arena */
struct asset_cache_store asset_cache_store_open(void)
{
__prof;
struct asset_cache_store store = {
.rw_mutex = &L.store_rw_mutex,
.arena = &L.store_arena
};
sys_rw_mutex_lock_exclusive(store.rw_mutex);
return store;
}
void asset_cache_store_close(struct asset_cache_store *store)
{
__prof;
sys_rw_mutex_unlock_exclusive(store->rw_mutex);
}

57
src/asset_cache.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef ASSET_CACHE_H
#define ASSET_CACHE_H
#include "sys.h"
#include "work.h"
#include "util.h"
enum asset_status {
ASSET_STATUS_NONE,
ASSET_STATUS_UNINITIALIZED,
/* TODO: ASSET_STATUS_QUEUED? */
ASSET_STATUS_LOADING,
ASSET_STATUS_READY
};
struct asset {
/* Managed via asset_cache_touch */
u64 hash;
struct string key;
/* Managed via asset_cache_set_work */
struct work_handle work;
struct sync_flag work_ready_sf;
/* Managed via asset_cache_mark_x functions */
enum asset_status status;
struct sync_flag asset_ready_sf;
/* Accessed via asset_cache_get_data */
void *store_data;
};
struct asset_cache_store {
struct arena *arena;
/* Internal */
struct sys_rw_mutex *rw_mutex;
};
void asset_cache_startup(void);
struct asset *asset_cache_touch(struct string key, u64 hash, b32 *is_first_touch);
void asset_cache_mark_loading(struct asset *asset);
void asset_cache_mark_ready(struct asset *asset, void *store_data);
void asset_cache_set_work(struct asset *asset, struct work_handle *handle);
void asset_cache_wait(struct asset *asset);
void *asset_cache_get_store_data(struct asset *asset);
struct asset_cache_store asset_cache_store_open(void);
void asset_cache_store_close(struct asset_cache_store *store);
u64 asset_cache_hash(struct string key);
#endif

18
src/atomic.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef ATOMICS_H
#define ATOMICS_H
#if OS_WINDOWS
/* winnt.h declarations */
i64 _InterlockedIncrement64(i64 volatile *addend);
i64 _InterlockedDecrement64(i64 volatile *addend);
#define atomic_inc_eval64(ptr) _InterlockedIncrement64(ptr)
#define atomic_dec_eval64(ptr) _InterlockedDecrement64(ptr)
#else
# error "Atomics not implemented"
#endif
#endif

218
src/byteio.c Normal file
View File

@ -0,0 +1,218 @@
#include "byteio.h"
#include "memory.h"
/* ========================== *
* Writer
* ========================== */
void bw_seek(struct byte_writer *bw, u64 amount)
{
if (bw_overflow_check(bw, amount)) {
return;
}
bw->at += amount;
}
void bw_seek_to(struct byte_writer *bw, u64 pos)
{
if (pos > bw->buff.size) {
ASSERT(false);
bw->overflowed = true;
}
bw->at = bw->buff.data + pos;
}
INTERNAL void write_unsafe(struct byte_writer *bw, void *v, u64 size)
{
MEMCPY(bw->at, v, size);
bw->at += size;
}
INTERNAL void write(struct byte_writer *bw, void *v, u64 size)
{
if (bw_overflow_check(bw, sizeof(v))) {
return;
}
write_unsafe(bw, v, size);
}
void bw_write_buffer(struct byte_writer *bw, struct buffer buff)
{
write(bw, buff.data, buff.size);
}
void bw_write_u8(struct byte_writer *bw, u8 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_u16(struct byte_writer *bw, u16 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_u32(struct byte_writer *bw, u32 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_u64(struct byte_writer *bw, u64 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_i8(struct byte_writer *bw, i8 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_i16(struct byte_writer *bw, i16 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_i32(struct byte_writer *bw, i32 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_i64(struct byte_writer *bw, i64 v)
{
write(bw, &v, sizeof(v));
}
void bw_write_var_uint(struct byte_writer *bw, u64 v)
{
/* TODO: real varint write */
bw_write_u64(bw, v);
}
void bw_write_var_sint(struct byte_writer *bw, i64 v)
{
/* TODO: real varint write */
bw_write_i64(bw, v);
}
/* ========================== *
* Reader
* ========================== */
void br_seek(struct byte_reader *br, u64 amount)
{
if (br_overflow_check(br, amount)) {
return;
}
br->at += amount;
}
void br_seek_to(struct byte_reader *br, u64 pos)
{
if (pos > br->buff.size) {
ASSERT(false);
br->overflowed = true;
}
br->at = br->buff.data + pos;
}
INTERNAL void *read_unsafe(struct byte_reader *br, u64 size)
{
void *prev = br->at;
br->at += size;
return prev;
}
/* Will return NULL on overflow */
void *br_read_raw(struct byte_reader *br, u64 size)
{
if (br_overflow_check(br, size)) {
return NULL;
}
return read_unsafe(br, size);
}
/* Will not read any data on overflow */
void br_read_to_buffer(struct byte_reader *br, struct buffer buff)
{
if (br_overflow_check(br, buff.size)) {
return;
}
u8 *bytes = read_unsafe(br, buff.size);
MEMCPY(buff.data, bytes, buff.size);
}
u8 br_read_u8(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(u8))) {
return 0;
}
return *(u8 *)read_unsafe(br, sizeof(u8));
}
u16 br_read_u16(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(u16))) {
return 0;
}
return *(u16 *)read_unsafe(br, sizeof(u16));
}
u32 br_read_u32(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(u32))) {
return 0;
}
return *(u32 *)read_unsafe(br, sizeof(u32));
}
u64 br_read_u64(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(u64))) {
return 0;
}
return *(u64 *)read_unsafe(br, sizeof(u64));
}
i8 br_read_i8(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(i8))) {
return 0;
}
return *(i8 *)read_unsafe(br, sizeof(i8));
}
i16 br_read_i16(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(i16))) {
return 0;
}
return *(i16 *)read_unsafe(br, sizeof(i16));
}
i32 br_read_i32(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(i32))) {
return 0;
}
return *(i32 *)read_unsafe(br, sizeof(i32));
}
i64 br_read_i64(struct byte_reader *br)
{
if (br_overflow_check(br, sizeof(i64))) {
return 0;
}
return *(i64 *)read_unsafe(br, sizeof(i64));
}
u64 br_read_var_uint(struct byte_reader *br)
{
/* TODO: real varint read */
return br_read_u64(br);
}
i64 br_read_var_sint(struct byte_reader *br)
{
/* TODO: real varint read */
return br_read_i64(br);
}

143
src/byteio.h Normal file
View File

@ -0,0 +1,143 @@
#ifndef BYTEIO_H
#define BYTEIO_H
struct byte_writer {
struct buffer buff;
b32 overflowed;
u8 *at;
};
struct byte_reader {
struct buffer buff;
b32 overflowed;
u8 *at;
};
/* ========================== *
* Constructor utils
* ========================== */
INLINE struct byte_writer bw_create_from_buffer(struct buffer buff)
{
return (struct byte_writer) {
.buff = buff,
.overflowed = false,
.at = buff.data
};
}
INLINE struct byte_reader br_create_from_buffer(struct buffer buff)
{
return (struct byte_reader) {
.buff = buff,
.overflowed = false,
.at = buff.data
};
}
INLINE struct byte_writer bw_copy(struct byte_writer *bw)
{
return *bw;
}
INLINE struct byte_reader br_copy(struct byte_reader *br)
{
return *br;
}
/* Generate a buffer struct containing written bytes only */
INLINE struct buffer bw_get_written_buffer(struct byte_writer *bw)
{
return (struct buffer) {
.data = bw->buff.data,
.size = bw->at - bw->buff.data
};
}
INLINE u64 bw_pos(struct byte_reader *bw)
{
return bw->at - bw->buff.data;
}
/* ========================== *
* Overflow utils
* ========================== */
INLINE b32 bw_overflow_check(struct byte_writer *bw, u64 amount)
{
if (bw->overflowed || bw->at + amount > bw->buff.data + bw->buff.size) {
ASSERT(false);
bw->overflowed = true;
return true;
}
return false;
}
INLINE b32 br_overflow_check(struct byte_reader *br, u64 amount)
{
if (br->overflowed || br->at + amount > br->buff.data + br->buff.size) {
ASSERT(false);
br->overflowed = true;
return true;
}
return false;
}
/* ========================== *
* Write
* ========================== */
void bw_seek(struct byte_writer *bw, u64 amount);
void bw_seek_to(struct byte_writer *bw, u64 pos);
void bw_write_buffer(struct byte_writer *bw, struct buffer buff);
void bw_write_u8(struct byte_writer *bw, u8 v);
void bw_write_u16(struct byte_writer *bw, u16 v);
void bw_write_u32(struct byte_writer *bw, u32 v);
void bw_write_u64(struct byte_writer *bw, u64 v);
void bw_write_i8(struct byte_writer *bw, i8 v);
void bw_write_i16(struct byte_writer *bw, i16 v);
void bw_write_i32(struct byte_writer *bw, i32 v);
void bw_write_i64(struct byte_writer *bw, i64 v);
void bw_write_var_uint(struct byte_writer *bw, u64 v);
void bw_write_var_sint(struct byte_writer *bw, i64 v);
INLINE u64 bw_bytes_left(struct byte_writer *bw)
{
return bw->overflowed ? 0 : bw->buff.size - (bw->at - bw->buff.data);
}
/* ========================== *
* Read
* ========================== */
void br_seek(struct byte_reader *br, u64 amount);
void br_seek_to(struct byte_reader *br, u64 pos);
/* Will not read any data on overflow */
#define br_read_to_struct(br_ptr, var_ptr) (br_read_to_buffer(br_ptr, BUFFER(sizeof(*var_ptr), (u8 *)var_ptr)))
void *br_read_raw(struct byte_reader *br, u64 size);
void br_read_to_buffer(struct byte_reader *br, struct buffer buff);
u8 br_read_u8(struct byte_reader *br);
u16 br_read_u16(struct byte_reader *br);
u32 br_read_u32(struct byte_reader *br);
u64 br_read_u64(struct byte_reader *br);
i8 br_read_i8(struct byte_reader *br);
i16 br_read_i16(struct byte_reader *br);
i32 br_read_i32(struct byte_reader *br);
i64 br_read_i64(struct byte_reader *br);
u64 br_read_var_uint(struct byte_reader *br);
i64 br_read_var_sint(struct byte_reader *br);
INLINE u64 br_bytes_left(const struct byte_reader *br)
{
return br->overflowed ? 0 : br->buff.size - (br->at - br->buff.data);
}
INLINE u64 br_pos(struct byte_reader *br)
{
return br->at - br->buff.data;
}
#endif

509
src/common.h Normal file
View File

@ -0,0 +1,509 @@
/* This header is precompiled and automatically included into all source files */
/* NOTE: Include guards disabled since it breaks editor parsing */
//#ifndef COMMON_H
//#define COMMON_H
#ifdef __cplusplus
extern "C" {
#endif
/* ========================== *
* Compiler headers
* ========================== */
/* Intrinsic header info:
* <mmintrin.h> MMX
* <xmmintrin.h> SSE
* <emmintrin.h> SSE2
* <pmmintrin.h> SSE3
* <tmmintrin.h> SSSE3
* <smmintrin.h> SSE4.1
* <nmmintrin.h> SSE4.2
* <ammintrin.h> SSE4A
* <wmmintrin.h> AES
* <immintrin.h> AVX, AVX2, FMA
*/
#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
#include <nmmintrin.h> /* SSE4.2 */
/* ========================== *
* Flag defaults
* ========================== */
/* Compile definition defaults */
#ifndef RTC
# define RTC 0
#endif
#ifndef CRTLIB
# define CRTLIB 0
#endif
#ifndef DEBINFO
# define DEBINFO 0
#endif
#ifndef DEVELOPER
# define DEVELOPER 0
#endif
#ifndef PROFILING
# define PROFILING 0
#endif
#ifndef UNOPTIMIZED
# define UNOPTIMIZED 0
#endif
#ifndef RUN_TESTS
# define RUN_TESTS 0
#endif
/* ========================== *
* Machine context
* ========================== */
/* Compiler */
#if defined(_MSC_VER) && !defined(__clang__)
# define COMPILER_MSVC 1
# define COMPILER_CLANG 0
# define COMPILER_GCC 0
#elif defined(__clang__)
# define COMPILER_MSVC 0
# define COMPILER_CLANG 1
# define COMPILER_GCC 0
#elif defined(__GNUC__) || defined(__GNUG__)
# define COMPILER_MSVC 0
# define COMPILER_CLANG 0
# define COMPILER_GCC 1
#else
# error "Unknown compiler"
#endif
/* Operating system */
#if defined(_WIN32)
# define OS_WINDOWS 1
# define OS_MAC 0
# define OS_LINUX 0
#elif defined(__APPLE__) && defined(__MACH__)
# define OS_WINDOWS 0
# define OS_MAC 1
# define OS_LINUX 0
#elif defined(__gnu_linux__)
# define OS_WINDOWS 0
# define OS_MAC 0
# define OS_LINUX 1
#else
# error "Unknown OS"
#endif
/* ========================== *
* Debug
* ========================== */
/* Compile time assert */
#define CT_ASSERT3(cond, line) struct CT_ASSERT_____##line {int foo[(cond) ? 1 : -1];}
#define CT_ASSERT2(cond, line) CT_ASSERT3(cond, line)
#define CT_ASSERT(cond) CT_ASSERT2(cond, __LINE__)
#if RTC
/* For use after a static 'L' state variable is declared. This will create a
* variable that points to the 'L' variable and is kept alive (so that the
* debugger can reference the state with a variable other than 'L') */
#define DEBUG_LVAR(var) ,__attribute((used)) *var = &L;
#define ASSERT(cond) ((cond) ? 1 : (__builtin_trap(), 0))
#define DEBUGBREAK __builtin_debugtrap()
/* Address sanitization */
#if 0
void __asan_poison_memory_region(void *, size_t);
void __asan_unpoison_memory_region(void *, size_t);
# define ASAN_POISON(addr, size) __asan_poison_memory_region(addr, size);
# define ASAN_UNPOISON(addr, size) __asan_unpoison_memory_region(addr, size);
#else
# define ASAN_POISON(addr, size)
# define ASAN_UNPOISON(addr, size)
#endif
#else
#define DEBUG_VAR_GLOBAL(type, var, value)
#define DEBUG_LVAR(var)
#define ASSERT(cond) (void)(0)
#define DEBUGBREAK
#define ASAN_POISON(addr, size)
#define ASAN_UNPOISON(addr, size)
#endif
/* ========================== *
* Common macros
* ========================== */
#if 1
# define INLINE static inline
#else
/* TODO: benchmark benefits of forced inlining */
# define INLINE __attribute((always_inline)) static inline
#endif
/* Separate `static` usage into different keywords for easier grepping */
#define LOCAL_PERSIST static
#define INTERNAL static
#define GLOBAL static
/* Markup */
#define UNUSED void
#define FALLTHROUGH __attribute((fallthrough))
/* Sizes */
#define KILOBYTE(n) (n*1024ULL)
#define MEGABYTE(n) (n*KILOBYTE(1024ULL))
#define GIGABYTE(n) (n*MEGABYTE(1024ULL))
#define TERABYTE(n) (n*GIGABYTE(1024ULL))
/* Sizeof & Alignof */
#ifdef __cplusplus
# define ALIGNOF(type) alignof(type)
#else
# define ALIGNOF(type) __alignof__(type)
#endif
#define ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
#define FIELD_SIZEOF(type, field) sizeof(((type *)0)->field)
#if COMPILER_MSVC && !defined _CRT_USE_BUILTIN_OFFSETOF
# define FIELD_OFFSETOF(type, field) ((u64)&(((type *)0)->field))
#else
# define FIELD_OFFSETOF(type, field) __builtin_offsetof(type, field)
#endif
#define FIELD_SIZEOF(type, field) sizeof(((type *)0)->field)
/* Bool */
#ifndef __cplusplus
# define true 1
# define false 0
#endif
/* Array */
#define IS_INDEXABLE(a) (sizeof(a[0]))
#define IS_ARRAY(a) (IS_INDEXABLE(a) && (((void *)&a) == ((void *)a)))
/* Pack */
#if 0
#if COMPILER_MSVC
# define PACKED(declaration) __pragma(pack(push, 1)) declaration __pragma(pack(pop))
#else
# define PACKED(declaration) declaration __attribute((__packed__))
#endif
#endif
#define PACKED __attribute((__packed__))
/* Color */
#define RGBA(r, g, b, a) (u32)((u32)(r) | ((u32)(g) << 8) | ((u32)(b) << 16) | ((u32)(a) << 24))
#define RGB(r, g, b) RGBA(r, g, b, 0xFF)
#define _RGB_F_TO_U8(f) ((u8)((f * 255.0f) + 0.5f))
#define RGBA_F(r, g, b, a) RGBA(_RGB_F_TO_U8(r), _RGB_F_TO_U8(g), _RGB_F_TO_U8(b), _RGB_F_TO_U8(a))
#define RGB_F(r, g, b) RGBA_F(r, g, b, 1.f)
#define COLOR_WHITE RGB(0xFF, 0xFF, 0xFF)
#define COLOR_RED RGB(0xFF, 0, 0 )
#define COLOR_GREEN RGB(0, 0xFF, 0 )
#define COLOR_BLUE RGB(0, 0, 0xFF)
/* Barrier */
#if defined(__x86_64) || defined(__i386__)
# define WRITE_BARRIER() __asm__ volatile("" ::: "memory")
# define READ_BARRIER() __asm__ volatile("" ::: "memory")
#else
# error "Memory barriers not implemented"
#endif
/* Cat */
#define CAT1(a, b) a ## b
#define CAT(a, b) CAT1(a, b)
#if 0
/* ========================== *
* Bit utils
* ========================== */
#define bit_set(i, mask) (i |= mask)
#define bit_clear(i, mask) (i &= ~mask)
#define bit_flip(i, mask) (i ^= mask)
#endif
/* ========================== *
* Primitive types
* ========================== */
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef float f32;
typedef double f64;
typedef i8 b8;
typedef i32 b32;
#if 0
/* Unsigned memory (like size_t) */
typedef u64 umm;
#endif
#define U8_MAX (0xFF)
#define U16_MAX (0xFFFF)
#define U32_MAX (0xFFFFFFFF)
#define U64_MAX (0xFFFFFFFFFFFFFFFFULL)
#define I8_MAX (0x7F)
#define I16_MAX (0x7FFF)
#define I32_MAX (0x7FFFFFFF)
#define I64_MAX (0x7FFFFFFFFFFFFFFFLL)
#define I8_MIN ((i8)-0x80LL)
#define I16_MIN ((i16)-0x8000LL)
#define I32_MIN ((i32)-0x80000000LL)
#define I64_MIN ((i64)-0x8000000000000000LL)
/* ========================== *
* Common structs
* ========================== */
struct arena {
u64 pos;
u64 committed;
u64 reserved;
u8 *base;
};
struct string {
u64 len;
u8 *text;
};
struct string32 {
u64 len;
u32 *text;
};
struct image_rgba {
u32 width;
u32 height;
u32 *pixels; /* Array of [width * height] pixels */
};
struct pcm {
u64 count;
i16 *samples;
};
struct buffer {
u64 size;
u8 *data;
};
/* ========================== *
* Buffer utils
* ========================== */
/* Utility buffer constructor */
#define BUFFER(size, data) (struct buffer) {size, data}
/* Utility buffer constructor from static array */
#define BUFFER_FROM_ARRAY(a) \
( \
/* Must be array */ \
ASSERT(IS_ARRAY(a)), \
/* Must be array of bytes */ \
ASSERT(sizeof(a[0]) == sizeof(u8)), \
((struct buffer) { .size = ARRAY_COUNT(a), .data = (u8 *)a }) \
)
#define BUFFER_FROM_STRING(str) ((struct buffer) { str.len, str.text })
#define BUFFER_FROM_POINTERS(p1, p2) ((struct buffer) { (u8 *)(p2) - (u8 *)(p1), (u8 *)p1 })
#define BUFFER_FROM_STRUCT(ptr) ((struct buffer) { sizeof(*ptr), ptr })
/* ========================== *
* String utils
* ========================== */
/* Expand C string literal with size for string initialization */
#ifdef __cplusplus
#define STR(cstr_lit) { \
(sizeof((cstr_lit)) - 1), \
(u8 *)(cstr_lit) \
}
#else
#define STR(cstr_lit) (struct string) { \
.len = (sizeof((cstr_lit)) - 1), \
.text = (u8 *)(cstr_lit) \
}
#endif
/* Same as `STR`, but works with static variable initialization */
#define STR_NOCAST(cstr_lit) { \
.len = (sizeof((cstr_lit)) - 1), \
.text = (u8 *)(cstr_lit) \
}
#define STRING_FROM_ARRAY(a) \
( \
/* Must be array */ \
ASSERT(IS_ARRAY(a)), \
/* Must be array of bytes */ \
ASSERT(sizeof(a[0]) == sizeof(u8)), \
((struct string) { .len = ARRAY_COUNT(a), .text = (u8 *)(a) }) \
)
/* ========================== *
* Math types
* ========================== */
#define V2(x, y) ((struct v2) { (x), (y) })
struct v2 {
f32 x, y;
};
#define V3(x, y, z) ((struct v3) { (x), (y), (z) })
struct v3 {
f32 x, y, z;
};
#define V4(x, y, z, w) ((struct v4) { (x), (y), (z), (w) })
struct v4 {
f32 x, y, z, w;
};
struct mat3x3 {
union {
f32 e[3][3];
struct v3 rows[3];
};
};
struct mat4x4 {
union {
f32 e[4][4];
struct v4 rows[4];
};
};
#define RECT(x, y, width, height) (struct rect) { (x), (y), (width), (height) }
struct rect {
f32 x, y, width, height;
};
/* Values expected to be normalized 0.0 -> 1.0 */
#define CLIP_ALL ((struct clip_rect) { { 0.0f, 0.0f }, { 1.0f, 1.0f } })
struct clip_rect {
struct v2 p1, p2;
};
struct quad {
struct v2 p1, p2, p3, p4;
};
/* ========================== *
* Common utilities
* ========================== */
INLINE u32 min_u32(u32 a, u32 b) { return a <= b ? a : b; }
INLINE u32 max_u32(u32 a, u32 b) { return a >= b ? a : b; }
INLINE u64 min_u64(u64 a, u64 b) { return a <= b ? a : b; }
INLINE u64 max_u64(u64 a, u64 b) { return a >= b ? a : b; }
INLINE i32 min_i32(i32 a, i32 b) { return a <= b ? a : b; }
INLINE i32 max_i32(i32 a, i32 b) { return a >= b ? a : b; }
INLINE i64 min_i64(i64 a, i64 b) { return a <= b ? a : b; }
INLINE i64 max_i64(i64 a, i64 b) { return a >= b ? a : b; }
INLINE f32 min_f32(f32 a, f32 b) { return a <= b ? a : b; }
INLINE f32 max_f32(f32 a, f32 b) { return a >= b ? a : b; }
INLINE f64 min_f64(f64 a, f64 b) { return a <= b ? a : b; }
INLINE f64 max_f64(f64 a, f64 b) { return a >= b ? a : b; }
INLINE f32 clamp_f32(f32 v, f32 min, f32 max) { return v < min ? min : v > max ? max : v; }
INLINE f64 clamp_f64(f64 v, f64 min, f64 max) { return v < min ? min : v > max ? max : v; }
INLINE i32 clamp_i32(i32 v, i32 min, i32 max) { return v < min ? min : v > max ? max : v; }
INLINE i64 clamp_i64(i64 v, i64 min, i64 max) { return v < min ? min : v > max ? max : v; }
INLINE u64 cstr_len(char *cstr)
{
u64 len = 0;
for (char *c = cstr; *c != 0; ++c) {
++len;
}
return len;
}
/* ========================== *
* Profiling
* ========================== */
#if PROFILING
#include "third_party/tracy/tracy/TracyC.h"
#define PROFILING_CAPTURE_FRAME_IMAGE 1
/* Clang/GCC cleanup macros */
#if COMPILER_CLANG || COMPILER_GCC
# if !TRACY_NO_CALLSTACK
# define __prof static const struct ___tracy_source_location_data CAT(__tracy_source_location,__LINE__) = { NULL, __func__, __FILE__, (uint32_t)__LINE__, 0 }; TracyCZoneCtx ctx = __attribute((cleanup(__prof_zone_cleanup_func))) ___tracy_emit_zone_begin_callstack( &CAT(__tracy_source_location,__LINE__), TRACY_CALLSTACK, true );
# define __proscope(name) static const struct ___tracy_source_location_data CAT(__tracy_source_location,__LINE__) = { NULL, name, __FILE__, (uint32_t)__LINE__, 0 }; TracyCZoneCtx ctx = __attribute((cleanup(__prof_zone_cleanup_func))) ___tracy_emit_zone_begin_callstack( &CAT(__tracy_source_location,__LINE__), TRACY_CALLSTACK, true );
# endif
# define __prof static const struct ___tracy_source_location_data CAT(__tracy_source_location,__LINE__) = { NULL, __func__, __FILE__, (uint32_t)__LINE__, 0 }; __attribute((cleanup(__prof_zone_cleanup_func))) TracyCZoneCtx ctx = ___tracy_emit_zone_begin( &CAT(__tracy_source_location,__LINE__), true );
# define __profscope(name) static const struct ___tracy_source_location_data CAT(__tracy_source_location,__LINE__) = { NULL, #name, __FILE__, (uint32_t)__LINE__, 0 }; __attribute((cleanup(__prof_zone_cleanup_func))) TracyCZoneCtx ctx = ___tracy_emit_zone_begin( &CAT(__tracy_source_location,__LINE__), true );
#endif
INLINE void __prof_zone_cleanup_func(TracyCZoneCtx *ctx) { TracyCZoneEnd(*ctx); }
#define __profalloc(ptr, size) TracyCAlloc(ptr, size)
#define __proffree(ptr) TracyCFree(ptr)
#define __profmsg(txt, len, col) TracyCMessageC(txt, len, col);
#define __profframe(name) TracyCFrameMarkNamed(name)
#if PROFILING_CAPTURE_FRAME_IMAGE
# define __profframeimage(image, width, height, offset, flipped) TracyCFrameImage(image, width, height, offset, flipped);
#else
# define __profframeimage(image, width, height, offset, flipped)
#endif /* PROFILING_CAPTURE_FRAME_IMAGE */
#else
#define PROFILING_CAPTURE_FRAME_IMAGE 0
#define __prof
#define __profscope(name)
#define __profalloc(ptr, size)
#define __proffree(ptr)
#define __profmsg(txt, len, col)
#define __profframe(name)
#define __profframeimage(image, width, height, offset, flipped)
#endif /* PROFILING */
/* ========================== *
* Configurable constants
* ========================== */
#include "config.h"
#ifdef __cplusplus
}
#endif

12
src/config.h Normal file
View File

@ -0,0 +1,12 @@
/* Project-wide configurable constants */
/* If we are not compiling in developer mode, assume resources are embedded as
* a tar archive in the executable. Otherwise, look for resources in the file
* system. */
#define RESOURCES_EMBEDDED !(DEVELOPER)
#define PIXELS_PER_UNIT 256
#define MAX_ENTITIES 4096
#define AUDIO_ENABLED 0
#define VSYNC_ENABLED 0

31
src/console.c Normal file
View File

@ -0,0 +1,31 @@
#include "console.h"
#include "log.h"
#include "sys.h"
//GLOBAL struct {
//i32 i;
//} L = { 0 } DEBUG_LVAR(L_console);
/* ========================== *
* Log callback
* ========================== */
INTERNAL void console_log_callback(struct log_event event)
{
(UNUSED)event;
}
void console_startup(void)
{
log_register_callback(&console_log_callback);
}
/* ========================== *
* Event callback
* ========================== */
b32 console_process_event(struct sys_event event)
{
(UNUSED)event;
return false;
}

9
src/console.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef CONSOLE_H
#define CONSOLE_H
#include "sys.h"
void console_startup(void);
b32 console_process_event(struct sys_event event);
#endif

173
src/draw.c Normal file
View File

@ -0,0 +1,173 @@
#include "draw.h"
#include "renderer.h"
#include "math.h"
#include "texture.h"
#include "font.h"
#include "scratch.h"
GLOBAL struct {
struct renderer_handle solid_white;
} L = { 0 } DEBUG_LVAR(L_draw);
/* ========================== *
* Startup
* ========================== */
void draw_startup(void)
{
/* Generate solid white texture */
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct image_rgba image_data = {
.width = 1,
.height = 1,
.pixels = arena_push(scratch.arena, u32)
};
image_data.pixels[0] = COLOR_WHITE;
L.solid_white = renderer_texture_alloc(image_data);
scratch_end(scratch);
}
}
/* ========================== *
* Texture
* ========================== */
INTERNAL void draw_texture_quad_internal(struct renderer_canvas *canvas, struct clip_rect clip, u32 tint, struct quad quad)
{
struct texture_shader_vertex *vertices = NULL;
vidx *indices = NULL;
u32 offset = renderer_canvas_push_vertices(canvas, (u8 **)&vertices, &indices, 4, 6);
/* Top left */
vertices[0] = (struct texture_shader_vertex) {
.pos = quad.p1,
.uv = { clip.p1.x, clip.p1.y },
.color = tint
};
/* Top right */
vertices[1] = (struct texture_shader_vertex) {
.pos = quad.p2,
.uv = { clip.p2.x, clip.p1.y },
.color = tint
};
/* Bottom right */
vertices[2] = (struct texture_shader_vertex) {
.pos = quad.p3,
.uv = { clip.p2.x, clip.p2.y },
.color = tint
};
/* Bottom left */
vertices[3] = (struct texture_shader_vertex) {
.pos = quad.p4,
.uv = { clip.p1.x, clip.p2.y },
.color = tint
};
/* Top / right triangle */
indices[0] = offset + 0;
indices[1] = offset + 1;
indices[2] = offset + 2;
/* Bottom / left triangle */
indices[3] = offset + 0;
indices[4] = offset + 2;
indices[5] = offset + 3;
}
void draw_texture_quad(struct renderer_canvas *canvas, struct draw_texture_params params, struct quad quad)
{
ASSERT(params.texture);
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = params.texture->renderer_handle });
draw_texture_quad_internal(canvas, params.clip, params.tint, quad);
}
void draw_texture_rect(struct renderer_canvas *canvas, struct draw_texture_params params, struct rect rect)
{
struct quad quad = quad_from_rect(rect);
draw_texture_quad(canvas, params, quad);
}
/* ========================== *
* Shapes
* ========================== */
void draw_solid_rect(struct renderer_canvas *canvas, struct rect rect, u32 color)
{
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = L.solid_white });
struct quad quad = quad_from_rect(rect);
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
}
void draw_solid_line(struct renderer_canvas *canvas, struct v2 start, struct v2 end, f32 thickness, u32 color)
{
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = L.solid_white });
struct quad quad = quad_from_line(start, end, thickness);
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
}
void draw_solid_ray(struct renderer_canvas *canvas, struct v2 pos, struct v2 rel, f32 thickness, u32 color)
{
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = L.solid_white });
struct quad quad = quad_from_ray(pos, rel, thickness);
draw_texture_quad_internal(canvas, CLIP_ALL, color, quad);
}
/* ========================== *
* Text
* ========================== */
void draw_text(struct renderer_canvas *canvas, struct font *font, struct v2 pos, struct string str)
{
draw_text_ex(canvas, font, pos, 1.0, str);
}
void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 pos, f32 scale, struct string str)
{
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = font->texture.renderer_handle });
struct v2 draw_pos = pos;
draw_pos.y += font->point_size * scale;
for (u64 i = 0; i < str.len; ++i) {
u8 c = str.text[i];
/* TODO: Remove this (placeholder \n) */
if (c == '\n') {
draw_pos.x = pos.x;
draw_pos.y += (font->point_size * 1.5f) * scale;
continue;
}
struct font_glyph *glyph = font_get_glyph(font, c);
if (!glyph) {
glyph = font_get_glyph(font, (u8)'?');
if (!glyph) {
continue;
}
}
f32 x = draw_pos.x + glyph->off_x * scale;
f32 y = draw_pos.y + glyph->off_y * scale;
f32 width = glyph->width * scale;
f32 height = glyph->height * scale;
struct clip_rect clip = {
{
glyph->atlas_rect.x / font->texture.width,
glyph->atlas_rect.y / font->texture.height
},
{
(glyph->atlas_rect.x + glyph->atlas_rect.width) / font->texture.width,
(glyph->atlas_rect.y + glyph->atlas_rect.height) / font->texture.height
}
};
struct quad quad = quad_from_rect(RECT(x, y, width, height));
draw_texture_quad_internal(canvas, clip, 0xFFFFFFFF, quad);
draw_pos.x += glyph->advance * scale;
}
}

30
src/draw.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef DRAW_H
#define DRAW_H
struct renderer_canvas;
struct font;
#define DRAW_TEXTURE_PARAMS(...) ((struct draw_texture_params) { \
.tint = COLOR_WHITE, \
.clip = CLIP_ALL, \
__VA_ARGS__ \
})
struct draw_texture_params {
struct texture *texture;
struct clip_rect clip;
u32 tint;
};
void draw_startup(void);
void draw_texture_quad(struct renderer_canvas *canvas, struct draw_texture_params params, struct quad quad);
void draw_texture_rect(struct renderer_canvas *canvas, struct draw_texture_params params, struct rect rect);
void draw_solid_rect(struct renderer_canvas *canvas, struct rect rect, u32 color);
void draw_solid_line(struct renderer_canvas *canvas, struct v2 start, struct v2 end, f32 thickness, u32 color);
void draw_solid_ray(struct renderer_canvas *canvas, struct v2 start, struct v2 ray, f32 thickness, u32 color);
void draw_text(struct renderer_canvas *canvas, struct font *font, struct v2 pos, struct string str);
void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 pos, f32 scale, struct string str);
#endif

101
src/entity.h Normal file
View File

@ -0,0 +1,101 @@
#ifndef ENTITY_H
#define ENTITY_H
#include "sheet.h"
#include "mixer.h"
/* ========================== *
* Entity
* ========================== */
enum entity_prop {
ENTITY_PROP_NONE,
ENTITY_PROP_TEST,
ENTITY_PROP_TEST_FOLLOW_MOUSE,
ENTITY_PROP_TEST_SOUND_EMITTER,
ENTITY_PROP_TEXTURED,
ENTITY_PROP_ANIMATED,
ENTITY_PROP_PLAYER_CONTROLLED,
ENTITY_PROP_COUNT
};
struct entity {
b32 active;
u64 gen;
u64 continuity_gen;
u64 props[(ENTITY_PROP_COUNT + 63) / 64];
struct entity *next_free;
struct v2 pos;
/* ENTITY_PROP_TEST */
struct v2 start_pos;
/* ENTITY_PROP_TEST_SOUND_EMITTER */
struct string sound_name;
struct mixer_desc sound_desc;
struct mixer_track_handle sound_handle;
/* ENTITY_PROP_TEXTURED */
struct string texture_name;
struct v2 draw_size;
/* ENTITY_PROP_ANIMATED */
/* Use entity_start_animation */
struct string animation_name;
u64 animation_flags;
f64 animation_time_in_frame;
u64 animation_start_gen;
u64 animation_gen;
struct sheet_frame frame;
};
struct entity_handle {
u64 idx;
u64 gen;
};
/* ========================== *
* Prop
* ========================== */
INLINE void entity_enable_prop(struct entity *entity, enum entity_prop prop) {
u64 index = prop / 64;
u64 bit = prop % 64;
entity->props[index] |= ((u64)1 << bit);
}
INLINE void entity_disable_prop(struct entity *entity, enum entity_prop prop)
{
u64 index = prop / 64;
u64 bit = prop % 64;
entity->props[index] &= ~((u64)1 << bit);
}
INLINE b32 entity_has_prop(struct entity *entity, enum entity_prop prop)
{
u64 index = prop / 64;
u64 bit = prop % 64;
return !!(entity->props[index] & ((u64)1 << bit));
}
/* ========================== *
* Animation
* ========================== */
/* TODO: Move this kind of stuff to game_thread? */
#define ANIMATION_FLAG_NONE 0x0
#define ANIMATION_FLAG_LOOPING 0x1
INLINE void entity_start_animation(struct entity *entity, struct string animation_name, u64 flags)
{
entity_enable_prop(entity, ENTITY_PROP_ANIMATED);
entity->animation_name = animation_name;
entity->animation_flags = flags;
entity->animation_time_in_frame = 0;
++entity->animation_start_gen;
}
#endif

220
src/font.c Normal file
View File

@ -0,0 +1,220 @@
#include "font.h"
#include "arena.h"
#include "ttf.h"
#include "work.h"
#include "scratch.h"
#include "asset_cache.h"
#include "resource.h"
#include "log.h"
#include "string.h"
#define FONT_CHARS " !\"#$%&\'()-.,*/0123456789:;<=+>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
#define LOOKUP_TABLE_SIZE (256)
struct font_task_params {
struct font_task_params *next_free;
struct asset *asset;
f32 point_size;
u64 path_len;
char path_cstr[1024];
};
struct font_task_params_store {
struct font_task_params *head_free;
struct arena arena;
struct sys_mutex mutex;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct font_task_params_store params;
} L = { 0 } DEBUG_LVAR(L_font);
/* ========================== *
* Startup
* ========================== */
void font_startup(void)
{
L.params.arena = arena_alloc(GIGABYTE(64));
L.params.mutex = sys_mutex_alloc();
}
/* ========================== *
* Load task param store
* ========================== */
INTERNAL struct font_task_params *font_task_params_alloc(void)
{
struct font_task_params *p = NULL;
{
sys_mutex_lock(&L.params.mutex);
if (L.params.head_free) {
p = L.params.head_free;
L.params.head_free = p->next_free;
} else {
p = arena_push_zero(&L.params.arena, struct font_task_params);
}
sys_mutex_unlock(&L.params.mutex);
}
return p;
}
INTERNAL void font_task_params_release(struct font_task_params *p)
{
sys_mutex_lock(&L.params.mutex);
p->next_free = L.params.head_free;
L.params.head_free = p;
sys_mutex_unlock(&L.params.mutex);
}
/* ========================== *
* Load
* ========================== */
INTERNAL void font_load_asset_task(void *vparams)
{
__prof;
struct font_task_params *params = (struct font_task_params *)vparams;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string path = string_from_cstr_len(params->path_cstr, params->path_len);
f32 point_size = params->point_size;
struct asset *asset = params->asset;
logf_info("Loading font \"%F\" (point size %F)", FMT_STR(path), FMT_FLOAT((f64)point_size));
sys_timestamp_t start_ts = sys_timestamp();
ASSERT(string_ends_with(path, STR(".ttf")));
if (!resource_exists(path)) {
/* TODO: Load baked font instead of panicking */
sys_panic(string_format(scratch.arena,
STR("Font \"%F\" not found"),
FMT_STR(path)));
}
struct string font_chars = STR(FONT_CHARS);
/* Decode */
struct resource res = resource_open(path);
struct ttf_decode_result result = ttf_decode(scratch.arena, res.bytes, point_size, font_chars);
resource_close(res);
/* Send texture to GPU */
struct renderer_handle texture_renderer_handle = renderer_texture_alloc(result.image_data);
/* Allocate store memory */
struct font *font = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
font = arena_push_zero(store.arena, struct font);
font->glyphs = arena_push_array(store.arena, struct font_glyph, result.glyphs_count);
font->lookup = arena_push_array_zero(store.arena, u16, LOOKUP_TABLE_SIZE);
asset_cache_store_close(&store);
}
/* Set font data */
font->texture = (struct texture) {
.renderer_handle = texture_renderer_handle,
.width = result.image_data.width,
.height = result.image_data.height
};
font->glyphs_count = result.glyphs_count;
font->point_size = point_size;
/* Copy glyphs from decode result */
MEMCPY(font->glyphs, result.glyphs, sizeof(*font->glyphs) * result.glyphs_count);
/* Build lookup table */
for (u64 i = 0; i < font_chars.len; ++i) {
u8 c = font_chars.text[i];
font->lookup[c] = result.cache_indices[i];
}
font_task_params_release(params);
logf_info("Finished loading font \"%F\" (point size %F) in %F seconds", FMT_STR(path), FMT_FLOAT((f64)point_size), FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)));
asset_cache_mark_ready(asset, font);
scratch_end_and_decommit(scratch);
}
/* Returns the asset from the asset cache */
struct asset *font_load_asset(struct string path, f32 point_size, b32 help)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* Concatenate point_size to path for key */
struct string key = string_format(scratch.arena,
STR("%F%F_font"),
FMT_STR(path),
FMT_FLOAT_P((f64)point_size, 3));
u64 hash = asset_cache_hash(key);
b32 is_first_touch;
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch);
if (is_first_touch) {
/* Assemble task params */
struct font_task_params *params = font_task_params_alloc();
if (path.len > (sizeof(params->path_cstr) - 1)) {
sys_panic(string_format(scratch.arena,
STR("Font path \"%F\" too long!"),
FMT_STR(path)));
}
string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr));
params->path_len = path.len;
params->asset = asset;
params->point_size = point_size;
/* Push task */
asset_cache_mark_loading(asset);
struct work_handle wh = { 0 };
if (help) {
wh = work_push_task_and_help(&font_load_asset_task, params, WORK_PRIORITY_NORMAL);
} else {
wh = work_push_task(&font_load_asset_task, params, WORK_PRIORITY_NORMAL);
}
asset_cache_set_work(asset, &wh);
}
scratch_end(scratch);
return asset;
}
struct font *font_load_async(struct string path, f32 point_size)
{
__prof;
struct asset *asset = font_load_asset(path, point_size, false);
struct font *f = (struct font *)asset_cache_get_store_data(asset);
return f;
}
struct font *font_load(struct string path, f32 point_size)
{
__prof;
struct asset *asset = font_load_asset(path, point_size, true);
asset_cache_wait(asset);
struct font *f = (struct font *)asset_cache_get_store_data(asset);
return f;
}
/* ========================== *
* Other
* ========================== */
struct font_glyph *font_get_glyph(struct font *font, u8 c)
{
__prof;
struct font_glyph *g;
u16 index = font->lookup[c];
if (index < LOOKUP_TABLE_SIZE) {
g = &font->glyphs[index];
} else {
g = &font->glyphs[0];
}
return g;
}

34
src/font.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef FONT_H
#define FONT_H
#include "texture.h"
#include "util.h"
struct asset;
struct font_glyph {
f32 off_x;
f32 off_y;
i32 advance;
f32 width;
f32 height;
struct rect atlas_rect;
};
struct font {
f32 point_size;
struct texture texture;
u16 glyphs_count;
struct font_glyph *glyphs;
u16 *lookup;
};
void font_startup(void);
struct asset *font_load_asset(struct string path, f32 point_size, b32 help);
struct font *font_load_async(struct string path, f32 point_size);
struct font *font_load(struct string path, f32 point_size);
struct font_glyph *font_get_glyph(struct font *font, u8 c);
#endif

409
src/game.c Normal file
View File

@ -0,0 +1,409 @@
#include "game.h"
#include "app.h"
#include "sys.h"
#include "util.h"
#include "entity.h"
#include "sheet.h"
#include "tick.h"
#include "sound.h"
#include "mixer.h"
#include "math.h"
#include "scratch.h"
#define GAME_FPS 50
GLOBAL struct {
b32 shutdown;
struct sys_thread game_thread;
f64 timescale;
f64 dt;
f64 time;
struct entity *free_entity_head;
struct tick tick;
/* Game thread input */
struct sys_mutex game_cmds_mutex;
struct arena game_cmds_arena;
/* Game thread output */
struct sys_mutex published_tick_mutex;
struct tick published_tick;
} L = { 0 } DEBUG_LVAR(L_game);
/* ========================== *
* Entity allocation
* ========================== */
INTERNAL struct entity *entity_alloc(void)
{
struct entity *entity = NULL;
if (L.free_entity_head) {
/* Reuse from free list */
entity = L.free_entity_head;
L.free_entity_head = entity->next_free;
*entity = (struct entity) {
.gen = entity->gen
};
} else {
/* Make new */
if (L.tick.entities_count >= MAX_ENTITIES) {
sys_panic(STR("MAX_ENTITIES reached"));
}
entity = &L.tick.entities[L.tick.entities_count++];
*entity = (struct entity) {
.gen = 1
};
}
return entity;
}
#if 0
INTERNAL void entity_release(struct entity *entity)
{
entity->next_free = L.free_entity_head;
L.free_entity_head = entity;
*entity = (struct entity) {
.gen = entity->gen + 1
};
}
#endif
/* ========================== *
* Game cmd
* ========================== */
INTERNAL void push_cmds(struct game_cmd_array cmd_array)
{
sys_mutex_lock(&L.game_cmds_mutex);
{
for (u64 i = 0; i < cmd_array.count; ++i) {
struct game_cmd *write_cmd = arena_push(&L.game_cmds_arena, struct game_cmd);
*write_cmd = cmd_array.cmds[i];
}
}
sys_mutex_unlock(&L.game_cmds_mutex);
}
INTERNAL struct game_cmd_array pop_cmds(struct arena *arena)
{
struct game_cmd_array array = { 0 };
if (L.game_cmds_arena.pos > 0) {
sys_mutex_lock(&L.game_cmds_mutex);
{
struct buffer game_cmds_buff = arena_to_buffer(&L.game_cmds_arena);
arena_align(arena, ALIGNOF(struct game_cmd));
array.cmds = (struct game_cmd *)arena_push_array(arena, u8, game_cmds_buff.size);
array.count = game_cmds_buff.size / sizeof(struct game_cmd);
MEMCPY(array.cmds, game_cmds_buff.data, game_cmds_buff.size);
arena_reset(&L.game_cmds_arena);
}
sys_mutex_unlock(&L.game_cmds_mutex);
}
return array;
}
/* ========================== *
* Update
* ========================== */
INTERNAL void publish_game_tick(void)
{
__prof;
sys_mutex_lock(&L.published_tick_mutex);
L.tick.published_ts = sys_timestamp();
tick_cpy(&L.published_tick, &L.tick);
sys_mutex_unlock(&L.published_tick_mutex);
}
INTERNAL void game_update(void)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
#if 0
/* TODO: remove this (testing variable frame latency) */
u32 rand = sys_rand_u32();
u32 sleep_ms = rand % 500;
sys_sleep((f64)sleep_ms / 1000);
#endif
++L.tick.id;
L.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale);
L.time += L.dt;
/* TODO: remove this (testing) */
/* Initialize entities */
static b32 run = 0;
if (!run) {
run = 1;
/* Create moving ents */
{
f32 start_x = -1;
f32 start_y = -1;
struct v2 draw_size = V2(0.25f, 0.25f);
{
struct entity *e = entity_alloc();
e->active = true;
e->start_pos = V2(start_x, start_y);
entity_enable_prop(e, ENTITY_PROP_TEST);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
e->draw_size = draw_size;
e->texture_name = STR("res/graphics/timmy.ase");
}
{
struct entity *e = entity_alloc();
e->active = true;
e->start_pos = V2(start_x + 0.5f, start_y);
entity_enable_prop(e, ENTITY_PROP_TEST);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
e->draw_size = draw_size;
e->texture_name = STR("res/graphics/bla.ase");
entity_start_animation(e, STR("Test"), ANIMATION_FLAG_LOOPING);
}
{
struct entity *e = entity_alloc();
e->active = true;
e->start_pos = V2(start_x + 1.f, start_y);
entity_enable_prop(e, ENTITY_PROP_TEST);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
e->draw_size = draw_size;
e->texture_name = STR("res/graphics/floor_tiles.ase");
entity_start_animation(e, STR("Test"), ANIMATION_FLAG_LOOPING);
}
{
struct entity *e = entity_alloc();
e->active = true;
e->start_pos = V2(start_x + 1.5f, start_y);
entity_enable_prop(e, ENTITY_PROP_TEST);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
e->draw_size = draw_size;
e->texture_name = STR("res/graphics/white.ase");
entity_start_animation(e, STR("Test"), ANIMATION_FLAG_LOOPING);
}
{
struct entity *e = entity_alloc();
e->active = true;
e->start_pos = V2(start_x + 2.f, start_y);
entity_enable_prop(e, ENTITY_PROP_TEST);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
e->draw_size = draw_size;
e->texture_name = STR("res/graphics/bad.ase");
entity_start_animation(e, STR("Test"), ANIMATION_FLAG_LOOPING);
}
}
/* Player ent */
{
struct entity *e = entity_alloc();
e->active = true;
e->pos = V2(0, 0.25);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
e->texture_name = STR("res/graphics/timmy.ase");
struct sheet *sheet = sheet_load(e->texture_name);
f32 meters_width = sheet->image_size.x / PIXELS_PER_UNIT;
f32 meters_height = sheet->image_size.y / PIXELS_PER_UNIT;
e->draw_size = V2(meters_width, meters_height);
}
/* Screen center ent */
{
struct entity *e = entity_alloc();
e->active = true;
e->pos = V2(0, 0);
entity_enable_prop(e, ENTITY_PROP_TEST);
//entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
e->sound_name = STR("res/sounds/test2.mp3");
e->sound_desc = MIXER_DESC(
.volume = 1.0,
.flags = MIXER_FLAG_SPATIALIZE,
.looping = true,
);
entity_enable_prop(e, ENTITY_PROP_TEST_SOUND_EMITTER);
e->draw_size = V2(0.05, 0.05);
e->texture_name = STR("res/graphics/bad.ase");
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
}
{
struct entity *e = entity_alloc();
e->active = true;
e->pos = V2(0, 0);
entity_enable_prop(e, ENTITY_PROP_TEXTURED);
//entity_enable_prop(e, ENTITY_PROP_TEST);
entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
e->draw_size = V2(0.5, 0.5);
e->texture_name = STR("res/graphics/bla.ase");
entity_start_animation(e, STR("Test"), ANIMATION_FLAG_LOOPING);
}
}
/* ========================== *
* Process game cmds
* ========================== */
struct game_cmd_array game_cmds = pop_cmds(scratch.arena);
for (u64 i = 0; i < game_cmds.count; ++i) {
struct game_cmd cmd = game_cmds.cmds[i];
switch (cmd.kind) {
case GAME_CMD_KIND_SET_PLAYER_FOCUS: {
L.tick.player_focus = cmd.pos;
} break;
default: break;
};
}
/* ========================== *
* Update entities
* ========================== */
for (u64 i = 0; i < ARRAY_COUNT(L.tick.entities); ++i) {
struct entity *ent = &L.tick.entities[i];
if (!ent->active) continue;
/* ========================== *
* Animation
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_ANIMATED)) {
struct sheet *sheet = sheet_load(ent->texture_name);
struct sheet_tag tag = sheet_get_tag(sheet, ent->animation_name);
if (ent->animation_start_gen == ent->animation_gen) {
ent->animation_time_in_frame += L.dt;
} else {
ent->frame = sheet_get_frame(sheet, tag.start);
ent->animation_start_gen = ent->animation_gen;
}
while (ent->frame.duration != 0 && ent->animation_time_in_frame > ent->frame.duration) {
u64 new_frame_index = ent->frame.index + 1;
if (new_frame_index > tag.end) {
if (ent->animation_flags & ANIMATION_FLAG_LOOPING) {
/* Restart animation */
new_frame_index = tag.start;
} else {
/* End animation */
entity_start_animation(ent, STR("Default"), ANIMATION_FLAG_LOOPING);
goto break_animation;
}
}
ent->frame = sheet_get_frame(sheet, new_frame_index);
ent->animation_time_in_frame -= ent->frame.duration;
}
}
break_animation:
/* ========================== *
* Update entity positions
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_TEST_FOLLOW_MOUSE)) {
//ent->pos = v2_sub(L.tick.focus, v2_mul(ent->draw_size, 0.5f));
ent->pos = L.tick.player_focus;
ent->start_pos = ent->pos;
}
/* Update pos */
if (entity_has_prop(ent, ENTITY_PROP_TEST)) {
f32 t = ((f32)L.time);
f32 x = (math_cos(t * 2.f) / 10.f);
f32 y = (math_sin(t * 2.f) / 10.f);
ent->pos = v2_add(ent->start_pos, V2(x, y));
}
/* ========================== *
* Update sound emitter
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_TEST_SOUND_EMITTER)) {
struct mixer_desc desc = ent->sound_desc;
desc.speed = L.timescale;
desc.pos = ent->pos;
struct sound *sound = sound_load_async(ent->sound_name, 0);
b32 played = ent->sound_handle.gen != 0;
if (sound) {
if (!played) {
ent->sound_handle = mixer_play_ex(sound, desc);
} else {
mixer_track_set(ent->sound_handle, desc);
}
}
}
}
/* Publish tick */
publish_game_tick();
__profframe("Game");
scratch_end(scratch);
}
/* ========================== *
* Startup
* ========================== */
INTERNAL void game_thread_entry_point(void *arg)
{
(UNUSED)arg;
sys_timestamp_t last_frame_ts = 0;
f64 target_dt = GAME_FPS > 0 ? (1.0 / GAME_FPS) : 0;
while (!L.shutdown) {
__profscope(game_update_w_sleep);
sleep_frame(last_frame_ts, target_dt);
last_frame_ts = sys_timestamp();
game_update();
}
}
void game_startup(void)
{
/* Initialize game cmd storage */
L.game_cmds_mutex = sys_mutex_alloc();
L.game_cmds_arena = arena_alloc(GIGABYTE(64));
/* Initialize tick storage */
L.published_tick_mutex = sys_mutex_alloc();
L.timescale = 1.0;
L.game_thread = sys_thread_init(&game_thread_entry_point, NULL, STR("[P2] Game thread"));
}
void game_shutdown(void)
{
L.shutdown = true;
sys_thread_join(&L.game_thread);
}
/* ========================== *
* Interface
* ========================== */
void game_get_latest_tick(struct tick *dest)
{
sys_mutex_lock(&L.published_tick_mutex);
tick_cpy(dest, &L.published_tick);
sys_mutex_unlock(&L.published_tick_mutex);
}
u64 game_get_latest_tick_id(void)
{
return L.published_tick.id;
}
void game_push_cmds(struct game_cmd_array cmd_array)
{
push_cmds(cmd_array);
}

35
src/game.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef GAME_H
#define GAME_H
struct tick;
enum game_cmd_kind {
GAME_CMD_KIND_NONE,
GAME_CMD_KIND_SET_PLAYER_FOCUS,
GAME_CMD_KIND_COUNT
};
struct game_cmd {
enum game_cmd_kind kind;
b32 state; /* 1 = start, 0 = stop */
/* GAME_CMD_KIND_SET_PLAYER_FOCUS */
struct v2 pos;
};
struct game_cmd_array {
struct game_cmd *cmds;
u64 count;
};
void game_startup(void);
void game_shutdown(void);
void game_get_latest_tick(struct tick *dest);
u64 game_get_latest_tick_id(void);
void game_push_cmds(struct game_cmd_array cmd_array);
#endif

20
src/inc.c Normal file
View File

@ -0,0 +1,20 @@
#include "inc.h"
#include "incbin.h"
/* This is the file that actually includes binary data meant to be embedded in
* the executable. Embedded files should be added as dependencies to this source
* file via the build system. */
#if RESOURCES_EMBEDDED
INCBIN_INCLUDE(res_tar, "../build/res.tar");
struct buffer inc_res_tar(void)
{
return INCBIN_GET(res_tar);
}
#endif
INCBIN_INCLUDE(shaders_tar, "../build/shaders.tar");
struct buffer inc_shaders_tar(void)
{
return INCBIN_GET(shaders_tar);
}

10
src/inc.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef INC_H
#define INC_H
#if RESOURCES_EMBEDDED
struct buffer inc_res_tar(void);
#endif
struct buffer inc_shaders_tar(void);
#endif

34
src/incbin.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef INCBIN_H
#define INCBIN_H
#define INCBINSTR2(x) #x
#define INCBINSTR(x) INCBINSTR2(x)
#if OS_WINDOWS
# define INCBIN_SECTION ".rdata, \"dr\""
#elif OS_MAC
# define INCBIN_SECTION "__TEXT,__const"
#else
# define INCBIN_SECTION ".rodata"
#endif
/* Includes raw binary data into the executable. */
/* https://gist.github.com/mmozeiko/ed9655cf50341553d282 */
#define INCBIN_INCLUDE(name, file) \
__asm__(".section " INCBIN_SECTION "\n" \
".global _incbin_" INCBINSTR(name) "_start\n" \
".balign 16\n" \
"_incbin_" INCBINSTR(name) "_start:\n" \
".incbin \"" file "\"\n" \
\
".global _incbin_" INCBINSTR(name) "_end\n" \
".balign 1\n" \
"_incbin_" INCBINSTR(name) "_end:\n" \
); \
extern __attribute((aligned(16))) const char _incbin_ ## name ## _start[]; \
extern const char _incbin_ ## name ## _end[]
/* Retrieve a buffer for included data using the name supplied to INCBIN_INCLUDE */
#define INCBIN_GET(name) BUFFER_FROM_POINTERS(_incbin_ ## name ## _start, _incbin_ ## name ## _end)
#endif

59
src/intrinsics.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef INTRINSICS_H
#define INTRINSICS_H
/* ========================== *
* Math
* ========================== */
#if 0
INLINE f32 ix_sqrt_f(f32 f)
{
return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(f)));
}
#endif
INLINE i32 ix_round_f32_to_i32(f32 f)
{
return _mm_cvtss_si32(_mm_round_ss(_mm_setzero_ps(), _mm_set_ss(f), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
}
INLINE i64 ix_round_f64_to_i64(f64 f)
{
return _mm_cvtsd_si64(_mm_round_sd(_mm_setzero_pd(), _mm_set_sd(f), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
}
INLINE i32 ix_floor_f32_to_i32(f32 f)
{
return _mm_cvtss_si32(_mm_floor_ss(_mm_setzero_ps(), _mm_set_ss(f)));
}
INLINE i64 ix_floor_f64_to_i64(f64 f)
{
return _mm_cvtsd_si64(_mm_floor_sd(_mm_setzero_pd(), _mm_set_sd(f)));
}
INLINE i32 ix_ceil_f32_to_i32(f32 f)
{
return _mm_cvtss_si32(_mm_ceil_ss(_mm_setzero_ps(), _mm_set_ss(f)));
}
INLINE i64 ix_ceil_f64_to_i64(f64 f)
{
return _mm_cvtsd_si64(_mm_ceil_sd(_mm_setzero_pd(), _mm_set_sd(f)));
}
/* ========================== *
* Util
* ========================== */
INLINE void ix_pause(void)
{
_mm_pause();
}
INLINE i64 ix_clock(void)
{
return __rdtsc();
}
#endif

1083
src/json.c Normal file

File diff suppressed because it is too large Load Diff

143
src/json.h Normal file
View File

@ -0,0 +1,143 @@
#ifndef JSON_H
#define JSON_H
enum json_type {
JSON_TYPE_INVALID,
JSON_TYPE_STRING,
JSON_TYPE_NULL,
JSON_TYPE_BOOL,
JSON_TYPE_ARRAY,
JSON_TYPE_OBJECT,
JSON_TYPE_NUMBER
};
struct json_object_entry;
struct json_ir_parent_data {
u32 child_count;
struct json_ir *child_first;
struct json_ir *child_last;
};
/* Intermediate representation of JSON hierarchy tree in memory. Used for mutating
* JSON in parsing stage or for creating new objects. Should be manipulated via the
* API. */
struct json_ir {
enum json_type type;
struct json_ir *next_child;
struct string key;
union {
struct json_ir_parent_data children;
b32 boolean;
struct string string;
f64 number;
} val;
};
/* Final representation of JSON hierarchy. Should be manipulated via the API. */
struct json_val {
enum json_type type;
u32 child_count;
union {
void *object_table;
struct json_val *array_children;
b32 boolean;
struct string *string;
f64 number;
} val;
};
struct json_object_entry {
struct string key;
struct json_val value;
};
/* Parse */
const struct json_ir *json_parse(struct arena *arena, struct buffer bytes, struct string **error);
/* Format */
const struct json_val *json_format(struct arena *arena, const struct json_ir *ir);
const struct json_val *json_parse_and_format(struct arena *arena, struct buffer bytes, struct string **error);
/* Index */
const struct json_val *json_array_get(const struct json_val *a, u32 index);
const struct json_val *json_object_get(const struct json_val *obj, struct string key);
const struct json_object_entry *json_object_get_index(const struct json_val *obj, u32 index);
/* Dump */
struct string json_dump_to_string(struct arena *arena, const struct json_val *val, u32 indent);
/* Write */
struct json_ir *json_ir_object(struct arena *arena);
struct json_ir *json_ir_number(struct arena *arena, f64 n);
struct json_ir *json_ir_bool(struct arena *arena, b32 b);
struct json_ir *json_ir_object_set(struct json_ir *obj, struct string key, struct json_ir *value);
/* ========================== *
* Type util
* ========================== */
INLINE b32 json_is_object(const struct json_val *v)
{
return v && v->type == JSON_TYPE_OBJECT;
}
INLINE b32 json_is_string(const struct json_val *v)
{
return v && v->type == JSON_TYPE_STRING;
}
INLINE b32 json_is_number(const struct json_val *v)
{
return v && v->type == JSON_TYPE_NUMBER;
}
INLINE b32 json_is_array(const struct json_val *v)
{
return v && v->type == JSON_TYPE_ARRAY;
}
INLINE b32 json_is_bool(const struct json_val *v)
{
return v && v->type == JSON_TYPE_BOOL;
}
INLINE b32 json_is_null(const struct json_val *v)
{
return v && v->type == JSON_TYPE_NULL;
}
/* ========================== *
* Val util
* ========================== */
INLINE struct string json_string(const struct json_val *v)
{
ASSERT(json_is_string(v));
return *v->val.string;
}
INLINE f64 json_number(const struct json_val *v)
{
ASSERT(json_is_number(v));
return v->val.number;
}
INLINE b32 json_bool(const struct json_val *v)
{
ASSERT(json_is_bool(v));
return v->val.boolean;
}
/* ========================== *
* Parent util
* ========================== */
INLINE u32 json_child_count(const struct json_val *v)
{
ASSERT(json_is_object(v) || json_is_array(v));
return v->child_count;
}
#endif

196
src/log.c Normal file
View File

@ -0,0 +1,196 @@
#include "log.h"
#include "scratch.h"
#include "string.h"
#include "app.h"
struct log_event_callback {
log_event_callback_func *func;
i32 level;
struct log_event_callback *next;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct sys_mutex mutex;
struct arena arena;
log_event_callback_func *callbacks_head;
struct sys_file file;
b32 file_valid;
} L = { 0 } DEBUG_LVAR(L_log);
GLOBAL const struct log_level_settings g_log_level_settings[LOG_LEVEL_COUNT] = {
[LOG_LEVEL_CRITICAL] = {
STR_NOCAST("CRITICAL"),
0xFFFF00FF
},
[LOG_LEVEL_ERROR] = {
STR_NOCAST("ERROR"),
0xFFFF0000
},
[LOG_LEVEL_WARNING] = {
STR_NOCAST("WARNING"),
0xFFFFFF00
},
[LOG_LEVEL_INFO] = {
STR_NOCAST("INFO"),
0xFFFFFFFF
},
[LOG_LEVEL_DEBUG] = {
STR_NOCAST("DEBUG"),
0xFF30D5C8
}
};
/* ========================== *
* Startup
* ========================== */
void log_startup(struct string logfile_path)
{
L.mutex = sys_mutex_alloc();
L.arena = arena_alloc(GIGABYTE(64));
if (logfile_path.len > 0) {
/* Create / wipe log file */
sys_file_close(sys_file_open_write(logfile_path));
/* Keep log file open for appending */
if (sys_is_file(logfile_path)) {
L.file = sys_file_open_append(logfile_path);
L.file_valid = true;
}
}
}
/* ========================== *
* Callback
* ========================== */
void log_register_callback(log_event_callback_func *func)
{
sys_mutex_lock(&L.mutex);
(UNUSED)func;
sys_mutex_unlock(&L.mutex);
}
/* ========================== *
* Log
* ========================== */
INTERNAL void append_to_logfile(struct string msg)
{
__prof;
if (L.file_valid) {
struct temp_arena scratch = scratch_begin_no_conflict();
struct string msg_line = string_cat(scratch.arena, msg, STR("\n"));
sys_file_write(L.file, BUFFER_FROM_STRING(msg_line));
scratch_end(scratch);
}
}
#if LOG_INCLUDE_SOURCE_LOCATION
void _log(i32 level, struct string file, u32 line, struct string msg)
#else
void _log(i32 level, struct string msg)
#endif
{
__prof;
if (level < 0 || level >= LOG_LEVEL_COUNT) {
sys_panic(STR("Invalid log level"));
}
struct temp_arena scratch = scratch_begin_no_conflict();
struct sys_local_time_info lt = sys_local_time();
u32 tid = sys_thread_id();
struct log_level_settings settings = g_log_level_settings[level];
struct string shorthand = settings.shorthand;
#if LOG_INCLUDE_SOURCE_LOCATION
struct string msg_formatted = string_format(
scratch.arena,
STR("%F:%F:%F |Thread %F| [%F] <%F:%F> %F"),
/* Time */
FMT_UINT(lt.hour),
FMT_UINT(lt.minute),
FMT_UINT(lt.second),
/* TID */
FMT_UINT(tid),
/* Level */
FMT_STR(shorthand),
/* Source location */
FMT_STR(file),
FMT_SINT(line),
/* Message */
FMT_STR(msg)
);
#else
struct string msg_formatted = string_format(
scratch.arena,
STR("%F:%F:%F |Thread %F| [%F] %F"),
/* Time */
FMT_UINT(lt.hour),
FMT_UINT(lt.minute),
FMT_UINT(lt.second),
/* TID */
FMT_UINT(tid),
/* Level */
FMT_STR(shorthand),
/* Message */
FMT_STR(msg)
);
#endif
__profmsg((char *)msg.text, msg.len, settings.color);
append_to_logfile(msg_formatted);
scratch_end(scratch);
}
#if LOG_INCLUDE_SOURCE_LOCATION
void _logfv(i32 level, struct string file, u32 line, struct string fmt, va_list args)
#else
void _logfv(i32 level, struct string fmt, va_list args)
#endif
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct string msg = string_formatv(scratch.arena, fmt, args);
#if LOG_INCLUDE_SOURCE_LOCATION
_log(level, file, line, msg);
#else
_log(level, msg);
#endif
scratch_end(scratch);
}
#if LOG_INCLUDE_SOURCE_LOCATION
void _logf(i32 level, struct string file, u32 line, struct string fmt, ...)
#else
void _logf(i32 level, struct string fmt, ...)
#endif
{
va_list args;
va_start(args, fmt);
#if LOG_INCLUDE_SOURCE_LOCATION
_logfv(level, file, line, fmt, args);
#else
_logfv(level, fmt, args);
#endif
va_end(args);
}

147
src/log.h Normal file
View File

@ -0,0 +1,147 @@
#ifndef LOG_H
#define LOG_H
#include "string.h"
#define LOG_LEVEL(l) (l <= LOG_LEVEL_COMPTIME)
/* Log level configuration */
#ifndef LOG_LEVEL_COMPTIME
# if RTC || PROFILING
# define LOG_LEVEL_COMPTIME LOG_LEVEL_DEBUG
# else
# define LOG_LEVEL_COMPTIME LOG_LEVEL_INFO
# endif
#endif
/* Source location configuration */
#ifndef LOG_INCLUDE_SOURCE_LOCATION
# define LOG_INCLUDE_SOURCE_LOCATION (DEBINFO)
#endif
#define LOG_LEVEL_NONE -1
#define LOG_LEVEL_CRITICAL 0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARNING 2
#define LOG_LEVEL_INFO 3
#define LOG_LEVEL_DEBUG 4
#define LOG_LEVEL_COUNT 5
/* ========================== *
* Callback interface
* ========================== */
struct log_level_settings {
struct string shorthand;
u32 color;
};
struct log_event {
i32 level;
struct string msg; /* Lifetime is only as long as the callback function call */
struct log_level_settings settings;
/* These will be nulled if LOG_INCLUDE_SOURCE_LOCATION is disabled */
struct string file;
i32 line;
};
typedef void(log_event_callback_func)(struct log_event);
void log_register_callback(log_event_callback_func *func);
/* ========================== *
* Logging macros
* ========================== */
#if LOG_LEVEL(LOG_LEVEL_CRITICAL)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_critical(msg) _log(LOG_LEVEL_CRITICAL, STR(__FILE__), __LINE__, msg)
# define logf_critical(fmt, ...) _logf(LOG_LEVEL_CRITICAL, STR(__FILE__), __LINE__, STR(fmt), __VA_ARGS__, FMT_END)
# else
# define log_critical(msg) _log(LOG_LEVEL_CRITICAL, msg)
# define logf_critical(fmt, ...) _logf(LOG_LEVEL_CRITICAL, STR(fmt), __VA_ARGS__, FMT_END)
# endif
#else
# define log_critical(msg)
# define logf_critical(fmt, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_ERROR)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_error(msg) _log(LOG_LEVEL_ERROR, STR(__FILE__), __LINE__, msg)
# define logf_error(fmt, ...) _logf(LOG_LEVEL_ERROR, STR(__FILE__), __LINE__, STR(fmt), __VA_ARGS__, FMT_END)
# else
# define log_error(msg) _log(LOG_LEVEL_ERROR, msg)
# define logf_error(fmt, ...) _logf(LOG_LEVEL_ERROR, STR(fmt), __VA_ARGS__, FMT_END)
# endif
#else
# define log_error(msg)
# define logf_error(fmt, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_WARNING)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_warning(msg) _log(LOG_LEVEL_WARNING, STR(__FILE__), __LINE__, msg)
# define logf_warning(fmt, ...) _logf(LOG_LEVEL_WARNING, STR(__FILE__), __LINE__, STR(fmt), __VA_ARGS__, FMT_END)
# else
# define log_warning(msg) _log(LOG_LEVEL_WARNING, msg)
# define logf_warning(fmt, ...) _logf(LOG_LEVEL_WARNING, STR(fmt), __VA_ARGS__, FMT_END)
# endif
#else
# define log_warning(msg)
# define logf_warning(fmt, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_DEBUG)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_debug(msg) _log(LOG_LEVEL_DEBUG, STR(__FILE__), __LINE__, msg)
# define logf_debug(fmt, ...) _logf(LOG_LEVEL_DEBUG, STR(__FILE__), __LINE__, STR(fmt), __VA_ARGS__, FMT_END)
# else
# define log_debug(msg) _log(LOG_LEVEL_DEBUG, msg)
# define logf_debug(fmt, ...) _logf(LOG_LEVEL_DEBUG, STR(fmt), __VA_ARGS__, FMT_END)
# endif
#else
# define log_debug(msg)
# define logf_debug(fmt, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_INFO)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_info(msg) _log(LOG_LEVEL_INFO, STR(__FILE__), __LINE__, msg)
# define logf_info(fmt, ...) _logf(LOG_LEVEL_INFO, STR(__FILE__), __LINE__, STR(fmt), __VA_ARGS__, FMT_END)
# else
# define log_info(msg) _log(LOG_LEVEL_INFO, msg)
# define logf_info(fmt, ...) _logf(LOG_LEVEL_INFO, STR(fmt), __VA_ARGS__, FMT_END)
# endif
#else
# define log_info(msg)
# define logf_info(fmt, ...)
#endif
/* ========================== *
* Function declarations
* ========================== */
void log_startup(struct string logfile_path);
#if LOG_INCLUDE_SOURCE_LOCATION
void _log(i32 level, struct string file, u32 line, struct string msg);
#else
void _log(i32 level, struct string msg);
#endif
#if LOG_INCLUDE_SOURCE_LOCATION
void _logfv(i32 level, struct string file, u32 line, struct string fmt, va_list args);
#else
void _logfv(i32 level, struct string fmt, va_list args);
#endif
#if LOG_INCLUDE_SOURCE_LOCATION
void _logf(i32 level, struct string file, u32 line, struct string fmt, ...);
#else
void _logf(i32 level, struct string fmt, ...);
#endif
#endif

564
src/math.h Normal file
View File

@ -0,0 +1,564 @@
#ifndef MATH_H
#define MATH_H
#include "intrinsics.h"
#define PI ((f32)3.14159265358979323846)
#define TAU ((f32)6.28318530717958647693)
/* ========================== *
* Rounding
* ========================== */
/* TODO: Don't use intrinsics for these. */
INLINE i32 math_round_f32(f32 f)
{
return ix_round_f32_to_i32(f);
}
INLINE i32 math_floor_f32(f32 f)
{
return ix_floor_f32_to_i32(f);
}
INLINE i32 math_ceil_f32(f32 f)
{
return ix_ceil_f32_to_i32(f);
}
INLINE i64 math_round_f64(f64 f)
{
return ix_round_f64_to_i64(f);
}
INLINE i64 math_floor_f64(f64 f)
{
return ix_floor_f64_to_i64(f);
}
INLINE i64 math_ceil_f64(f64 f)
{
return ix_ceil_f64_to_i64(f);
}
INLINE f32 math_mod_f32(f32 x, f32 m)
{
return x - m * math_floor_f32(x / m);
}
INLINE f32 math_abs_f32(f32 f)
{
u32 truncated = *(u32 *)&f & 0x7FFFFFFF;
return *(f32 *)&truncated;
}
INLINE f64 math_abs_f64(f64 f)
{
u64 truncated = *(u64 *)&f & 0x7FFFFFFFFFFFFFFF;
return *(f64 *)&truncated;
}
INLINE i32 math_sign_f32(f32 f)
{
u32 bits = *(u32 *)&f;
return bits & ((u32)1 << 31);
}
INLINE i32 math_sign_f64(f64 f)
{
u64 bits = *(u64 *)&f;
return bits & ((u64)1 << 31);
}
/* ========================== *
* Exponential
* ========================== */
/* Taken from https://gist.github.com/orlp/3551590 */
INLINE u64 math_pow_u64(u64 base, u8 exp) {
LOCAL_PERSIST const u8 highest_bit_set[] = {
0, 1, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 255, /* Anything past 63 is a guaranteed overflow with base > 1 */
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
};
u64 result = 1;
switch (highest_bit_set[exp]) {
case 255: {
/* 255 = overflow, return 0 */
if (base == 1) {
return 1;
}
// if (base == -1) {
// return 1 - 2 * (exp & 1);
// }
return 0;
} break;
case 6: {
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 5: {
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 4: {
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 3: {
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 2: {
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 1: {
if (exp & 1) result *= base;
} FALLTHROUGH;
default: return result;
}
}
/* From Quake III -
* https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/game/q_math.c#L552
*/
INLINE f32 math_rsqrt(f32 x)
{
const f32 three_halfs = 1.5f;
f32 x2 = x * 0.5f;
f32 y = x;
i32 i = *(i32 *)&y;
i = 0x5f3759df - (i >> 1);
y = *(f32 *)&i;
y *= three_halfs - (x2 * y * y); /* 1st iteration */
return y;
}
INLINE f32 math_sqrt(f32 x)
{
return x * math_rsqrt(x);
}
/* ========================== *
* Lerp
* ========================== */
INLINE f32 math_lerp_f32(f32 val0, f32 val1, f32 t)
{
return val0 + ((val1 - val0) * t);
}
/* ========================== *
* Trig
* ========================== */
/* Sine approximation using a parabola adjusted to minimize error, as described in
* https://web.archive.org/web/20080228213915/http://www.devmaster.net/forums/showthread.php?t=5784
*
* https://www.desmos.com/calculator/gbtjvt2we8
* c: adjustment weight
* f(x): original parabola
* g(x): adjusted parabola
* h(x): error
*/
INLINE f32 math_sin(f32 x)
{
const f32 c = 0.225;
/* Wrap to range [0, TAU] */
x -= (TAU * (f32)math_floor_f32(x / TAU));
/* Wrap to range [-PI, PI] */
x += (TAU * (x < -PI)) - (TAU * (x > PI));
/* Parabola */
f32 y = (4.0f/PI) * x + (-4.0f/(PI*PI)) * x * math_abs_f32(x);
/* Precision adjustment */
y = c * (y * math_abs_f32(y) - y) + y;
return y;
}
INLINE f32 math_cos(f32 x)
{
return math_sin(x + (TAU / 4.0f));
}
/* ========================== *
* V2
* ========================== */
INLINE struct v2 v2_mul(struct v2 a, f32 s)
{
return V2(a.x * s, a.y * s);
}
INLINE struct v2 v2_mul_v2(struct v2 a, struct v2 b)
{
return V2(a.x * b.x, a.y * b.y);
}
INLINE struct v2 v2_add(struct v2 a, struct v2 b)
{
return V2(a.x + b.x, a.y + b.y);
}
INLINE struct v2 v2_sub(struct v2 a, struct v2 b)
{
return V2(a.x - b.x, a.y - b.y);
}
INLINE f32 v2_len(struct v2 a)
{
return math_sqrt(a.x * a.x + a.y * a.y);
}
INLINE f32 v2_len_squared(struct v2 a)
{
return a.x * a.x + a.y * a.y;
}
INLINE struct v2 v2_perp(struct v2 a)
{
return V2(-a.y, a.x);
}
INLINE struct v2 v2_norm(struct v2 a)
{
f32 len_squared = v2_len_squared(a);
f32 r_sqrt = math_rsqrt(len_squared);
a.x *= r_sqrt;
a.y *= r_sqrt;
return a;
}
INLINE f32 v2_dot(struct v2 a, struct v2 b)
{
return a.x * b.x + a.y * b.y;
}
INLINE f32 v2_det(struct v2 a, struct v2 b)
{
return a.x * b.y - a.y * b.x;
}
INLINE f32 v2_distance(struct v2 a, struct v2 b)
{
f32 dx = b.x - a.x;
f32 dy = b.y - a.y;
return math_sqrt(dx * dx + dy * dy);
}
INLINE b32 v2_eq(struct v2 a, struct v2 b)
{
return a.x == b.x && a.y == b.y;
}
INLINE struct v2 v2_lerp(struct v2 val0, struct v2 val1, f32 t)
{
struct v2 res;
res.x = math_lerp_f32(val0.x, val1.x, t);
res.y = math_lerp_f32(val0.y, val1.y, t);
return res;
}
/* ========================== *
* Mat3x3
* ========================== */
INLINE struct mat3x3 mat3x3_from_translate(struct v2 v)
{
return (struct mat3x3) {
.e = {
{1, 0, 0},
{0, 1, 0},
{v.x, v.y, 1}
}
};
}
INLINE struct mat3x3 mat3x3_translate(struct mat3x3 m, struct v2 v)
{
m.e[2][0] = m.e[0][0] * v.x + m.e[1][0] * v.y + m.e[2][0];
m.e[2][1] = m.e[0][1] * v.x + m.e[1][1] * v.y + m.e[2][1];
m.e[2][2] = m.e[0][2] * v.x + m.e[1][2] * v.y + m.e[2][2];
return m;
}
INLINE struct mat3x3 mat3x3_rotate(struct mat3x3 m, f32 angle)
{
f32 c = math_cos(angle);
f32 s = math_sin(angle);
struct mat3x3 res = m;
f32 m00 = m.e[0][0], m10 = m.e[1][0],
m01 = m.e[0][1], m11 = m.e[1][1],
m02 = m.e[0][2], m12 = m.e[1][2];
res.e[0][0] = m00 * c + m10 * s;
res.e[0][1] = m01 * c + m11 * s;
res.e[0][2] = m02 * c + m12 * s;
res.e[1][0] = m00 * -s + m10 * c;
res.e[1][1] = m01 * -s + m11 * c;
res.e[1][2] = m02 * -s + m12 * c;
return res;
}
INLINE struct mat3x3 mat3x3_rotate_pivot(struct mat3x3 m, f32 angle, struct v2 pivot)
{
m = mat3x3_translate(m, V2(pivot.x, pivot.y)); /* Start pivot */
m = mat3x3_rotate(m, angle);
m = mat3x3_translate(m, V2(-pivot.x, -pivot.y)); /* End pivot */
return m;
}
INLINE struct mat3x3 mat3x3_scale(struct mat3x3 m, struct v3 v)
{
m.e[0][0] *= v.x;
m.e[0][1] *= v.x;
m.e[0][2] *= v.x;
m.e[1][0] *= v.y;
m.e[1][1] *= v.y;
m.e[1][2] *= v.y;
m.e[2][0] *= v.z;
m.e[2][1] *= v.z;
m.e[2][2] *= v.z;
return m;
}
INLINE struct mat3x3 mat3x3_scale_pivot(struct mat3x3 m, struct v3 v, struct v2 pivot)
{
m = mat3x3_translate(m, V2(pivot.x, pivot.y)); /* Start pivot */
m = mat3x3_scale(m, v);
m = mat3x3_translate(m, V2(-pivot.x, -pivot.y)); /* End pivot */
return m;
}
INLINE struct mat3x3 mat3x3_from_trs(struct v2 pos, f32 rot, struct v2 scale)
{
struct mat3x3 m = mat3x3_from_translate(pos);
m = mat3x3_rotate(m, rot);
m = mat3x3_scale(m, V3(scale.x, scale.y, 1));
return m;
}
INLINE struct mat3x3 mat3x3_from_trs_pivot(struct v2 pos, f32 rot, struct v2 scale, struct v2 pivot)
{
struct mat3x3 m = mat3x3_from_translate(pos);
m = mat3x3_translate(m, V2(pivot.x, pivot.y)); /* Start pivot */
m = mat3x3_rotate(m, rot);
m = mat3x3_scale(m, V3(scale.x, scale.y, 1));
m = mat3x3_translate(m, V2(-pivot.x, -pivot.y)); /* End pivot */
return m;
}
INLINE struct mat3x3 mat3x3_trs(struct mat3x3 m, struct v2 pos, f32 rot, struct v2 scale)
{
m = mat3x3_translate(m, pos);
m = mat3x3_rotate(m, rot);
m = mat3x3_scale(m, V3(scale.x, scale.y, 1));
return m;
}
INLINE struct mat3x3 mat3x3_trs_pivot(struct mat3x3 m, struct v2 pos, f32 rot, struct v2 scale , struct v2 pivot)
{
m = mat3x3_translate(m, V2(pos.x + pivot.x, pos.y + pivot.y)); /* Start pivot */
m = mat3x3_rotate(m, rot);
m = mat3x3_scale(m, V3(scale.x, scale.y, 1));
m = mat3x3_translate(m, V2(-pivot.x, -pivot.y)); /* End pivot */
return m;
}
INLINE struct v3 mat3x3_mul_v3(struct mat3x3 m, struct v3 v)
{
struct v3 res;
res.x = m.e[0][0] * v.x + m.e[1][0] * v.y + m.e[2][0] * v.z;
res.y = m.e[0][1] * v.x + m.e[1][1] * v.y + m.e[2][1] * v.z;
res.z = m.e[0][2] * v.x + m.e[1][2] * v.y + m.e[2][2] * v.z;
return res;
}
INLINE struct mat3x3 mat3x3_inverse(struct mat3x3 m)
{
f32 a = m.e[0][0], b = m.e[0][1], c = m.e[0][2],
d = m.e[1][0], e = m.e[1][1], f = m.e[1][2],
g = m.e[2][0], h = m.e[2][1], i = m.e[2][2];
struct mat3x3 res;
res.e[0][0] = e * i - f * h;
res.e[0][1] = -(b * i - h * c);
res.e[0][2] = b * f - e * c;
res.e[1][0] = -(d * i - g * f);
res.e[1][1] = a * i - c * g;
res.e[1][2] = -(a * f - d * c);
res.e[2][0] = d * h - g * e;
res.e[2][1] = -(a * h - g * b);
res.e[2][2] = a * e - b * d;
f32 det = 1.0f / (a * res.e[0][0] + b * res.e[1][0] + c * res.e[2][0]);
res = mat3x3_scale(res, V3(det, det, det));
return res;
}
INLINE struct v2 mat3x3_right(struct mat3x3 m)
{
return V2(m.e[0][0], m.e[0][1]);
}
INLINE struct v2 mat3x3_left(struct mat3x3 m)
{
return V2(-m.e[0][0], -m.e[0][1]);
}
INLINE struct v2 mat3x3_up(struct mat3x3 m)
{
return V2(-m.e[1][0], -m.e[1][1]);
}
INLINE struct v2 mat3x3_down(struct mat3x3 m)
{
return V2(m.e[1][0], m.e[1][1]);
}
/* ========================== *
* Mat4x4
* ========================== */
/* NOTE: Mat4x4 only used for projection matrix */
INLINE struct mat4x4 mat4x4_from_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far)
{
struct mat4x4 m = {0};
f32 rl = 1.0f / (right - left);
f32 tb = 1.0f / (top - bottom);
f32 fn = -1.0f / (far - near);
m.e[0][0] = 2.0f * rl;
m.e[1][1] = 2.0f * tb;
m.e[2][2] = 2.0f * fn;
m.e[3][0] = -(right + left) * rl;
m.e[3][1] = -(top + bottom) * tb;
m.e[3][2] = (far + near) * fn;
m.e[3][3] = 1.0f;
return m;
}
INLINE struct mat4x4 mat4x4_mul(struct mat4x4 m1, struct mat4x4 m2)
{
f32 a00 = m1.e[0][0], a01 = m1.e[0][1], a02 = m1.e[0][2], a03 = m1.e[0][3],
a10 = m1.e[1][0], a11 = m1.e[1][1], a12 = m1.e[1][2], a13 = m1.e[1][3],
a20 = m1.e[2][0], a21 = m1.e[2][1], a22 = m1.e[2][2], a23 = m1.e[2][3],
a30 = m1.e[3][0], a31 = m1.e[3][1], a32 = m1.e[3][2], a33 = m1.e[3][3],
b00 = m2.e[0][0], b01 = m2.e[0][1], b02 = m2.e[0][2], b03 = m2.e[0][3],
b10 = m2.e[1][0], b11 = m2.e[1][1], b12 = m2.e[1][2], b13 = m2.e[1][3],
b20 = m2.e[2][0], b21 = m2.e[2][1], b22 = m2.e[2][2], b23 = m2.e[2][3],
b30 = m2.e[3][0], b31 = m2.e[3][1], b32 = m2.e[3][2], b33 = m2.e[3][3];
struct mat4x4 res;
res.e[0][0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03;
res.e[0][1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03;
res.e[0][2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03;
res.e[0][3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03;
res.e[1][0] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13;
res.e[1][1] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13;
res.e[1][2] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13;
res.e[1][3] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13;
res.e[2][0] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23;
res.e[2][1] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23;
res.e[2][2] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23;
res.e[2][3] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23;
res.e[3][0] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33;
res.e[3][1] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33;
res.e[3][2] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33;
res.e[3][3] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33;
return res;
}
/* ========================== *
* Quad
* ========================== */
INLINE struct quad quad_from_rect(struct rect rect)
{
return (struct quad) {
(struct v2) { rect.x, rect.y }, /* Top left */
(struct v2) { rect.x + rect.width, rect.y }, /* Top right */
(struct v2) { rect.x + rect.width, rect.y + rect.height }, /* Bottom right */
(struct v2) { rect.x, rect.y + rect.height }, /* Bottom left */
};
}
INLINE struct quad quad_from_line(struct v2 start, struct v2 end, f32 thickness)
{
f32 width = thickness / 2.f;
struct v2 rel = v2_sub(end, start);
struct v2 dir = v2_norm(rel);
struct v2 dir_perp = v2_perp(dir);
struct v2 left = v2_mul(dir_perp, -width);
struct v2 right = v2_mul(dir_perp, width);
return (struct quad) {
.p1 = v2_add(start, left),
.p2 = v2_add(start, right),
.p3 = v2_add(end, right),
.p4 = v2_add(end, left)
};
}
INLINE struct quad quad_from_ray(struct v2 pos, struct v2 rel, f32 thickness)
{
struct v2 end = v2_add(pos, rel);
return quad_from_line(pos, end, thickness);
}
#endif

27
src/memory.c Normal file
View File

@ -0,0 +1,27 @@
#include "memory.h"
#if !CRTLIB
__attribute((section(".text.memcpy")))
void *memcpy(void *restrict dest, const void *restrict src, u64 n)
{
__prof;
/* TODO: Faster memcpy */
for (u64 i = 0; i < n; ++i) {
((u8 *)dest)[i] = ((u8 *)src)[i];
}
return dest;
}
__attribute((section(".text.memset")))
void *memset(void *dest, int c, u64 n)
{
__prof;
/* TODO: Faster memset */
for (u64 i = 0; i < (n); ++i) {
((u8 *)dest)[i] = c;
}
return dest;
}
#endif /* !CRTLIB */

18
src/memory.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef MEMORY_H
#define MEMORY_H
#if CRTLIB
# include <memory.h>
#endif
#define MEMZERO_STRUCT(ptr) MEMZERO(ptr, sizeof(*ptr))
#define MEMZERO_ARRAY(a) MEMZERO(a, sizeof(a))
#define MEMZERO(ptr, count) MEMSET(ptr, 0, count)
#define MEMCPY(dest, src, count) memcpy(dest, src, count)
#define MEMSET(ptr, val, count) memset(ptr, val, count)
void *memcpy(void *__restrict dest, const void *__restrict src, u64 n);
void *memset(void *dest, int c, u64 n);
#endif

482
src/mixer.c Normal file
View File

@ -0,0 +1,482 @@
#include "mixer.h"
#include "arena.h"
#include "scratch.h"
#include "sound.h"
#include "sys.h"
#include "playback.h"
#include "math.h"
#include "atomic.h"
/* TODO: Cap max sounds playing. */
/* Terminology:
*
* `Sample`: Once "PCM" data point representing the smallest unit of audio available for a single channel at a point in time.
* Examples:
* - Single 32 bit float output by mixer and consumed by playback API, that the API interprets as a sound sample for a single channel
* - Single 16 bit integer output by audio file decoder, that may represent a mono sound sample
*
* `Frame`: Represents a single data point of audio for all audio channels at a point in time.
* Examples:
* - Single 16 bit integer output by audio file decoder representing one mono sound sample
* - 2 16 bit integer samples output by audio file decoder representing two sound samples, one sample for each audio channel
* - 2 32 bit float samples output by mixer and consumed by playback API, one sample for each audio channel
*/
struct effect_data {
/* Spatialization */
f32 spatial_volume;
f32 spatial_pan;
};
struct mix {
struct mixer_track_handle track_handle;
b32 track_finished;
struct mixer_desc desc;
struct effect_data effect_data;
struct sound *source;
u64 source_pos;
};
struct track {
u64 gen;
/* Controlled via interface */
struct sound *sound;
struct mixer_desc desc;
/* Internal */
struct mix mix;
struct track *next;
struct track *prev;
};
GLOBAL struct {
struct sys_mutex mutex;
/* Listener */
struct v2 listener_pos;
struct v2 listener_dir;
/* Track list */
struct arena track_arena;
struct track *track_first_playing;
struct track *track_last_playing;
u64 track_playing_count;
struct track *track_first_free;
} L = { 0 } DEBUG_LVAR(L_mixer);
/* ========================== *
* Startup
* ========================== */
void mixer_startup(void)
{
L.track_arena = arena_alloc(GIGABYTE(64));
L.mutex = sys_mutex_alloc();
L.listener_pos = V2(0, 0);
L.listener_dir = V2(0, -1);
}
/* ========================== *
* Track
* ========================== */
INTERNAL struct mixer_track_handle track_to_handle(struct track *track)
{
return (struct mixer_track_handle) {
.gen = track->gen,
.data = track
};
}
INTERNAL struct track *track_from_handle(struct mixer_track_handle handle)
{
struct track *track = (struct track *)handle.data;
if (track && track->gen == handle.gen) {
return track;
} else {
return NULL;
}
}
INTERNAL struct track *track_alloc_assume_locked(struct sound *sound)
{
sys_mutex_assert_locked(&L.mutex);
struct track *track = NULL;
if (L.track_first_free) {
/* Take from free list */
track = L.track_first_free;
struct track *next_free = track->next;
L.track_first_free = next_free;
if (next_free) {
next_free->prev = NULL;
}
*track = (struct track) { 0 };
} else {
/* Allocate new */
track = arena_push_zero(&L.track_arena, struct track);
track->gen = 1;
}
track->sound = sound;
track->mix.source = sound;
track->mix.track_handle = track_to_handle(track);
/* Append to playing list */
struct track *prev = L.track_last_playing;
if (prev) {
prev->next = track;
} else {
L.track_first_playing = track;
}
L.track_last_playing = track;
track->prev = prev;
++L.track_playing_count;
return track;
}
INTERNAL void track_release_assume_locked(struct track *track)
{
sys_mutex_assert_locked(&L.mutex);
/* Remove from playing list */
struct track *prev = track->prev;
struct track *next = track->next;
if (prev) {
prev->next = next;
} else {
/* Track was first in list */
L.track_first_playing = next;
}
if (next) {
next->prev = prev;
} else {
/* Track was last in list */
L.track_last_playing = prev;
}
--L.track_playing_count;
++track->gen;
/* Add to free list */
track->prev = NULL;
track->next = L.track_first_free;
if (L.track_first_free) {
L.track_first_free->prev = track;
}
L.track_first_free = track;
}
/* ========================== *
* Interface
* ========================== */
/* TODO: Rework interface to take "mixer_cmd"s instead of
* directly modifying tracks. */
struct mixer_track_handle mixer_play(struct sound *sound)
{
return mixer_play_ex(sound, MIXER_DESC());
}
struct mixer_track_handle mixer_play_ex(struct sound *sound, struct mixer_desc desc)
{
struct track *track;
{
sys_mutex_lock(&L.mutex);
track = track_alloc_assume_locked(sound);
track->desc = desc;
sys_mutex_unlock(&L.mutex);
}
return track_to_handle(track);
}
/* NOTE: This is quite inefficient. */
struct mixer_desc mixer_track_get(struct mixer_track_handle handle)
{
struct mixer_desc res = { 0 };
struct track *track = track_from_handle(handle);
if (track) {
/* TODO: Only lock mutex on track itself or something */
sys_mutex_lock(&L.mutex);
/* Confirm handle is still valid now that we're locked */
track = track_from_handle(handle);
if (track) {
res = track->desc;
}
sys_mutex_unlock(&L.mutex);
}
return res;
}
/* NOTE: This is quite inefficient. */
void mixer_track_set(struct mixer_track_handle handle, struct mixer_desc desc)
{
struct track *track = track_from_handle(handle);
if (track) {
/* TODO: Only lock mutex on track itself or something */
sys_mutex_lock(&L.mutex);
/* Confirm handle is still valid now that we're locked */
track = track_from_handle(handle);
if (track) {
track->desc = desc;
}
sys_mutex_unlock(&L.mutex);
}
}
void mixer_set_listener(struct v2 pos, struct v2 dir)
{
sys_mutex_lock(&L.mutex);
{
L.listener_pos = pos;
L.listener_dir = dir;
}
sys_mutex_unlock(&L.mutex);
}
/* ========================== *
* Update
* ========================== */
INTERNAL i16 sample_sound(struct sound *sound, u64 sample_pos, b32 wrap)
{
if (wrap) {
return sound->pcm.samples[sample_pos % sound->pcm.count];
} else if (sample_pos < sound->pcm.count) {
return sound->pcm.samples[sample_pos];
} else {
return 0;
}
}
/* To be called once per audio playback interval */
struct mixed_pcm_f32 mixer_update(struct arena *arena, u64 frame_count)
{
__prof;
struct temp_arena scratch = scratch_begin(arena);
struct mixed_pcm_f32 res = { 0 };
res.count = frame_count * 2;
res.samples = arena_push_array_zero(arena, f32, res.count);
struct v2 listener_pos = V2(0, 0);
struct v2 listener_dir = V2(0, 0);
/* Create temp array of mixes */
struct mix **mixes = NULL;
u64 mixes_count = 0;
sys_mutex_lock(&L.mutex);
{
/* Read listener info */
listener_pos = L.listener_pos;
listener_dir = L.listener_dir;
/* Update & read mixes */
mixes = arena_push_array(scratch.arena, struct mix *, L.track_playing_count);
for (struct track *track = L.track_first_playing; track; track = track->next) {
__profscope(prepare_track);
struct mix *mix = &track->mix;
mix->desc = track->desc;
mixes[mixes_count++] = mix;
}
}
sys_mutex_unlock(&L.mutex);
for (u64 mix_index = 0; mix_index < mixes_count; ++mix_index) {
__profscope(mix_track);
struct mix *mix = mixes[mix_index];
if (mix->source->pcm.count <= 0) {
/* Skip empty sounds */
continue;
}
struct sound *source = mix->source;
struct mixer_desc desc = mix->desc;
struct effect_data *effect_data = &mix->effect_data;
b32 source_is_stereo = source->flags & SOUND_FLAG_STEREO;
f32 speed = max_f32(0, desc.speed);
/* Determine sample range */
u64 source_samples_count = 0;
if (source_is_stereo) {
source_samples_count = frame_count * 2;
/* Round <samples_count * speed> to nearest frame boundary (nearest multiple of 2) */
source_samples_count = (u64)math_ceil_f32((f32)source_samples_count * speed);
source_samples_count &= ~1;
} else {
source_samples_count = frame_count;
/* Round <samples_count * speed> to nearest sample */
source_samples_count = (u64)math_round_f32((f32)source_samples_count * speed);
}
u64 source_sample_pos_start = mix->source_pos;
u64 source_sample_pos_end = source_sample_pos_start + source_samples_count;
if (source_sample_pos_end >= source->pcm.count) {
if (desc.looping) {
source_sample_pos_end = source_sample_pos_end % source->pcm.count;
} else {
source_sample_pos_end = source->pcm.count;
mix->track_finished = true;
}
}
u64 source_frames_count = source_is_stereo ? source_samples_count / 2 : source_samples_count;
u64 source_frame_pos_start = source_is_stereo ? source_sample_pos_start / 2 : source_sample_pos_start;
mix->source_pos = source_sample_pos_end;
struct mixed_pcm_f32 mix_pcm = {
.count = res.count,
.samples = arena_push_array_zero(scratch.arena, f32, res.count)
};
/* ========================== *
* Resample
* ========================== */
/* Transform 16 bit source -> 32 bit stereo at output duration */
{
__profscope(resample);
f32 *out_samples = mix_pcm.samples;
u64 out_frames_count = mix_pcm.count / 2;
/* TODO: Fast path for 1:1 copy when speed = 1.0? */
/* TODO: Optimize */
if (source_is_stereo) {
/* 16 bit Stereo -> 32 bit Stereo */
for (u64 out_frame_pos = 0; out_frame_pos < out_frames_count; ++out_frame_pos) {
f32 in_frame_pos_exact = source_frame_pos_start + (((f32)out_frame_pos / (f32)out_frames_count) * (f32)source_frames_count);
u32 in_frame_pos_prev = math_floor_f32(in_frame_pos_exact);
u32 in_frame_pos_next = math_ceil_f32(in_frame_pos_exact);
/* Sample source */
f32 sample1_prev = sample_sound(source, (in_frame_pos_prev * 2) + 0, desc.looping) * (1.f / 32768.f);
f32 sample1_next = sample_sound(source, (in_frame_pos_next * 2) + 0, desc.looping) * (1.f / 32768.f);
f32 sample2_prev = sample_sound(source, (in_frame_pos_prev * 2) + 1, desc.looping) * (1.f / 32768.f);
f32 sample2_next = sample_sound(source, (in_frame_pos_next * 2) + 1, desc.looping) * (1.f / 32768.f);
/* Lerp */
f32 t = in_frame_pos_exact - (f32)in_frame_pos_prev;
f32 sample1 = math_lerp_f32(sample1_prev, sample1_next, t);
f32 sample2 = math_lerp_f32(sample2_prev, sample2_next, t);
out_samples[(out_frame_pos * 2) + 0] += sample1;
out_samples[(out_frame_pos * 2) + 1] += sample2;
}
} else {
/* 16 bit Mono -> 32 bit Stereo */
for (u64 out_frame_pos = 0; out_frame_pos < out_frames_count; ++out_frame_pos) {
f32 in_frame_pos_exact = source_frame_pos_start + (((f32)out_frame_pos / (f32)out_frames_count) * (f32)source_frames_count);
u32 in_frame_pos_prev = math_floor_f32(in_frame_pos_exact);
u32 in_frame_pos_next = math_ceil_f32(in_frame_pos_exact);
/* Sample source */
f32 sample_prev = sample_sound(source, in_frame_pos_prev, desc.looping) * (1.f / 32768.f);
f32 sample_next = sample_sound(source, in_frame_pos_next, desc.looping) * (1.f / 32768.f);
/* Lerp */
f32 t = (f32)in_frame_pos_exact - in_frame_pos_prev;
f32 sample = math_lerp_f32(sample_prev, sample_next, t);
out_samples[(out_frame_pos * 2) + 0] += sample;
out_samples[(out_frame_pos * 2) + 1] += sample;
}
}
}
/* ========================== *
* Spatialize
* ========================== */
if (desc.flags & MIXER_FLAG_SPATIALIZE) {
__profscope(spatialize);
/* Algorithm constants */
const f32 rolloff_height = 1.2f;
const f32 rolloff_scale = 6.0f;
const f32 pan_scale = 0.75;
struct v2 pos = desc.pos;
/* If sound pos = listener pos, pretend sound is close in front of listener. */
if (v2_eq(listener_pos, pos)) {
pos = v2_add(listener_pos, v2_mul(listener_dir, 0.001));
}
struct v2 sound_rel = v2_sub(pos, listener_pos);
struct v2 sound_rel_dir = v2_norm(sound_rel);
/* Calculate volume */
f32 volume_start = effect_data->spatial_volume;
f32 volume_end;
{
/* https://www.desmos.com/calculator/c2h941hobz
* h = `rolloff_height`
* s = `rolloff_scale`
*/
f32 dist = v2_len(sound_rel);
f32 v = (dist / rolloff_scale) + 1.0f;
volume_end = rolloff_height * (1.0f / (v * v));
}
effect_data->spatial_volume = volume_end;
/* Calculate pan */
f32 pan_start = effect_data->spatial_pan;
f32 pan_end = v2_det(listener_dir, sound_rel_dir) * pan_scale;
effect_data->spatial_pan = pan_end;
/* Spatialize samples */
for (u64 frame_pos = 0; frame_pos < frame_count; ++frame_pos) {
f32 t = (f32)frame_pos / (f32)(frame_count - 1);
f32 volume = math_lerp_f32(volume_start, volume_end, t);
f32 pan = math_lerp_f32(pan_start, pan_end, t);
u64 sample1_index = frame_pos * 2;
u64 sample2_index = sample1_index + 1;
f32 sample_mono = ((mix_pcm.samples[sample1_index + 0] / 2.0f) + (mix_pcm.samples[sample2_index] / 2.0f)) * volume;
mix_pcm.samples[sample1_index] = sample_mono * (1.0f - pan);
mix_pcm.samples[sample2_index] = sample_mono * (1.0f + pan);
}
}
/* ========================== *
* Mix into result
* ========================== */
for (u64 i = 0; i < mix_pcm.count; ++i) {
res.samples[i] += mix_pcm.samples[i] * desc.volume;
}
}
sys_mutex_lock(&L.mutex);
{
__profscope(update_track_effect_data);
for (u64 i = 0; i < mixes_count; ++i) {
struct mix *mix = mixes[i];
struct track *track = track_from_handle(mix->track_handle);
if (track) {
if (mix->track_finished) {
/* Release finished tracks */
track_release_assume_locked(track);
}
}
}
}
sys_mutex_unlock(&L.mutex);
scratch_end(scratch);
return res;
}

54
src/mixer.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef MIXER_H
#define MIXER_H
struct sound;
#define MIXER_FLAG_NONE 0x0
#define MIXER_FLAG_SPATIALIZE 0x1
#define MIXER_DESC(...) ((struct mixer_desc) { \
.flags = 0, \
\
.volume = 1.0, \
.speed = 1.0, \
.looping = false, \
\
.pos = V2(0, 0), \
\
__VA_ARGS__ \
})
struct mixer_desc {
u32 flags;
f32 volume; /* 0 -> 1.0+ scale */
f32 speed; /* 0 -> 1.0+ scale */
b32 looping;
/* MIXER_FLAG_SPATIALIZE */
struct v2 pos;
};
struct mixer_track_handle {
u64 gen;
void *data;
};
/* Stereo mix of 32 bit float samples */
struct mixed_pcm_f32 {
u64 count;
f32 *samples;
};
void mixer_startup(void);
/* Interface */
struct mixer_track_handle mixer_play(struct sound *sound);
struct mixer_track_handle mixer_play_ex(struct sound *sound, struct mixer_desc desc);
struct mixer_desc mixer_track_get(struct mixer_track_handle handle);
void mixer_track_set(struct mixer_track_handle handle, struct mixer_desc desc);
void mixer_set_listener(struct v2 pos, struct v2 dir);
/* Mixing */
struct mixed_pcm_f32 mixer_update(struct arena *arena, u64 frame_request_count);
void mixer_advance(u64 frames_written_count);
#endif

14
src/mp3.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef MP3
#define MP3
#define MP3_DECODE_FLAG_NONE 0x00
#define MP3_DECODE_FLAG_STEREO 0x01
struct mp3_decode_result {
struct pcm pcm;
b32 success;
};
struct mp3_decode_result mp3_decode(struct arena *arena, struct buffer encoded, u32 flags);
#endif

135
src/mp3_mmf.c Normal file
View File

@ -0,0 +1,135 @@
/* ========================== *
* Microsoft Media Foundation (MMF) mp3 decoder
* ========================== */
#include "mp3.h"
#include "arena.h"
#include "playback.h"
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <uuids.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <Shlwapi.h>
struct mp3_decode_result mp3_decode(struct arena *arena, struct buffer encoded, u32 flags)
{
struct mp3_decode_result res = { 0 };
u64 sample_rate = PLAYBACK_SAMPLE_RATE;
u64 bytes_per_sample = 2;
u64 channel_count;
u32 channel_mask;
if (flags & MP3_DECODE_FLAG_STEREO) {
channel_count = 2;
channel_mask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
} else {
channel_count = 1;
channel_mask = SPEAKER_FRONT_CENTER;
}
/* ========================== *
* Startup
* ========================== */
MFStartup(MF_VERSION, MFSTARTUP_LITE);
/* Create IStream from buffer */
IStream *i_stream = SHCreateMemStream(encoded.data, encoded.size);
/* Create IMFByteStream from IStream */
IMFByteStream *byte_stream = NULL;
MFCreateMFByteStreamOnStream(i_stream, &byte_stream);
/* Create reader from IMFByteStream */
IMFSourceReader *reader;
MFCreateSourceReaderFromByteStream(byte_stream, NULL, &reader);
/* ========================== *
* Get media type
* ========================== */
/* Read only first audio stream */
IMFSourceReader_SetStreamSelection(reader, (DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE);
IMFSourceReader_SetStreamSelection(reader, (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
WAVEFORMATEXTENSIBLE format = {
.Format = {
.wFormatTag = WAVE_FORMAT_EXTENSIBLE,
.nChannels = (WORD)channel_count,
.nSamplesPerSec = (WORD)sample_rate,
.nAvgBytesPerSec = (DWORD)(sample_rate * channel_count * bytes_per_sample),
.nBlockAlign = (WORD)(channel_count * bytes_per_sample),
.wBitsPerSample = (WORD)(8 * bytes_per_sample),
.cbSize = sizeof(format) - sizeof(format.Format)
},
.Samples.wValidBitsPerSample = 8 * bytes_per_sample,
.dwChannelMask = channel_mask,
.SubFormat = MEDIASUBTYPE_PCM
};
/* Media Foundation in Windows 8+ allows reader to convert output to different format than native */
IMFMediaType *type;
MFCreateMediaType(&type);
MFInitMediaTypeFromWaveFormatEx(type, &format.Format, sizeof(format));
IMFSourceReader_SetCurrentMediaType(reader, (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, type);
IMFMediaType_Release(type);
/* ========================== *
* Read
* ========================== */
arena_align(arena, ALIGNOF(i16));
res.pcm.samples = (i16 *)arena_dry_push(arena, u8);
u64 sample_bytes_read = 0;
while (true) {
IMFSample *sample;
DWORD sample_flags = 0;
HRESULT hr = IMFSourceReader_ReadSample(reader, (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &sample_flags, NULL, &sample);
if (FAILED(hr)) {
break;
}
/* Check if done */
if (sample_flags & MF_SOURCE_READERF_ENDOFSTREAM) {
res.success = true;
break;
}
ASSERT(sample_flags == 0);
/* Read samples */
IMFMediaBuffer *buffer;
IMFSample_ConvertToContiguousBuffer(sample, &buffer);
BYTE *data;
DWORD size;
IMFMediaBuffer_Lock(buffer, &data, NULL, &size);
{
i16 *cursor = (i16 *)arena_push_array(arena, u8, size);
MEMCPY(cursor, data, size);
sample_bytes_read += size;
}
IMFMediaBuffer_Unlock(buffer);
IMediaBuffer_Release(buffer);
IMFSample_Release(sample);
}
res.pcm.count = sample_bytes_read / bytes_per_sample;
/* ========================== *
* Cleanup
* ========================== */
IMFSourceReader_Release(reader);
IMFByteStream_Close(byte_stream);
IStream_Release(i_stream);
MFShutdown();
return res;
}

9
src/playback.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef PLAYBACK_H
#define PLAYBACK_H
#define PLAYBACK_SAMPLE_RATE 48000
void playback_startup(void);
void playback_shutdown(void);
#endif

234
src/playback_wasapi.c Normal file
View File

@ -0,0 +1,234 @@
/* ========================== *
* WASAPI backend for audio playback
*
* Based on mmozeiko's WASAPI example
* https://gist.github.com/mmozeiko/5a5b168e61aff4c1eaec0381da62808f#file-win32_wasapi-h
* ========================== */
#include "playback.h"
#include "scratch.h"
#include "sys.h"
#include "mixer.h"
#include "app.h"
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <initguid.h>
#include <Windows.h>
#include <objbase.h>
#include <uuids.h>
#include <avrt.h>
#include <Audioclient.h>
#include <mmdeviceapi.h>
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e);
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6);
DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2);
DEFINE_GUID(IID_IAudioClient3, 0x7ed4ee07, 0x8e67, 0x4cd4, 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42);
DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2);
struct wasapi_buffer {
u32 frames_count;
u8 *frames;
};
GLOBAL struct {
b32 shutdown;
struct sys_thread playback_thread;
IAudioClient *client;
HANDLE event;
IAudioRenderClient *playback;
WAVEFORMATEX *buffer_format;
u32 buffer_frames;
HANDLE mmtc_handle;
} L = { 0 } DEBUG_LVAR(L_playback_wasapi);
/* ========================== *
* Initialize
* ========================== */
INTERNAL void wasapi_initialize(void)
{
/* https://learn.microsoft.com/en-us/windows/win32/procthread/multimedia-class-scheduler-service#registry-settings */
DWORD task = 0;
L.mmtc_handle = AvSetMmThreadCharacteristicsW(L"Pro Audio", &task);
u64 sample_rate = PLAYBACK_SAMPLE_RATE;
u64 channel_count = 2;
u32 channel_mask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
/* Create enumerator to get audio device */
IMMDeviceEnumerator *enumerator;
CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID *)&enumerator);
/* Get default playback device */
IMMDevice *device;
IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, eRender, eConsole, &device);
IMMDeviceEnumerator_Release(enumerator);
/* Create audio client for device */
IMMDevice_Activate(device, &IID_IAudioClient, CLSCTX_ALL, NULL, (LPVOID *)&L.client);
IMMDevice_Release(device);
WAVEFORMATEXTENSIBLE format_ex = {
.Format = {
.wFormatTag = WAVE_FORMAT_EXTENSIBLE,
.nChannels = (WORD)channel_count,
.nSamplesPerSec = (WORD)sample_rate,
.nAvgBytesPerSec = (DWORD)(sample_rate * channel_count * sizeof(f32)),
.nBlockAlign = (WORD)(channel_count * sizeof(f32)),
.wBitsPerSample = (WORD)(8 * sizeof(f32)),
.cbSize = sizeof(format_ex) - sizeof(format_ex.Format),
},
.Samples.wValidBitsPerSample = 8 * sizeof(f32),
.dwChannelMask = channel_mask,
.SubFormat = MEDIASUBTYPE_IEEE_FLOAT,
};
WAVEFORMATEX *wfx = &format_ex.Format;
#if 1
b32 client_initialized = FALSE;
IAudioClient3 *client3;
if (SUCCEEDED(IAudioClient_QueryInterface(L.client, &IID_IAudioClient3, (LPVOID *)&client3))) {
/* From Martins: Minimum buffer size will typically be 480 samples (10msec @ 48khz)
* but it can be 128 samples (2.66 msec @ 48khz) if driver is properly installed
* see bullet-point instructions here: https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/low-latency-audio#measurement-tools
*/
UINT32 default_period_samples, fundamental_period_samples, min_period_samples, max_period_samples;
IAudioClient3_GetSharedModeEnginePeriod(client3, wfx, &default_period_samples, &fundamental_period_samples, &min_period_samples, &max_period_samples);
const DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
if (SUCCEEDED(IAudioClient3_InitializeSharedAudioStream(client3, flags, min_period_samples, wfx, NULL))) {
client_initialized = TRUE;
}
IAudioClient3_Release(client3);
}
#else
b32 client_initialized = false;
#endif
if (!client_initialized) {
/* Get duration for shared-mode streams, this will typically be 480 samples (10msec @ 48khz) */
REFERENCE_TIME duration;
IAudioClient_GetDevicePeriod(L.client, &duration, NULL);
/* Initialize audio playback
*
* NOTE:
* Passing AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM will tell WASAPI to
* always convert to native mixing format. This may introduce latency
* but allows for any input format.
*/
const DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
IAudioClient_Initialize(L.client, AUDCLNT_SHAREMODE_SHARED, flags, duration, 0, wfx, NULL);
}
IAudioClient_GetMixFormat(L.client, &L.buffer_format);
/* Set up event handler to wait on */
L.event = CreateEventW(NULL, FALSE, FALSE, NULL);
IAudioClient_SetEventHandle(L.client, L.event);
/* Get playback client */
IAudioClient_GetService(L.client, &IID_IAudioRenderClient, (LPVOID *)&L.playback);
/* Start the playback */
IAudioClient_Start(L.client);
/* Get audio buffer size in samples */
IAudioClient_GetBufferSize(L.client, &L.buffer_frames);
}
/* ========================== *
* Update
* ========================== */
INTERNAL struct wasapi_buffer wasapi_update_begin(void)
{
__prof;
struct wasapi_buffer wspbuf = { 0 };
/* Wait */
{
__profscope(wasapi_wait_on_event);
WaitForSingleObject(L.event, INFINITE);
}
/* Get padding frames */
u32 padding_frames;
IAudioClient_GetCurrentPadding(L.client, &padding_frames);
/* Get output buffer from WASAPI */
wspbuf.frames_count = 0;
if (padding_frames <= L.buffer_frames) {
wspbuf.frames_count = L.buffer_frames - padding_frames;
}
IAudioRenderClient_GetBuffer(L.playback, wspbuf.frames_count, &wspbuf.frames);
return wspbuf;
}
INTERNAL void wasapi_update_end(struct wasapi_buffer *wspbuf, struct mixed_pcm_f32 src)
{
__prof;
u32 frames_in_source = src.count / 2;
u32 frames_in_output = wspbuf->frames_count;
u32 flags = 0;
if (frames_in_source == frames_in_output) {
/* Copy bytes to output */
u32 bytes_per_sample = L.buffer_format->nBlockAlign / L.buffer_format->nChannels;
u32 write_size = frames_in_source * 2 * bytes_per_sample;
MEMCPY(wspbuf->frames, src.samples, write_size);
} else {
/* Submit silence if not enough samples */
flags = AUDCLNT_BUFFERFLAGS_SILENT;
/* This shouldn't occur, mixer should be generating samples equivilent
* to value returned from `playback_update_begin`. */
ASSERT(false);
}
#if !AUDIO_ENABLED
flags = AUDCLNT_BUFFERFLAGS_SILENT;
#endif
/* Submit output buffer to WASAPI */
IAudioRenderClient_ReleaseBuffer(L.playback, frames_in_source, flags);
__profframe("Audio");
}
/* ========================== *
* Startup
* ========================== */
INTERNAL void playback_thread_entry_point(void *arg)
{
(UNUSED)arg;
/* FIXME: If playback fails at any point and mixer stops advancing, we
* need to halt mixer to prevent memory leak when sounds are played. */
while (!L.shutdown) {
struct temp_arena scratch = scratch_begin_no_conflict();
struct wasapi_buffer wspbuf = wasapi_update_begin();
struct mixed_pcm_f32 pcm = mixer_update(scratch.arena, wspbuf.frames_count);
wasapi_update_end(&wspbuf, pcm);
scratch_end(scratch);
}
}
void playback_startup(void)
{
wasapi_initialize();
L.playback_thread = sys_thread_init(&playback_thread_entry_point, NULL, STR("[P3] Audio thread"));
}
void playback_shutdown(void)
{
L.shutdown = true;
sys_thread_join(&L.playback_thread);
}

85
src/renderer.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef RENDERER_H
#define RENDERER_H
struct sys_window;
struct texture;
#define RENDERER_TEXTURE_MAX_WIDTH 16384
#define RENDERER_TEXTURE_MAX_HEIGHT 16384
typedef u32 vidx;
struct renderer_canvas;
struct renderer_handle {
u64 v[1];
};
/* ========================== *
* Shaders
* ========================== */
enum shader_kind {
SHADER_NONE,
SHADER_TEXTURE,
NUM_SHADERS
};
struct texture_shader_parameters {
struct renderer_handle texture;
};
struct texture_shader_vertex {
struct v2 pos;
struct v2 uv;
u32 color;
} PACKED;
/* ========================== *
* Startup
* ========================== */
void renderer_startup(struct sys_window *window);
/* ========================== *
* Canvas
* ========================== */
struct renderer_canvas *renderer_canvas_alloc(void);
void renderer_canvas_release(struct renderer_canvas *canvas);
/* Set the canvas view matrix to be used when presenting the canvas.
* NOTE: `view` should be in row-major order.
*/
void renderer_canvas_set_view(struct renderer_canvas *canvas, struct mat3x3 view);
/* Pushes array of vertices based on `vertices_count` & `indices_count`.
* Sets `vertices_out` and `indices_out` to start of the pushed arrays, to be filled out by the caller.
* Returns the index of the first vertex. Each inserted index should be incremented by this.
*
* NOTE: This should be preceded by an `ensure_cmd` call to ensure that the correct vertex types
* are being pushed.
*/
u32 renderer_canvas_push_vertices(struct renderer_canvas *canvas, u8 **vertices_out, vidx **indices_out, u32 vertices_count, u32 indices_count);
/* Checks the currently active draw command. Ensures a new command is created
* if it doesn't match the expected type (otherwise the command can be re-used
* for batching). */
void renderer_canvas_ensure_texture_cmd(struct renderer_canvas *canvas, struct texture_shader_parameters params);
void renderer_canvas_send_to_gpu(struct renderer_canvas *canvas);
void renderer_canvas_present(struct renderer_canvas **canvases, u32 canvases_count, f32 viewport_width, f32 viewport_height, i32 vsync);
/* ========================== *
* Texture
* ========================== */
struct renderer_handle renderer_texture_alloc(struct image_rgba data);
void renderer_texture_release(struct renderer_handle handle);
#endif

1069
src/renderer_d3d11.c Normal file

File diff suppressed because it is too large Load Diff

71
src/resource.c Normal file
View File

@ -0,0 +1,71 @@
#include "resource.h"
#include "arena.h"
#include "tar.h"
#include "incbin.h"
#if RESOURCES_EMBEDDED
#include "inc.h"
/* Add resource data to binary */
GLOBAL struct {
struct arena arena;
struct tar_archive archive;
} L = { 0 } DEBUG_LVAR(L_resource);
#endif
void resource_startup(void)
{
#if RESOURCES_EMBEDDED
struct buffer embedded_data = inc_res_tar();
//struct buffer embedded_data = ((struct buffer) { (u8 *)(_incbin_res_tar_end) - (u8 *)(_incbin_res_tar_start), (u8 *)_incbin_res_tar_start });;
L.arena = arena_alloc(GIGABYTE(64));
if (embedded_data.size <= 0) {
sys_panic(STR("No embedded resources found"));
}
L.archive = tar_parse(&L.arena, embedded_data, STR("res/"));
#else
/* Ensure we have the right working directory */
if (!sys_is_dir(STR("res"))) {
sys_panic(STR("Resource directory \"res\" not found"));
}
#endif
}
b32 resource_exists(struct string path)
{
#if RESOURCES_EMBEDDED
struct tar_entry *entry = tar_get(&L.archive, path);
return entry && !entry->is_dir;
#else
return sys_is_file(path);
#endif
}
struct resource resource_open(struct string path)
{
#if RESOURCES_EMBEDDED
struct tar_entry *entry = tar_get(&L.archive, path);
return (struct resource) {
.bytes = entry ? entry->buff : BUFFER(0, 0)
};
#else
struct sys_file file = sys_file_open_read(path);
struct sys_file_map file_map = sys_file_map_open_read(file);
struct buffer bytes = sys_file_map_data(file_map);
return (struct resource) {
.bytes = bytes,
.file = file,
.file_map = file_map
};
#endif
}
#if !RESOURCES_EMBEDDED
void resource_close(struct resource res)
{
sys_file_map_close(res.file_map);
sys_file_close(res.file);
}
#endif

29
src/resource.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef RESOURCE_H
#define RESOURCE_H
#include "sys.h"
/* Represents raw bytes that can back a resource. This can be file data or embedded
* data in the executable. */
struct resource {
struct buffer bytes;
#if !RESOURCES_EMBEDDED
struct sys_file file;
struct sys_file_map file_map;
#endif
};
void resource_startup(void);
b32 resource_exists(struct string path);
struct resource resource_open(struct string path);
#if RESOURCES_EMBEDDED
/* Embedded resources don't need to be closed */
#define resource_close(res) (UNUSED)res
#else
void resource_close(struct resource res);
#endif
#endif

134
src/scratch.h Normal file
View File

@ -0,0 +1,134 @@
#ifndef SCRATCH_H
#define SCRATCH_H
#include "arena.h"
#include "sys.h"
#define SCRATCH_ARENAS_PER_THREAD 2
#define SCRATCH_ARENA_RESERVE (GIGABYTE(64))
struct scratch_context {
struct arena arenas[SCRATCH_ARENAS_PER_THREAD];
#if RTC
u64 next_scratch_id;
u64 scratch_id_stack[16384];
u64 scratch_id_stack_count;
#endif
};
/* Any arena parameters in the calling function's context should be passed into this
* function as a potential `conflict`. This is to prevent conflicts when the
* context's arena is itself a scratch arena (since parameterized arenas are
* often used to allocate persistent results for the caller).
*
* Call `scratch_begin_no_conflict` instead if there is no arena in the current
* context that could potentially be a scratch arena. */
#define scratch_begin(c) _scratch_begin(c)
INLINE struct temp_arena _scratch_begin(struct arena *potential_conflict)
{
/* This function is currently hard-coded to support 2 scratch arenas */
CT_ASSERT(SCRATCH_ARENAS_PER_THREAD == 2);
/* Use `scratch_begin_no_conflict` if no conflicts are present */
ASSERT(potential_conflict != NULL);
struct scratch_context *ctx = sys_thread_get_scratch_context();
struct arena *scratch = &ctx->arenas[0];
if (potential_conflict && scratch->base == potential_conflict->base) {
scratch = &ctx->arenas[1];
}
struct temp_arena temp = arena_push_temp(scratch);
#if RTC
if (ctx->scratch_id_stack_count >= ARRAY_COUNT(ctx->scratch_id_stack)) {
sys_panic(STR("Max debug scratch depth reached"));
}
temp.scratch_id = ctx->next_scratch_id++;
ctx->scratch_id_stack[ctx->scratch_id_stack_count++] = temp.scratch_id;
#endif
return temp;
}
/* This macro declares an unused "arena" variable that will error if an existing "arena"
* variable is present (due to shadowing). This is for catching obvious cases of
* `scratch_begin_no_conflict` getting called when an `arena` variable already
* exists in the caller's context (`scratch_begin(arena)` should be called
* instead). */
#define scratch_begin_no_conflict() \
_scratch_begin_no_conflict(); \
do { \
struct arena *arena = NULL; \
(UNUSED)arena; \
} while (0)
INLINE struct temp_arena _scratch_begin_no_conflict(void)
{
struct scratch_context *ctx = sys_thread_get_scratch_context();
struct arena *scratch = &ctx->arenas[0];
struct temp_arena temp = arena_push_temp(scratch);
#if RTC
if (ctx->scratch_id_stack_count >= ARRAY_COUNT(ctx->scratch_id_stack)) {
sys_panic(STR("Max debug scratch depth reached"));
}
temp.scratch_id = ctx->next_scratch_id++;
ctx->scratch_id_stack[ctx->scratch_id_stack_count++] = temp.scratch_id;
#endif
return temp;
}
INLINE void scratch_end(struct temp_arena scratch_temp)
{
#if RTC
struct scratch_context *ctx = sys_thread_get_scratch_context();
if (ctx->scratch_id_stack_count > 0) {
u64 scratch_id = scratch_temp.scratch_id;
u64 expected_id = ctx->scratch_id_stack[--ctx->scratch_id_stack_count];
/* This assertion exists to catch cases where a scratch_end was forgotten.
* It will fail if a scratch arena is reset out of order.
* IE there is a missing scratch_end somewhere on a different scratch
* arena (one that was created between the scratch_begin & the
* scratch_end of the arena being reset here). */
ASSERT(scratch_id == expected_id);
}
#endif
arena_pop_temp(scratch_temp);
}
INLINE void scratch_end_and_decommit(struct temp_arena scratch_temp)
{
scratch_end(scratch_temp);
/* Disabled for now */
// arena_decommit_unused_blocks(scratch_temp.arena);
}
/* ========================== *
* Scratch context
* ========================== */
INLINE struct scratch_context scratch_context_alloc(void)
{
struct scratch_context ctx = { 0 };
for (u32 i = 0; i < ARRAY_COUNT(ctx.arenas); ++i) {
ctx.arenas[i] = arena_alloc(SCRATCH_ARENA_RESERVE);
}
return ctx;
}
INLINE void scratch_context_release(struct scratch_context *ctx)
{
for (u32 i = 0; i < ARRAY_COUNT(ctx->arenas); ++i) {
arena_release(&ctx->arenas[i]);
}
}
#endif

168
src/settings.c Normal file
View File

@ -0,0 +1,168 @@
#include "settings.h"
#include "sys.h"
#include "json.h"
#include "scratch.h"
#include "string.h"
#include "app.h"
#include "math.h"
/* TODO:
* Rework the whole settings system to be not so rigid.
* - Multi-threaded
* - Current settings state is readable & modifiable at any time
* - Can be done w/ a settings_open / settings_close block
* - Allow for dot notation in settings queries (should probably be implemented in the json layer).
* - "." denotes subindexing, IE: settings_get("window.width") (numbers can also be implemented for arrays).
* - "\." escapable
*/
#define SETTINGS_FILENAME "settings.json"
INTERNAL struct buffer serialize_window_settings(struct arena *arena, const struct sys_window_settings *settings)
{
__prof;
struct temp_arena scratch = scratch_begin(arena);
struct json_ir *root_obj = json_ir_object(scratch.arena);
struct json_ir *window_settings_obj = json_ir_object_set(root_obj, STR("window"), json_ir_object(scratch.arena));
json_ir_object_set(window_settings_obj, STR("minimized"), json_ir_bool(scratch.arena, settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED));
json_ir_object_set(window_settings_obj, STR("maximized"), json_ir_bool(scratch.arena, settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED));
json_ir_object_set(window_settings_obj, STR("fullscreen"), json_ir_bool(scratch.arena, settings->flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN));
json_ir_object_set(window_settings_obj, STR("x"), json_ir_number(scratch.arena, settings->floating_x));
json_ir_object_set(window_settings_obj, STR("y"), json_ir_number(scratch.arena, settings->floating_y));
json_ir_object_set(window_settings_obj, STR("width"), json_ir_number(scratch.arena, settings->floating_width));
json_ir_object_set(window_settings_obj, STR("height"), json_ir_number(scratch.arena, settings->floating_height));
const struct json_val *formatted = json_format(scratch.arena, root_obj);
struct buffer buff = BUFFER_FROM_STRING(json_dump_to_string(arena, formatted, 2));
scratch_end(scratch);
return buff;
}
INTERNAL void deserialize_window_settings(struct buffer json_bytes, struct sys_window_settings *settings)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string error = { 0 };
if (json_bytes.size <= 0) {
goto end;
}
struct string *parse_error = NULL;
const struct json_val *root_json = json_parse_and_format(scratch.arena, json_bytes, &parse_error);
if (parse_error) {
error = *parse_error;
goto end;
}
const struct json_val *window_settings_json = json_object_get(root_json, STR("window"));
if (!window_settings_json || !json_is_object(window_settings_json)) {
goto end;
}
const struct json_val *minimized = json_object_get(window_settings_json, STR("minimized"));
const struct json_val *maximized = json_object_get(window_settings_json, STR("maximized"));
const struct json_val *fullscreen = json_object_get(window_settings_json, STR("fullscreen"));
const struct json_val *width = json_object_get(window_settings_json, STR("width"));
const struct json_val *height = json_object_get(window_settings_json, STR("height"));
const struct json_val *x = json_object_get(window_settings_json, STR("x"));
const struct json_val *y = json_object_get(window_settings_json, STR("y"));
if (!(json_is_bool(minimized) && json_bool(minimized))) {
/* Only trust pos/size settings if window wasn't closed while minimized */
settings->floating_x = json_is_number(x) ? (i32)clamp_f64(json_number(x), I32_MIN, I32_MAX) : settings->floating_x;
settings->floating_y = json_is_number(y) ? (i32)clamp_f64(json_number(y), I32_MIN, I32_MAX) : settings->floating_y;
settings->floating_width = json_is_number(width) ? (i32)clamp_f64(json_number(width), I32_MIN, I32_MAX) : settings->floating_width;
settings->floating_height = json_is_number(height) ? (i32)clamp_f64(json_number(height), I32_MIN, I32_MAX) : settings->floating_height;
}
settings->flags = json_is_bool(maximized) ? (json_bool(maximized) ? (settings->flags | SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) : (settings->flags & ~SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED)) : settings->flags;
settings->flags = json_is_bool(fullscreen) ? (json_bool(fullscreen) ? (settings->flags | SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN) : (settings->flags & ~SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN)) : settings->flags;
end:
if (error.len > 0) {
sys_message_box(
SYS_MESSAGE_BOX_KIND_WARNING,
string_format(
scratch.arena,
STR("Error loading settings file:\n%F"),
FMT_STR(error)
)
);
}
scratch_end(scratch);
}
struct sys_window_settings settings_default_window_settings(struct sys_window *window)
{
__prof;
struct v2 window_size = sys_window_get_size(window);
struct v2 monitor_size = sys_window_get_monitor_size(window);
i32 window_width = math_round_f32(window_size.x);
i32 window_height = math_round_f32(window_size.y);
i32 monitor_width = math_round_f32(monitor_size.x);
i32 monitor_height = math_round_f32(monitor_size.y);
i32 width = window_width / 2;
i32 height = window_height / 2;
i32 x = monitor_width / 2 - width / 2;
i32 y = monitor_height / 2 - height / 2;
return (struct sys_window_settings) {
#if RTC
# if DEVELOPER
.title = "Debug (Developer Build)",
# else
.title = "Debug",
# endif
#else
# if DEVELOPER
.title = "Power Play (Developer Build)",
# else
.title = "Power Play",
# endif
#endif
.floating_x = x,
.floating_y = y,
.floating_width = width,
.floating_height = height
};
}
void settings_read_from_file(struct sys_window_settings *default_settings)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* Read window settings file if it exists */
struct string window_settings_path = app_write_path_cat(scratch.arena, STR(SETTINGS_FILENAME));
if (sys_is_file(window_settings_path)) {
struct sys_file settings_file = sys_file_open_read(window_settings_path);
struct buffer file_data = sys_file_read_all(scratch.arena, settings_file);
sys_file_close(settings_file);
deserialize_window_settings(file_data, default_settings);
}
scratch_end(scratch);
}
void settings_write_to_file(const struct sys_window_settings *settings)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* Write window settings to file */
struct buffer settings_file_data = serialize_window_settings(scratch.arena, settings);
if (settings_file_data.size > 0) {
struct string window_settings_path = app_write_path_cat(scratch.arena, STR(SETTINGS_FILENAME));
struct sys_file settings_file = sys_file_open_write(window_settings_path);
sys_file_write(settings_file, settings_file_data);
sys_file_close(settings_file);
}
scratch_end(scratch);
}

10
src/settings.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef SETTINGS_H
#define SETTINGS_H
struct sys_window;
struct sys_window_settings settings_default_window_settings(struct sys_window *window);
void settings_read_from_file(struct sys_window_settings *default_settings);
void settings_write_to_file(const struct sys_window_settings *settings);
#endif

46
src/shaders/texture.hlsl Normal file
View File

@ -0,0 +1,46 @@
/*
* TODO:
* * maybe rename this to 'sprite.hlsl' (draw_rect and stuff would become draw_sprite)
* that way UI & text can have a separate shader (IE: draw_ui_rect, draw_ui_text, etc...)
*
* for tiles - separate shader. maybe upload all tile data in constant buffer, and drawing
* tiles can just be one giant quad (that the fragment shader indexes into)
*/
struct {
SamplerState sampler0;
Texture2D texture0;
} globals;
struct vs_input {
float4 pos : POSITION;
float2 uv : TEXCOORD;
float4 col : COLOR;
};
struct ps_input {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD;
float4 col : COLOR;
};
cbuffer vs_constants : register(b0)
{
float4x4 projection;
};
ps_input vs_main(vs_input input)
{
ps_input output;
output.pos = mul(projection, float4(input.pos.xy, 0.f, 1.f));
output.uv = input.uv;
output.col = input.col;
return output;
}
float4 ps_main(ps_input input) : SV_TARGET
{
return globals.texture0.Sample(globals.sampler0, input.uv) * input.col;
}

287
src/sheet.c Normal file
View File

@ -0,0 +1,287 @@
#include "sheet.h"
#include "arena.h"
#include "log.h"
#include "sys.h"
#include "scratch.h"
#include "resource.h"
#include "asset_cache.h"
#include "ase.h"
#include "util.h"
struct sheet_task_params {
struct sheet_task_params *next_free;
struct asset *asset;
u64 path_len;
char path_cstr[1024];
};
struct sheet_task_params_store {
struct sheet_task_params *head_free;
struct arena arena;
struct sys_mutex mutex;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct sheet_task_params_store params;
} L = { 0 } DEBUG_LVAR(L_sheet);
/* ========================== *
* Startup
* ========================== */
void sheet_startup(void)
{
L.params.arena = arena_alloc(GIGABYTE(64));
L.params.mutex = sys_mutex_alloc();
}
/* ========================== *
* Load task param store
* ========================== */
INTERNAL struct sheet_task_params *sheet_task_params_alloc(void)
{
struct sheet_task_params *p = NULL;
{
sys_mutex_lock(&L.params.mutex);
if (L.params.head_free) {
p = L.params.head_free;
L.params.head_free = p->next_free;
} else {
p = arena_push_zero(&L.params.arena, struct sheet_task_params);
}
sys_mutex_unlock(&L.params.mutex);
}
return p;
}
INTERNAL void sheet_task_params_release(struct sheet_task_params *p)
{
sys_mutex_lock(&L.params.mutex);
p->next_free = L.params.head_free;
L.params.head_free = p;
sys_mutex_unlock(&L.params.mutex);
}
/* ========================== *
* Init
* ========================== */
#define SHEET_LOOKUP_TABLE_CAPACITY_FACTOR 2.0
INTERNAL struct sheet sheet_from_ase(struct arena *arena, struct ase_decode_sheet_result ase)
{
struct sheet sheet = { 0 };
ASSERT(ase.num_frames >= 1);
/* Init frames */
sheet.image_size = ase.image_size;
sheet.frames = arena_push_array_zero(arena, struct sheet_frame, ase.num_frames);
sheet.frames_count = ase.num_frames;
for (struct ase_frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) {
u32 index = ase_frame->index;
sheet.frames[index] = (struct sheet_frame) {
.index = index,
.duration = ase_frame->duration,
.clip = ase_frame->clip
};
}
/* Init tags */
sheet.tags_count = ase.num_tags;
if (ase.num_tags > 0) {
sheet.tags_dict = fixed_dict_init(arena, (u64)(ase.num_tags * SHEET_LOOKUP_TABLE_CAPACITY_FACTOR));
for (struct ase_tag *ase_tag = ase.tag_head; ase_tag; ase_tag = ase_tag->next) {
struct string name = string_cpy(arena, ase_tag->name);
struct sheet_tag *tag = arena_push(arena, struct sheet_tag);
*tag = (struct sheet_tag) {
.name = name,
.start = ase_tag->start,
.end = ase_tag->end
};
fixed_dict_set(arena, &sheet.tags_dict, name, tag);
}
}
return sheet;
}
INTERNAL struct sheet sheet_default(struct arena *arena)
{
struct sheet sheet = { 0 };
sheet.frames_count = 1;
sheet.frames = arena_push(arena, struct sheet_frame);
sheet.frames[0] = (struct sheet_frame) {
.index = 0,
.duration = 0,
.clip = CLIP_ALL
};
return sheet;
}
/* ========================== *
* Load
* ========================== */
INTERNAL void sheet_load_asset_task(void *vparams)
{
__prof;
struct sheet_task_params *params = (struct sheet_task_params *)vparams;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string path = string_from_cstr_len(params->path_cstr, params->path_len);
struct asset *asset = params->asset;
logf_info("Loading sheet \"%F\"", FMT_STR(path));
sys_timestamp_t start_ts = sys_timestamp();
b32 success = false;
struct string error_msg = STR("Unknown error");
ASSERT(string_ends_with(path, STR(".ase")));
if (resource_exists(path)) {
/* Decode */
struct resource sheet_rs = resource_open(path);
struct ase_decode_sheet_result decoded = ase_decode_sheet(scratch.arena, sheet_rs.bytes);
resource_close(sheet_rs);
/* Failure paths */
if (!decoded.valid) {
if (decoded.error_msg.len > 0) {
error_msg = decoded.error_msg;
}
goto abort;
} else {
success = true;
}
/* Initialize sheet & its data into store */
struct sheet *sheet = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
sheet = arena_push(store.arena, struct sheet);
*sheet = sheet_from_ase(store.arena, decoded);
asset_cache_store_close(&store);
}
logf_info("Finished loading sheet \"%F\" in %F seconds",
FMT_STR(path),
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)));
asset_cache_mark_ready(asset, sheet);
} else {
success = false;
error_msg = STR("Resource not found");
goto abort;
}
abort:
if (!success) {
logf_error("Error loading sheet \"%F\": %F", FMT_STR(path), FMT_STR(error_msg));
/* TODO: Return default sheet */
/* Generate purple & black image */
/* Store */
struct sheet *sheet = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
sheet = arena_push(store.arena, struct sheet);
*sheet = sheet_default(store.arena);
asset_cache_store_close(&store);
}
asset_cache_mark_ready(asset, sheet);
}
sheet_task_params_release(params);
/* Decommit decoded sheet data */
scratch_end_and_decommit(scratch);
}
struct asset *sheet_load_asset(struct string path, b32 help)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string key = string_cat(scratch.arena, path, STR("_sheet"));
u64 hash = asset_cache_hash(key);
b32 is_first_touch;
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch);
if (is_first_touch) {
/* Assemble task params */
struct sheet_task_params *params = sheet_task_params_alloc();
if (path.len > (sizeof(params->path_cstr) - 1)) {
sys_panic(string_format(scratch.arena,
STR("Sheet path \"%F\" too long!"),
FMT_STR(path)));
}
string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr));
params->path_len = path.len;
params->asset = asset;
/* Push task */
asset_cache_mark_loading(asset);
struct work_handle wh = { 0 };
if (help) {
wh = work_push_task_and_help(&sheet_load_asset_task, params, WORK_PRIORITY_NORMAL);
} else {
wh = work_push_task(&sheet_load_asset_task, params, WORK_PRIORITY_NORMAL);
}
asset_cache_set_work(asset, &wh);
}
scratch_end(scratch);
return asset;
}
struct sheet *sheet_load_async(struct string path)
{
__prof;
struct asset *asset = sheet_load_asset(path, false);
struct sheet *sheet = (struct sheet *)asset_cache_get_store_data(asset);
return sheet;
}
struct sheet *sheet_load(struct string path)
{
__prof;
struct asset *asset = sheet_load_asset(path, true);
asset_cache_wait(asset);
struct sheet *sheet = (struct sheet *)asset_cache_get_store_data(asset);
return sheet;
}
/* ========================== *
* Sheet data
* ========================== */
GLOBAL struct sheet_tag g_default_tag = { 0 };
struct sheet_tag sheet_get_tag(struct sheet *sheet, struct string name)
{
struct sheet_tag res = g_default_tag;
if (sheet->tags_count > 0) {
struct sheet_tag *entry = fixed_dict_get(&sheet->tags_dict, name);
if (entry) {
res = *entry;
}
}
return res;
}
struct sheet_frame sheet_get_frame(struct sheet *sheet, u32 index)
{
index = min_u32(sheet->frames_count - 1, index);
return sheet->frames[index];
}

38
src/sheet.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef SHEET_H
#define SHEET_H
#include "util.h"
struct asset;
struct sheet_frame;
struct sheet {
struct v2 image_size;
u32 frames_count;
struct sheet_frame *frames;
u32 tags_count;
struct fixed_dict tags_dict;
};
struct sheet_tag {
struct string name;
u32 start;
u32 end;
};
struct sheet_frame {
u32 index;
f64 duration;
struct clip_rect clip;
};
void sheet_startup(void);
struct asset *sheet_load_asset(struct string path, b32 wait);
struct sheet *sheet_load_async(struct string path);
struct sheet *sheet_load(struct string path);
struct sheet_tag sheet_get_tag(struct sheet *sheet, struct string name);
struct sheet_frame sheet_get_frame(struct sheet *sheet, u32 index);
#endif

217
src/sound.c Normal file
View File

@ -0,0 +1,217 @@
#include "sound.h"
#include "arena.h"
#include "log.h"
#include "sys.h"
#include "scratch.h"
#include "resource.h"
#include "asset_cache.h"
#include "mp3.h"
struct sound_task_params {
struct sound_task_params *next_free;
u32 flags;
struct asset *asset;
u64 path_len;
char path_cstr[1024];
};
struct sound_task_params_store {
struct sound_task_params *head_free;
struct arena arena;
struct sys_mutex mutex;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct sound_task_params_store params;
} L = { 0 } DEBUG_LVAR(L_sound);
/* ========================== *
* Startup
* ========================== */
void sound_startup(void)
{
L.params.arena = arena_alloc(GIGABYTE(64));
L.params.mutex = sys_mutex_alloc();
}
/* ========================== *
* Load task param store
* ========================== */
INTERNAL struct sound_task_params *sound_task_params_alloc(void)
{
struct sound_task_params *p = NULL;
{
sys_mutex_lock(&L.params.mutex);
if (L.params.head_free) {
p = L.params.head_free;
L.params.head_free = p->next_free;
} else {
p = arena_push_zero(&L.params.arena, struct sound_task_params);
}
sys_mutex_unlock(&L.params.mutex);
}
return p;
}
INTERNAL void sound_task_params_release(struct sound_task_params *p)
{
sys_mutex_lock(&L.params.mutex);
p->next_free = L.params.head_free;
L.params.head_free = p;
sys_mutex_unlock(&L.params.mutex);
}
/* ========================== *
* Load
* ========================== */
INTERNAL void sound_load_asset_task(void *vparams)
{
__prof;
struct sound_task_params *params = (struct sound_task_params *)vparams;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string path = string_from_cstr_len(params->path_cstr, params->path_len);
struct asset *asset = params->asset;
u32 flags = params->flags;
logf_info("Loading sound \"%F\"", FMT_STR(path));
sys_timestamp_t start_ts = sys_timestamp();
b32 success = true;
struct string error_msg = STR("Unknown error");
ASSERT(string_ends_with(path, STR(".mp3")));
if (resource_exists(path)) {
u64 decode_flags = 0;
if (flags & SOUND_FLAG_STEREO) {
decode_flags |= MP3_DECODE_FLAG_STEREO;
}
/* Decode */
struct resource sound_rs = resource_open(path);
struct mp3_decode_result decoded = mp3_decode(scratch.arena, sound_rs.bytes, decode_flags);
resource_close(sound_rs);
if (!decoded.success) {
success = false;
error_msg = STR("Failed to decode sound file");
goto abort;
}
/* Store */
struct sound *sound = NULL;
i16 *samples = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
sound = arena_push(store.arena, struct sound);
samples = arena_push_array(store.arena, i16, decoded.pcm.count);
asset_cache_store_close(&store);
}
/* Initialize */
*sound = (struct sound) {
.flags = flags,
.pcm.count = decoded.pcm.count,
.pcm.samples = samples
};
MEMCPY(sound->pcm.samples, decoded.pcm.samples, decoded.pcm.count * sizeof(*decoded.pcm.samples));
logf_info("Finished loading sound \"%F\" in %F seconds",
FMT_STR(path),
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)));
asset_cache_mark_ready(asset, sound);
} else {
success = false;
error_msg = STR("Resource not found");
goto abort;
}
abort:
if (!success) {
logf_error("Error loading sound \"%F\": %F", FMT_STR(path), FMT_STR(error_msg));
/* Store */
struct sound *sound = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
sound = arena_push(store.arena, struct sound);
asset_cache_store_close(&store);
}
*sound = (struct sound) { 0 };
asset_cache_mark_ready(asset, sound);
}
sound_task_params_release(params);
/* Decommit decoded sound data */
scratch_end_and_decommit(scratch);
}
struct asset *sound_load_asset(struct string path, u32 flags, b32 help)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* Generate and append sound flags to path key */
struct string key = string_format(scratch.arena,
STR("%F%F_sound"),
FMT_STR(path),
FMT_UINT((u64)flags));
u64 hash = asset_cache_hash(key);
b32 is_first_touch;
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch);
if (is_first_touch) {
/* Assemble task params */
struct sound_task_params *params = sound_task_params_alloc();
if (path.len > (sizeof(params->path_cstr) - 1)) {
sys_panic(string_format(scratch.arena,
STR("Sound path \"%F\" too long!"),
FMT_STR(path)));
}
string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr));
params->path_len = path.len;
params->asset = asset;
params->flags = flags;
/* Push task */
asset_cache_mark_loading(asset);
struct work_handle wh = { 0 };
if (help) {
wh = work_push_task_and_help(&sound_load_asset_task, params, WORK_PRIORITY_NORMAL);
} else {
wh = work_push_task(&sound_load_asset_task, params, WORK_PRIORITY_NORMAL);
}
asset_cache_set_work(asset, &wh);
}
scratch_end(scratch);
return asset;
}
struct sound *sound_load_async(struct string path, u32 flags)
{
__prof;
struct asset *asset = sound_load_asset(path, flags, false);
struct sound *sound = (struct sound *)asset_cache_get_store_data(asset);
return sound;
}
struct sound *sound_load(struct string path, u32 flags)
{
__prof;
struct asset *asset = sound_load_asset(path, flags, true);
asset_cache_wait(asset);
struct sound *sound = (struct sound *)asset_cache_get_store_data(asset);
return sound;
}

20
src/sound.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef SOUND_H
#define SOUND_H
#define SOUND_FLAG_NONE 0x0
#define SOUND_FLAG_STEREO 0x1
struct asset;
struct sound {
u32 flags;
struct pcm pcm;
};
void sound_startup(void);
struct asset *sound_load_asset(struct string path, u32 flags, b32 wait);
struct sound *sound_load_async(struct string path, u32 flags);
struct sound *sound_load(struct string path, u32 flags);
#endif

555
src/string.c Normal file
View File

@ -0,0 +1,555 @@
#include "string.h"
#include "arena.h"
#include "memory.h"
#include "scratch.h"
#include "math.h"
/*
* NOTE: Strings should be considered ~IMMUTABLE~
*
* All string functions return a new string as a result. Any strings used as
* an argument (IE: in string_cat) will not be modified.
*
* Use the STR macro to create strings from string literals
*
* NOTE: It is valid for a string to have len 0 but a non-NULL text pointer.
* Always check string.len rather than string.text for string presence.
* (If we want to change this behavior then we need to check for length = 0 in
* our functions that return a pointer from arena_dry_push, or guarantee that
* all functions returning an arena_dry_push do allocate.)
*/
/* ========================== *
* Conversion
* ========================== */
#define INT_CHARS ("0123456789abcdef")
struct string string_from_char(struct arena *arena, char c)
{
u8 *dest = arena_push(arena, u8);
*dest = c;
return (struct string) {
.len = 1,
.text = dest
};
}
struct string string_from_uint(struct arena *arena, u64 n, u32 base)
{
/* Base too large */
ASSERT(base <= (ARRAY_COUNT(INT_CHARS) - 1));
/* TODO: we can probably use a fixed buffer here rather than scratch */
struct temp_arena scratch = scratch_begin(arena);
/* Build backwards text starting from least significant digit */
u64 len = 0;
u8 *backwards_text = arena_dry_push(scratch.arena, u8);
do {
string_from_char(scratch.arena, INT_CHARS[n % base]);
++len;
n /= base;
} while (n > 0);
/* Reverse text into final string */
u8 *final_text = arena_push_array(arena, u8, len);
for (u64 i = 0; i < len; ++i) {
final_text[i] = backwards_text[len - i - 1];
}
scratch_end(scratch);
return (struct string) {
.len = len,
.text = final_text
};
}
struct string string_from_int(struct arena *arena, i64 n, u32 base)
{
u8 *final_text = arena_dry_push(arena, u8);
u8 len = 0;
if (n < 0) {
/* Push sign */
string_from_char(arena, '-');
len = 1;
n = -n;
}
/* Push unsigned number */
struct string uint_str = string_from_uint(arena, n, base);
return (struct string) {
.len = len + uint_str.len,
.text = final_text
};
}
struct string string_from_ptr(struct arena *arena, void *ptr)
{
struct string prepend = string_cpy(arena, STR("0x"));
struct string uint_str = string_from_uint(arena, (u64)ptr, 16);
return (struct string) {
.len = prepend.len + uint_str.len,
.text = prepend.text
};
}
/* NOTE: This is an imprecise and inefficient way of doing this */
struct string string_from_float(struct arena *arena, f64 f, u32 precision)
{
u8 *final_text = arena_dry_push(arena, u8);
u64 final_len = 0;
/* Currently this function doesn't support large floats. We should
* rewrite this function if this needs to change. */
b32 unrepresentable = (f >= (f64)((u64)1 << 63));
b32 nan = f != f;
if (nan) {
final_len += string_cpy(arena, STR("NaN")).len;
} else if (unrepresentable) {
string_from_char(arena, '?');
++final_len;
} else {
if (f < 0) {
string_from_char(arena, '-');
f = -f;
++final_len;
}
/* Add one half of next precision level to round up */
f += 0.5 / (f64)math_pow_u64(10, (u8)precision);
/* Print whole part */
u64 whole = (u64)f;
struct string whole_str = string_from_uint(arena, whole, 10);
final_len += whole_str.len;
/* Print decimal part */
if (precision > 0) {
string_from_char(arena, '.');
f -= (f64)whole;
for (u64 i = 0; i < precision; ++i) {
f *= 10.0;
u64 digit = (u64)f;
f -= (f64)digit;
string_from_char(arena, INT_CHARS[digit % 10]);
}
final_len += (u64)precision + 1;
}
}
return (struct string) {
.len = final_len,
.text = final_text
};
}
/* ========================== *
* String operations
* ========================== */
struct string string_cpy(struct arena *arena, struct string src)
{
struct string str = {
.len = src.len,
.text = arena_push_array(arena, u8, src.len)
};
MEMCPY(str.text, src.text, src.len);
return str;
}
/* TODO: Benchmark performance of appending each character while calculating size here */
// //struct string string_cpy_cstr(struct arena *arena, char *cstr)
// //{
// // u8 *final_text = arena_next(arena);
// // char *c = cstr;
// // for (; *c != 0; ++c) {
// // u8 *new_char = arena_push(arena, 1);
// // *new_char = *c;
// // }
// // return (struct string) {
// // .len = c - cstr,
// // .text = final_text
// // };
// //}
struct string string_from_cstr(char *cstr)
{
struct string str = { 0 };
if (cstr) {
char *c = cstr;
while (*c != 0) {
++c;
}
str.len = c - cstr;
str.text = (u8 *)cstr;
}
return str;
}
struct string string_from_cstr_len(char *cstr, u64 len)
{
return (struct string) {
.text = (u8 *)cstr,
.len = len
};
}
struct string string_repeat(struct arena *arena, struct string src, u64 count)
{
u64 final_len = src.len * count;
u8 *final_text = arena_push_array(arena, u8, final_len);
for (u64 i = 0; i < count; ++i) {
MEMCPY(final_text + (src.len * i), src.text, src.len);
}
return (struct string) {
.text = final_text,
.len = final_len
};
}
struct string string_cat(struct arena *arena, struct string str1, struct string str2)
{
struct string new_str = { 0 };
new_str.len = str1.len + str2.len;
new_str.text = arena_push_array(arena, u8, new_str.len);
MEMCPY(new_str.text, str1.text, str1.len);
MEMCPY(new_str.text + str1.len, str2.text, str2.len);
return new_str;
}
/* `arena` is where pieces items will be allocated. These strings point
* into the existing supplied string and do not allocate any new text. */
struct string_array string_split(struct arena *arena, struct string str, struct string delim)
{
struct string_array pieces = {
.count = 0,
.strings = arena_dry_push(arena, struct string)
};
struct string piece = {
.len = 0,
.text = str.text
};
for (u64 i = 0; i <= str.len - delim.len; ++i) {
/* Clamp comparison string so we don't overflow. */
struct string comp_str = {
.len = delim.len,
.text = &str.text[i]
};
b32 is_delimiter = string_eq(comp_str, delim);
b32 is_end = i == str.len - 1;
if (!is_delimiter || is_end) {
++piece.len;
}
if (is_delimiter || is_end) {
/* Delimiter found */
struct string *piece_pushed = arena_push(arena, struct string);
*piece_pushed = piece;
++pieces.count;
piece.text = piece.text + piece.len + delim.len;
piece.len = 0;
}
}
return pieces;
}
/* NOTE: Really slow */
struct string string_indent(struct arena *arena, struct string str, u32 indent)
{
struct temp_arena scratch = scratch_begin(arena);
u64 final_len = 0;
u8 *final_text = arena_dry_push(arena, u8);
struct string_array split = string_split(scratch.arena, str, STR("\n"));
for (u64 i = 0; i < split.count; ++i) {
struct string piece = split.strings[i];
for (u32 j = 0; j < indent; ++j) {
string_from_char(arena, ' ');
++final_len;
}
string_cpy(arena, piece);
final_len += piece.len;
if (i < split.count - 1) {
string_from_char(arena, '\n');
++final_len;
}
}
scratch_end(scratch);
return (struct string) {
.len = final_len,
.text = final_text
};
}
b32 string_eq(struct string str1, struct string str2)
{
if (str1.len == str2.len) {
for (u64 i = 0; i < str1.len; ++i) {
if (str1.text[i] != str2.text[i]) {
return false;
}
}
return true;
}
return false;
}
b32 string_contains(struct string str, struct string substring)
{
if (substring.len > str.len) {
return false;
}
for (u64 i = 0; i <= str.len - substring.len; ++i) {
b32 match = true;
for (u64 j = 0; j < substring.len; ++j) {
if (str.text[i + j] != substring.text[j]) {
match = false;
break;
}
}
if (match) {
return true;
}
}
return false;
}
b32 string_starts_with(struct string str, struct string substring)
{
if (str.len >= substring.len) {
for (u64 i = 0; i < substring.len; ++i) {
if (str.text[i] != substring.text[i]) {
return false;
}
}
return true;
}
return false;
}
b32 string_ends_with(struct string str, struct string substring)
{
if (str.len >= substring.len) {
u64 start = str.len - substring.len;
for (u64 i = 0; i < substring.len; ++i) {
if (str.text[start + i] != substring.text[i]) {
return false;
}
}
return true;
}
return false;
}
#if 0
/* NOTE: This is a LOSSY conversion.
* `wstr` must be null-terminated.
*/
struct string string_from_wstr(struct arena *arena, u16 *wstr)
{
u8 *final_text = arena_next(arena);
u16 *wchar = wstr;
for (; *wchar != 0; ++wchar) {
u8 *c = arena_push(arena, 1);
*c = (u8)(*wchar & 0xFF);
}
return (struct string) {
.len = wchar - wstr,
.text = final_text
};
}
#endif
char *string_to_cstr(struct arena *arena, struct string str)
{
u8 *text = arena_push_array(arena, u8, str.len + 1);
MEMCPY(text, str.text, str.len);
text[str.len] = '\0';
return (char *)text;
}
char *string_to_cstr_buff(struct string str, struct buffer buff)
{
if (buff.size > 0) {
u64 len = min_u64(str.len, buff.size - 1);
MEMCPY(buff.data, str.text, len);
buff.data[len] = '\0';
}
return (char *)buff.data;
}
wchar_t *string_to_wstr(struct arena *arena, struct string str)
{
/* FIXME: Do proper encoding. */
u16 *text = arena_push_array(arena, u16, str.len + 1);
for (u64 i = 0; i < str.len; ++i) {
text[i] = (u16)str.text[i];
}
text[str.len] = '\0';
return (wchar_t *)text;
}
/* ========================== *
* Format
* ========================== */
/* String formatting only has one format specifier: "%F". All specifier info is
* included in the arguments (instead of w/ the specifier like in printf). This
* is safer.
*
* Example:
* string_format(arena,
* STR("Hello there %F. You are %F feet %F inches tall!"),
* FMT_STR(STR("George")),
* FMT_UINT(6),
* FMT_FLOAT(5.375));
*
* NOTE: FMT_END must be passed as the last arg in the va_list (This is
* done automatically by the `string_format` macro).
*
* Format arguments:
* FMT_CHAR: Format a single u8 character
* FMT_STR: Format a `string` struct
* FMT_UINT: Format a u64
* FMT_SINT: Format an i64
* FMT_FLOAT: Format an f64 with DEFAULT_FMT_PRECISION
* FMT_FLOAT_P: Format an f64 with specified precision
* FMT_HEX: Format a u64 in hexadecimal notation
* FMT_PTR: Format a pointer in hexadecimal notation prefixed by "0x"
*
* FMT_END (internal): Denote the end of the va_list
*
* TODO:
* %n equivalent? (nothing)
* %e/%E equivalent? (scientific notation of floats)
* %o equivalent? (octal representation)
*/
struct string string_formatv(struct arena *arena, struct string fmt, va_list args)
{
u64 final_len = 0;
u8 *final_text = arena_dry_push(arena, u8);
u8 *end = fmt.text + fmt.len;
b32 no_more_args = false;
for (u8 *c = fmt.text; c < end; ++c) {
u8 *next = ((c + 1) < end) ? (c + 1) : (u8 *)"\0";
/* Escape '%%' */
b32 escape = !no_more_args && *c == '%' && *next == '%';
if (escape) {
/* Skip the escape '%' char from parsing */
++c;
}
if (!no_more_args && !escape && *c == '%' && *next == 'F') {
struct string parsed_str = { 0 };
/* Detect arg type and parse to string */
struct fmt_arg arg = va_arg(args, struct fmt_arg);
switch (arg.type) {
case FMT_TYPE_CHAR: {
parsed_str = string_from_char(arena, arg.value.c);
} break;
case FMT_TYPE_STR: {
parsed_str = string_cpy(arena, arg.value.string);
} break;
case FMT_TYPE_UINT: {
parsed_str = string_from_uint(arena, arg.value.uint, 10);
} break;
case FMT_TYPE_SINT: {
parsed_str = string_from_int(arena, arg.value.sint, 10);
} break;
case FMT_TYPE_HEX: {
parsed_str = string_from_uint(arena, arg.value.sint, 16);
} break;
case FMT_TYPE_PTR: {
parsed_str = string_from_ptr(arena, arg.value.ptr);
} break;
case FMT_TYPE_FLOAT: {
parsed_str = string_from_float(arena, arg.value.f, arg.precision);
} break;
case FMT_TYPE_END: {
/* Unexpected end. Not enough FMT args passed to function. */
ASSERT(false);
parsed_str = string_cpy(arena, STR("<?>"));
no_more_args = true;
} break;
case FMT_TYPE_NONE: {
/* Unknown format type */
ASSERT(false);
parsed_str = string_cpy(arena, STR("<?>"));
no_more_args = true;
} break;
}
/* Update final string len / start */
final_len += parsed_str.len;
/* Skip 'F' from parsing */
++c;
} else {
/* Parse character normally */
string_from_char(arena, *c);
++final_len;
}
}
#if RTC
if (!no_more_args) {
struct fmt_arg last_arg = va_arg(args, struct fmt_arg);
/* End arg not reached. Too many FMT values passed to function. */
ASSERT(last_arg.type == FMT_TYPE_END);
}
#endif
return (struct string) {
.len = final_len,
.text = final_text
};
}
struct string _string_format(struct arena *arena, struct string fmt, ...)
{
va_list args;
va_start(args, fmt);
struct string new_str = string_formatv(arena, fmt, args);
va_end(args);
return new_str;
}
/* ========================== *
* Unicode
* ========================== */
/* Placeholder functions. Unicode not supported yet. */
struct string32 string32_from_string(struct arena *arena, struct string str)
{
u32 *text = arena_push_array(arena, u32, str.len);
for (u64 i = 0; i < str.len; ++i) {
u8 c = str.text[i];
text[i] = (u32)c;
}
return (struct string32) {
.text = text,
.len = str.len
};
}

95
src/string.h Normal file
View File

@ -0,0 +1,95 @@
#ifndef STRING_H
#define STRING_H
struct string_array {
u64 count;
struct string *strings;
};
/* ========================== *
* Conversion
* ========================== */
struct string string_from_char(struct arena *arena, char c);
struct string string_from_uint(struct arena *arena, u64 n, u32 base);
struct string string_from_int(struct arena *arena, i64 n, u32 base);
struct string string_from_ptr(struct arena *arena, void *ptr);
struct string string_from_float(struct arena *arena, f64 f, u32 precision);
/* ========================== *
* String operations
* ========================== */
struct string string_cpy(struct arena *arena, struct string src);
struct string string_from_cstr(char *cstr);
struct string string_from_cstr_len(char *cstr, u64 len);
struct string string_repeat(struct arena *arena, struct string src, u64 count);
struct string string_cat(struct arena *arena, struct string str1, struct string str2);
struct string_array string_split(struct arena *arena, struct string str, struct string delim);
struct string string_indent(struct arena *arena, struct string str, u32 indent);
b32 string_eq(struct string str1, struct string str2);
b32 string_contains(struct string str, struct string substring);
b32 string_starts_with(struct string str, struct string substring);
b32 string_ends_with(struct string str, struct string substring);
char *string_to_cstr(struct arena *arena, struct string str);
char *string_to_cstr_buff(struct string str, struct buffer buff);
wchar_t *string_to_wstr(struct arena *arena, struct string str);
/* ========================== *
* Format
* ========================== */
#define DEFAULT_FMT_PRECISION 5
enum fmt_type {
FMT_TYPE_NONE,
/* Arbitrary magic numbers to avoid accidental non-fmt arguments */
FMT_TYPE_CHAR = 0xC6ECD1,
FMT_TYPE_STR = 0xC6ECD2,
FMT_TYPE_UINT = 0xC6ECD3,
FMT_TYPE_SINT = 0xC6ECD4,
FMT_TYPE_HEX = 0xC6ECD5,
FMT_TYPE_PTR = 0xC6ECD6,
FMT_TYPE_FLOAT = 0xC6ECD7,
FMT_TYPE_END = 0xAD32F3
};
struct fmt_arg {
enum fmt_type type;
union {
u8 c;
struct string string;
u64 uint;
i64 sint;
void *ptr;
f64 f;
} value;
u32 precision;
};
#define FMT_END (struct fmt_arg) {.type = FMT_TYPE_END}
#define FMT_CHAR(v) (struct fmt_arg) {.type = FMT_TYPE_CHAR, .value.c = (v)}
#define FMT_STR(v) (struct fmt_arg) {.type = FMT_TYPE_STR, .value.string = (v)}
#define FMT_UINT(v) (struct fmt_arg) {.type = FMT_TYPE_UINT, .value.uint = (v)}
#define FMT_SINT(v) (struct fmt_arg) {.type = FMT_TYPE_SINT, .value.sint = (v)}
#define FMT_HEX(v) (struct fmt_arg) {.type = FMT_TYPE_HEX, .value.uint = (v)}
#define FMT_PTR(v) (struct fmt_arg) {.type = FMT_TYPE_PTR, .value.ptr = (v)}
#define FMT_FLOAT(v) FMT_FLOAT_P(v, DEFAULT_FMT_PRECISION)
#define FMT_FLOAT_P(v, p) (struct fmt_arg) {.type = FMT_TYPE_FLOAT, .value.f = (v), .precision = p}
#define string_format(arena, fmt, ...) _string_format(arena, fmt, __VA_ARGS__, FMT_END)
struct string _string_format(struct arena *arena, struct string fmt, ...);
struct string string_formatv(struct arena *arena, struct string fmt, va_list args);
/* ========================== *
* Unicode
* ========================== */
/* TODO: Real unicode conversions */
struct string32 string32_from_string(struct arena *arena, struct string str);
#endif

471
src/sys.h Normal file
View File

@ -0,0 +1,471 @@
#ifndef SYS_H
#define SYS_H
struct worker_context;
struct scratch_context;
/* ========================== *
* Events
* ========================== */
enum sys_event_kind {
SYS_EVENT_KIND_NONE,
SYS_EVENT_KIND_BUTTON_DOWN,
SYS_EVENT_KIND_BUTTON_UP,
//SYS_EVENT_KIND_MOUSE_MOVE,
SYS_EVENT_KIND_TEXT,
SYS_EVENT_KIND_QUIT,
SYS_EVENT_KIND_COUNT
};
enum sys_btn {
SYS_BTN_NONE,
SYS_BTN_M1,
SYS_BTN_M2,
SYS_BTN_M3,
SYS_BTN_M4,
SYS_BTN_M5,
SYS_BTN_MWHEELUP,
SYS_BTN_MWHEELDOWN,
SYS_BTN_ESC,
SYS_BTN_F1,
SYS_BTN_F2,
SYS_BTN_F3,
SYS_BTN_F4,
SYS_BTN_F5,
SYS_BTN_F6,
SYS_BTN_F7,
SYS_BTN_F8,
SYS_BTN_F9,
SYS_BTN_F10,
SYS_BTN_F11,
SYS_BTN_F12,
SYS_BTN_F13,
SYS_BTN_F14,
SYS_BTN_F15,
SYS_BTN_F16,
SYS_BTN_F17,
SYS_BTN_F18,
SYS_BTN_F19,
SYS_BTN_F20,
SYS_BTN_F21,
SYS_BTN_F22,
SYS_BTN_F23,
SYS_BTN_F24,
SYS_BTN_GRAVE_ACCENT,
SYS_BTN_0,
SYS_BTN_1,
SYS_BTN_2,
SYS_BTN_3,
SYS_BTN_4,
SYS_BTN_5,
SYS_BTN_6,
SYS_BTN_7,
SYS_BTN_8,
SYS_BTN_9,
SYS_BTN_MINUS,
SYS_BTN_EQUAL,
SYS_BTN_BACKSPACE,
SYS_BTN_DELETE,
SYS_BTN_TAB,
SYS_BTN_A,
SYS_BTN_B,
SYS_BTN_C,
SYS_BTN_D,
SYS_BTN_E,
SYS_BTN_F,
SYS_BTN_G,
SYS_BTN_H,
SYS_BTN_I,
SYS_BTN_J,
SYS_BTN_K,
SYS_BTN_L,
SYS_BTN_M,
SYS_BTN_N,
SYS_BTN_O,
SYS_BTN_P,
SYS_BTN_Q,
SYS_BTN_R,
SYS_BTN_S,
SYS_BTN_T,
SYS_BTN_U,
SYS_BTN_V,
SYS_BTN_W,
SYS_BTN_X,
SYS_BTN_Y,
SYS_BTN_Z,
SYS_BTN_SPACE,
SYS_BTN_ENTER,
SYS_BTN_CTRL,
SYS_BTN_SHIFT,
SYS_BTN_ALT,
SYS_BTN_UP,
SYS_BTN_LEFT,
SYS_BTN_DOWN,
SYS_BTN_RIGHT,
SYS_BTN_PAGE_UP,
SYS_BTN_PAGE_DOWN,
SYS_BTN_HOME,
SYS_BTN_END,
SYS_BTN_FORWARD_SLASH,
SYS_BTN_PERIOD,
SYS_BTN_COMMA,
SYS_BTN_QUOTE,
SYS_BTN_LEFT_BRACKET,
SYS_BTN_RIGHT_BRACKET,
SYS_BTN_INSERT,
SYS_BTN_SEMICOLON,
SYS_BTN_COUNT
};
struct sys_event {
enum sys_event_kind kind;
enum sys_btn button;
b32 is_repeat;
u32 text_character;
struct v2 position;
};
struct sys_event_array {
u64 count;
struct sys_event *events;
};
/* ========================== *
* Memory
* ========================== */
/* Reserve a continuous block of virtual address space. Shouldn't be
* read / written to until memory is committed.
* NOTE: Guaranteed to be 16 byte aligned. */
void *sys_memory_reserve(u64 size);
/* Release a continuous block of virtual address space. Before releasing,
* decommit any committed memory within the reserved space to be safe. */
void sys_memory_release(void *address);
/* Commit a region of reserved address space to make it readable / writable */
void *sys_memory_commit(void *address, u64 size);
/* Decommit a region of committed memory (does not release the address space) */
void sys_memory_decommit(void *address, u64 size);
/* ========================== *
* File system
*
* NOTE: File paths use forward slash '/' as delimiter
* ========================== */
struct sys_file {
u64 handle;
};
struct string sys_get_write_path(struct arena *arena);
b32 sys_is_file(struct string path);
b32 sys_is_dir(struct string path);
void sys_mkdir(struct string path);
struct sys_file sys_file_open_read(struct string path);
struct sys_file sys_file_open_write(struct string path);
struct sys_file sys_file_open_append(struct string path);
void sys_file_close(struct sys_file file);
struct buffer sys_file_read_all(struct arena *arena, struct sys_file file);
void sys_file_write(struct sys_file file, struct buffer data);
u64 sys_file_size(struct sys_file file);
/* ========================== *
* File map
* ========================== */
struct sys_file_map {
u64 handle;
struct buffer mapped_memory;
};
struct sys_file_map sys_file_map_open_read(struct sys_file file);
void sys_file_map_close(struct sys_file_map map);
INLINE struct buffer sys_file_map_data(struct sys_file_map map)
{
return map.mapped_memory;
}
/* ========================== *
* Dir iter
* ========================== */
/* Opaque */
struct sys_dir_iter;
struct sys_dir_iter_info {
struct string file_name;
b32 is_dir;
};
/* Iterate all files in a directory */
struct sys_dir_iter *sys_dir_iter_begin(struct arena *arena, struct string dir_name);
struct sys_dir_iter_info *sys_dir_iter_next(struct arena *arena, struct sys_dir_iter *iter);
void sys_dir_iter_end(struct sys_dir_iter *iter);
/* ========================== *
* Window
* ========================== */
/* NOTE:
* A window object can only be interacted with by the thread that created it.
* This restriction is in place because of how Win32 works, IE you cannot
* create a Win32 window in one thread and process its messages on another. */
enum sys_window_settings_flags {
SYS_WINDOW_SETTINGS_FLAG_NONE = 0x00,
SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN = 0x01,
/* NOTE: Both maximized and minimized can be true at the same time. This
* means that the window was minimized from a maximized state, and will
* restore to being maximized once it's un-minimized. */
SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED = 0x02,
SYS_WINDOW_SETTINGS_FLAG_MINIMIZED = 0x04
};
enum sys_window_flags {
SYS_WINDOW_FLAG_NONE = 0x00,
SYS_WINDOW_FLAG_SHOWING = 0x02
};
typedef void (sys_window_event_callback_func)(struct sys_event event);
/* sys_window_update_settings should be used when altering settings values */
struct sys_window_settings {
char title[256];
u32 flags;
/* NOTE: Below fields are NOT representative of actual window dimensions.
* These values represent the window dimensions when the window is neither
* maximized nor minimized, AKA 'floating'. Use `sys_window_get_size` for
* drawing instead. */
i32 floating_x;
i32 floating_y;
i32 floating_width;
i32 floating_height;
};
struct sys_window {
u64 handle;
};
struct sys_window sys_window_alloc(void);
void sys_window_release(struct sys_window *sys_window);
void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func);
void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func);
void sys_window_update_settings(struct sys_window *sys_window, struct sys_window_settings *settings);
struct sys_window_settings sys_window_get_settings(struct sys_window *sys_window);
void sys_window_show(struct sys_window *sys_window);
struct v2 sys_window_get_size(struct sys_window *sys_window);
struct v2 sys_window_get_monitor_size(struct sys_window *sys_window);
struct v2 sys_window_get_mouse_pos(struct sys_window *sys_window);
/* Returns a platform specific representation of the window. E.g. `hwnd` on win32. */
u64 sys_window_get_internal_handle(struct sys_window *sys_window);
/* ========================== *
* Mutex
* ========================== */
struct sys_mutex {
u64 handle;
#if RTC
u64 owner_tid;
# if OS_WINDOWS
wchar_t *owner_name;
# else
char *owner_name;
# endif
#endif
};
struct sys_mutex sys_mutex_alloc(void);
void sys_mutex_release(struct sys_mutex *mutex);
void sys_mutex_lock(struct sys_mutex *mutex);
void sys_mutex_unlock(struct sys_mutex *mutex);
#if RTC
void sys_mutex_assert_locked(struct sys_mutex *mutex);
#else
# define sys_mutex_assert_locked(m)
#endif
/* ========================== *
* RW Mutex
* ========================== */
struct sys_rw_mutex {
u64 handle;
#if RTC
i64 num_shared;
u64 owner_tid;
# if _WIN32
wchar_t *owner_name;
# else
char *owner_name;
# endif
#endif
};
struct sys_rw_mutex sys_rw_mutex_alloc(void);
void sys_rw_mutex_release(struct sys_rw_mutex *mutex);
void sys_rw_mutex_lock_exclusive(struct sys_rw_mutex *mutex);
void sys_rw_mutex_unlock_exclusive(struct sys_rw_mutex *mutex);
void sys_rw_mutex_lock_shared(struct sys_rw_mutex *mutex);
void sys_rw_mutex_unlock_shared(struct sys_rw_mutex *mutex);
#if RTC
void sys_rw_mutex_assert_locked_exclusive(struct sys_rw_mutex *mutex);
#else
# define sys_rw_mutex_assert_locked_exclusive(m)
#endif
/* ========================== *
* Condition variable
* ========================== */
struct sys_condition_variable {
u64 handle;
#if RTC
i64 num_sleepers;
#endif
};
struct sys_condition_variable sys_condition_variable_alloc(void);
void sys_condition_variable_release(struct sys_condition_variable *cv);
void sys_condition_variable_wait(struct sys_condition_variable *cv, struct sys_mutex *mutex);
void sys_condition_variable_wait_time(struct sys_condition_variable *cv, struct sys_mutex *mutex, f64 seconds);
void sys_condition_variable_signal(struct sys_condition_variable *cv);
/* ========================== *
* Semaphore
* ========================== */
struct sys_semaphore {
u64 handle;
};
struct sys_semaphore sys_semaphore_alloc(u32 max_count);
void sys_semaphore_release(struct sys_semaphore *semaphore);
void sys_semaphore_wait(struct sys_semaphore *semaphore);
void sys_semaphore_signal(struct sys_semaphore *semaphore, u32 count);
/* ========================== *
* Thread local storage
* ========================== */
struct scratch_context *sys_thread_get_scratch_context(void);
struct worker_context *sys_thread_get_worker_context(void);
/* ========================== *
* Threads
* ========================== */
#define SYS_THREAD_STACK_SIZE MEGABYTE(1)
typedef void (sys_thread_func)(void *data);
struct sys_thread {
u64 handle;
};
/* Creates and starts running the thread in the supplied `thread_func` */
struct sys_thread sys_thread_init(
sys_thread_func *thread_func,
void *thread_data, /* Passed to thread_func */
struct string thread_name
);
/* Halt and wait for a thread to finish */
void sys_thread_join(struct sys_thread *thread);
/* Gets the current executing thread's ID */
u32 sys_thread_id(void);
/* Asserts that the current thread matches the supplied thread id (tid) */
#if RTC
void sys_thread_assert(u32 tid);
#else
# define sys_thread_assert(tid)
#endif
/* ========================== *
* Message box
* ========================== */
enum sys_message_box_kind {
SYS_MESSAGE_BOX_KIND_OK,
SYS_MESSAGE_BOX_KIND_WARNING,
SYS_MESSAGE_BOX_KIND_ERROR,
SYS_MESSAGE_BOX_KIND_FATAL
};
void sys_message_box(enum sys_message_box_kind kind, struct string message);
/* ========================== *
* Time
* ========================== */
struct sys_local_time_info {
u32 year;
u32 month;
u32 dayOfWeek;
u32 day;
u32 hour;
u32 minute;
u32 second;
u32 milliseconds;
};
typedef u64 sys_timestamp_t;
sys_timestamp_t sys_timestamp(void);
f64 sys_timestamp_seconds(sys_timestamp_t ts);
struct sys_local_time_info sys_local_time(void);
/* ========================== *
* Util
* ========================== */
u32 sys_num_logical_processors(void);
void sys_exit(void);
u32 sys_rand_u32(void);
/* Implementation of this function should have minimal side effects (e.g. avoid
* memory allocation), since it may be called in circumstances where those side
* effects are non-functioning. */
void sys_panic_raw(char *msg_cstr);
void sys_panic(struct string message);
/* ========================== *
* Sleep
* ========================== */
void sys_sleep(f64 seconds);
#endif

1749
src/sys_win32.c Normal file

File diff suppressed because it is too large Load Diff

193
src/tar.c Normal file
View File

@ -0,0 +1,193 @@
#include "tar.h"
#include "byteio.h"
#include "string.h"
#include "util.h"
#define ARCHIVE_LOOKUP_TABLE_CAPACITY_FACTOR 2.0
/* File types:
* '0' or (ASCII NUL) Normal file
* '1' Hard link
* '2' Symbolic link
* '3' Character special
* '4' Block special
* '5' Directory
* '6' FIFO
* '7' Contiguous file
* 'g' Global extended header with meta data(POSIX.1 - 2001)
* 'x' Extended header with metadata for the next file in the archive(POSIX.1 - 2001)
* 'A'-'Z' Vendor specific extensions(POSIX.1 - 1988)
*/
#define TAR_TYPE_FILE '0'
#define TAR_TYPE_DIRECTORY '5'
struct tar_header {
/* Pre-posix */
u8 file_name[100];
u8 file_mode[8];
u8 owner_id[8];
u8 group_id[8];
u8 file_size[12];
u8 last_modified[12];
u8 checksum[8];
/* Both */
u8 file_type;
u8 linked_file_name[100];
/* UStar */
u8 ustar_indicator[6];
u8 ustar_version[2];
u8 owner_user_name[32];
u8 owner_group_name[32];
u8 device_major_number[8];
u8 device_minor_number[8];
u8 file_name_prefix[155];
u8 padding[12];
} PACKED;
INTERNAL u64 str_oct_to_u64(struct string str)
{
u64 n = 0;
for (u64 i = 0; i < str.len; ++i) {
n *= 8;
n += (u64)(str.text[i]) - '0';
}
return n;
}
/* `prefix` will be prepended to all file names in the archive
*
* NOTE: The resulting archive merely points into the supplied tar data. No
* copying is done. Accessing the archive assumes that the data it's derived
* from is valid (AKA open if from a file / memory map).
*/
struct tar_archive tar_parse(struct arena *arena, struct buffer data, struct string prefix)
{
__prof;
struct tar_archive archive = { 0 };
struct byte_reader br = br_create_from_buffer(data);
/* NOTE: For some reason the bottom of the tar is filled with 0s.
* Don't read when bytes left <= 1024. */
u64 num_files = 0;
while (br_bytes_left(&br) > 1024) {
struct tar_header *header = br_read_raw(&br, sizeof(*header));
if (!header) {
/* Overflow */
ASSERT(false);
continue;
}
if (!string_eq(STRING_FROM_ARRAY(header->ustar_indicator), STR("ustar\0"))) {
/* Invalid header */
ASSERT(false);
continue;
}
if (header->file_name_prefix[0] != 0) {
/* Header file name prefix not supported */
ASSERT(false);
continue;
}
struct string file_size_oct_str = { .len = 11, .text = header->file_size };
u64 file_size = str_oct_to_u64(file_size_oct_str);
struct buffer file_data = {
.size = file_size,
.data = br_read_raw(&br, file_size)
};
/* Skip sector padding */
u64 remaining = (512 - (file_size % 512)) % 512;
br_seek(&br, remaining);
b32 is_dir = header->file_type == TAR_TYPE_DIRECTORY;
if (!is_dir && header->file_type != TAR_TYPE_FILE) {
/* Unsupported type */
ASSERT(false);
continue;
}
struct string file_name_cstr = string_from_cstr((char *)header->file_name);
if (file_name_cstr.len >= 2) {
/* Chop off './' prefix */
file_name_cstr.len -= 2;
file_name_cstr.text += 2;
}
struct string file_name = string_cat(arena, prefix, file_name_cstr);
struct tar_entry *entry = arena_push(arena, struct tar_entry);
*entry = (struct tar_entry) {
.is_dir = is_dir,
.file_name = file_name,
.buff = file_data
};
entry->next = archive.head;
archive.head = entry;
++num_files;
}
/* Build lookup table */
archive.lookup = fixed_dict_init(arena, (u64)((f64)num_files * ARCHIVE_LOOKUP_TABLE_CAPACITY_FACTOR));
for (struct tar_entry *entry = archive.head; entry; entry = entry->next) {
fixed_dict_set(arena, &archive.lookup, entry->file_name, entry);
}
/* Build hierarchy */
/* NOTE: This is a separate pass because tar entry order is not guaranteed
* (IE file entries may be encountered before their parent directory entry) */
for (struct tar_entry *entry = archive.head; entry; entry = entry->next) {
/* Enter into hierarchy */
if (!entry->is_dir) {
/* Find parent entry */
struct tar_entry *parent_entry = NULL;
for (struct string parent_dir_name = entry->file_name; parent_dir_name.len > 0; --parent_dir_name.len) {
if (parent_dir_name.text[parent_dir_name.len - 1] == '/') {
parent_entry = fixed_dict_get(&archive.lookup, parent_dir_name);
break;
}
}
/* Insert child into parent's list */
if (parent_entry) {
entry->next_child = parent_entry->next_child;
parent_entry->next_child = entry;
}
}
}
return archive;
}
struct tar_entry *tar_get(const struct tar_archive *archive, struct string name)
{
return fixed_dict_get(&archive->lookup, name);
}
#if 0
b32 tar_is_file(const struct tar_archive *archive, struct string name)
{
struct tar_entry *entry = fixed_dict_get(&archive->file_lookup, name);
return entry && !entry->is_dir;
}
b32 tar_is_dir(const struct tar_archive *archive, struct string name)
{
struct tar_entry *entry = fixed_dict_get(&archive->file_lookup, name);
return entry && entry->is_dir;
}
struct buffer *tar_data(const struct tar_archive *archive, struct string name)
{
struct tar_entry *entry = fixed_dict_get(&archive->file_lookup, name);
if (entry) {
return entry->data;
}
return BUFFER(0, 0);
}
#endif

23
src/tar.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef TAR_H
#define TAR_H
#include "util.h"
struct tar_entry {
struct string file_name;
struct buffer buff;
b32 is_dir;
struct tar_entry *next;
struct tar_entry *next_child; /* If entry is dir, points to first child. Otherwise points to next sibling. */
};
struct tar_archive {
struct fixed_dict lookup;
struct tar_entry *head;
};
struct tar_archive tar_parse(struct arena *arena, struct buffer data, struct string prefix);
struct tar_entry *tar_get(const struct tar_archive *archive, struct string name);
#endif

272
src/texture.c Normal file
View File

@ -0,0 +1,272 @@
#include "texture.h"
#include "arena.h"
#include "memory.h"
#include "json.h"
#include "renderer.h"
#include "util.h"
#include "asset_cache.h"
#include "sys.h"
#include "scratch.h"
#include "resource.h"
#include "ase.h"
#include "log.h"
#define LOOKUP_TABLE_CAPACITY_FACTOR 2.0f
struct texture_task_params {
struct texture_task_params *next_free;
struct asset *asset;
u64 path_len;
char path_cstr[1024];
};
struct texture_task_params_store {
struct texture_task_params *head_free;
struct arena arena;
struct sys_mutex mutex;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct texture_task_params_store params;
} L = { 0 } DEBUG_LVAR(L_texture);
/* ========================== *
* Startup
* ========================== */
void texture_startup(void)
{
L.params.arena = arena_alloc(GIGABYTE(64));
L.params.mutex = sys_mutex_alloc();
}
/* ========================== *
* Load task param store
* ========================== */
INTERNAL struct texture_task_params *texture_task_params_alloc(void)
{
struct texture_task_params *p = NULL;
{
sys_mutex_lock(&L.params.mutex);
if (L.params.head_free) {
p = L.params.head_free;
L.params.head_free = p->next_free;
} else {
p = arena_push_zero(&L.params.arena, struct texture_task_params);
}
sys_mutex_unlock(&L.params.mutex);
}
return p;
}
INTERNAL void texture_task_params_release(struct texture_task_params *p)
{
sys_mutex_lock(&L.params.mutex);
p->next_free = L.params.head_free;
L.params.head_free = p;
sys_mutex_unlock(&L.params.mutex);
}
/* ========================== *
* Default texture load
* ========================== */
#define DEFAULT_TEXTURE_NAME "__texture_default"
INTERNAL struct image_rgba generate_purple_black_image(struct arena *arena, u32 width, u32 height)
{
u32 *pixels = arena_push_array(arena, u32, width * height);
/* Create texture containing alternating blocks of purple and black */
u32 color_size = 4;
u32 color_1 = 0xFFDC00FF;
u32 color_2 = 0xFF000000;
for (u32 x = 0; x < width; ++x) {
for (u32 y = 0; y < height; ++y) {
u32 pixel_index = x + width * y;
if ((y / color_size) % 2 == 0) {
if ((x / color_size) % 2 == 0) {
pixels[pixel_index] = color_1;
} else {
pixels[pixel_index] = color_2;
}
} else {
if ((x / color_size) % 2 == 0) {
pixels[pixel_index] = color_2;
} else {
pixels[pixel_index] = color_1;
}
}
}
}
return (struct image_rgba) {
.width = width,
.height = height,
.pixels = pixels
};
}
/* ========================== *
* Load
* ========================== */
INTERNAL void texture_load_asset_task(void *vparams)
{
__prof;
struct texture_task_params *params = (struct texture_task_params *)vparams;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string path = string_from_cstr_len(params->path_cstr, params->path_len);
struct asset *asset = params->asset;
logf_info("Loading texture \"%F\"", FMT_STR(path));
sys_timestamp_t start_ts = sys_timestamp();
b32 success = false;
struct string error_msg = STR("Unknown error");
ASSERT(string_ends_with(path, STR(".ase")));
if (resource_exists(path)) {
/* Decode */
struct resource texture_rs = resource_open(path);
struct ase_decode_image_result decoded = ase_decode_image(scratch.arena, texture_rs.bytes);
resource_close(texture_rs);
/* Failure paths */
if (!decoded.valid) {
if (decoded.error_msg.len > 0) {
error_msg = decoded.error_msg;
}
goto abort;
} else if (decoded.image.width > RENDERER_TEXTURE_MAX_WIDTH || decoded.image.height > RENDERER_TEXTURE_MAX_HEIGHT) {
error_msg = string_format(scratch.arena,
STR("Image with dimensions %F x %F exceeds maximum allowed dimensions (%F x %F)"),
FMT_UINT(decoded.image.width),
FMT_UINT(decoded.image.height),
FMT_UINT(RENDERER_TEXTURE_MAX_WIDTH),
FMT_UINT(RENDERER_TEXTURE_MAX_HEIGHT));
goto abort;
} else {
success = true;
}
struct image_rgba image_data = decoded.image;
/* Create renderer texture */
struct renderer_handle handle = renderer_texture_alloc(image_data);
/* Initialize texture & its data into store */
struct texture *texture = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
texture = arena_push(store.arena, struct texture);
*texture = (struct texture) {
.width = image_data.width,
.height = image_data.height,
.renderer_handle = handle
};
asset_cache_store_close(&store);
}
logf_info("Finished loading texture \"%F\" in %F seconds",
FMT_STR(path),
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)));
asset_cache_mark_ready(asset, texture);
} else {
success = false;
error_msg = STR("Resource not found");
goto abort;
}
abort:
if (!success) {
logf_error("Error loading texture \"%F\": %F", FMT_STR(path), FMT_STR(error_msg));
/* Generate purple & black image */
struct renderer_handle handle = { 0 };
struct image_rgba default_image_data = generate_purple_black_image(scratch.arena, 64, 64);
handle = renderer_texture_alloc(default_image_data);
/* Store */
struct texture *texture = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
texture = arena_push(store.arena, struct texture);
*texture = (struct texture) {
.width = default_image_data.width,
.height = default_image_data.height,
.renderer_handle = handle
};
asset_cache_store_close(&store);
}
asset_cache_mark_ready(asset, texture);
}
texture_task_params_release(params);
/* Decommit decoded texture data */
scratch_end_and_decommit(scratch);
}
struct asset *texture_load_asset(struct string path, b32 help)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string key = string_cat(scratch.arena, path, STR("_tex"));
u64 hash = asset_cache_hash(key);
b32 is_first_touch;
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch);
if (is_first_touch) {
/* Assemble task params */
struct texture_task_params *params = texture_task_params_alloc();
if (path.len > (sizeof(params->path_cstr) - 1)) {
sys_panic(string_format(scratch.arena,
STR("Texture path \"%F\" too long!"),
FMT_STR(path)));
}
string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr));
params->path_len = path.len;
params->asset = asset;
/* Push task */
asset_cache_mark_loading(asset);
struct work_handle wh = { 0 };
if (help) {
wh = work_push_task_and_help(&texture_load_asset_task, params, WORK_PRIORITY_NORMAL);
} else {
wh = work_push_task(&texture_load_asset_task, params, WORK_PRIORITY_NORMAL);
}
asset_cache_set_work(asset, &wh);
}
scratch_end(scratch);
return asset;
}
struct texture *texture_load_async(struct string path)
{
__prof;
struct asset *asset = texture_load_asset(path, false);
struct texture *tex = (struct texture *)asset_cache_get_store_data(asset);
return tex;
}
struct texture *texture_load(struct string path)
{
__prof;
struct asset *asset = texture_load_asset(path, true);
asset_cache_wait(asset);
struct texture *tex = (struct texture *)asset_cache_get_store_data(asset);
return tex;
}

36
src/texture.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef TEXTURE_H
#define TEXTURE_H
#include "json.h"
#include "renderer.h"
#include "util.h"
struct asset;
struct texture_frame {
struct clip_rect clip;
u32 duration_ms;
};
struct texture_slice {
struct clip_rect clip;
};
struct texture_anim {
u32 frame_start;
u32 frame_end;
};
struct texture {
struct renderer_handle renderer_handle;
u32 width;
u32 height;
};
void texture_startup(void);
struct asset *texture_load_asset(struct string path, b32 wait);
struct texture *texture_load_async(struct string path);
struct texture *texture_load(struct string path);
#endif

58
src/third_party/tracy/TracyClient.cpp vendored Normal file
View File

@ -0,0 +1,58 @@
//
// Tracy profiler
// ----------------
//
// For fast integration, compile and
// link with this source file (and none
// other) in your executable (or in the
// main DLL / shared object on multi-DLL
// projects).
//
// Define TRACY_ENABLE to enable profiler.
#include "common/TracySystem.cpp"
#ifdef TRACY_ENABLE
#ifdef _MSC_VER
# pragma warning(push, 0)
#endif
#include "common/tracy_lz4.cpp"
#include "client/TracyProfiler.cpp"
#include "client/TracyCallstack.cpp"
#include "client/TracySysPower.cpp"
#include "client/TracySysTime.cpp"
#include "client/TracySysTrace.cpp"
#include "common/TracySocket.cpp"
#include "client/tracy_rpmalloc.cpp"
#include "client/TracyDxt1.cpp"
#include "client/TracyAlloc.cpp"
#include "client/TracyOverride.cpp"
#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6
# include "libbacktrace/alloc.cpp"
# include "libbacktrace/dwarf.cpp"
# include "libbacktrace/fileline.cpp"
# include "libbacktrace/mmapio.cpp"
# include "libbacktrace/posix.cpp"
# include "libbacktrace/sort.cpp"
# include "libbacktrace/state.cpp"
# if TRACY_HAS_CALLSTACK == 4
# include "libbacktrace/macho.cpp"
# else
# include "libbacktrace/elf.cpp"
# endif
# include "common/TracyStackFrames.cpp"
#endif
#ifdef _MSC_VER
# pragma comment(lib, "ws2_32.lib")
# pragma comment(lib, "dbghelp.lib")
# pragma comment(lib, "advapi32.lib")
# pragma comment(lib, "user32.lib")
# pragma warning(pop)
#endif
#endif

View File

@ -0,0 +1,43 @@
#include "../common/TracyAlloc.hpp"
#ifdef TRACY_USE_RPMALLOC
#include <atomic>
#include "../common/TracyForceInline.hpp"
#include "../common/TracyYield.hpp"
namespace tracy
{
extern thread_local bool RpThreadInitDone;
extern std::atomic<int> RpInitDone;
extern std::atomic<int> RpInitLock;
tracy_no_inline static void InitRpmallocPlumbing()
{
const auto done = RpInitDone.load( std::memory_order_acquire );
if( !done )
{
int expected = 0;
while( !RpInitLock.compare_exchange_weak( expected, 1, std::memory_order_release, std::memory_order_relaxed ) ) { expected = 0; YieldThread(); }
const auto done = RpInitDone.load( std::memory_order_acquire );
if( !done )
{
rpmalloc_initialize();
RpInitDone.store( 1, std::memory_order_release );
}
RpInitLock.store( 0, std::memory_order_release );
}
rpmalloc_thread_initialize();
RpThreadInitDone = true;
}
TRACY_API void InitRpmalloc()
{
if( !RpThreadInitDone ) InitRpmallocPlumbing();
}
}
#endif

View File

@ -0,0 +1,419 @@
namespace tracy
{
#if defined __linux__ && defined __ARM_ARCH
static const char* DecodeArmImplementer( uint32_t v )
{
static char buf[16];
switch( v )
{
case 0x41: return "ARM";
case 0x42: return "Broadcom";
case 0x43: return "Cavium";
case 0x44: return "DEC";
case 0x46: return "Fujitsu";
case 0x48: return "HiSilicon";
case 0x49: return "Infineon";
case 0x4d: return "Motorola";
case 0x4e: return "Nvidia";
case 0x50: return "Applied Micro";
case 0x51: return "Qualcomm";
case 0x53: return "Samsung";
case 0x54: return "Texas Instruments";
case 0x56: return "Marvell";
case 0x61: return "Apple";
case 0x66: return "Faraday";
case 0x68: return "HXT";
case 0x69: return "Intel";
case 0xc0: return "Ampere Computing";
default: break;
}
sprintf( buf, "0x%x", v );
return buf;
}
static const char* DecodeArmPart( uint32_t impl, uint32_t part )
{
static char buf[16];
switch( impl )
{
case 0x41: // ARM
switch( part )
{
case 0x810: return "810";
case 0x920: return "920";
case 0x922: return "922";
case 0x926: return "926";
case 0x940: return "940";
case 0x946: return "946";
case 0x966: return "966";
case 0xa20: return "1020";
case 0xa22: return "1022";
case 0xa26: return "1026";
case 0xb02: return "11 MPCore";
case 0xb36: return "1136";
case 0xb56: return "1156";
case 0xb76: return "1176";
case 0xc05: return " Cortex-A5";
case 0xc07: return " Cortex-A7";
case 0xc08: return " Cortex-A8";
case 0xc09: return " Cortex-A9";
case 0xc0c: return " Cortex-A12";
case 0xc0d: return " Rockchip RK3288";
case 0xc0e: return " Cortex-A17";
case 0xc0f: return " Cortex-A15";
case 0xc14: return " Cortex-R4";
case 0xc15: return " Cortex-R5";
case 0xc17: return " Cortex-R7";
case 0xc18: return " Cortex-R8";
case 0xc20: return " Cortex-M0";
case 0xc21: return " Cortex-M1";
case 0xc23: return " Cortex-M3";
case 0xc24: return " Cortex-M4";
case 0xc27: return " Cortex-M7";
case 0xc60: return " Cortex-M0+";
case 0xd00: return " AArch64 simulator";
case 0xd01: return " Cortex-A32";
case 0xd02: return " Cortex-A34";
case 0xd03: return " Cortex-A53";
case 0xd04: return " Cortex-A35";
case 0xd05: return " Cortex-A55";
case 0xd06: return " Cortex-A65";
case 0xd07: return " Cortex-A57";
case 0xd08: return " Cortex-A72";
case 0xd09: return " Cortex-A73";
case 0xd0a: return " Cortex-A75";
case 0xd0b: return " Cortex-A76";
case 0xd0c: return " Neoverse N1";
case 0xd0d: return " Cortex-A77";
case 0xd0e: return " Cortex-A76AE";
case 0xd0f: return " AEMv8";
case 0xd13: return " Cortex-R52";
case 0xd20: return " Cortex-M23";
case 0xd21: return " Cortex-M33";
case 0xd22: return " Cortex-M55";
case 0xd40: return " Neoverse V1";
case 0xd41: return " Cortex-A78";
case 0xd42: return " Cortex-A78AE";
case 0xd43: return " Cortex-A65AE";
case 0xd44: return " Cortex-X1";
case 0xd47: return " Cortex-A710";
case 0xd48: return " Cortex-X2";
case 0xd49: return " Neoverse N2";
case 0xd4a: return " Neoverse E1";
case 0xd4b: return " Cortex-A78C";
case 0xd4c: return " Cortex-X1C";
default: break;
}
case 0x42: // Broadcom
switch( part )
{
case 0xf: return " Brahma B15";
case 0x100: return " Brahma B53";
case 0x516: return " ThunderX2";
default: break;
}
case 0x43: // Cavium
switch( part )
{
case 0xa0: return " ThunderX";
case 0xa1: return " ThunderX 88XX";
case 0xa2: return " ThunderX 81XX";
case 0xa3: return " ThunderX 83XX";
case 0xaf: return " ThunderX2 99xx";
case 0xb0: return " OcteonTX2";
case 0xb1: return " OcteonTX2 T98";
case 0xb2: return " OcteonTX2 T96";
case 0xb3: return " OcteonTX2 F95";
case 0xb4: return " OcteonTX2 F95N";
case 0xb5: return " OcteonTX2 F95MM";
case 0xb6: return " OcteonTX2 F95O";
case 0xb8: return " ThunderX3 T110";
default: break;
}
case 0x44: // DEC
switch( part )
{
case 0xa10: return " SA110";
case 0xa11: return " SA1100";
default: break;
}
case 0x46: // Fujitsu
switch( part )
{
case 0x1: return " A64FX";
default: break;
}
case 0x48: // HiSilicon
switch( part )
{
case 0xd01: return " TSV100";
case 0xd40: return " Kirin 980";
default: break;
}
case 0x4e: // Nvidia
switch( part )
{
case 0x0: return " Denver";
case 0x3: return " Denver 2";
case 0x4: return " Carmel";
default: break;
}
case 0x50: // Applied Micro
switch( part )
{
case 0x0: return " X-Gene";
default: break;
}
case 0x51: // Qualcomm
switch( part )
{
case 0xf: return " Scorpion";
case 0x2d: return " Scorpion";
case 0x4d: return " Krait";
case 0x6f: return " Krait";
case 0x200: return " Kryo";
case 0x201: return " Kryo Silver (Snapdragon 821)";
case 0x205: return " Kryo Gold";
case 0x211: return " Kryo Silver (Snapdragon 820)";
case 0x800: return " Kryo 260 / 280 Gold";
case 0x801: return " Kryo 260 / 280 Silver";
case 0x802: return " Kryo 385 Gold";
case 0x803: return " Kryo 385 Silver";
case 0x804: return " Kryo 485 Gold";
case 0x805: return " Kryo 4xx/5xx Silver";
case 0xc00: return " Falkor";
case 0xc01: return " Saphira";
default: break;
}
case 0x53: // Samsung
switch( part )
{
case 0x1: return " Exynos M1/M2";
case 0x2: return " Exynos M3";
case 0x3: return " Exynos M4";
case 0x4: return " Exynos M5";
default: break;
}
case 0x54: // Texas Instruments
switch( part )
{
case 0x925: return " TI925";
default: break;
}
case 0x56: // Marvell
switch( part )
{
case 0x131: return " Feroceon 88FR131";
case 0x581: return " PJ4 / PJ4B";
case 0x584: return " PJ4B-MP / PJ4C";
default: break;
}
case 0x61: // Apple
switch( part )
{
case 0x1: return " Cyclone";
case 0x2: return " Typhoon";
case 0x3: return " Typhoon/Capri";
case 0x4: return " Twister";
case 0x5: return " Twister/Elba/Malta";
case 0x6: return " Hurricane";
case 0x7: return " Hurricane/Myst";
case 0x22: return " M1 Icestorm";
case 0x23: return " M1 Firestorm";
case 0x24: return " M1 Icestorm Pro";
case 0x25: return " M1 Firestorm Pro";
case 0x28: return " M1 Icestorm Max";
case 0x29: return " M1 Firestorm Max";
default: break;
}
case 0x66: // Faraday
switch( part )
{
case 0x526: return " FA526";
case 0x626: return " FA626";
default: break;
}
case 0x68: // HXT
switch( part )
{
case 0x0: return " Phecda";
default: break;
}
case 0xc0: // Ampere Computing
switch( part )
{
case 0xac3: return " Ampere1";
default: break;
}
default: break;
}
sprintf( buf, " 0x%x", part );
return buf;
}
#elif defined __APPLE__ && TARGET_OS_IPHONE == 1
static const char* DecodeIosDevice( const char* id )
{
static const char* DeviceTable[] = {
"i386", "32-bit simulator",
"x86_64", "64-bit simulator",
"iPhone1,1", "iPhone",
"iPhone1,2", "iPhone 3G",
"iPhone2,1", "iPhone 3GS",
"iPhone3,1", "iPhone 4 (GSM)",
"iPhone3,2", "iPhone 4 (GSM)",
"iPhone3,3", "iPhone 4 (CDMA)",
"iPhone4,1", "iPhone 4S",
"iPhone5,1", "iPhone 5 (A1428)",
"iPhone5,2", "iPhone 5 (A1429)",
"iPhone5,3", "iPhone 5c (A1456/A1532)",
"iPhone5,4", "iPhone 5c (A1507/A1516/1526/A1529)",
"iPhone6,1", "iPhone 5s (A1433/A1533)",
"iPhone6,2", "iPhone 5s (A1457/A1518/A1528/A1530)",
"iPhone7,1", "iPhone 6 Plus",
"iPhone7,2", "iPhone 6",
"iPhone8,1", "iPhone 6S",
"iPhone8,2", "iPhone 6S Plus",
"iPhone8,4", "iPhone SE",
"iPhone9,1", "iPhone 7 (CDMA)",
"iPhone9,2", "iPhone 7 Plus (CDMA)",
"iPhone9,3", "iPhone 7 (GSM)",
"iPhone9,4", "iPhone 7 Plus (GSM)",
"iPhone10,1", "iPhone 8 (CDMA)",
"iPhone10,2", "iPhone 8 Plus (CDMA)",
"iPhone10,3", "iPhone X (CDMA)",
"iPhone10,4", "iPhone 8 (GSM)",
"iPhone10,5", "iPhone 8 Plus (GSM)",
"iPhone10,6", "iPhone X (GSM)",
"iPhone11,2", "iPhone XS",
"iPhone11,4", "iPhone XS Max",
"iPhone11,6", "iPhone XS Max China",
"iPhone11,8", "iPhone XR",
"iPhone12,1", "iPhone 11",
"iPhone12,3", "iPhone 11 Pro",
"iPhone12,5", "iPhone 11 Pro Max",
"iPhone12,8", "iPhone SE 2nd Gen",
"iPhone13,1", "iPhone 12 Mini",
"iPhone13,2", "iPhone 12",
"iPhone13,3", "iPhone 12 Pro",
"iPhone13,4", "iPhone 12 Pro Max",
"iPhone14,2", "iPhone 13 Pro",
"iPhone14,3", "iPhone 13 Pro Max",
"iPhone14,4", "iPhone 13 Mini",
"iPhone14,5", "iPhone 13",
"iPhone14,6", "iPhone SE 3rd Gen",
"iPhone14,7", "iPhone 14",
"iPhone14,8", "iPhone 14 Plus",
"iPhone15,2", "iPhone 14 Pro",
"iPhone15,3", "iPhone 14 Pro Max",
"iPhone15,4", "iPhone 15",
"iPhone15,5", "iPhone 15 Plus",
"iPhone16,1", "iPhone 15 Pro",
"iPhone16,2", "iPhone 15 Pro Max",
"iPad1,1", "iPad (A1219/A1337)",
"iPad2,1", "iPad 2 (A1395)",
"iPad2,2", "iPad 2 (A1396)",
"iPad2,3", "iPad 2 (A1397)",
"iPad2,4", "iPad 2 (A1395)",
"iPad2,5", "iPad Mini (A1432)",
"iPad2,6", "iPad Mini (A1454)",
"iPad2,7", "iPad Mini (A1455)",
"iPad3,1", "iPad 3 (A1416)",
"iPad3,2", "iPad 3 (A1403)",
"iPad3,3", "iPad 3 (A1430)",
"iPad3,4", "iPad 4 (A1458)",
"iPad3,5", "iPad 4 (A1459)",
"iPad3,6", "iPad 4 (A1460)",
"iPad4,1", "iPad Air (A1474)",
"iPad4,2", "iPad Air (A1475)",
"iPad4,3", "iPad Air (A1476)",
"iPad4,4", "iPad Mini 2 (A1489)",
"iPad4,5", "iPad Mini 2 (A1490)",
"iPad4,6", "iPad Mini 2 (A1491)",
"iPad4,7", "iPad Mini 3 (A1599)",
"iPad4,8", "iPad Mini 3 (A1600)",
"iPad4,9", "iPad Mini 3 (A1601)",
"iPad5,1", "iPad Mini 4 (A1538)",
"iPad5,2", "iPad Mini 4 (A1550)",
"iPad5,3", "iPad Air 2 (A1566)",
"iPad5,4", "iPad Air 2 (A1567)",
"iPad6,3", "iPad Pro 9.7\" (A1673)",
"iPad6,4", "iPad Pro 9.7\" (A1674)",
"iPad6,5", "iPad Pro 9.7\" (A1675)",
"iPad6,7", "iPad Pro 12.9\" (A1584)",
"iPad6,8", "iPad Pro 12.9\" (A1652)",
"iPad6,11", "iPad 5th gen (A1822)",
"iPad6,12", "iPad 5th gen (A1823)",
"iPad7,1", "iPad Pro 12.9\" 2nd gen (A1670)",
"iPad7,2", "iPad Pro 12.9\" 2nd gen (A1671/A1821)",
"iPad7,3", "iPad Pro 10.5\" (A1701)",
"iPad7,4", "iPad Pro 10.5\" (A1709)",
"iPad7,5", "iPad 6th gen (A1893)",
"iPad7,6", "iPad 6th gen (A1954)",
"iPad7,11", "iPad 7th gen 10.2\" (Wifi)",
"iPad7,12", "iPad 7th gen 10.2\" (Wifi+Cellular)",
"iPad8,1", "iPad Pro 11\" (A1980)",
"iPad8,2", "iPad Pro 11\" (A1980)",
"iPad8,3", "iPad Pro 11\" (A1934/A1979/A2013)",
"iPad8,4", "iPad Pro 11\" (A1934/A1979/A2013)",
"iPad8,5", "iPad Pro 12.9\" 3rd gen (A1876)",
"iPad8,6", "iPad Pro 12.9\" 3rd gen (A1876)",
"iPad8,7", "iPad Pro 12.9\" 3rd gen (A1895/A1983/A2014)",
"iPad8,8", "iPad Pro 12.9\" 3rd gen (A1895/A1983/A2014)",
"iPad8,9", "iPad Pro 11\" 2nd gen (Wifi)",
"iPad8,10", "iPad Pro 11\" 2nd gen (Wifi+Cellular)",
"iPad8,11", "iPad Pro 12.9\" 4th gen (Wifi)",
"iPad8,12", "iPad Pro 12.9\" 4th gen (Wifi+Cellular)",
"iPad11,1", "iPad Mini 5th gen (A2133)",
"iPad11,2", "iPad Mini 5th gen (A2124/A2125/A2126)",
"iPad11,3", "iPad Air 3rd gen (A2152)",
"iPad11,4", "iPad Air 3rd gen (A2123/A2153/A2154)",
"iPad11,6", "iPad 8th gen (WiFi)",
"iPad11,7", "iPad 8th gen (WiFi+Cellular)",
"iPad12,1", "iPad 9th Gen (WiFi)",
"iPad12,2", "iPad 9th Gen (WiFi+Cellular)",
"iPad13,1", "iPad Air 4th gen (WiFi)",
"iPad13,2", "iPad Air 4th gen (WiFi+Cellular)",
"iPad13,4", "iPad Pro 11\" 3rd gen",
"iPad13,5", "iPad Pro 11\" 3rd gen",
"iPad13,6", "iPad Pro 11\" 3rd gen",
"iPad13,7", "iPad Pro 11\" 3rd gen",
"iPad13,8", "iPad Pro 12.9\" 5th gen",
"iPad13,9", "iPad Pro 12.9\" 5th gen",
"iPad13,10", "iPad Pro 12.9\" 5th gen",
"iPad13,11", "iPad Pro 12.9\" 5th gen",
"iPad13,16", "iPad Air 5th Gen (WiFi)",
"iPad13,17", "iPad Air 5th Gen (WiFi+Cellular)",
"iPad13,18", "iPad 10th Gen",
"iPad13,19", "iPad 10th Gen",
"iPad14,1", "iPad mini 6th Gen (WiFi)",
"iPad14,2", "iPad mini 6th Gen (WiFi+Cellular)",
"iPad14,3", "iPad Pro 11\" 4th Gen",
"iPad14,4", "iPad Pro 11\" 4th Gen",
"iPad14,5", "iPad Pro 12.9\" 6th Gen",
"iPad14,6", "iPad Pro 12.9\" 6th Gen",
"iPod1,1", "iPod Touch",
"iPod2,1", "iPod Touch 2nd gen",
"iPod3,1", "iPod Touch 3rd gen",
"iPod4,1", "iPod Touch 4th gen",
"iPod5,1", "iPod Touch 5th gen",
"iPod7,1", "iPod Touch 6th gen",
"iPod9,1", "iPod Touch 7th gen",
nullptr
};
auto ptr = DeviceTable;
while( *ptr )
{
if( strcmp( ptr[0], id ) == 0 ) return ptr[1];
ptr += 2;
}
return id;
}
#endif
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
#ifndef __TRACYCALLSTACK_H__
#define __TRACYCALLSTACK_H__
#ifndef TRACY_NO_CALLSTACK
# if !defined _WIN32
# include <sys/param.h>
# endif
# if defined _WIN32
# include "../common/TracyUwp.hpp"
# ifndef TRACY_UWP
# define TRACY_HAS_CALLSTACK 1
# endif
# elif defined __ANDROID__
# if !defined __arm__ || __ANDROID_API__ >= 21
# define TRACY_HAS_CALLSTACK 2
# else
# define TRACY_HAS_CALLSTACK 5
# endif
# elif defined __linux
# if defined _GNU_SOURCE && defined __GLIBC__
# define TRACY_HAS_CALLSTACK 3
# else
# define TRACY_HAS_CALLSTACK 2
# endif
# elif defined __APPLE__
# define TRACY_HAS_CALLSTACK 4
# elif defined BSD
# define TRACY_HAS_CALLSTACK 6
# endif
#endif
#endif

View File

@ -0,0 +1,153 @@
#ifndef __TRACYCALLSTACK_HPP__
#define __TRACYCALLSTACK_HPP__
#include "../common/TracyApi.h"
#include "../common/TracyForceInline.hpp"
#include "TracyCallstack.h"
#if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 5
# include <unwind.h>
#elif TRACY_HAS_CALLSTACK >= 3
# ifdef TRACE_CLIENT_LIBUNWIND_BACKTRACE
// libunwind is, in general, significantly faster than execinfo based backtraces
# define UNW_LOCAL_ONLY
# include <libunwind.h>
# else
# include <execinfo.h>
# endif
#endif
#ifndef TRACY_HAS_CALLSTACK
namespace tracy
{
static tracy_force_inline void* Callstack( int depth ) { return nullptr; }
}
#else
#ifdef TRACY_DEBUGINFOD
# include <elfutils/debuginfod.h>
#endif
#include <assert.h>
#include <stdint.h>
#include "../common/TracyAlloc.hpp"
namespace tracy
{
struct CallstackSymbolData
{
const char* file;
uint32_t line;
bool needFree;
uint64_t symAddr;
};
struct CallstackEntry
{
const char* name;
const char* file;
uint32_t line;
uint32_t symLen;
uint64_t symAddr;
};
struct CallstackEntryData
{
const CallstackEntry* data;
uint8_t size;
const char* imageName;
};
CallstackSymbolData DecodeSymbolAddress( uint64_t ptr );
const char* DecodeCallstackPtrFast( uint64_t ptr );
CallstackEntryData DecodeCallstackPtr( uint64_t ptr );
void InitCallstack();
void InitCallstackCritical();
void EndCallstack();
const char* GetKernelModulePath( uint64_t addr );
#ifdef TRACY_DEBUGINFOD
const uint8_t* GetBuildIdForImage( const char* image, size_t& size );
debuginfod_client* GetDebuginfodClient();
#endif
#if TRACY_HAS_CALLSTACK == 1
extern "C"
{
typedef unsigned long (__stdcall *___tracy_t_RtlWalkFrameChain)( void**, unsigned long, unsigned long );
TRACY_API extern ___tracy_t_RtlWalkFrameChain ___tracy_RtlWalkFrameChain;
}
static tracy_force_inline void* Callstack( int depth )
{
assert( depth >= 1 && depth < 63 );
auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) );
const auto num = ___tracy_RtlWalkFrameChain( (void**)( trace + 1 ), depth, 0 );
*trace = num;
return trace;
}
#elif TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 5
struct BacktraceState
{
void** current;
void** end;
};
static _Unwind_Reason_Code tracy_unwind_callback( struct _Unwind_Context* ctx, void* arg )
{
auto state = (BacktraceState*)arg;
uintptr_t pc = _Unwind_GetIP( ctx );
if( pc )
{
if( state->current == state->end ) return _URC_END_OF_STACK;
*state->current++ = (void*)pc;
}
return _URC_NO_REASON;
}
static tracy_force_inline void* Callstack( int depth )
{
assert( depth >= 1 && depth < 63 );
auto trace = (uintptr_t*)tracy_malloc( ( 1 + depth ) * sizeof( uintptr_t ) );
BacktraceState state = { (void**)(trace+1), (void**)(trace+1+depth) };
_Unwind_Backtrace( tracy_unwind_callback, &state );
*trace = (uintptr_t*)state.current - trace + 1;
return trace;
}
#elif TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6
static tracy_force_inline void* Callstack( int depth )
{
assert( depth >= 1 );
auto trace = (uintptr_t*)tracy_malloc( ( 1 + (size_t)depth ) * sizeof( uintptr_t ) );
#ifdef TRACE_CLIENT_LIBUNWIND_BACKTRACE
size_t num = unw_backtrace( (void**)(trace+1), depth );
#else
const auto num = (size_t)backtrace( (void**)(trace+1), depth );
#endif
*trace = num;
return trace;
}
#endif
}
#endif
#endif

View File

@ -0,0 +1,12 @@
#ifndef __TRACYCPUID_HPP__
#define __TRACYCPUID_HPP__
// Prior to GCC 11 the cpuid.h header did not have any include guards and thus
// including it more than once would cause a compiler error due to symbol
// redefinitions. In order to support older GCC versions, we have to wrap this
// include between custom include guards to prevent this issue.
// See also https://github.com/wolfpld/tracy/issues/452
#include <cpuid.h>
#endif

View File

@ -0,0 +1,11 @@
#ifndef __TRACYPRINT_HPP__
#define __TRACYPRINT_HPP__
#ifdef TRACY_VERBOSE
# include <stdio.h>
# define TracyDebug(...) fprintf( stderr, __VA_ARGS__ );
#else
# define TracyDebug(...)
#endif
#endif

View File

@ -0,0 +1,644 @@
#include "TracyDxt1.hpp"
#include "../common/TracyForceInline.hpp"
#include <assert.h>
#include <stdint.h>
#include <string.h>
#ifdef __ARM_NEON
# include <arm_neon.h>
#endif
#if defined __AVX__ && !defined __SSE4_1__
# define __SSE4_1__
#endif
#if defined __SSE4_1__ || defined __AVX2__
# ifdef _MSC_VER
# include <intrin.h>
# else
# include <x86intrin.h>
# ifndef _mm256_cvtsi256_si32
# define _mm256_cvtsi256_si32( v ) ( _mm_cvtsi128_si32( _mm256_castsi256_si128( v ) ) )
# endif
# endif
#endif
namespace tracy
{
static inline uint16_t to565( uint8_t r, uint8_t g, uint8_t b )
{
return ( ( r & 0xF8 ) << 8 ) | ( ( g & 0xFC ) << 3 ) | ( b >> 3 );
}
static inline uint16_t to565( uint32_t c )
{
return
( ( c & 0xF80000 ) >> 19 ) |
( ( c & 0x00FC00 ) >> 5 ) |
( ( c & 0x0000F8 ) << 8 );
}
static const uint16_t DivTable[255*3+1] = {
0xffff, 0xffff, 0xffff, 0xffff, 0xcccc, 0xaaaa, 0x9249, 0x8000, 0x71c7, 0x6666, 0x5d17, 0x5555, 0x4ec4, 0x4924, 0x4444, 0x4000,
0x3c3c, 0x38e3, 0x35e5, 0x3333, 0x30c3, 0x2e8b, 0x2c85, 0x2aaa, 0x28f5, 0x2762, 0x25ed, 0x2492, 0x234f, 0x2222, 0x2108, 0x2000,
0x1f07, 0x1e1e, 0x1d41, 0x1c71, 0x1bac, 0x1af2, 0x1a41, 0x1999, 0x18f9, 0x1861, 0x17d0, 0x1745, 0x16c1, 0x1642, 0x15c9, 0x1555,
0x14e5, 0x147a, 0x1414, 0x13b1, 0x1352, 0x12f6, 0x129e, 0x1249, 0x11f7, 0x11a7, 0x115b, 0x1111, 0x10c9, 0x1084, 0x1041, 0x1000,
0x0fc0, 0x0f83, 0x0f48, 0x0f0f, 0x0ed7, 0x0ea0, 0x0e6c, 0x0e38, 0x0e07, 0x0dd6, 0x0da7, 0x0d79, 0x0d4c, 0x0d20, 0x0cf6, 0x0ccc,
0x0ca4, 0x0c7c, 0x0c56, 0x0c30, 0x0c0c, 0x0be8, 0x0bc5, 0x0ba2, 0x0b81, 0x0b60, 0x0b40, 0x0b21, 0x0b02, 0x0ae4, 0x0ac7, 0x0aaa,
0x0a8e, 0x0a72, 0x0a57, 0x0a3d, 0x0a23, 0x0a0a, 0x09f1, 0x09d8, 0x09c0, 0x09a9, 0x0991, 0x097b, 0x0964, 0x094f, 0x0939, 0x0924,
0x090f, 0x08fb, 0x08e7, 0x08d3, 0x08c0, 0x08ad, 0x089a, 0x0888, 0x0876, 0x0864, 0x0853, 0x0842, 0x0831, 0x0820, 0x0810, 0x0800,
0x07f0, 0x07e0, 0x07d1, 0x07c1, 0x07b3, 0x07a4, 0x0795, 0x0787, 0x0779, 0x076b, 0x075d, 0x0750, 0x0743, 0x0736, 0x0729, 0x071c,
0x070f, 0x0703, 0x06f7, 0x06eb, 0x06df, 0x06d3, 0x06c8, 0x06bc, 0x06b1, 0x06a6, 0x069b, 0x0690, 0x0685, 0x067b, 0x0670, 0x0666,
0x065c, 0x0652, 0x0648, 0x063e, 0x0634, 0x062b, 0x0621, 0x0618, 0x060f, 0x0606, 0x05fd, 0x05f4, 0x05eb, 0x05e2, 0x05d9, 0x05d1,
0x05c9, 0x05c0, 0x05b8, 0x05b0, 0x05a8, 0x05a0, 0x0598, 0x0590, 0x0588, 0x0581, 0x0579, 0x0572, 0x056b, 0x0563, 0x055c, 0x0555,
0x054e, 0x0547, 0x0540, 0x0539, 0x0532, 0x052b, 0x0525, 0x051e, 0x0518, 0x0511, 0x050b, 0x0505, 0x04fe, 0x04f8, 0x04f2, 0x04ec,
0x04e6, 0x04e0, 0x04da, 0x04d4, 0x04ce, 0x04c8, 0x04c3, 0x04bd, 0x04b8, 0x04b2, 0x04ad, 0x04a7, 0x04a2, 0x049c, 0x0497, 0x0492,
0x048d, 0x0487, 0x0482, 0x047d, 0x0478, 0x0473, 0x046e, 0x0469, 0x0465, 0x0460, 0x045b, 0x0456, 0x0452, 0x044d, 0x0448, 0x0444,
0x043f, 0x043b, 0x0436, 0x0432, 0x042d, 0x0429, 0x0425, 0x0421, 0x041c, 0x0418, 0x0414, 0x0410, 0x040c, 0x0408, 0x0404, 0x0400,
0x03fc, 0x03f8, 0x03f4, 0x03f0, 0x03ec, 0x03e8, 0x03e4, 0x03e0, 0x03dd, 0x03d9, 0x03d5, 0x03d2, 0x03ce, 0x03ca, 0x03c7, 0x03c3,
0x03c0, 0x03bc, 0x03b9, 0x03b5, 0x03b2, 0x03ae, 0x03ab, 0x03a8, 0x03a4, 0x03a1, 0x039e, 0x039b, 0x0397, 0x0394, 0x0391, 0x038e,
0x038b, 0x0387, 0x0384, 0x0381, 0x037e, 0x037b, 0x0378, 0x0375, 0x0372, 0x036f, 0x036c, 0x0369, 0x0366, 0x0364, 0x0361, 0x035e,
0x035b, 0x0358, 0x0355, 0x0353, 0x0350, 0x034d, 0x034a, 0x0348, 0x0345, 0x0342, 0x0340, 0x033d, 0x033a, 0x0338, 0x0335, 0x0333,
0x0330, 0x032e, 0x032b, 0x0329, 0x0326, 0x0324, 0x0321, 0x031f, 0x031c, 0x031a, 0x0317, 0x0315, 0x0313, 0x0310, 0x030e, 0x030c,
0x0309, 0x0307, 0x0305, 0x0303, 0x0300, 0x02fe, 0x02fc, 0x02fa, 0x02f7, 0x02f5, 0x02f3, 0x02f1, 0x02ef, 0x02ec, 0x02ea, 0x02e8,
0x02e6, 0x02e4, 0x02e2, 0x02e0, 0x02de, 0x02dc, 0x02da, 0x02d8, 0x02d6, 0x02d4, 0x02d2, 0x02d0, 0x02ce, 0x02cc, 0x02ca, 0x02c8,
0x02c6, 0x02c4, 0x02c2, 0x02c0, 0x02be, 0x02bc, 0x02bb, 0x02b9, 0x02b7, 0x02b5, 0x02b3, 0x02b1, 0x02b0, 0x02ae, 0x02ac, 0x02aa,
0x02a8, 0x02a7, 0x02a5, 0x02a3, 0x02a1, 0x02a0, 0x029e, 0x029c, 0x029b, 0x0299, 0x0297, 0x0295, 0x0294, 0x0292, 0x0291, 0x028f,
0x028d, 0x028c, 0x028a, 0x0288, 0x0287, 0x0285, 0x0284, 0x0282, 0x0280, 0x027f, 0x027d, 0x027c, 0x027a, 0x0279, 0x0277, 0x0276,
0x0274, 0x0273, 0x0271, 0x0270, 0x026e, 0x026d, 0x026b, 0x026a, 0x0268, 0x0267, 0x0265, 0x0264, 0x0263, 0x0261, 0x0260, 0x025e,
0x025d, 0x025c, 0x025a, 0x0259, 0x0257, 0x0256, 0x0255, 0x0253, 0x0252, 0x0251, 0x024f, 0x024e, 0x024d, 0x024b, 0x024a, 0x0249,
0x0247, 0x0246, 0x0245, 0x0243, 0x0242, 0x0241, 0x0240, 0x023e, 0x023d, 0x023c, 0x023b, 0x0239, 0x0238, 0x0237, 0x0236, 0x0234,
0x0233, 0x0232, 0x0231, 0x0230, 0x022e, 0x022d, 0x022c, 0x022b, 0x022a, 0x0229, 0x0227, 0x0226, 0x0225, 0x0224, 0x0223, 0x0222,
0x0220, 0x021f, 0x021e, 0x021d, 0x021c, 0x021b, 0x021a, 0x0219, 0x0218, 0x0216, 0x0215, 0x0214, 0x0213, 0x0212, 0x0211, 0x0210,
0x020f, 0x020e, 0x020d, 0x020c, 0x020b, 0x020a, 0x0209, 0x0208, 0x0207, 0x0206, 0x0205, 0x0204, 0x0203, 0x0202, 0x0201, 0x0200,
0x01ff, 0x01fe, 0x01fd, 0x01fc, 0x01fb, 0x01fa, 0x01f9, 0x01f8, 0x01f7, 0x01f6, 0x01f5, 0x01f4, 0x01f3, 0x01f2, 0x01f1, 0x01f0,
0x01ef, 0x01ee, 0x01ed, 0x01ec, 0x01eb, 0x01ea, 0x01e9, 0x01e9, 0x01e8, 0x01e7, 0x01e6, 0x01e5, 0x01e4, 0x01e3, 0x01e2, 0x01e1,
0x01e0, 0x01e0, 0x01df, 0x01de, 0x01dd, 0x01dc, 0x01db, 0x01da, 0x01da, 0x01d9, 0x01d8, 0x01d7, 0x01d6, 0x01d5, 0x01d4, 0x01d4,
0x01d3, 0x01d2, 0x01d1, 0x01d0, 0x01cf, 0x01cf, 0x01ce, 0x01cd, 0x01cc, 0x01cb, 0x01cb, 0x01ca, 0x01c9, 0x01c8, 0x01c7, 0x01c7,
0x01c6, 0x01c5, 0x01c4, 0x01c3, 0x01c3, 0x01c2, 0x01c1, 0x01c0, 0x01c0, 0x01bf, 0x01be, 0x01bd, 0x01bd, 0x01bc, 0x01bb, 0x01ba,
0x01ba, 0x01b9, 0x01b8, 0x01b7, 0x01b7, 0x01b6, 0x01b5, 0x01b4, 0x01b4, 0x01b3, 0x01b2, 0x01b2, 0x01b1, 0x01b0, 0x01af, 0x01af,
0x01ae, 0x01ad, 0x01ad, 0x01ac, 0x01ab, 0x01aa, 0x01aa, 0x01a9, 0x01a8, 0x01a8, 0x01a7, 0x01a6, 0x01a6, 0x01a5, 0x01a4, 0x01a4,
0x01a3, 0x01a2, 0x01a2, 0x01a1, 0x01a0, 0x01a0, 0x019f, 0x019e, 0x019e, 0x019d, 0x019c, 0x019c, 0x019b, 0x019a, 0x019a, 0x0199,
0x0198, 0x0198, 0x0197, 0x0197, 0x0196, 0x0195, 0x0195, 0x0194, 0x0193, 0x0193, 0x0192, 0x0192, 0x0191, 0x0190, 0x0190, 0x018f,
0x018f, 0x018e, 0x018d, 0x018d, 0x018c, 0x018b, 0x018b, 0x018a, 0x018a, 0x0189, 0x0189, 0x0188, 0x0187, 0x0187, 0x0186, 0x0186,
0x0185, 0x0184, 0x0184, 0x0183, 0x0183, 0x0182, 0x0182, 0x0181, 0x0180, 0x0180, 0x017f, 0x017f, 0x017e, 0x017e, 0x017d, 0x017d,
0x017c, 0x017b, 0x017b, 0x017a, 0x017a, 0x0179, 0x0179, 0x0178, 0x0178, 0x0177, 0x0177, 0x0176, 0x0175, 0x0175, 0x0174, 0x0174,
0x0173, 0x0173, 0x0172, 0x0172, 0x0171, 0x0171, 0x0170, 0x0170, 0x016f, 0x016f, 0x016e, 0x016e, 0x016d, 0x016d, 0x016c, 0x016c,
0x016b, 0x016b, 0x016a, 0x016a, 0x0169, 0x0169, 0x0168, 0x0168, 0x0167, 0x0167, 0x0166, 0x0166, 0x0165, 0x0165, 0x0164, 0x0164,
0x0163, 0x0163, 0x0162, 0x0162, 0x0161, 0x0161, 0x0160, 0x0160, 0x015f, 0x015f, 0x015e, 0x015e, 0x015d, 0x015d, 0x015d, 0x015c,
0x015c, 0x015b, 0x015b, 0x015a, 0x015a, 0x0159, 0x0159, 0x0158, 0x0158, 0x0158, 0x0157, 0x0157, 0x0156, 0x0156
};
#if defined __ARM_NEON && defined __aarch64__
static const uint16_t DivTableNEON[255*3+1] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x1c71, 0x1af2, 0x1999, 0x1861, 0x1745, 0x1642, 0x1555, 0x147a, 0x13b1, 0x12f6, 0x1249, 0x11a7, 0x1111, 0x1084, 0x1000,
0x0f83, 0x0f0f, 0x0ea0, 0x0e38, 0x0dd6, 0x0d79, 0x0d20, 0x0ccc, 0x0c7c, 0x0c30, 0x0be8, 0x0ba2, 0x0b60, 0x0b21, 0x0ae4, 0x0aaa,
0x0a72, 0x0a3d, 0x0a0a, 0x09d8, 0x09a9, 0x097b, 0x094f, 0x0924, 0x08fb, 0x08d3, 0x08ad, 0x0888, 0x0864, 0x0842, 0x0820, 0x0800,
0x07e0, 0x07c1, 0x07a4, 0x0787, 0x076b, 0x0750, 0x0736, 0x071c, 0x0703, 0x06eb, 0x06d3, 0x06bc, 0x06a6, 0x0690, 0x067b, 0x0666,
0x0652, 0x063e, 0x062b, 0x0618, 0x0606, 0x05f4, 0x05e2, 0x05d1, 0x05c0, 0x05b0, 0x05a0, 0x0590, 0x0581, 0x0572, 0x0563, 0x0555,
0x0547, 0x0539, 0x052b, 0x051e, 0x0511, 0x0505, 0x04f8, 0x04ec, 0x04e0, 0x04d4, 0x04c8, 0x04bd, 0x04b2, 0x04a7, 0x049c, 0x0492,
0x0487, 0x047d, 0x0473, 0x0469, 0x0460, 0x0456, 0x044d, 0x0444, 0x043b, 0x0432, 0x0429, 0x0421, 0x0418, 0x0410, 0x0408, 0x0400,
0x03f8, 0x03f0, 0x03e8, 0x03e0, 0x03d9, 0x03d2, 0x03ca, 0x03c3, 0x03bc, 0x03b5, 0x03ae, 0x03a8, 0x03a1, 0x039b, 0x0394, 0x038e,
0x0387, 0x0381, 0x037b, 0x0375, 0x036f, 0x0369, 0x0364, 0x035e, 0x0358, 0x0353, 0x034d, 0x0348, 0x0342, 0x033d, 0x0338, 0x0333,
0x032e, 0x0329, 0x0324, 0x031f, 0x031a, 0x0315, 0x0310, 0x030c, 0x0307, 0x0303, 0x02fe, 0x02fa, 0x02f5, 0x02f1, 0x02ec, 0x02e8,
0x02e4, 0x02e0, 0x02dc, 0x02d8, 0x02d4, 0x02d0, 0x02cc, 0x02c8, 0x02c4, 0x02c0, 0x02bc, 0x02b9, 0x02b5, 0x02b1, 0x02ae, 0x02aa,
0x02a7, 0x02a3, 0x02a0, 0x029c, 0x0299, 0x0295, 0x0292, 0x028f, 0x028c, 0x0288, 0x0285, 0x0282, 0x027f, 0x027c, 0x0279, 0x0276,
0x0273, 0x0270, 0x026d, 0x026a, 0x0267, 0x0264, 0x0261, 0x025e, 0x025c, 0x0259, 0x0256, 0x0253, 0x0251, 0x024e, 0x024b, 0x0249,
0x0246, 0x0243, 0x0241, 0x023e, 0x023c, 0x0239, 0x0237, 0x0234, 0x0232, 0x0230, 0x022d, 0x022b, 0x0229, 0x0226, 0x0224, 0x0222,
0x021f, 0x021d, 0x021b, 0x0219, 0x0216, 0x0214, 0x0212, 0x0210, 0x020e, 0x020c, 0x020a, 0x0208, 0x0206, 0x0204, 0x0202, 0x0200,
0x01fe, 0x01fc, 0x01fa, 0x01f8, 0x01f6, 0x01f4, 0x01f2, 0x01f0, 0x01ee, 0x01ec, 0x01ea, 0x01e9, 0x01e7, 0x01e5, 0x01e3, 0x01e1,
0x01e0, 0x01de, 0x01dc, 0x01da, 0x01d9, 0x01d7, 0x01d5, 0x01d4, 0x01d2, 0x01d0, 0x01cf, 0x01cd, 0x01cb, 0x01ca, 0x01c8, 0x01c7,
0x01c5, 0x01c3, 0x01c2, 0x01c0, 0x01bf, 0x01bd, 0x01bc, 0x01ba, 0x01b9, 0x01b7, 0x01b6, 0x01b4, 0x01b3, 0x01b2, 0x01b0, 0x01af,
0x01ad, 0x01ac, 0x01aa, 0x01a9, 0x01a8, 0x01a6, 0x01a5, 0x01a4, 0x01a2, 0x01a1, 0x01a0, 0x019e, 0x019d, 0x019c, 0x019a, 0x0199,
0x0198, 0x0197, 0x0195, 0x0194, 0x0193, 0x0192, 0x0190, 0x018f, 0x018e, 0x018d, 0x018b, 0x018a, 0x0189, 0x0188, 0x0187, 0x0186,
0x0184, 0x0183, 0x0182, 0x0181, 0x0180, 0x017f, 0x017e, 0x017d, 0x017b, 0x017a, 0x0179, 0x0178, 0x0177, 0x0176, 0x0175, 0x0174,
0x0173, 0x0172, 0x0171, 0x0170, 0x016f, 0x016e, 0x016d, 0x016c, 0x016b, 0x016a, 0x0169, 0x0168, 0x0167, 0x0166, 0x0165, 0x0164,
0x0163, 0x0162, 0x0161, 0x0160, 0x015f, 0x015e, 0x015d, 0x015c, 0x015b, 0x015a, 0x0159, 0x0158, 0x0158, 0x0157, 0x0156, 0x0155,
0x0154, 0x0153, 0x0152, 0x0151, 0x0150, 0x0150, 0x014f, 0x014e, 0x014d, 0x014c, 0x014b, 0x014a, 0x014a, 0x0149, 0x0148, 0x0147,
0x0146, 0x0146, 0x0145, 0x0144, 0x0143, 0x0142, 0x0142, 0x0141, 0x0140, 0x013f, 0x013e, 0x013e, 0x013d, 0x013c, 0x013b, 0x013b,
0x013a, 0x0139, 0x0138, 0x0138, 0x0137, 0x0136, 0x0135, 0x0135, 0x0134, 0x0133, 0x0132, 0x0132, 0x0131, 0x0130, 0x0130, 0x012f,
0x012e, 0x012e, 0x012d, 0x012c, 0x012b, 0x012b, 0x012a, 0x0129, 0x0129, 0x0128, 0x0127, 0x0127, 0x0126, 0x0125, 0x0125, 0x0124,
0x0123, 0x0123, 0x0122, 0x0121, 0x0121, 0x0120, 0x0120, 0x011f, 0x011e, 0x011e, 0x011d, 0x011c, 0x011c, 0x011b, 0x011b, 0x011a,
0x0119, 0x0119, 0x0118, 0x0118, 0x0117, 0x0116, 0x0116, 0x0115, 0x0115, 0x0114, 0x0113, 0x0113, 0x0112, 0x0112, 0x0111, 0x0111,
0x0110, 0x010f, 0x010f, 0x010e, 0x010e, 0x010d, 0x010d, 0x010c, 0x010c, 0x010b, 0x010a, 0x010a, 0x0109, 0x0109, 0x0108, 0x0108,
0x0107, 0x0107, 0x0106, 0x0106, 0x0105, 0x0105, 0x0104, 0x0104, 0x0103, 0x0103, 0x0102, 0x0102, 0x0101, 0x0101, 0x0100, 0x0100,
0x00ff, 0x00ff, 0x00fe, 0x00fe, 0x00fd, 0x00fd, 0x00fc, 0x00fc, 0x00fb, 0x00fb, 0x00fa, 0x00fa, 0x00f9, 0x00f9, 0x00f8, 0x00f8,
0x00f7, 0x00f7, 0x00f6, 0x00f6, 0x00f5, 0x00f5, 0x00f4, 0x00f4, 0x00f4, 0x00f3, 0x00f3, 0x00f2, 0x00f2, 0x00f1, 0x00f1, 0x00f0,
0x00f0, 0x00f0, 0x00ef, 0x00ef, 0x00ee, 0x00ee, 0x00ed, 0x00ed, 0x00ed, 0x00ec, 0x00ec, 0x00eb, 0x00eb, 0x00ea, 0x00ea, 0x00ea,
0x00e9, 0x00e9, 0x00e8, 0x00e8, 0x00e7, 0x00e7, 0x00e7, 0x00e6, 0x00e6, 0x00e5, 0x00e5, 0x00e5, 0x00e4, 0x00e4, 0x00e3, 0x00e3,
0x00e3, 0x00e2, 0x00e2, 0x00e1, 0x00e1, 0x00e1, 0x00e0, 0x00e0, 0x00e0, 0x00df, 0x00df, 0x00de, 0x00de, 0x00de, 0x00dd, 0x00dd,
0x00dd, 0x00dc, 0x00dc, 0x00db, 0x00db, 0x00db, 0x00da, 0x00da, 0x00da, 0x00d9, 0x00d9, 0x00d9, 0x00d8, 0x00d8, 0x00d7, 0x00d7,
0x00d7, 0x00d6, 0x00d6, 0x00d6, 0x00d5, 0x00d5, 0x00d5, 0x00d4, 0x00d4, 0x00d4, 0x00d3, 0x00d3, 0x00d3, 0x00d2, 0x00d2, 0x00d2,
0x00d1, 0x00d1, 0x00d1, 0x00d0, 0x00d0, 0x00d0, 0x00cf, 0x00cf, 0x00cf, 0x00ce, 0x00ce, 0x00ce, 0x00cd, 0x00cd, 0x00cd, 0x00cc,
0x00cc, 0x00cc, 0x00cb, 0x00cb, 0x00cb, 0x00ca, 0x00ca, 0x00ca, 0x00c9, 0x00c9, 0x00c9, 0x00c9, 0x00c8, 0x00c8, 0x00c8, 0x00c7,
0x00c7, 0x00c7, 0x00c6, 0x00c6, 0x00c6, 0x00c5, 0x00c5, 0x00c5, 0x00c5, 0x00c4, 0x00c4, 0x00c4, 0x00c3, 0x00c3, 0x00c3, 0x00c3,
0x00c2, 0x00c2, 0x00c2, 0x00c1, 0x00c1, 0x00c1, 0x00c1, 0x00c0, 0x00c0, 0x00c0, 0x00bf, 0x00bf, 0x00bf, 0x00bf, 0x00be, 0x00be,
0x00be, 0x00bd, 0x00bd, 0x00bd, 0x00bd, 0x00bc, 0x00bc, 0x00bc, 0x00bc, 0x00bb, 0x00bb, 0x00bb, 0x00ba, 0x00ba, 0x00ba, 0x00ba,
0x00b9, 0x00b9, 0x00b9, 0x00b9, 0x00b8, 0x00b8, 0x00b8, 0x00b8, 0x00b7, 0x00b7, 0x00b7, 0x00b7, 0x00b6, 0x00b6, 0x00b6, 0x00b6,
0x00b5, 0x00b5, 0x00b5, 0x00b5, 0x00b4, 0x00b4, 0x00b4, 0x00b4, 0x00b3, 0x00b3, 0x00b3, 0x00b3, 0x00b2, 0x00b2, 0x00b2, 0x00b2,
0x00b1, 0x00b1, 0x00b1, 0x00b1, 0x00b0, 0x00b0, 0x00b0, 0x00b0, 0x00af, 0x00af, 0x00af, 0x00af, 0x00ae, 0x00ae, 0x00ae, 0x00ae,
0x00ae, 0x00ad, 0x00ad, 0x00ad, 0x00ad, 0x00ac, 0x00ac, 0x00ac, 0x00ac, 0x00ac, 0x00ab, 0x00ab, 0x00ab, 0x00ab,
};
#endif
static tracy_force_inline uint64_t ProcessRGB( const uint8_t* src )
{
#ifdef __SSE4_1__
__m128i px0 = _mm_loadu_si128(((__m128i*)src) + 0);
__m128i px1 = _mm_loadu_si128(((__m128i*)src) + 1);
__m128i px2 = _mm_loadu_si128(((__m128i*)src) + 2);
__m128i px3 = _mm_loadu_si128(((__m128i*)src) + 3);
__m128i smask = _mm_set1_epi32( 0xF8FCF8 );
__m128i sd0 = _mm_and_si128( px0, smask );
__m128i sd1 = _mm_and_si128( px1, smask );
__m128i sd2 = _mm_and_si128( px2, smask );
__m128i sd3 = _mm_and_si128( px3, smask );
__m128i sc = _mm_shuffle_epi32(sd0, _MM_SHUFFLE(0, 0, 0, 0));
__m128i sc0 = _mm_cmpeq_epi8(sd0, sc);
__m128i sc1 = _mm_cmpeq_epi8(sd1, sc);
__m128i sc2 = _mm_cmpeq_epi8(sd2, sc);
__m128i sc3 = _mm_cmpeq_epi8(sd3, sc);
__m128i sm0 = _mm_and_si128(sc0, sc1);
__m128i sm1 = _mm_and_si128(sc2, sc3);
__m128i sm = _mm_and_si128(sm0, sm1);
if( _mm_testc_si128(sm, _mm_set1_epi32(-1)) )
{
return uint64_t( to565( src[0], src[1], src[2] ) ) << 16;
}
__m128i amask = _mm_set1_epi32( 0xFFFFFF );
px0 = _mm_and_si128( px0, amask );
px1 = _mm_and_si128( px1, amask );
px2 = _mm_and_si128( px2, amask );
px3 = _mm_and_si128( px3, amask );
__m128i min0 = _mm_min_epu8( px0, px1 );
__m128i min1 = _mm_min_epu8( px2, px3 );
__m128i min2 = _mm_min_epu8( min0, min1 );
__m128i max0 = _mm_max_epu8( px0, px1 );
__m128i max1 = _mm_max_epu8( px2, px3 );
__m128i max2 = _mm_max_epu8( max0, max1 );
__m128i min3 = _mm_shuffle_epi32( min2, _MM_SHUFFLE( 2, 3, 0, 1 ) );
__m128i max3 = _mm_shuffle_epi32( max2, _MM_SHUFFLE( 2, 3, 0, 1 ) );
__m128i min4 = _mm_min_epu8( min2, min3 );
__m128i max4 = _mm_max_epu8( max2, max3 );
__m128i min5 = _mm_shuffle_epi32( min4, _MM_SHUFFLE( 0, 0, 2, 2 ) );
__m128i max5 = _mm_shuffle_epi32( max4, _MM_SHUFFLE( 0, 0, 2, 2 ) );
__m128i rmin = _mm_min_epu8( min4, min5 );
__m128i rmax = _mm_max_epu8( max4, max5 );
__m128i range1 = _mm_subs_epu8( rmax, rmin );
__m128i range2 = _mm_sad_epu8( rmax, rmin );
uint32_t vrange = _mm_cvtsi128_si32( range2 ) >> 1;
__m128i range = _mm_set1_epi16( DivTable[vrange] );
__m128i inset1 = _mm_srli_epi16( range1, 4 );
__m128i inset = _mm_and_si128( inset1, _mm_set1_epi8( 0xF ) );
__m128i min = _mm_adds_epu8( rmin, inset );
__m128i max = _mm_subs_epu8( rmax, inset );
__m128i c0 = _mm_subs_epu8( px0, rmin );
__m128i c1 = _mm_subs_epu8( px1, rmin );
__m128i c2 = _mm_subs_epu8( px2, rmin );
__m128i c3 = _mm_subs_epu8( px3, rmin );
__m128i is0 = _mm_maddubs_epi16( c0, _mm_set1_epi8( 1 ) );
__m128i is1 = _mm_maddubs_epi16( c1, _mm_set1_epi8( 1 ) );
__m128i is2 = _mm_maddubs_epi16( c2, _mm_set1_epi8( 1 ) );
__m128i is3 = _mm_maddubs_epi16( c3, _mm_set1_epi8( 1 ) );
__m128i s0 = _mm_hadd_epi16( is0, is1 );
__m128i s1 = _mm_hadd_epi16( is2, is3 );
__m128i m0 = _mm_mulhi_epu16( s0, range );
__m128i m1 = _mm_mulhi_epu16( s1, range );
__m128i p0 = _mm_packus_epi16( m0, m1 );
__m128i p1 = _mm_or_si128( _mm_srai_epi32( p0, 6 ), _mm_srai_epi32( p0, 12 ) );
__m128i p2 = _mm_or_si128( _mm_srai_epi32( p0, 18 ), p0 );
__m128i p3 = _mm_or_si128( p1, p2 );
__m128i p =_mm_shuffle_epi8( p3, _mm_set1_epi32( 0x0C080400 ) );
uint32_t vmin = _mm_cvtsi128_si32( min );
uint32_t vmax = _mm_cvtsi128_si32( max );
uint32_t vp = _mm_cvtsi128_si32( p );
return uint64_t( ( uint64_t( to565( vmin ) ) << 16 ) | to565( vmax ) | ( uint64_t( vp ) << 32 ) );
#elif defined __ARM_NEON
# ifdef __aarch64__
uint8x16x4_t px = vld4q_u8( src );
uint8x16_t lr = px.val[0];
uint8x16_t lg = px.val[1];
uint8x16_t lb = px.val[2];
uint8_t rmaxr = vmaxvq_u8( lr );
uint8_t rmaxg = vmaxvq_u8( lg );
uint8_t rmaxb = vmaxvq_u8( lb );
uint8_t rminr = vminvq_u8( lr );
uint8_t rming = vminvq_u8( lg );
uint8_t rminb = vminvq_u8( lb );
int rr = rmaxr - rminr;
int rg = rmaxg - rming;
int rb = rmaxb - rminb;
int vrange1 = rr + rg + rb;
uint16_t vrange2 = DivTableNEON[vrange1];
uint8_t insetr = rr >> 4;
uint8_t insetg = rg >> 4;
uint8_t insetb = rb >> 4;
uint8_t minr = rminr + insetr;
uint8_t ming = rming + insetg;
uint8_t minb = rminb + insetb;
uint8_t maxr = rmaxr - insetr;
uint8_t maxg = rmaxg - insetg;
uint8_t maxb = rmaxb - insetb;
uint8x16_t cr = vsubq_u8( lr, vdupq_n_u8( rminr ) );
uint8x16_t cg = vsubq_u8( lg, vdupq_n_u8( rming ) );
uint8x16_t cb = vsubq_u8( lb, vdupq_n_u8( rminb ) );
uint16x8_t is0l = vaddl_u8( vget_low_u8( cr ), vget_low_u8( cg ) );
uint16x8_t is0h = vaddl_u8( vget_high_u8( cr ), vget_high_u8( cg ) );
uint16x8_t is1l = vaddw_u8( is0l, vget_low_u8( cb ) );
uint16x8_t is1h = vaddw_u8( is0h, vget_high_u8( cb ) );
int16x8_t range = vdupq_n_s16( vrange2 );
uint16x8_t m0 = vreinterpretq_u16_s16( vqdmulhq_s16( vreinterpretq_s16_u16( is1l ), range ) );
uint16x8_t m1 = vreinterpretq_u16_s16( vqdmulhq_s16( vreinterpretq_s16_u16( is1h ), range ) );
uint8x8_t p00 = vmovn_u16( m0 );
uint8x8_t p01 = vmovn_u16( m1 );
uint8x16_t p0 = vcombine_u8( p00, p01 );
uint32x4_t p1 = vaddq_u32( vshrq_n_u32( vreinterpretq_u32_u8( p0 ), 6 ), vshrq_n_u32( vreinterpretq_u32_u8( p0 ), 12 ) );
uint32x4_t p2 = vaddq_u32( vshrq_n_u32( vreinterpretq_u32_u8( p0 ), 18 ), vreinterpretq_u32_u8( p0 ) );
uint32x4_t p3 = vaddq_u32( p1, p2 );
uint16x4x2_t p4 = vuzp_u16( vget_low_u16( vreinterpretq_u16_u32( p3 ) ), vget_high_u16( vreinterpretq_u16_u32( p3 ) ) );
uint8x8x2_t p = vuzp_u8( vreinterpret_u8_u16( p4.val[0] ), vreinterpret_u8_u16( p4.val[0] ) );
uint32_t vp;
vst1_lane_u32( &vp, vreinterpret_u32_u8( p.val[0] ), 0 );
return uint64_t( ( uint64_t( to565( minr, ming, minb ) ) << 16 ) | to565( maxr, maxg, maxb ) | ( uint64_t( vp ) << 32 ) );
# else
uint32x4_t px0 = vld1q_u32( (uint32_t*)src );
uint32x4_t px1 = vld1q_u32( (uint32_t*)src + 4 );
uint32x4_t px2 = vld1q_u32( (uint32_t*)src + 8 );
uint32x4_t px3 = vld1q_u32( (uint32_t*)src + 12 );
uint32x4_t smask = vdupq_n_u32( 0xF8FCF8 );
uint32x4_t sd0 = vandq_u32( smask, px0 );
uint32x4_t sd1 = vandq_u32( smask, px1 );
uint32x4_t sd2 = vandq_u32( smask, px2 );
uint32x4_t sd3 = vandq_u32( smask, px3 );
uint32x4_t sc = vdupq_n_u32( sd0[0] );
uint32x4_t sc0 = vceqq_u32( sd0, sc );
uint32x4_t sc1 = vceqq_u32( sd1, sc );
uint32x4_t sc2 = vceqq_u32( sd2, sc );
uint32x4_t sc3 = vceqq_u32( sd3, sc );
uint32x4_t sm0 = vandq_u32( sc0, sc1 );
uint32x4_t sm1 = vandq_u32( sc2, sc3 );
int64x2_t sm = vreinterpretq_s64_u32( vandq_u32( sm0, sm1 ) );
if( sm[0] == -1 && sm[1] == -1 )
{
return uint64_t( to565( src[0], src[1], src[2] ) ) << 16;
}
uint32x4_t mask = vdupq_n_u32( 0xFFFFFF );
uint8x16_t l0 = vreinterpretq_u8_u32( vandq_u32( mask, px0 ) );
uint8x16_t l1 = vreinterpretq_u8_u32( vandq_u32( mask, px1 ) );
uint8x16_t l2 = vreinterpretq_u8_u32( vandq_u32( mask, px2 ) );
uint8x16_t l3 = vreinterpretq_u8_u32( vandq_u32( mask, px3 ) );
uint8x16_t min0 = vminq_u8( l0, l1 );
uint8x16_t min1 = vminq_u8( l2, l3 );
uint8x16_t min2 = vminq_u8( min0, min1 );
uint8x16_t max0 = vmaxq_u8( l0, l1 );
uint8x16_t max1 = vmaxq_u8( l2, l3 );
uint8x16_t max2 = vmaxq_u8( max0, max1 );
uint8x16_t min3 = vreinterpretq_u8_u32( vrev64q_u32( vreinterpretq_u32_u8( min2 ) ) );
uint8x16_t max3 = vreinterpretq_u8_u32( vrev64q_u32( vreinterpretq_u32_u8( max2 ) ) );
uint8x16_t min4 = vminq_u8( min2, min3 );
uint8x16_t max4 = vmaxq_u8( max2, max3 );
uint8x16_t min5 = vcombine_u8( vget_high_u8( min4 ), vget_low_u8( min4 ) );
uint8x16_t max5 = vcombine_u8( vget_high_u8( max4 ), vget_low_u8( max4 ) );
uint8x16_t rmin = vminq_u8( min4, min5 );
uint8x16_t rmax = vmaxq_u8( max4, max5 );
uint8x16_t range1 = vsubq_u8( rmax, rmin );
uint8x8_t range2 = vget_low_u8( range1 );
uint8x8x2_t range3 = vzip_u8( range2, vdup_n_u8( 0 ) );
uint16x4_t range4 = vreinterpret_u16_u8( range3.val[0] );
uint16_t vrange1;
uint16x4_t range5 = vpadd_u16( range4, range4 );
uint16x4_t range6 = vpadd_u16( range5, range5 );
vst1_lane_u16( &vrange1, range6, 0 );
uint32_t vrange2 = ( 2 << 16 ) / uint32_t( vrange1 + 1 );
uint16x8_t range = vdupq_n_u16( vrange2 );
uint8x16_t inset = vshrq_n_u8( range1, 4 );
uint8x16_t min = vaddq_u8( rmin, inset );
uint8x16_t max = vsubq_u8( rmax, inset );
uint8x16_t c0 = vsubq_u8( l0, rmin );
uint8x16_t c1 = vsubq_u8( l1, rmin );
uint8x16_t c2 = vsubq_u8( l2, rmin );
uint8x16_t c3 = vsubq_u8( l3, rmin );
uint16x8_t is0 = vpaddlq_u8( c0 );
uint16x8_t is1 = vpaddlq_u8( c1 );
uint16x8_t is2 = vpaddlq_u8( c2 );
uint16x8_t is3 = vpaddlq_u8( c3 );
uint16x4_t is4 = vpadd_u16( vget_low_u16( is0 ), vget_high_u16( is0 ) );
uint16x4_t is5 = vpadd_u16( vget_low_u16( is1 ), vget_high_u16( is1 ) );
uint16x4_t is6 = vpadd_u16( vget_low_u16( is2 ), vget_high_u16( is2 ) );
uint16x4_t is7 = vpadd_u16( vget_low_u16( is3 ), vget_high_u16( is3 ) );
uint16x8_t s0 = vcombine_u16( is4, is5 );
uint16x8_t s1 = vcombine_u16( is6, is7 );
uint16x8_t m0 = vreinterpretq_u16_s16( vqdmulhq_s16( vreinterpretq_s16_u16( s0 ), vreinterpretq_s16_u16( range ) ) );
uint16x8_t m1 = vreinterpretq_u16_s16( vqdmulhq_s16( vreinterpretq_s16_u16( s1 ), vreinterpretq_s16_u16( range ) ) );
uint8x8_t p00 = vmovn_u16( m0 );
uint8x8_t p01 = vmovn_u16( m1 );
uint8x16_t p0 = vcombine_u8( p00, p01 );
uint32x4_t p1 = vaddq_u32( vshrq_n_u32( vreinterpretq_u32_u8( p0 ), 6 ), vshrq_n_u32( vreinterpretq_u32_u8( p0 ), 12 ) );
uint32x4_t p2 = vaddq_u32( vshrq_n_u32( vreinterpretq_u32_u8( p0 ), 18 ), vreinterpretq_u32_u8( p0 ) );
uint32x4_t p3 = vaddq_u32( p1, p2 );
uint16x4x2_t p4 = vuzp_u16( vget_low_u16( vreinterpretq_u16_u32( p3 ) ), vget_high_u16( vreinterpretq_u16_u32( p3 ) ) );
uint8x8x2_t p = vuzp_u8( vreinterpret_u8_u16( p4.val[0] ), vreinterpret_u8_u16( p4.val[0] ) );
uint32_t vmin, vmax, vp;
vst1q_lane_u32( &vmin, vreinterpretq_u32_u8( min ), 0 );
vst1q_lane_u32( &vmax, vreinterpretq_u32_u8( max ), 0 );
vst1_lane_u32( &vp, vreinterpret_u32_u8( p.val[0] ), 0 );
return uint64_t( ( uint64_t( to565( vmin ) ) << 16 ) | to565( vmax ) | ( uint64_t( vp ) << 32 ) );
# endif
#else
uint32_t ref;
memcpy( &ref, src, 4 );
uint32_t refMask = ref & 0xF8FCF8;
auto stmp = src + 4;
for( int i=1; i<16; i++ )
{
uint32_t px;
memcpy( &px, stmp, 4 );
if( ( px & 0xF8FCF8 ) != refMask ) break;
stmp += 4;
}
if( stmp == src + 64 )
{
return uint64_t( to565( ref ) ) << 16;
}
uint8_t min[3] = { src[0], src[1], src[2] };
uint8_t max[3] = { src[0], src[1], src[2] };
auto tmp = src + 4;
for( int i=1; i<16; i++ )
{
for( int j=0; j<3; j++ )
{
if( tmp[j] < min[j] ) min[j] = tmp[j];
else if( tmp[j] > max[j] ) max[j] = tmp[j];
}
tmp += 4;
}
const uint32_t range = DivTable[max[0] - min[0] + max[1] - min[1] + max[2] - min[2]];
const uint32_t rmin = min[0] + min[1] + min[2];
for( int i=0; i<3; i++ )
{
const uint8_t inset = ( max[i] - min[i] ) >> 4;
min[i] += inset;
max[i] -= inset;
}
uint32_t data = 0;
for( int i=0; i<16; i++ )
{
const uint32_t c = src[0] + src[1] + src[2] - rmin;
const uint8_t idx = ( c * range ) >> 16;
data |= idx << (i*2);
src += 4;
}
return uint64_t( ( uint64_t( to565( min[0], min[1], min[2] ) ) << 16 ) | to565( max[0], max[1], max[2] ) | ( uint64_t( data ) << 32 ) );
#endif
}
#ifdef __AVX2__
static tracy_force_inline void ProcessRGB_AVX( const uint8_t* src, char*& dst )
{
__m256i px0 = _mm256_loadu_si256(((__m256i*)src) + 0);
__m256i px1 = _mm256_loadu_si256(((__m256i*)src) + 1);
__m256i px2 = _mm256_loadu_si256(((__m256i*)src) + 2);
__m256i px3 = _mm256_loadu_si256(((__m256i*)src) + 3);
__m256i smask = _mm256_set1_epi32( 0xF8FCF8 );
__m256i sd0 = _mm256_and_si256( px0, smask );
__m256i sd1 = _mm256_and_si256( px1, smask );
__m256i sd2 = _mm256_and_si256( px2, smask );
__m256i sd3 = _mm256_and_si256( px3, smask );
__m256i sc = _mm256_shuffle_epi32(sd0, _MM_SHUFFLE(0, 0, 0, 0));
__m256i sc0 = _mm256_cmpeq_epi8( sd0, sc );
__m256i sc1 = _mm256_cmpeq_epi8( sd1, sc );
__m256i sc2 = _mm256_cmpeq_epi8( sd2, sc );
__m256i sc3 = _mm256_cmpeq_epi8( sd3, sc );
__m256i sm0 = _mm256_and_si256( sc0, sc1 );
__m256i sm1 = _mm256_and_si256( sc2, sc3 );
__m256i sm = _mm256_and_si256( sm0, sm1 );
const int64_t solid0 = 1 - _mm_testc_si128( _mm256_castsi256_si128( sm ), _mm_set1_epi32( -1 ) );
const int64_t solid1 = 1 - _mm_testc_si128( _mm256_extracti128_si256( sm, 1 ), _mm_set1_epi32( -1 ) );
if( solid0 + solid1 == 0 )
{
const auto c0 = uint64_t( to565( src[0], src[1], src[2] ) ) << 16;
const auto c1 = uint64_t( to565( src[16], src[17], src[18] ) ) << 16;
memcpy( dst, &c0, 8 );
memcpy( dst+8, &c1, 8 );
dst += 16;
return;
}
__m256i amask = _mm256_set1_epi32( 0xFFFFFF );
px0 = _mm256_and_si256( px0, amask );
px1 = _mm256_and_si256( px1, amask );
px2 = _mm256_and_si256( px2, amask );
px3 = _mm256_and_si256( px3, amask );
__m256i min0 = _mm256_min_epu8( px0, px1 );
__m256i min1 = _mm256_min_epu8( px2, px3 );
__m256i min2 = _mm256_min_epu8( min0, min1 );
__m256i max0 = _mm256_max_epu8( px0, px1 );
__m256i max1 = _mm256_max_epu8( px2, px3 );
__m256i max2 = _mm256_max_epu8( max0, max1 );
__m256i min3 = _mm256_shuffle_epi32( min2, _MM_SHUFFLE( 2, 3, 0, 1 ) );
__m256i max3 = _mm256_shuffle_epi32( max2, _MM_SHUFFLE( 2, 3, 0, 1 ) );
__m256i min4 = _mm256_min_epu8( min2, min3 );
__m256i max4 = _mm256_max_epu8( max2, max3 );
__m256i min5 = _mm256_shuffle_epi32( min4, _MM_SHUFFLE( 0, 0, 2, 2 ) );
__m256i max5 = _mm256_shuffle_epi32( max4, _MM_SHUFFLE( 0, 0, 2, 2 ) );
__m256i rmin = _mm256_min_epu8( min4, min5 );
__m256i rmax = _mm256_max_epu8( max4, max5 );
__m256i range1 = _mm256_subs_epu8( rmax, rmin );
__m256i range2 = _mm256_sad_epu8( rmax, rmin );
uint16_t vrange0 = DivTable[_mm256_cvtsi256_si32( range2 ) >> 1];
uint16_t vrange1 = DivTable[_mm256_extract_epi16( range2, 8 ) >> 1];
__m256i range00 = _mm256_set1_epi16( vrange0 );
__m256i range = _mm256_inserti128_si256( range00, _mm_set1_epi16( vrange1 ), 1 );
__m256i inset1 = _mm256_srli_epi16( range1, 4 );
__m256i inset = _mm256_and_si256( inset1, _mm256_set1_epi8( 0xF ) );
__m256i min = _mm256_adds_epu8( rmin, inset );
__m256i max = _mm256_subs_epu8( rmax, inset );
__m256i c0 = _mm256_subs_epu8( px0, rmin );
__m256i c1 = _mm256_subs_epu8( px1, rmin );
__m256i c2 = _mm256_subs_epu8( px2, rmin );
__m256i c3 = _mm256_subs_epu8( px3, rmin );
__m256i is0 = _mm256_maddubs_epi16( c0, _mm256_set1_epi8( 1 ) );
__m256i is1 = _mm256_maddubs_epi16( c1, _mm256_set1_epi8( 1 ) );
__m256i is2 = _mm256_maddubs_epi16( c2, _mm256_set1_epi8( 1 ) );
__m256i is3 = _mm256_maddubs_epi16( c3, _mm256_set1_epi8( 1 ) );
__m256i s0 = _mm256_hadd_epi16( is0, is1 );
__m256i s1 = _mm256_hadd_epi16( is2, is3 );
__m256i m0 = _mm256_mulhi_epu16( s0, range );
__m256i m1 = _mm256_mulhi_epu16( s1, range );
__m256i p0 = _mm256_packus_epi16( m0, m1 );
__m256i p1 = _mm256_or_si256( _mm256_srai_epi32( p0, 6 ), _mm256_srai_epi32( p0, 12 ) );
__m256i p2 = _mm256_or_si256( _mm256_srai_epi32( p0, 18 ), p0 );
__m256i p3 = _mm256_or_si256( p1, p2 );
__m256i p =_mm256_shuffle_epi8( p3, _mm256_set1_epi32( 0x0C080400 ) );
__m256i mm0 = _mm256_unpacklo_epi8( _mm256_setzero_si256(), min );
__m256i mm1 = _mm256_unpacklo_epi8( _mm256_setzero_si256(), max );
__m256i mm2 = _mm256_unpacklo_epi64( mm1, mm0 );
__m256i mmr = _mm256_slli_epi64( _mm256_srli_epi64( mm2, 11 ), 11 );
__m256i mmg = _mm256_slli_epi64( _mm256_srli_epi64( mm2, 26 ), 5 );
__m256i mmb = _mm256_srli_epi64( _mm256_slli_epi64( mm2, 16 ), 59 );
__m256i mm3 = _mm256_or_si256( mmr, mmg );
__m256i mm4 = _mm256_or_si256( mm3, mmb );
__m256i mm5 = _mm256_shuffle_epi8( mm4, _mm256_set1_epi32( 0x09080100 ) );
__m256i d0 = _mm256_unpacklo_epi32( mm5, p );
__m256i d1 = _mm256_permute4x64_epi64( d0, _MM_SHUFFLE( 3, 2, 2, 0 ) );
__m128i d2 = _mm256_castsi256_si128( d1 );
__m128i mask = _mm_set_epi64x( 0xFFFF0000 | -solid1, 0xFFFF0000 | -solid0 );
__m128i d3 = _mm_and_si128( d2, mask );
_mm_storeu_si128( (__m128i*)dst, d3 );
dst += 16;
}
#endif
void CompressImageDxt1( const char* src, char* dst, int w, int h )
{
assert( (w % 4) == 0 && (h % 4) == 0 );
#ifdef __AVX2__
if( w%8 == 0 )
{
uint32_t buf[8*4];
int i = 0;
auto blocks = w * h / 32;
do
{
auto tmp = (char*)buf;
memcpy( tmp, src, 8*4 );
memcpy( tmp + 8*4, src + w * 4, 8*4 );
memcpy( tmp + 16*4, src + w * 8, 8*4 );
memcpy( tmp + 24*4, src + w * 12, 8*4 );
src += 8*4;
if( ++i == w/8 )
{
src += w * 3 * 4;
i = 0;
}
ProcessRGB_AVX( (uint8_t*)buf, dst );
}
while( --blocks );
}
else
#endif
{
uint32_t buf[4*4];
int i = 0;
auto ptr = dst;
auto blocks = w * h / 16;
do
{
auto tmp = (char*)buf;
memcpy( tmp, src, 4*4 );
memcpy( tmp + 4*4, src + w * 4, 4*4 );
memcpy( tmp + 8*4, src + w * 8, 4*4 );
memcpy( tmp + 12*4, src + w * 12, 4*4 );
src += 4*4;
if( ++i == w/4 )
{
src += w * 3 * 4;
i = 0;
}
const auto c = ProcessRGB( (uint8_t*)buf );
memcpy( ptr, &c, sizeof( uint64_t ) );
ptr += sizeof( uint64_t );
}
while( --blocks );
}
}
}

View File

@ -0,0 +1,11 @@
#ifndef __TRACYDXT1_HPP__
#define __TRACYDXT1_HPP__
namespace tracy
{
void CompressImageDxt1( const char* src, char* dst, int w, int h );
}
#endif

View File

@ -0,0 +1,118 @@
#ifndef __TRACYFASTVECTOR_HPP__
#define __TRACYFASTVECTOR_HPP__
#include <assert.h>
#include <stddef.h>
#include "../common/TracyAlloc.hpp"
#include "../common/TracyForceInline.hpp"
namespace tracy
{
template<typename T>
class FastVector
{
public:
using iterator = T*;
using const_iterator = const T*;
FastVector( size_t capacity )
: m_ptr( (T*)tracy_malloc( sizeof( T ) * capacity ) )
, m_write( m_ptr )
, m_end( m_ptr + capacity )
{
assert( capacity != 0 );
}
FastVector( const FastVector& ) = delete;
FastVector( FastVector&& ) = delete;
~FastVector()
{
tracy_free( m_ptr );
}
FastVector& operator=( const FastVector& ) = delete;
FastVector& operator=( FastVector&& ) = delete;
bool empty() const { return m_ptr == m_write; }
size_t size() const { return m_write - m_ptr; }
T* data() { return m_ptr; }
const T* data() const { return m_ptr; };
T* begin() { return m_ptr; }
const T* begin() const { return m_ptr; }
T* end() { return m_write; }
const T* end() const { return m_write; }
T& front() { assert( !empty() ); return m_ptr[0]; }
const T& front() const { assert( !empty() ); return m_ptr[0]; }
T& back() { assert( !empty() ); return m_write[-1]; }
const T& back() const { assert( !empty() ); return m_write[-1]; }
T& operator[]( size_t idx ) { return m_ptr[idx]; }
const T& operator[]( size_t idx ) const { return m_ptr[idx]; }
T* push_next()
{
if( m_write == m_end ) AllocMore();
return m_write++;
}
T* prepare_next()
{
if( m_write == m_end ) AllocMore();
return m_write;
}
void commit_next()
{
m_write++;
}
void clear()
{
m_write = m_ptr;
}
void swap( FastVector& vec )
{
const auto ptr1 = m_ptr;
const auto ptr2 = vec.m_ptr;
const auto write1 = m_write;
const auto write2 = vec.m_write;
const auto end1 = m_end;
const auto end2 = vec.m_end;
m_ptr = ptr2;
vec.m_ptr = ptr1;
m_write = write2;
vec.m_write = write1;
m_end = end2;
vec.m_end = end1;
}
private:
tracy_no_inline void AllocMore()
{
const auto cap = size_t( m_end - m_ptr ) * 2;
const auto size = size_t( m_write - m_ptr );
T* ptr = (T*)tracy_malloc( sizeof( T ) * cap );
memcpy( ptr, m_ptr, size * sizeof( T ) );
tracy_free_fast( m_ptr );
m_ptr = ptr;
m_write = m_ptr + size;
m_end = m_ptr + cap;
}
T* m_ptr;
T* m_write;
T* m_end;
};
}
#endif

View File

@ -0,0 +1,546 @@
#ifndef __TRACYLOCK_HPP__
#define __TRACYLOCK_HPP__
#include <atomic>
#include <limits>
#include "../common/TracySystem.hpp"
#include "../common/TracyAlign.hpp"
#include "TracyProfiler.hpp"
namespace tracy
{
class LockableCtx
{
public:
tracy_force_inline LockableCtx( const SourceLocationData* srcloc )
: m_id( GetLockCounter().fetch_add( 1, std::memory_order_relaxed ) )
#ifdef TRACY_ON_DEMAND
, m_lockCount( 0 )
, m_active( false )
#endif
{
assert( m_id != (std::numeric_limits<uint32_t>::max)() );
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockAnnounce );
MemWrite( &item->lockAnnounce.id, m_id );
MemWrite( &item->lockAnnounce.time, Profiler::GetTime() );
MemWrite( &item->lockAnnounce.lckloc, (uint64_t)srcloc );
MemWrite( &item->lockAnnounce.type, LockType::Lockable );
#ifdef TRACY_ON_DEMAND
GetProfiler().DeferItem( *item );
#endif
Profiler::QueueSerialFinish();
}
LockableCtx( const LockableCtx& ) = delete;
LockableCtx& operator=( const LockableCtx& ) = delete;
tracy_force_inline ~LockableCtx()
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockTerminate );
MemWrite( &item->lockTerminate.id, m_id );
MemWrite( &item->lockTerminate.time, Profiler::GetTime() );
#ifdef TRACY_ON_DEMAND
GetProfiler().DeferItem( *item );
#endif
Profiler::QueueSerialFinish();
}
tracy_force_inline bool BeforeLock()
{
#ifdef TRACY_ON_DEMAND
bool queue = false;
const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed );
const auto active = m_active.load( std::memory_order_relaxed );
if( locks == 0 || active )
{
const bool connected = GetProfiler().IsConnected();
if( active != connected ) m_active.store( connected, std::memory_order_relaxed );
if( connected ) queue = true;
}
if( !queue ) return false;
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockWait );
MemWrite( &item->lockWait.thread, GetThreadHandle() );
MemWrite( &item->lockWait.id, m_id );
MemWrite( &item->lockWait.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
return true;
}
tracy_force_inline void AfterLock()
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockObtain );
MemWrite( &item->lockObtain.thread, GetThreadHandle() );
MemWrite( &item->lockObtain.id, m_id );
MemWrite( &item->lockObtain.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
tracy_force_inline void AfterUnlock()
{
#ifdef TRACY_ON_DEMAND
m_lockCount.fetch_sub( 1, std::memory_order_relaxed );
if( !m_active.load( std::memory_order_relaxed ) ) return;
if( !GetProfiler().IsConnected() )
{
m_active.store( false, std::memory_order_relaxed );
return;
}
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockRelease );
MemWrite( &item->lockRelease.id, m_id );
MemWrite( &item->lockRelease.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
tracy_force_inline void AfterTryLock( bool acquired )
{
#ifdef TRACY_ON_DEMAND
if( !acquired ) return;
bool queue = false;
const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed );
const auto active = m_active.load( std::memory_order_relaxed );
if( locks == 0 || active )
{
const bool connected = GetProfiler().IsConnected();
if( active != connected ) m_active.store( connected, std::memory_order_relaxed );
if( connected ) queue = true;
}
if( !queue ) return;
#endif
if( acquired )
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockObtain );
MemWrite( &item->lockObtain.thread, GetThreadHandle() );
MemWrite( &item->lockObtain.id, m_id );
MemWrite( &item->lockObtain.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
}
tracy_force_inline void Mark( const SourceLocationData* srcloc )
{
#ifdef TRACY_ON_DEMAND
const auto active = m_active.load( std::memory_order_relaxed );
if( !active ) return;
const auto connected = GetProfiler().IsConnected();
if( !connected )
{
if( active ) m_active.store( false, std::memory_order_relaxed );
return;
}
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockMark );
MemWrite( &item->lockMark.thread, GetThreadHandle() );
MemWrite( &item->lockMark.id, m_id );
MemWrite( &item->lockMark.srcloc, (uint64_t)srcloc );
Profiler::QueueSerialFinish();
}
tracy_force_inline void CustomName( const char* name, size_t size )
{
assert( size < (std::numeric_limits<uint16_t>::max)() );
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, name, size );
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockName );
MemWrite( &item->lockNameFat.id, m_id );
MemWrite( &item->lockNameFat.name, (uint64_t)ptr );
MemWrite( &item->lockNameFat.size, (uint16_t)size );
#ifdef TRACY_ON_DEMAND
GetProfiler().DeferItem( *item );
#endif
Profiler::QueueSerialFinish();
}
private:
uint32_t m_id;
#ifdef TRACY_ON_DEMAND
std::atomic<uint32_t> m_lockCount;
std::atomic<bool> m_active;
#endif
};
template<class T>
class Lockable
{
public:
tracy_force_inline Lockable( const SourceLocationData* srcloc )
: m_ctx( srcloc )
{
}
Lockable( const Lockable& ) = delete;
Lockable& operator=( const Lockable& ) = delete;
tracy_force_inline void lock()
{
const auto runAfter = m_ctx.BeforeLock();
m_lockable.lock();
if( runAfter ) m_ctx.AfterLock();
}
tracy_force_inline void unlock()
{
m_lockable.unlock();
m_ctx.AfterUnlock();
}
tracy_force_inline bool try_lock()
{
const auto acquired = m_lockable.try_lock();
m_ctx.AfterTryLock( acquired );
return acquired;
}
tracy_force_inline void Mark( const SourceLocationData* srcloc )
{
m_ctx.Mark( srcloc );
}
tracy_force_inline void CustomName( const char* name, size_t size )
{
m_ctx.CustomName( name, size );
}
private:
T m_lockable;
LockableCtx m_ctx;
};
class SharedLockableCtx
{
public:
tracy_force_inline SharedLockableCtx( const SourceLocationData* srcloc )
: m_id( GetLockCounter().fetch_add( 1, std::memory_order_relaxed ) )
#ifdef TRACY_ON_DEMAND
, m_lockCount( 0 )
, m_active( false )
#endif
{
assert( m_id != (std::numeric_limits<uint32_t>::max)() );
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockAnnounce );
MemWrite( &item->lockAnnounce.id, m_id );
MemWrite( &item->lockAnnounce.time, Profiler::GetTime() );
MemWrite( &item->lockAnnounce.lckloc, (uint64_t)srcloc );
MemWrite( &item->lockAnnounce.type, LockType::SharedLockable );
#ifdef TRACY_ON_DEMAND
GetProfiler().DeferItem( *item );
#endif
Profiler::QueueSerialFinish();
}
SharedLockableCtx( const SharedLockableCtx& ) = delete;
SharedLockableCtx& operator=( const SharedLockableCtx& ) = delete;
tracy_force_inline ~SharedLockableCtx()
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockTerminate );
MemWrite( &item->lockTerminate.id, m_id );
MemWrite( &item->lockTerminate.time, Profiler::GetTime() );
#ifdef TRACY_ON_DEMAND
GetProfiler().DeferItem( *item );
#endif
Profiler::QueueSerialFinish();
}
tracy_force_inline bool BeforeLock()
{
#ifdef TRACY_ON_DEMAND
bool queue = false;
const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed );
const auto active = m_active.load( std::memory_order_relaxed );
if( locks == 0 || active )
{
const bool connected = GetProfiler().IsConnected();
if( active != connected ) m_active.store( connected, std::memory_order_relaxed );
if( connected ) queue = true;
}
if( !queue ) return false;
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockWait );
MemWrite( &item->lockWait.thread, GetThreadHandle() );
MemWrite( &item->lockWait.id, m_id );
MemWrite( &item->lockWait.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
return true;
}
tracy_force_inline void AfterLock()
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockObtain );
MemWrite( &item->lockObtain.thread, GetThreadHandle() );
MemWrite( &item->lockObtain.id, m_id );
MemWrite( &item->lockObtain.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
tracy_force_inline void AfterUnlock()
{
#ifdef TRACY_ON_DEMAND
m_lockCount.fetch_sub( 1, std::memory_order_relaxed );
if( !m_active.load( std::memory_order_relaxed ) ) return;
if( !GetProfiler().IsConnected() )
{
m_active.store( false, std::memory_order_relaxed );
return;
}
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockRelease );
MemWrite( &item->lockRelease.id, m_id );
MemWrite( &item->lockRelease.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
tracy_force_inline void AfterTryLock( bool acquired )
{
#ifdef TRACY_ON_DEMAND
if( !acquired ) return;
bool queue = false;
const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed );
const auto active = m_active.load( std::memory_order_relaxed );
if( locks == 0 || active )
{
const bool connected = GetProfiler().IsConnected();
if( active != connected ) m_active.store( connected, std::memory_order_relaxed );
if( connected ) queue = true;
}
if( !queue ) return;
#endif
if( acquired )
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockObtain );
MemWrite( &item->lockObtain.thread, GetThreadHandle() );
MemWrite( &item->lockObtain.id, m_id );
MemWrite( &item->lockObtain.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
}
tracy_force_inline bool BeforeLockShared()
{
#ifdef TRACY_ON_DEMAND
bool queue = false;
const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed );
const auto active = m_active.load( std::memory_order_relaxed );
if( locks == 0 || active )
{
const bool connected = GetProfiler().IsConnected();
if( active != connected ) m_active.store( connected, std::memory_order_relaxed );
if( connected ) queue = true;
}
if( !queue ) return false;
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockSharedWait );
MemWrite( &item->lockWait.thread, GetThreadHandle() );
MemWrite( &item->lockWait.id, m_id );
MemWrite( &item->lockWait.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
return true;
}
tracy_force_inline void AfterLockShared()
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockSharedObtain );
MemWrite( &item->lockObtain.thread, GetThreadHandle() );
MemWrite( &item->lockObtain.id, m_id );
MemWrite( &item->lockObtain.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
tracy_force_inline void AfterUnlockShared()
{
#ifdef TRACY_ON_DEMAND
m_lockCount.fetch_sub( 1, std::memory_order_relaxed );
if( !m_active.load( std::memory_order_relaxed ) ) return;
if( !GetProfiler().IsConnected() )
{
m_active.store( false, std::memory_order_relaxed );
return;
}
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockSharedRelease );
MemWrite( &item->lockReleaseShared.thread, GetThreadHandle() );
MemWrite( &item->lockReleaseShared.id, m_id );
MemWrite( &item->lockReleaseShared.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
tracy_force_inline void AfterTryLockShared( bool acquired )
{
#ifdef TRACY_ON_DEMAND
if( !acquired ) return;
bool queue = false;
const auto locks = m_lockCount.fetch_add( 1, std::memory_order_relaxed );
const auto active = m_active.load( std::memory_order_relaxed );
if( locks == 0 || active )
{
const bool connected = GetProfiler().IsConnected();
if( active != connected ) m_active.store( connected, std::memory_order_relaxed );
if( connected ) queue = true;
}
if( !queue ) return;
#endif
if( acquired )
{
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockSharedObtain );
MemWrite( &item->lockObtain.thread, GetThreadHandle() );
MemWrite( &item->lockObtain.id, m_id );
MemWrite( &item->lockObtain.time, Profiler::GetTime() );
Profiler::QueueSerialFinish();
}
}
tracy_force_inline void Mark( const SourceLocationData* srcloc )
{
#ifdef TRACY_ON_DEMAND
const auto active = m_active.load( std::memory_order_relaxed );
if( !active ) return;
const auto connected = GetProfiler().IsConnected();
if( !connected )
{
if( active ) m_active.store( false, std::memory_order_relaxed );
return;
}
#endif
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockMark );
MemWrite( &item->lockMark.thread, GetThreadHandle() );
MemWrite( &item->lockMark.id, m_id );
MemWrite( &item->lockMark.srcloc, (uint64_t)srcloc );
Profiler::QueueSerialFinish();
}
tracy_force_inline void CustomName( const char* name, size_t size )
{
assert( size < (std::numeric_limits<uint16_t>::max)() );
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, name, size );
auto item = Profiler::QueueSerial();
MemWrite( &item->hdr.type, QueueType::LockName );
MemWrite( &item->lockNameFat.id, m_id );
MemWrite( &item->lockNameFat.name, (uint64_t)ptr );
MemWrite( &item->lockNameFat.size, (uint16_t)size );
#ifdef TRACY_ON_DEMAND
GetProfiler().DeferItem( *item );
#endif
Profiler::QueueSerialFinish();
}
private:
uint32_t m_id;
#ifdef TRACY_ON_DEMAND
std::atomic<uint32_t> m_lockCount;
std::atomic<bool> m_active;
#endif
};
template<class T>
class SharedLockable
{
public:
tracy_force_inline SharedLockable( const SourceLocationData* srcloc )
: m_ctx( srcloc )
{
}
SharedLockable( const SharedLockable& ) = delete;
SharedLockable& operator=( const SharedLockable& ) = delete;
tracy_force_inline void lock()
{
const auto runAfter = m_ctx.BeforeLock();
m_lockable.lock();
if( runAfter ) m_ctx.AfterLock();
}
tracy_force_inline void unlock()
{
m_lockable.unlock();
m_ctx.AfterUnlock();
}
tracy_force_inline bool try_lock()
{
const auto acquired = m_lockable.try_lock();
m_ctx.AfterTryLock( acquired );
return acquired;
}
tracy_force_inline void lock_shared()
{
const auto runAfter = m_ctx.BeforeLockShared();
m_lockable.lock_shared();
if( runAfter ) m_ctx.AfterLockShared();
}
tracy_force_inline void unlock_shared()
{
m_lockable.unlock_shared();
m_ctx.AfterUnlockShared();
}
tracy_force_inline bool try_lock_shared()
{
const auto acquired = m_lockable.try_lock_shared();
m_ctx.AfterTryLockShared( acquired );
return acquired;
}
tracy_force_inline void Mark( const SourceLocationData* srcloc )
{
m_ctx.Mark( srcloc );
}
tracy_force_inline void CustomName( const char* name, size_t size )
{
m_ctx.CustomName( name, size );
}
private:
T m_lockable;
SharedLockableCtx m_ctx;
};
}
#endif

View File

@ -0,0 +1,26 @@
#ifdef TRACY_ENABLE
# ifdef __linux__
# include "TracyDebug.hpp"
# ifdef TRACY_VERBOSE
# include <dlfcn.h>
# include <link.h>
# endif
extern "C" int dlclose( void* hnd )
{
#ifdef TRACY_VERBOSE
struct link_map* lm;
if( dlinfo( hnd, RTLD_DI_LINKMAP, &lm ) == 0 )
{
TracyDebug( "Overriding dlclose for %s\n", lm->l_name );
}
else
{
TracyDebug( "Overriding dlclose for unknown object (%s)\n", dlerror() );
}
#endif
return 0;
}
# endif
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,141 @@
#include <atomic>
#include <assert.h>
#include <errno.h>
#include <linux/perf_event.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "TracyDebug.hpp"
namespace tracy
{
class RingBuffer
{
public:
RingBuffer( unsigned int size, int fd, int id, int cpu = -1 )
: m_size( size )
, m_id( id )
, m_cpu( cpu )
, m_fd( fd )
{
const auto pageSize = uint32_t( getpagesize() );
assert( size >= pageSize );
assert( __builtin_popcount( size ) == 1 );
m_mapSize = size + pageSize;
auto mapAddr = mmap( nullptr, m_mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
if( mapAddr == MAP_FAILED )
{
TracyDebug( "mmap failed: errno %i (%s)\n", errno, strerror( errno ) );
m_fd = 0;
m_metadata = nullptr;
close( fd );
return;
}
m_metadata = (perf_event_mmap_page*)mapAddr;
assert( m_metadata->data_offset == pageSize );
m_buffer = ((char*)mapAddr) + pageSize;
m_tail = m_metadata->data_tail;
}
~RingBuffer()
{
if( m_metadata ) munmap( m_metadata, m_mapSize );
if( m_fd ) close( m_fd );
}
RingBuffer( const RingBuffer& ) = delete;
RingBuffer& operator=( const RingBuffer& ) = delete;
RingBuffer( RingBuffer&& other )
{
memcpy( (char*)&other, (char*)this, sizeof( RingBuffer ) );
m_metadata = nullptr;
m_fd = 0;
}
RingBuffer& operator=( RingBuffer&& other )
{
memcpy( (char*)&other, (char*)this, sizeof( RingBuffer ) );
m_metadata = nullptr;
m_fd = 0;
return *this;
}
bool IsValid() const { return m_metadata != nullptr; }
int GetId() const { return m_id; }
int GetCpu() const { return m_cpu; }
void Enable()
{
ioctl( m_fd, PERF_EVENT_IOC_ENABLE, 0 );
}
void Read( void* dst, uint64_t offset, uint64_t cnt )
{
const auto size = m_size;
auto src = ( m_tail + offset ) % size;
if( src + cnt <= size )
{
memcpy( dst, m_buffer + src, cnt );
}
else
{
const auto s0 = size - src;
const auto buf = m_buffer;
memcpy( dst, buf + src, s0 );
memcpy( (char*)dst + s0, buf, cnt - s0 );
}
}
void Advance( uint64_t cnt )
{
m_tail += cnt;
StoreTail();
}
bool CheckTscCaps() const
{
return m_metadata->cap_user_time_zero;
}
int64_t ConvertTimeToTsc( int64_t timestamp ) const
{
if( !m_metadata->cap_user_time_zero ) return 0;
const auto time = timestamp - m_metadata->time_zero;
const auto quot = time / m_metadata->time_mult;
const auto rem = time % m_metadata->time_mult;
return ( quot << m_metadata->time_shift ) + ( rem << m_metadata->time_shift ) / m_metadata->time_mult;
}
uint64_t LoadHead() const
{
return std::atomic_load_explicit( (const volatile std::atomic<uint64_t>*)&m_metadata->data_head, std::memory_order_acquire );
}
uint64_t GetTail() const
{
return m_tail;
}
private:
void StoreTail()
{
std::atomic_store_explicit( (volatile std::atomic<uint64_t>*)&m_metadata->data_tail, m_tail, std::memory_order_release );
}
unsigned int m_size;
uint64_t m_tail;
char* m_buffer;
int m_id;
int m_cpu;
perf_event_mmap_page* m_metadata;
size_t m_mapSize;
int m_fd;
};
}

View File

@ -0,0 +1,175 @@
#ifndef __TRACYSCOPED_HPP__
#define __TRACYSCOPED_HPP__
#include <limits>
#include <stdint.h>
#include <string.h>
#include "../common/TracySystem.hpp"
#include "../common/TracyAlign.hpp"
#include "../common/TracyAlloc.hpp"
#include "TracyProfiler.hpp"
namespace tracy
{
class ScopedZone
{
public:
ScopedZone( const ScopedZone& ) = delete;
ScopedZone( ScopedZone&& ) = delete;
ScopedZone& operator=( const ScopedZone& ) = delete;
ScopedZone& operator=( ScopedZone&& ) = delete;
tracy_force_inline ScopedZone( const SourceLocationData* srcloc, bool is_active = true )
#ifdef TRACY_ON_DEMAND
: m_active( is_active && GetProfiler().IsConnected() )
#else
: m_active( is_active )
#endif
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
m_connectionId = GetProfiler().ConnectionId();
#endif
TracyQueuePrepare( QueueType::ZoneBegin );
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc );
TracyQueueCommit( zoneBeginThread );
}
tracy_force_inline ScopedZone( const SourceLocationData* srcloc, int depth, bool is_active = true )
#ifdef TRACY_ON_DEMAND
: m_active( is_active && GetProfiler().IsConnected() )
#else
: m_active( is_active )
#endif
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
m_connectionId = GetProfiler().ConnectionId();
#endif
GetProfiler().SendCallstack( depth );
TracyQueuePrepare( QueueType::ZoneBeginCallstack );
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
MemWrite( &item->zoneBegin.srcloc, (uint64_t)srcloc );
TracyQueueCommit( zoneBeginThread );
}
tracy_force_inline ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, bool is_active = true )
#ifdef TRACY_ON_DEMAND
: m_active( is_active && GetProfiler().IsConnected() )
#else
: m_active( is_active )
#endif
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
m_connectionId = GetProfiler().ConnectionId();
#endif
TracyQueuePrepare( QueueType::ZoneBeginAllocSrcLoc );
const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz );
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
MemWrite( &item->zoneBegin.srcloc, srcloc );
TracyQueueCommit( zoneBeginThread );
}
tracy_force_inline ScopedZone( uint32_t line, const char* source, size_t sourceSz, const char* function, size_t functionSz, const char* name, size_t nameSz, int depth, bool is_active = true )
#ifdef TRACY_ON_DEMAND
: m_active( is_active && GetProfiler().IsConnected() )
#else
: m_active( is_active )
#endif
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
m_connectionId = GetProfiler().ConnectionId();
#endif
GetProfiler().SendCallstack( depth );
TracyQueuePrepare( QueueType::ZoneBeginAllocSrcLocCallstack );
const auto srcloc = Profiler::AllocSourceLocation( line, source, sourceSz, function, functionSz, name, nameSz );
MemWrite( &item->zoneBegin.time, Profiler::GetTime() );
MemWrite( &item->zoneBegin.srcloc, srcloc );
TracyQueueCommit( zoneBeginThread );
}
tracy_force_inline ~ScopedZone()
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
#endif
TracyQueuePrepare( QueueType::ZoneEnd );
MemWrite( &item->zoneEnd.time, Profiler::GetTime() );
TracyQueueCommit( zoneEndThread );
}
tracy_force_inline void Text( const char* txt, size_t size )
{
assert( size < (std::numeric_limits<uint16_t>::max)() );
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
#endif
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, txt, size );
TracyQueuePrepare( QueueType::ZoneText );
MemWrite( &item->zoneTextFat.text, (uint64_t)ptr );
MemWrite( &item->zoneTextFat.size, (uint16_t)size );
TracyQueueCommit( zoneTextFatThread );
}
tracy_force_inline void Name( const char* txt, size_t size )
{
assert( size < (std::numeric_limits<uint16_t>::max)() );
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
#endif
auto ptr = (char*)tracy_malloc( size );
memcpy( ptr, txt, size );
TracyQueuePrepare( QueueType::ZoneName );
MemWrite( &item->zoneTextFat.text, (uint64_t)ptr );
MemWrite( &item->zoneTextFat.size, (uint16_t)size );
TracyQueueCommit( zoneTextFatThread );
}
tracy_force_inline void Color( uint32_t color )
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
#endif
TracyQueuePrepare( QueueType::ZoneColor );
MemWrite( &item->zoneColor.b, uint8_t( ( color ) & 0xFF ) );
MemWrite( &item->zoneColor.g, uint8_t( ( color >> 8 ) & 0xFF ) );
MemWrite( &item->zoneColor.r, uint8_t( ( color >> 16 ) & 0xFF ) );
TracyQueueCommit( zoneColorThread );
}
tracy_force_inline void Value( uint64_t value )
{
if( !m_active ) return;
#ifdef TRACY_ON_DEMAND
if( GetProfiler().ConnectionId() != m_connectionId ) return;
#endif
TracyQueuePrepare( QueueType::ZoneValue );
MemWrite( &item->zoneValue.value, value );
TracyQueueCommit( zoneValueThread );
}
tracy_force_inline bool IsActive() const { return m_active; }
private:
const bool m_active;
#ifdef TRACY_ON_DEMAND
uint64_t m_connectionId = 0;
#endif
};
}
#endif

View File

@ -0,0 +1,41 @@
#ifndef __TRACYSTRINGHELPERS_HPP__
#define __TRACYSTRINGHELPERS_HPP__
#include <assert.h>
#include <string.h>
#include "../common/TracyAlloc.hpp"
#include "../common/TracyForceInline.hpp"
namespace tracy
{
static tracy_force_inline char* CopyString( const char* src, size_t sz )
{
auto dst = (char*)tracy_malloc( sz + 1 );
memcpy( dst, src, sz );
dst[sz] = '\0';
return dst;
}
static tracy_force_inline char* CopyString( const char* src )
{
return CopyString( src, strlen( src ) );
}
static tracy_force_inline char* CopyStringFast( const char* src, size_t sz )
{
auto dst = (char*)tracy_malloc_fast( sz + 1 );
memcpy( dst, src, sz );
dst[sz] = '\0';
return dst;
}
static tracy_force_inline char* CopyStringFast( const char* src )
{
return CopyStringFast( src, strlen( src ) );
}
}
#endif

View File

@ -0,0 +1,164 @@
#include "TracySysPower.hpp"
#ifdef TRACY_HAS_SYSPOWER
#include <sys/types.h>
#include <dirent.h>
#include <chrono>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "TracyDebug.hpp"
#include "TracyProfiler.hpp"
#include "../common/TracyAlloc.hpp"
namespace tracy
{
SysPower::SysPower()
: m_domains( 4 )
, m_lastTime( 0 )
{
ScanDirectory( "/sys/devices/virtual/powercap/intel-rapl", -1 );
}
SysPower::~SysPower()
{
for( auto& v : m_domains )
{
fclose( v.handle );
// Do not release v.name, as it may be still needed
}
}
void SysPower::Tick()
{
auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();
if( t - m_lastTime > 10000000 ) // 10 ms
{
m_lastTime = t;
for( auto& v : m_domains )
{
char tmp[32];
if( fread( tmp, 1, 32, v.handle ) > 0 )
{
rewind( v.handle );
auto p = (uint64_t)atoll( tmp );
uint64_t delta;
if( p >= v.value )
{
delta = p - v.value;
}
else
{
delta = v.overflow - v.value + p;
}
v.value = p;
TracyLfqPrepare( QueueType::SysPowerReport );
MemWrite( &item->sysPower.time, Profiler::GetTime() );
MemWrite( &item->sysPower.delta, delta );
MemWrite( &item->sysPower.name, (uint64_t)v.name );
TracyLfqCommit;
}
}
}
}
void SysPower::ScanDirectory( const char* path, int parent )
{
DIR* dir = opendir( path );
if( !dir ) return;
struct dirent* ent;
uint64_t maxRange = 0;
char* name = nullptr;
FILE* handle = nullptr;
while( ( ent = readdir( dir ) ) )
{
if( ent->d_type == DT_REG )
{
if( strcmp( ent->d_name, "max_energy_range_uj" ) == 0 )
{
char tmp[PATH_MAX];
snprintf( tmp, PATH_MAX, "%s/max_energy_range_uj", path );
FILE* f = fopen( tmp, "r" );
if( f )
{
fscanf( f, "%" PRIu64, &maxRange );
fclose( f );
}
}
else if( strcmp( ent->d_name, "name" ) == 0 )
{
char tmp[PATH_MAX];
snprintf( tmp, PATH_MAX, "%s/name", path );
FILE* f = fopen( tmp, "r" );
if( f )
{
char ntmp[128];
if( fgets( ntmp, 128, f ) )
{
// Last character is newline, skip it
const auto sz = strlen( ntmp ) - 1;
if( parent < 0 )
{
name = (char*)tracy_malloc( sz + 1 );
memcpy( name, ntmp, sz );
name[sz] = '\0';
}
else
{
const auto p = m_domains[parent];
const auto psz = strlen( p.name );
name = (char*)tracy_malloc( psz + sz + 2 );
memcpy( name, p.name, psz );
name[psz] = ':';
memcpy( name+psz+1, ntmp, sz );
name[psz+sz+1] = '\0';
}
}
fclose( f );
}
}
else if( strcmp( ent->d_name, "energy_uj" ) == 0 )
{
char tmp[PATH_MAX];
snprintf( tmp, PATH_MAX, "%s/energy_uj", path );
handle = fopen( tmp, "r" );
}
}
if( name && handle && maxRange > 0 ) break;
}
if( name && handle && maxRange > 0 )
{
parent = (int)m_domains.size();
Domain* domain = m_domains.push_next();
domain->value = 0;
domain->overflow = maxRange;
domain->handle = handle;
domain->name = name;
TracyDebug( "Power domain id %i, %s found at %s\n", parent, name, path );
}
else
{
if( name ) tracy_free( name );
if( handle ) fclose( handle );
}
rewinddir( dir );
while( ( ent = readdir( dir ) ) )
{
if( ent->d_type == DT_DIR && strncmp( ent->d_name, "intel-rapl:", 11 ) == 0 )
{
char tmp[PATH_MAX];
snprintf( tmp, PATH_MAX, "%s/%s", path, ent->d_name );
ScanDirectory( tmp, parent );
}
}
closedir( dir );
}
}
#endif

View File

@ -0,0 +1,44 @@
#ifndef __TRACYSYSPOWER_HPP__
#define __TRACYSYSPOWER_HPP__
#if defined __linux__
# define TRACY_HAS_SYSPOWER
#endif
#ifdef TRACY_HAS_SYSPOWER
#include <stdint.h>
#include <stdio.h>
#include "TracyFastVector.hpp"
namespace tracy
{
class SysPower
{
struct Domain
{
uint64_t value;
uint64_t overflow;
FILE* handle;
const char* name;
};
public:
SysPower();
~SysPower();
void Tick();
private:
void ScanDirectory( const char* path, int parent );
FastVector<Domain> m_domains;
uint64_t m_lastTime;
};
}
#endif
#endif

View File

@ -0,0 +1,108 @@
#include "TracySysTime.hpp"
#ifdef TRACY_HAS_SYSTIME
# if defined _WIN32
# include <windows.h>
# elif defined __linux__
# include <stdio.h>
# include <inttypes.h>
# elif defined __APPLE__
# include <mach/mach_host.h>
# include <mach/host_info.h>
# elif defined BSD
# include <sys/types.h>
# include <sys/sysctl.h>
# endif
namespace tracy
{
# if defined _WIN32
static inline uint64_t ConvertTime( const FILETIME& t )
{
return ( uint64_t( t.dwHighDateTime ) << 32 ) | uint64_t( t.dwLowDateTime );
}
void SysTime::ReadTimes()
{
FILETIME idleTime;
FILETIME kernelTime;
FILETIME userTime;
GetSystemTimes( &idleTime, &kernelTime, &userTime );
idle = ConvertTime( idleTime );
const auto kernel = ConvertTime( kernelTime );
const auto user = ConvertTime( userTime );
used = kernel + user;
}
# elif defined __linux__
void SysTime::ReadTimes()
{
uint64_t user, nice, system;
FILE* f = fopen( "/proc/stat", "r" );
if( f )
{
int read = fscanf( f, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64" %" PRIu64, &user, &nice, &system, &idle );
fclose( f );
if (read == 4)
{
used = user + nice + system;
}
}
}
# elif defined __APPLE__
void SysTime::ReadTimes()
{
host_cpu_load_info_data_t info;
mach_msg_type_number_t cnt = HOST_CPU_LOAD_INFO_COUNT;
host_statistics( mach_host_self(), HOST_CPU_LOAD_INFO, reinterpret_cast<host_info_t>( &info ), &cnt );
used = info.cpu_ticks[CPU_STATE_USER] + info.cpu_ticks[CPU_STATE_NICE] + info.cpu_ticks[CPU_STATE_SYSTEM];
idle = info.cpu_ticks[CPU_STATE_IDLE];
}
# elif defined BSD
void SysTime::ReadTimes()
{
u_long data[5];
size_t sz = sizeof( data );
sysctlbyname( "kern.cp_time", &data, &sz, nullptr, 0 );
used = data[0] + data[1] + data[2] + data[3];
idle = data[4];
}
#endif
SysTime::SysTime()
{
ReadTimes();
}
float SysTime::Get()
{
const auto oldUsed = used;
const auto oldIdle = idle;
ReadTimes();
const auto diffIdle = idle - oldIdle;
const auto diffUsed = used - oldUsed;
#if defined _WIN32
return diffUsed == 0 ? -1 : ( diffUsed - diffIdle ) * 100.f / diffUsed;
#elif defined __linux__ || defined __APPLE__ || defined BSD
const auto total = diffUsed + diffIdle;
return total == 0 ? -1 : diffUsed * 100.f / total;
#endif
}
}
#endif

View File

@ -0,0 +1,36 @@
#ifndef __TRACYSYSTIME_HPP__
#define __TRACYSYSTIME_HPP__
#if defined _WIN32 || defined __linux__ || defined __APPLE__
# define TRACY_HAS_SYSTIME
#else
# include <sys/param.h>
#endif
#ifdef BSD
# define TRACY_HAS_SYSTIME
#endif
#ifdef TRACY_HAS_SYSTIME
#include <stdint.h>
namespace tracy
{
class SysTime
{
public:
SysTime();
float Get();
void ReadTimes();
private:
uint64_t idle, used;
};
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
#ifndef __TRACYSYSTRACE_HPP__
#define __TRACYSYSTRACE_HPP__
#if !defined TRACY_NO_SYSTEM_TRACING && ( defined _WIN32 || defined __linux__ )
# include "../common/TracyUwp.hpp"
# ifndef TRACY_UWP
# define TRACY_HAS_SYSTEM_TRACING
# endif
#endif
#ifdef TRACY_HAS_SYSTEM_TRACING
#include <stdint.h>
namespace tracy
{
bool SysTraceStart( int64_t& samplingPeriod );
void SysTraceStop();
void SysTraceWorker( void* ptr );
void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const char*& name );
}
#endif
#endif

View File

@ -0,0 +1,90 @@
#ifndef __TRACYTHREAD_HPP__
#define __TRACYTHREAD_HPP__
#if defined _WIN32
# include <windows.h>
#else
# include <pthread.h>
#endif
#ifdef TRACY_MANUAL_LIFETIME
# include "tracy_rpmalloc.hpp"
#endif
namespace tracy
{
#ifdef TRACY_MANUAL_LIFETIME
extern thread_local bool RpThreadInitDone;
#endif
class ThreadExitHandler
{
public:
~ThreadExitHandler()
{
#ifdef TRACY_MANUAL_LIFETIME
rpmalloc_thread_finalize( 1 );
RpThreadInitDone = false;
#endif
}
};
#if defined _WIN32
class Thread
{
public:
Thread( void(*func)( void* ptr ), void* ptr )
: m_func( func )
, m_ptr( ptr )
, m_hnd( CreateThread( nullptr, 0, Launch, this, 0, nullptr ) )
{}
~Thread()
{
WaitForSingleObject( m_hnd, INFINITE );
CloseHandle( m_hnd );
}
HANDLE Handle() const { return m_hnd; }
private:
static DWORD WINAPI Launch( void* ptr ) { ((Thread*)ptr)->m_func( ((Thread*)ptr)->m_ptr ); return 0; }
void(*m_func)( void* ptr );
void* m_ptr;
HANDLE m_hnd;
};
#else
class Thread
{
public:
Thread( void(*func)( void* ptr ), void* ptr )
: m_func( func )
, m_ptr( ptr )
{
pthread_create( &m_thread, nullptr, Launch, this );
}
~Thread()
{
pthread_join( m_thread, nullptr );
}
pthread_t Handle() const { return m_thread; }
private:
static void* Launch( void* ptr ) { ((Thread*)ptr)->m_func( ((Thread*)ptr)->m_ptr ); return nullptr; }
void(*m_func)( void* ptr );
void* m_ptr;
pthread_t m_thread;
};
#endif
}
#endif

View File

@ -0,0 +1,148 @@
/*
Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <atomic>
#include <cassert>
#include <cstddef>
#include <stdexcept>
#include <type_traits> // std::enable_if, std::is_*_constructible
#include "../common/TracyAlloc.hpp"
#if defined (_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4324)
#endif
namespace tracy {
template <typename T> class SPSCQueue {
public:
explicit SPSCQueue(const size_t capacity)
: capacity_(capacity) {
capacity_++; // Needs one slack element
slots_ = (T*)tracy_malloc(sizeof(T) * (capacity_ + 2 * kPadding));
static_assert(alignof(SPSCQueue<T>) == kCacheLineSize, "");
static_assert(sizeof(SPSCQueue<T>) >= 3 * kCacheLineSize, "");
assert(reinterpret_cast<char *>(&readIdx_) -
reinterpret_cast<char *>(&writeIdx_) >=
static_cast<std::ptrdiff_t>(kCacheLineSize));
}
~SPSCQueue() {
while (front()) {
pop();
}
tracy_free(slots_);
}
// non-copyable and non-movable
SPSCQueue(const SPSCQueue &) = delete;
SPSCQueue &operator=(const SPSCQueue &) = delete;
template <typename... Args>
void emplace(Args &&...args) noexcept(
std::is_nothrow_constructible<T, Args &&...>::value) {
static_assert(std::is_constructible<T, Args &&...>::value,
"T must be constructible with Args&&...");
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
auto nextWriteIdx = writeIdx + 1;
if (nextWriteIdx == capacity_) {
nextWriteIdx = 0;
}
while (nextWriteIdx == readIdxCache_) {
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
}
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
writeIdx_.store(nextWriteIdx, std::memory_order_release);
}
T *front() noexcept {
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
if (readIdx == writeIdxCache_) {
writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
if (writeIdxCache_ == readIdx) {
return nullptr;
}
}
return &slots_[readIdx + kPadding];
}
void pop() noexcept {
static_assert(std::is_nothrow_destructible<T>::value,
"T must be nothrow destructible");
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
assert(writeIdx_.load(std::memory_order_acquire) != readIdx);
slots_[readIdx + kPadding].~T();
auto nextReadIdx = readIdx + 1;
if (nextReadIdx == capacity_) {
nextReadIdx = 0;
}
readIdx_.store(nextReadIdx, std::memory_order_release);
}
size_t size() const noexcept {
std::ptrdiff_t diff = writeIdx_.load(std::memory_order_acquire) -
readIdx_.load(std::memory_order_acquire);
if (diff < 0) {
diff += capacity_;
}
return static_cast<size_t>(diff);
}
bool empty() const noexcept {
return writeIdx_.load(std::memory_order_acquire) ==
readIdx_.load(std::memory_order_acquire);
}
size_t capacity() const noexcept { return capacity_ - 1; }
private:
static constexpr size_t kCacheLineSize = 64;
// Padding to avoid false sharing between slots_ and adjacent allocations
static constexpr size_t kPadding = (kCacheLineSize - 1) / sizeof(T) + 1;
private:
size_t capacity_;
T *slots_;
// Align to cache line size in order to avoid false sharing
// readIdxCache_ and writeIdxCache_ is used to reduce the amount of cache
// coherency traffic
alignas(kCacheLineSize) std::atomic<size_t> writeIdx_ = {0};
alignas(kCacheLineSize) size_t readIdxCache_ = 0;
alignas(kCacheLineSize) std::atomic<size_t> readIdx_ = {0};
alignas(kCacheLineSize) size_t writeIdxCache_ = 0;
// Padding to avoid adjacent allocations to share cache line with
// writeIdxCache_
char padding_[kCacheLineSize - sizeof(SPSCQueue<T>::writeIdxCache_)];
};
} // namespace rigtorp
#if defined (_MSC_VER)
#pragma warning(pop)
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,363 @@
/* rpmalloc.h - Memory allocator - Public Domain - 2016 Mattias Jansson
*
* This library provides a cross-platform lock free thread caching malloc implementation in C11.
* The latest source code is always available at
*
* https://github.com/mjansson/rpmalloc
*
* This library is put in the public domain; you can redistribute it and/or modify it without any restrictions.
*
*/
#pragma once
#include <stddef.h>
#include "../common/TracyApi.h"
namespace tracy
{
#if defined(__clang__) || defined(__GNUC__)
# define RPMALLOC_EXPORT __attribute__((visibility("default")))
# define RPMALLOC_ALLOCATOR
# if (defined(__clang_major__) && (__clang_major__ < 4)) || (defined(__GNUC__) && defined(ENABLE_PRELOAD) && ENABLE_PRELOAD)
# define RPMALLOC_ATTRIB_MALLOC
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size)
# else
# define RPMALLOC_ATTRIB_MALLOC __attribute__((__malloc__))
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) __attribute__((alloc_size(size)))
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size) __attribute__((alloc_size(count, size)))
# endif
# define RPMALLOC_CDECL
#elif defined(_MSC_VER)
# define RPMALLOC_EXPORT
# define RPMALLOC_ALLOCATOR __declspec(allocator) __declspec(restrict)
# define RPMALLOC_ATTRIB_MALLOC
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
# define RPMALLOC_CDECL __cdecl
#else
# define RPMALLOC_EXPORT
# define RPMALLOC_ALLOCATOR
# define RPMALLOC_ATTRIB_MALLOC
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
# define RPMALLOC_CDECL
#endif
//! Define RPMALLOC_CONFIGURABLE to enable configuring sizes. Will introduce
// a very small overhead due to some size calculations not being compile time constants
#ifndef RPMALLOC_CONFIGURABLE
#define RPMALLOC_CONFIGURABLE 0
#endif
//! Define RPMALLOC_FIRST_CLASS_HEAPS to enable heap based API (rpmalloc_heap_* functions).
// Will introduce a very small overhead to track fully allocated spans in heaps
#ifndef RPMALLOC_FIRST_CLASS_HEAPS
#define RPMALLOC_FIRST_CLASS_HEAPS 0
#endif
//! Flag to rpaligned_realloc to not preserve content in reallocation
#define RPMALLOC_NO_PRESERVE 1
//! Flag to rpaligned_realloc to fail and return null pointer if grow cannot be done in-place,
// in which case the original pointer is still valid (just like a call to realloc which failes to allocate
// a new block).
#define RPMALLOC_GROW_OR_FAIL 2
typedef struct rpmalloc_global_statistics_t {
//! Current amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
size_t mapped;
//! Peak amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
size_t mapped_peak;
//! Current amount of memory in global caches for small and medium sizes (<32KiB)
size_t cached;
//! Current amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
size_t huge_alloc;
//! Peak amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
size_t huge_alloc_peak;
//! Total amount of memory mapped since initialization (only if ENABLE_STATISTICS=1)
size_t mapped_total;
//! Total amount of memory unmapped since initialization (only if ENABLE_STATISTICS=1)
size_t unmapped_total;
} rpmalloc_global_statistics_t;
typedef struct rpmalloc_thread_statistics_t {
//! Current number of bytes available in thread size class caches for small and medium sizes (<32KiB)
size_t sizecache;
//! Current number of bytes available in thread span caches for small and medium sizes (<32KiB)
size_t spancache;
//! Total number of bytes transitioned from thread cache to global cache (only if ENABLE_STATISTICS=1)
size_t thread_to_global;
//! Total number of bytes transitioned from global cache to thread cache (only if ENABLE_STATISTICS=1)
size_t global_to_thread;
//! Per span count statistics (only if ENABLE_STATISTICS=1)
struct {
//! Currently used number of spans
size_t current;
//! High water mark of spans used
size_t peak;
//! Number of spans transitioned to global cache
size_t to_global;
//! Number of spans transitioned from global cache
size_t from_global;
//! Number of spans transitioned to thread cache
size_t to_cache;
//! Number of spans transitioned from thread cache
size_t from_cache;
//! Number of spans transitioned to reserved state
size_t to_reserved;
//! Number of spans transitioned from reserved state
size_t from_reserved;
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
size_t map_calls;
} span_use[64];
//! Per size class statistics (only if ENABLE_STATISTICS=1)
struct {
//! Current number of allocations
size_t alloc_current;
//! Peak number of allocations
size_t alloc_peak;
//! Total number of allocations
size_t alloc_total;
//! Total number of frees
size_t free_total;
//! Number of spans transitioned to cache
size_t spans_to_cache;
//! Number of spans transitioned from cache
size_t spans_from_cache;
//! Number of spans transitioned from reserved state
size_t spans_from_reserved;
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
size_t map_calls;
} size_use[128];
} rpmalloc_thread_statistics_t;
typedef struct rpmalloc_config_t {
//! Map memory pages for the given number of bytes. The returned address MUST be
// aligned to the rpmalloc span size, which will always be a power of two.
// Optionally the function can store an alignment offset in the offset variable
// in case it performs alignment and the returned pointer is offset from the
// actual start of the memory region due to this alignment. The alignment offset
// will be passed to the memory unmap function. The alignment offset MUST NOT be
// larger than 65535 (storable in an uint16_t), if it is you must use natural
// alignment to shift it into 16 bits. If you set a memory_map function, you
// must also set a memory_unmap function or else the default implementation will
// be used for both. This function must be thread safe, it can be called by
// multiple threads simultaneously.
void* (*memory_map)(size_t size, size_t* offset);
//! Unmap the memory pages starting at address and spanning the given number of bytes.
// If release is set to non-zero, the unmap is for an entire span range as returned by
// a previous call to memory_map and that the entire range should be released. The
// release argument holds the size of the entire span range. If release is set to 0,
// the unmap is a partial decommit of a subset of the mapped memory range.
// If you set a memory_unmap function, you must also set a memory_map function or
// else the default implementation will be used for both. This function must be thread
// safe, it can be called by multiple threads simultaneously.
void (*memory_unmap)(void* address, size_t size, size_t offset, size_t release);
//! Called when an assert fails, if asserts are enabled. Will use the standard assert()
// if this is not set.
void (*error_callback)(const char* message);
//! Called when a call to map memory pages fails (out of memory). If this callback is
// not set or returns zero the library will return a null pointer in the allocation
// call. If this callback returns non-zero the map call will be retried. The argument
// passed is the number of bytes that was requested in the map call. Only used if
// the default system memory map function is used (memory_map callback is not set).
int (*map_fail_callback)(size_t size);
//! Size of memory pages. The page size MUST be a power of two. All memory mapping
// requests to memory_map will be made with size set to a multiple of the page size.
// Used if RPMALLOC_CONFIGURABLE is defined to 1, otherwise system page size is used.
size_t page_size;
//! Size of a span of memory blocks. MUST be a power of two, and in [4096,262144]
// range (unless 0 - set to 0 to use the default span size). Used if RPMALLOC_CONFIGURABLE
// is defined to 1.
size_t span_size;
//! Number of spans to map at each request to map new virtual memory blocks. This can
// be used to minimize the system call overhead at the cost of virtual memory address
// space. The extra mapped pages will not be written until actually used, so physical
// committed memory should not be affected in the default implementation. Will be
// aligned to a multiple of spans that match memory page size in case of huge pages.
size_t span_map_count;
//! Enable use of large/huge pages. If this flag is set to non-zero and page size is
// zero, the allocator will try to enable huge pages and auto detect the configuration.
// If this is set to non-zero and page_size is also non-zero, the allocator will
// assume huge pages have been configured and enabled prior to initializing the
// allocator.
// For Windows, see https://docs.microsoft.com/en-us/windows/desktop/memory/large-page-support
// For Linux, see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
int enable_huge_pages;
//! Respectively allocated pages and huge allocated pages names for systems
// supporting it to be able to distinguish among anonymous regions.
const char *page_name;
const char *huge_page_name;
} rpmalloc_config_t;
//! Initialize allocator with default configuration
TRACY_API int
rpmalloc_initialize(void);
//! Initialize allocator with given configuration
RPMALLOC_EXPORT int
rpmalloc_initialize_config(const rpmalloc_config_t* config);
//! Get allocator configuration
RPMALLOC_EXPORT const rpmalloc_config_t*
rpmalloc_config(void);
//! Finalize allocator
TRACY_API void
rpmalloc_finalize(void);
//! Initialize allocator for calling thread
TRACY_API void
rpmalloc_thread_initialize(void);
//! Finalize allocator for calling thread
TRACY_API void
rpmalloc_thread_finalize(int release_caches);
//! Perform deferred deallocations pending for the calling thread heap
RPMALLOC_EXPORT void
rpmalloc_thread_collect(void);
//! Query if allocator is initialized for calling thread
RPMALLOC_EXPORT int
rpmalloc_is_thread_initialized(void);
//! Get per-thread statistics
RPMALLOC_EXPORT void
rpmalloc_thread_statistics(rpmalloc_thread_statistics_t* stats);
//! Get global statistics
RPMALLOC_EXPORT void
rpmalloc_global_statistics(rpmalloc_global_statistics_t* stats);
//! Dump all statistics in human readable format to file (should be a FILE*)
RPMALLOC_EXPORT void
rpmalloc_dump_statistics(void* file);
//! Allocate a memory block of at least the given size
TRACY_API RPMALLOC_ALLOCATOR void*
rpmalloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1);
//! Free the given memory block
TRACY_API void
rpfree(void* ptr);
//! Allocate a memory block of at least the given size and zero initialize it
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpcalloc(size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(1, 2);
//! Reallocate the given block to at least the given size
TRACY_API RPMALLOC_ALLOCATOR void*
rprealloc(void* ptr, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Reallocate the given block to at least the given size and alignment,
// with optional control flags (see RPMALLOC_NO_PRESERVE).
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpaligned_realloc(void* ptr, size_t alignment, size_t size, size_t oldsize, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
//! Allocate a memory block of at least the given size and alignment.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpaligned_alloc(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Allocate a memory block of at least the given size and alignment, and zero initialize it.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpaligned_calloc(size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
//! Allocate a memory block of at least the given size and alignment.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmemalign(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Allocate a memory block of at least the given size and alignment.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT int
rpposix_memalign(void** memptr, size_t alignment, size_t size);
//! Query the usable size of the given memory block (from given pointer to the end of block)
RPMALLOC_EXPORT size_t
rpmalloc_usable_size(void* ptr);
#if RPMALLOC_FIRST_CLASS_HEAPS
//! Heap type
typedef struct heap_t rpmalloc_heap_t;
//! Acquire a new heap. Will reuse existing released heaps or allocate memory for a new heap
// if none available. Heap API is implemented with the strict assumption that only one single
// thread will call heap functions for a given heap at any given time, no functions are thread safe.
RPMALLOC_EXPORT rpmalloc_heap_t*
rpmalloc_heap_acquire(void);
//! Release a heap (does NOT free the memory allocated by the heap, use rpmalloc_heap_free_all before destroying the heap).
// Releasing a heap will enable it to be reused by other threads. Safe to pass a null pointer.
RPMALLOC_EXPORT void
rpmalloc_heap_release(rpmalloc_heap_t* heap);
//! Allocate a memory block of at least the given size using the given heap.
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_alloc(rpmalloc_heap_t* heap, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Allocate a memory block of at least the given size using the given heap. The returned
// block will have the requested alignment. Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB).
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_aligned_alloc(rpmalloc_heap_t* heap, size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
//! Allocate a memory block of at least the given size using the given heap and zero initialize it.
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_calloc(rpmalloc_heap_t* heap, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
//! Allocate a memory block of at least the given size using the given heap and zero initialize it. The returned
// block will have the requested alignment. Alignment must either be zero, or a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB).
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_aligned_calloc(rpmalloc_heap_t* heap, size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
// by the same heap given to this function.
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_realloc(rpmalloc_heap_t* heap, void* ptr, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
// by the same heap given to this function. The returned block will have the requested alignment.
// Alignment must be either zero, or a power of two and a multiple of sizeof(void*), and should ideally be
// less than memory page size. A caveat of rpmalloc internals is that this must also be strictly less than
// the span size (default 64KiB).
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_aligned_realloc(rpmalloc_heap_t* heap, void* ptr, size_t alignment, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(4);
//! Free the given memory block from the given heap. The memory block MUST be allocated
// by the same heap given to this function.
RPMALLOC_EXPORT void
rpmalloc_heap_free(rpmalloc_heap_t* heap, void* ptr);
//! Free all memory allocated by the heap
RPMALLOC_EXPORT void
rpmalloc_heap_free_all(rpmalloc_heap_t* heap);
//! Set the given heap as the current heap for the calling thread. A heap MUST only be current heap
// for a single thread, a heap can never be shared between multiple threads. The previous
// current heap for the calling thread is released to be reused by other threads.
RPMALLOC_EXPORT void
rpmalloc_heap_thread_set_current(rpmalloc_heap_t* heap);
#endif
}

View File

@ -0,0 +1,27 @@
#ifndef __TRACYALIGN_HPP__
#define __TRACYALIGN_HPP__
#include <string.h>
#include "TracyForceInline.hpp"
namespace tracy
{
template<typename T>
tracy_force_inline T MemRead( const void* ptr )
{
T val;
memcpy( &val, ptr, sizeof( T ) );
return val;
}
template<typename T>
tracy_force_inline void MemWrite( void* ptr, T val )
{
memcpy( ptr, &val, sizeof( T ) );
}
}
#endif

View File

@ -0,0 +1,72 @@
#ifndef __TRACYALLOC_HPP__
#define __TRACYALLOC_HPP__
#include <stdlib.h>
#if defined TRACY_ENABLE && !defined __EMSCRIPTEN__
# include "TracyApi.h"
# include "TracyForceInline.hpp"
# include "../client/tracy_rpmalloc.hpp"
# define TRACY_USE_RPMALLOC
#endif
namespace tracy
{
#ifdef TRACY_USE_RPMALLOC
TRACY_API void InitRpmalloc();
#else
static inline void InitRpmalloc() {}
#endif
static inline void* tracy_malloc( size_t size )
{
#ifdef TRACY_USE_RPMALLOC
InitRpmalloc();
return rpmalloc( size );
#else
return malloc( size );
#endif
}
static inline void* tracy_malloc_fast( size_t size )
{
#ifdef TRACY_USE_RPMALLOC
return rpmalloc( size );
#else
return malloc( size );
#endif
}
static inline void tracy_free( void* ptr )
{
#ifdef TRACY_USE_RPMALLOC
InitRpmalloc();
rpfree( ptr );
#else
free( ptr );
#endif
}
static inline void tracy_free_fast( void* ptr )
{
#ifdef TRACY_USE_RPMALLOC
rpfree( ptr );
#else
free( ptr );
#endif
}
static inline void* tracy_realloc( void* ptr, size_t size )
{
#ifdef TRACY_USE_RPMALLOC
InitRpmalloc();
return rprealloc( ptr, size );
#else
return realloc( ptr, size );
#endif
}
}
#endif

16
src/third_party/tracy/common/TracyApi.h vendored Normal file
View File

@ -0,0 +1,16 @@
#ifndef __TRACYAPI_H__
#define __TRACYAPI_H__
#if defined _WIN32
# if defined TRACY_EXPORTS
# define TRACY_API __declspec(dllexport)
# elif defined TRACY_IMPORTS
# define TRACY_API __declspec(dllimport)
# else
# define TRACY_API
# endif
#else
# define TRACY_API __attribute__((visibility("default")))
#endif
#endif // __TRACYAPI_H__

Some files were not shown because too many files have changed in this diff Show More