initial commit
This commit is contained in:
commit
8284923173
27
.gitattributes
vendored
Normal file
27
.gitattributes
vendored
Normal 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
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.*.swp
|
||||||
|
*.lnk
|
||||||
|
*.rdbg
|
||||||
|
*.10x
|
||||||
|
*.cap
|
||||||
|
*.tracy
|
||||||
|
.vs/*
|
||||||
|
|
||||||
|
# Build / output directories
|
||||||
|
build/
|
||||||
|
out/
|
||||||
38
.natvis
Normal file
38
.natvis
Normal 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"><NULL></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
226
CMakeLists.txt
Normal 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
111
build.bat
Normal 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%
|
||||||
24
profile.bat
Normal file
24
profile.bat
Normal 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
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
BIN
res/graphics/timmy.ase
(Stored with Git LFS)
Normal file
Binary file not shown.
156
src/app.c
Normal file
156
src/app.c
Normal 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
9
src/app.h
Normal 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
103
src/arena.c
Normal 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
106
src/arena.h
Normal 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
887
src/ase.c
Normal 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
37
src/ase.h
Normal 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
244
src/asset_cache.c
Normal 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
57
src/asset_cache.h
Normal 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
18
src/atomic.h
Normal 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
218
src/byteio.c
Normal 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
143
src/byteio.h
Normal 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
509
src/common.h
Normal 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
12
src/config.h
Normal 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
31
src/console.c
Normal 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
9
src/console.h
Normal 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
173
src/draw.c
Normal 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
30
src/draw.h
Normal 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
101
src/entity.h
Normal 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
220
src/font.c
Normal 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
34
src/font.h
Normal 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
409
src/game.c
Normal 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
35
src/game.h
Normal 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
20
src/inc.c
Normal 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
10
src/inc.h
Normal 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
34
src/incbin.h
Normal 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
59
src/intrinsics.h
Normal 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
1083
src/json.c
Normal file
File diff suppressed because it is too large
Load Diff
143
src/json.h
Normal file
143
src/json.h
Normal 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
196
src/log.c
Normal 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
147
src/log.h
Normal 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
564
src/math.h
Normal 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
27
src/memory.c
Normal 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
18
src/memory.h
Normal 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
482
src/mixer.c
Normal 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
54
src/mixer.h
Normal 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
14
src/mp3.h
Normal 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
135
src/mp3_mmf.c
Normal 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
9
src/playback.h
Normal 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
234
src/playback_wasapi.c
Normal 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
85
src/renderer.h
Normal 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
1069
src/renderer_d3d11.c
Normal file
File diff suppressed because it is too large
Load Diff
71
src/resource.c
Normal file
71
src/resource.c
Normal 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
29
src/resource.h
Normal 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
134
src/scratch.h
Normal 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
168
src/settings.c
Normal 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
10
src/settings.h
Normal 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
46
src/shaders/texture.hlsl
Normal 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
287
src/sheet.c
Normal 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
38
src/sheet.h
Normal 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
217
src/sound.c
Normal 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
20
src/sound.h
Normal 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
555
src/string.c
Normal 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
95
src/string.h
Normal 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
471
src/sys.h
Normal 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
1749
src/sys_win32.c
Normal file
File diff suppressed because it is too large
Load Diff
193
src/tar.c
Normal file
193
src/tar.c
Normal 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
23
src/tar.h
Normal 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
272
src/texture.c
Normal 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
36
src/texture.h
Normal 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
58
src/third_party/tracy/TracyClient.cpp
vendored
Normal 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
|
||||||
43
src/third_party/tracy/client/TracyAlloc.cpp
vendored
Normal file
43
src/third_party/tracy/client/TracyAlloc.cpp
vendored
Normal 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
|
||||||
419
src/third_party/tracy/client/TracyArmCpuTable.hpp
vendored
Normal file
419
src/third_party/tracy/client/TracyArmCpuTable.hpp
vendored
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
1206
src/third_party/tracy/client/TracyCallstack.cpp
vendored
Normal file
1206
src/third_party/tracy/client/TracyCallstack.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
35
src/third_party/tracy/client/TracyCallstack.h
vendored
Normal file
35
src/third_party/tracy/client/TracyCallstack.h
vendored
Normal 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
|
||||||
153
src/third_party/tracy/client/TracyCallstack.hpp
vendored
Normal file
153
src/third_party/tracy/client/TracyCallstack.hpp
vendored
Normal 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
|
||||||
12
src/third_party/tracy/client/TracyCpuid.hpp
vendored
Normal file
12
src/third_party/tracy/client/TracyCpuid.hpp
vendored
Normal 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
|
||||||
11
src/third_party/tracy/client/TracyDebug.hpp
vendored
Normal file
11
src/third_party/tracy/client/TracyDebug.hpp
vendored
Normal 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
|
||||||
644
src/third_party/tracy/client/TracyDxt1.cpp
vendored
Normal file
644
src/third_party/tracy/client/TracyDxt1.cpp
vendored
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/third_party/tracy/client/TracyDxt1.hpp
vendored
Normal file
11
src/third_party/tracy/client/TracyDxt1.hpp
vendored
Normal 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
|
||||||
118
src/third_party/tracy/client/TracyFastVector.hpp
vendored
Normal file
118
src/third_party/tracy/client/TracyFastVector.hpp
vendored
Normal 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
|
||||||
546
src/third_party/tracy/client/TracyLock.hpp
vendored
Normal file
546
src/third_party/tracy/client/TracyLock.hpp
vendored
Normal 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
|
||||||
26
src/third_party/tracy/client/TracyOverride.cpp
vendored
Normal file
26
src/third_party/tracy/client/TracyOverride.cpp
vendored
Normal 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
|
||||||
4511
src/third_party/tracy/client/TracyProfiler.cpp
vendored
Normal file
4511
src/third_party/tracy/client/TracyProfiler.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1006
src/third_party/tracy/client/TracyProfiler.hpp
vendored
Normal file
1006
src/third_party/tracy/client/TracyProfiler.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
141
src/third_party/tracy/client/TracyRingBuffer.hpp
vendored
Normal file
141
src/third_party/tracy/client/TracyRingBuffer.hpp
vendored
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
175
src/third_party/tracy/client/TracyScoped.hpp
vendored
Normal file
175
src/third_party/tracy/client/TracyScoped.hpp
vendored
Normal 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
|
||||||
41
src/third_party/tracy/client/TracyStringHelpers.hpp
vendored
Normal file
41
src/third_party/tracy/client/TracyStringHelpers.hpp
vendored
Normal 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
|
||||||
164
src/third_party/tracy/client/TracySysPower.cpp
vendored
Normal file
164
src/third_party/tracy/client/TracySysPower.cpp
vendored
Normal 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
|
||||||
44
src/third_party/tracy/client/TracySysPower.hpp
vendored
Normal file
44
src/third_party/tracy/client/TracySysPower.hpp
vendored
Normal 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
|
||||||
108
src/third_party/tracy/client/TracySysTime.cpp
vendored
Normal file
108
src/third_party/tracy/client/TracySysTime.cpp
vendored
Normal 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
|
||||||
36
src/third_party/tracy/client/TracySysTime.hpp
vendored
Normal file
36
src/third_party/tracy/client/TracySysTime.hpp
vendored
Normal 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
|
||||||
1602
src/third_party/tracy/client/TracySysTrace.cpp
vendored
Normal file
1602
src/third_party/tracy/client/TracySysTrace.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
28
src/third_party/tracy/client/TracySysTrace.hpp
vendored
Normal file
28
src/third_party/tracy/client/TracySysTrace.hpp
vendored
Normal 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
|
||||||
90
src/third_party/tracy/client/TracyThread.hpp
vendored
Normal file
90
src/third_party/tracy/client/TracyThread.hpp
vendored
Normal 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
|
||||||
148
src/third_party/tracy/client/tracy_SPSCQueue.h
vendored
Normal file
148
src/third_party/tracy/client/tracy_SPSCQueue.h
vendored
Normal 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
|
||||||
1441
src/third_party/tracy/client/tracy_concurrentqueue.h
vendored
Normal file
1441
src/third_party/tracy/client/tracy_concurrentqueue.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3518
src/third_party/tracy/client/tracy_rpmalloc.cpp
vendored
Normal file
3518
src/third_party/tracy/client/tracy_rpmalloc.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
363
src/third_party/tracy/client/tracy_rpmalloc.hpp
vendored
Normal file
363
src/third_party/tracy/client/tracy_rpmalloc.hpp
vendored
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
27
src/third_party/tracy/common/TracyAlign.hpp
vendored
Normal file
27
src/third_party/tracy/common/TracyAlign.hpp
vendored
Normal 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
|
||||||
72
src/third_party/tracy/common/TracyAlloc.hpp
vendored
Normal file
72
src/third_party/tracy/common/TracyAlloc.hpp
vendored
Normal 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
16
src/third_party/tracy/common/TracyApi.h
vendored
Normal 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
Loading…
Reference in New Issue
Block a user