Compare commits

..

1 Commits

Author SHA1 Message Date
Marcos Slomp
9410e633d5 note about iOS devices 2026-05-05 10:19:16 -07:00
240 changed files with 4948 additions and 15995 deletions

View File

@@ -1,35 +0,0 @@
name: 'Test Tracy'
description: 'Build the Tracy test application with various cmake flag combinations'
inputs:
extra_cmake_flags:
description: 'Additional cmake flags appended to each configure command (e.g. cross-compilation flags)'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Test application
shell: bash
run: |
# test compilation with different flags
# we clean the build folder to reset cached variables between runs
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_ON_DEMAND
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_DELAYED_INIT and TRACY_MANUAL_LIFETIME
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_DEMANGLE
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup emscripten
uses: emscripten-core/setup-emsdk@v16
with:
version: 5.0.7
version: 4.0.10
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4

View File

@@ -32,7 +32,7 @@ jobs:
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
cmake --build profiler/build
else
cmake --build profiler/build --parallel 2
cmake --build profiler/build --parallel
fi
- name: Update utility
run: |
@@ -66,7 +66,27 @@ jobs:
meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build-meson
meson compile -C build-meson
- name: Test application
uses: ./.github/actions/test-tracy
run: |
# test compilation with different flags
# we clean the build folder to reset cached variables between runs
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release
cmake --build test/build --parallel
rm -rf test/build
# same with TRACY_ON_DEMAND
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON .
cmake --build test/build --parallel
rm -rf test/build
# same with TRACY_DELAYED_INIT TRACY_MANUAL_LIFETIME
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON .
cmake --build test/build --parallel
rm -rf test/build
# same with TRACY_DEMANGLE
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON .
cmake --build test/build --parallel
rm -rf test/build
- name: Find Artifacts
id: find_artifacts
run: |

View File

@@ -28,7 +28,7 @@ jobs:
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel 2 --config Release
cmake --build profiler/build --parallel --config Release
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
@@ -51,8 +51,6 @@ jobs:
cmake --build merge/build --parallel --config Release
- name: Build library
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build && meson compile -C build && meson install -C build
- name: Test application
uses: ./.github/actions/test-tracy
- name: Package artifacts
run: |
mkdir -p bin

View File

@@ -53,9 +53,27 @@ jobs:
meson setup build-meson --cross-file mingw-cross.txt -Ddefault_library=static -Dtracy_enable=true
meson compile -C build-meson
- name: Test application
uses: ./.github/actions/test-tracy
with:
extra_cmake_flags: >-
-DCMAKE_SYSTEM_NAME=Windows
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc
run: |
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
cmake --build test/build --parallel
rm -rf test/build
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_ON_DEMAND=ON \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
cmake --build test/build --parallel
rm -rf test/build
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
cmake --build test/build --parallel
rm -rf test/build
cmake -B test/build -S test -DCMAKE_BUILD_TYPE=Release -DTRACY_DEMANGLE=ON \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++
cmake --build test/build --parallel

View File

@@ -32,7 +32,7 @@ jobs:
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel 2 --config Release
cmake --build profiler/build --parallel --config Release
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
@@ -53,8 +53,6 @@ jobs:
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel --config Release
- name: Test application
uses: ./.github/actions/test-tracy
- name: Package artifacts
run: |
mkdir bin

2
.gitignore vendored
View File

@@ -30,8 +30,6 @@ profiler/build/win32/Tracy.aps
extra/vswhere.exe
extra/tracy-build
.cache
.uv-cache/
.venv/
compile_commands.json
profiler/build/wasm/Tracy-release.*
profiler/build/wasm/Tracy-debug.*

View File

@@ -6,7 +6,7 @@
"${workspaceFolder}/import",
"${workspaceFolder}/merge",
"${workspaceFolder}/update",
"${workspaceFolder}/tests/tracy",
"${workspaceFolder}/test",
"${workspaceFolder}",
],
"cmake.buildDirectory": "${sourceDirectory}/build",

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.10)
# Run version helper script
include(cmake/version.cmake)
@@ -108,46 +108,60 @@ endif()
include(cmake/options.cmake)
set_option(TRACY_ENABLE "Enable profiling" OFF TracyClient)
set_option(TRACY_ON_DEMAND "On-demand profiling" OFF TracyClient)
set_option_value(TRACY_CALLSTACK "Override the callstack collection depth for tracy zones" "" TracyClient)
set_option_value_as_string(TRACY_PLATFORM_HEADER "Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform" "" TracyClient)
set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF TracyClient)
set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF TracyClient)
set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF TracyClient)
set_option(TRACY_NO_BROADCAST "Disable client discovery by broadcast to local network" OFF TracyClient)
set_option(TRACY_ONLY_IPV4 "Tracy will only accept connections on IPv4 addresses (disable IPv6)" OFF TracyClient)
set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF TracyClient)
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF TracyClient)
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF TracyClient)
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF TracyClient)
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF TracyClient)
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF TracyClient)
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF TracyClient)
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF TracyClient)
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF TracyClient)
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF TracyClient)
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF TracyClient)
set_option(TRACY_FIBERS "Enable fibers support" OFF TracyClient)
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF TracyClient)
set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF TracyClient)
set_option(TRACY_DISALLOW_HW_TIMER "Disallow hardware timer (may be useful on VMs). Requires TRACY_TIMER_FALLBACK=ON" OFF TracyClient)
set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF TracyClient)
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF TracyClient)
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF TracyClient)
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF TracyClient)
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF TracyClient)
set_option(TRACY_OPENGL_AUTO_CALIBRATION "Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)" OFF TracyClient)
# Local wrapper that also sets compile definitions for TracyClient
macro(tracy_set_option option help value)
set_option(${option} "${help}" ${value})
if(${option})
target_compile_definitions(TracyClient PUBLIC ${option})
endif()
endmacro()
# Local wrapper for value options that also sets compile definitions for TracyClient
macro(tracy_set_option_value var help default)
set_option_value(${var} "${help}" "${default}")
if(${var})
target_compile_definitions(TracyClient PUBLIC ${var}=${${var}})
endif()
endmacro()
tracy_set_option(TRACY_ENABLE "Enable profiling" OFF)
tracy_set_option(TRACY_ON_DEMAND "On-demand profiling" OFF)
tracy_set_option_value(TRACY_CALLSTACK "Override the callstack collection depth for tracy zones" "")
tracy_set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF)
tracy_set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF)
tracy_set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF)
tracy_set_option(TRACY_NO_BROADCAST "Disable client discovery by broadcast to local network" OFF)
tracy_set_option(TRACY_ONLY_IPV4 "Tracy will only accept connections on IPv4 addresses (disable IPv6)" OFF)
tracy_set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF)
tracy_set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF)
tracy_set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF)
tracy_set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF)
tracy_set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF)
tracy_set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF)
tracy_set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF)
tracy_set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF)
tracy_set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF)
tracy_set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF)
tracy_set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF)
tracy_set_option(TRACY_FIBERS "Enable fibers support" OFF)
tracy_set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF)
tracy_set_option(TRACY_TIMER_FALLBACK "Use lower resolution timers" OFF)
tracy_set_option(TRACY_DISALLOW_HW_TIMER "Disallow hardware timer (may be useful on VMs). Requires TRACY_TIMER_FALLBACK=ON" OFF)
tracy_set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported" OFF)
tracy_set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF)
tracy_set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF)
tracy_set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF)
tracy_set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF)
# advanced
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF TracyClient)
tracy_set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF)
mark_as_advanced(TRACY_VERBOSE)
set_option(TRACY_NO_INTERNAL_MESSAGE "[advanced] Prevent the profiler from logging messages" OFF TracyClient)
tracy_set_option(TRACY_NO_INTERNAL_MESSAGE "[advanced] Prevent the profiler from logging messages" OFF)
mark_as_advanced(TRACY_NO_INTERNAL_MESSAGE)
set_option(TRACY_DEMANGLE "[advanced] Don't use default demangling function - You'll need to provide your own" OFF TracyClient)
tracy_set_option(TRACY_DEMANGLE "[advanced] Don't use default demangling function - You'll need to provide your own" OFF)
mark_as_advanced(TRACY_DEMANGLE)
if(rocprofiler-sdk_FOUND)
set_option(TRACY_ROCPROF_CALIBRATION "[advanced] Use continuous calibration of the Rocprof GPU time." OFF TracyClient)
tracy_set_option(TRACY_ROCPROF_CALIBRATION "[advanced] Use continuous calibration of the Rocprof GPU time." OFF)
mark_as_advanced(TRACY_ROCPROF_CALIBRATION)
endif()
@@ -284,7 +298,3 @@ if(TRACY_CLIENT_PYTHON)
add_subdirectory(python)
endif()
if(PROJECT_IS_TOP_LEVEL)
set(CMAKE_COLOR_DIAGNOSTICS ON)
endif()

85
NEWS
View File

@@ -5,16 +5,9 @@ here.
vx.xx.x (2026-xx-xx)
--------------------
- API break: removed "secure" variants of memory alloc and free macros. The
secure code path is now always enabled. Migrate by removing "Secure" from
the macros you use, e.g. TracySecureAlloc(...) -> TracyAlloc(...).
- Added tracy-capture-daemon for automated multi-client trace capture.
- Added tracy-merge utility for combining multiple trace files into one.
- Added support for Windows on ARM64 with MSVC.
- Added support for WebGPU.
- Trace-specific settings storage has been completely overhauled. It is now
possible to make the settings sidecar file public, saved next to the trace
file.
- External frames are now omitted in the single-line call stack list visible
in messages list, or in memory allocation info window.
- External frames are now hidden by default in various contexts where they
@@ -22,7 +15,7 @@ vx.xx.x (2026-xx-xx)
- Flame graph window.
- Call stack window.
- Statistics window (sampling mode).
- External frames are now dimmed out in call stacks in various parts of UI.
- External frames are now dimmed out in call stacks.
- Single-line call stacks now have ellipsis at the end, if there are frames
remaining.
- System tracing on Windows has been refactored to be more robust.
@@ -49,15 +42,10 @@ vx.xx.x (2026-xx-xx)
- The protocol has been updated to use model templates. As a result, tools
are now specified in a common way and the reasoning is performed in a
separate content stream.
- Several new tools were added, which in concert enable the assistant to
answer very general questions, such as "how to optimize this program?".
- Smaller models are now viable to use. Models as small as 4B parameters do
now work really well. You can run such models on virtually all hardware.
- Added horizontal scroll bars to code segments.
- LLM thinking regions are now hidden by default.
- The assistant may notify the user about its current findings, then
resume thinking, after which it may give a more complete answer. In
such cases, the initial part of the reply will be faded out.
- Sampled execution costs are now included in assembly attachments.
- Source code retrieval now has an optional line context parameter.
- Added ability to search the code for keywords.
@@ -77,16 +65,13 @@ vx.xx.x (2026-xx-xx)
setting the "fast" model to a smaller and much quicker one.
- Chat topic description is now provided, based on the first user question.
- Each assistant reply is now labeled with used model and reply time.
- Follow-up questions can be automatically suggested.
- Expanded LLM attachments.
- You can now attach complete symbol assembly.
- Entry call stacks can be now attached (previously it was only regular
call stacks).
- Crash call stack attachments are now annotated with crash info.
- Wait stack attachments now contain wait metadata.
- Source code can be attached (also with execution costs in symbol view).
- Zone histogram data can be attached for analysis.
- Added MCP server.
- Markdown renderer improvements.
- Tables are now properly rendered.
- Tasklist rendering has been implemented.
@@ -110,7 +95,6 @@ vx.xx.x (2026-xx-xx)
locations won't be fixed.
- Call stack window will now display notification if viewing a crash call
stack.
- Call stack window will now show a proper label if viewing a wait stack.
- Removal of Tracy crash handler stack from the reported crash call stack
should now work again on Linux.
- In disassembly line view, source file names are now displayed instead of
@@ -153,75 +137,14 @@ vx.xx.x (2026-xx-xx)
options for the entire program.
- Message windows will now properly show full message in a tooltip for
multi-line messages.
- Greatly improved the in-profiler user manual.
- There is now chapter tree and the manual contents are displayed section
by section.
- Links to chapters are now properly working.
- The "bclogo" blocks are now correctly processed and displayed as proper
admonitions.
- The font awesome icons now show as in the rest of the UI.
- Footnotes are now rendered as proper footnotes.
- Tables are now rendered as intended.
- LaTeX math is now converted to readable form.
- Added a button to download the full PDF manual to the user manual window.
- The in-profiler user manual now properly handles links to chapters.
- Call stack window will now show the thread viewed call stack originates
from (if possible).
- "Visible threads" checkboxes in messages, flame graph and wait stacks
windows are now displayed in multiple columns, and the maximum number of
visible rows is limited, with fallback to scrollable view.
- Improved child call distribution list in the symbol view window.
- The visible area can be now resized horizontally.
- The list is now displayed as a table with resizable columns, etc.
- Child calls time percentage is now shown as a percentage of calls (as
it was before), and also as a percentage of total symbol time.
- Child calls time is now also displayed as a percentage.
- Prototype implementation of system tracing on Apple devices.
- Local (inline) call stack printouts were added to tooltips in statistics
window, in sampling mode.
- Ironed out some code corners to make integration of closed gaming console
platforms easier. Added support for custom platform headers.
- Bottom and top sample trees (in wait stacks, or in entry call stacks)
now display aggregation counts if "group by function name" is enabled.
- HW sample view in symbol view are now disabled by default.
- The profiler can no longer be built with the statistics disabled.
- Fixed NVCC builds.
- Fixed possible lockups in Vulkan timer calibration loop.
- The flame graph view now supports zooming in and panning with the mouse.
- General application crash information polish in the profiler UI.
- The achievements system has been converted to use markdown renderer.
- Offline symbol resolution with the update utility now supports custom
addr2line-compatible tools via -a and -A command line parameters.
Additionally, it is now possible to reset all call stack frame symbols to
unresolved with the -R parameter.
- Periodic recalibration of the clock drift in OpenGL contexts can be enabled
with the TRACY_OPENGL_AUTO_CALIBRATION compilation define. Note that this
requires a full CPU/GPU sync on each calibration event. These events will
not fire more often than once every second.
- Additional hardening of OpenGL tracing.
- Added missing C API for shared locks.
- Implemented semi-unique, nonsense random name generator.
- Can be used to set a trace description.
- Will be used to provide default description for newly added annotations.
- Polished look and feel of annotation regions on the timeline.
- Annotations can now be hidden.
- Expanded available tests.
- The previously existing "test" program now lives in "tests/tracy".
- The tests directory also contains some repro cases for solved problems.
- Expanded examples showing how to use the profiler.
- Includes "Dyna.net", a small 2D game that is a variation of Bomberman.
- Made numeric input field for trace parameters usable again.
- Added new type of trace parameter that exposes a trigger button.
- Added "sections" feature for marking high-level sections of program
execution.
- For example, a game with multiple levels may have each of the levels
marked as a section.
- Each section is a distinct region not restricted by other sections.
- Multiple sections can overlap.
- Find zone window now also displays coefficient of variation, in addition
to standard deviation.
- Moved frame statistics from trace information window to its own window.
- Added various statistical data to bring frame statistics up to par with
the find zone window.
- Child call distribution box in the symbol view window can be now resized
horizontally.
v0.13.1 (2025-12-11)

View File

@@ -4,7 +4,7 @@
### A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling profiler for games and other applications.
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphics/compute APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA, WebGPU.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphic APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
- [Documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf) for usage and build process instructions
- [Releases](https://github.com/wolfpld/tracy/releases) containing the documentation (`tracy.pdf`) and compiled Windows x64 binaries (`Tracy-<version>.7z`) as assets

View File

@@ -1,13 +0,0 @@
diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp
index a9e32b7ac..2cdbc4812 100644
--- a/backends/imgui_impl_opengl3.cpp
+++ b/backends/imgui_impl_opengl3.cpp
@@ -1069,7 +1069,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3);
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
- bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3);
+ //bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3);
#endif
bd->HasClipOrigin = (bd->GlVersion >= 450);
#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS

View File

@@ -1,47 +1,24 @@
# Reusable option macros for Tracy CMake projects
# Reusable option macros for CMake projects
#
# Usage:
# set_option(OPTION_NAME "Help text" ON/OFF [TARGET]) - for boolean options
# set_option_value(VAR_NAME "Help text" "value" [TARGET]) - for value options (CACHE STRING)
# set_option_value_as_string(VAR_NAME "Help text" "value" [TARGET]) - for value options as C string literals
#
# [TARGET] is optional and specifies a target to which the option will
# be added as a compile definition (e.g., -DOPTION_NAME or -DVAR_NAME=value).
# set_option(OPTION_NAME "Help text" ON/OFF) - for boolean options
# set_option_value(VAR_NAME "Help text" "value") - for value options (CACHE STRING)
# Boolean option (ON/OFF).
# Boolean options (ON/OFF)
macro(set_option option help value)
option(${option} ${help} ${value})
if(${option})
message(STATUS "${option}: ON")
if(${ARGC} GREATER 3)
target_compile_definitions(${ARGV3} PUBLIC ${option})
endif()
else()
message(STATUS "${option}: OFF")
endif()
endmacro()
# Value option (string/number).
# Value options (strings, numbers, etc.)
macro(set_option_value var help default)
set(${var} ${default} CACHE STRING "${help}")
if(${var})
message(STATUS "${var}: ${${var}}")
if(${ARGC} GREATER 3)
target_compile_definitions(${ARGV3} PUBLIC ${var}=${${var}})
endif()
else()
message(STATUS "${var}: (not set)")
endif()
endmacro()
# Value option embedded as a C string literal (VAR="value").
macro(set_option_value_as_string var help default)
set(${var} ${default} CACHE STRING "${help}")
if(${var})
message(STATUS "${var}: ${${var}}")
if(${ARGC} GREATER 3)
target_compile_definitions(${ARGV3} PUBLIC "${var}=\"${${var}}\"")
endif()
else()
message(STATUS "${var}: (not set)")
endif()

View File

@@ -26,7 +26,7 @@ else()
CPMAddPackage(
NAME capstone
GITHUB_REPOSITORY capstone-engine/capstone
GIT_TAG 6.0.0-Alpha9
GIT_TAG 6.0.0-Alpha7
OPTIONS
"CAPSTONE_X86_ATT_DISABLE ON"
"CAPSTONE_ALPHA_SUPPORT OFF"
@@ -137,12 +137,11 @@ target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.8-docking
GIT_TAG v1.92.7-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
)
set(IMGUI_SOURCES
@@ -218,9 +217,7 @@ CPMAddPackage(
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
OPTIONS
"BUILD_SHARED_LIBS OFF"
GIT_TAG release-0.5.2
EXCLUDE_FROM_ALL TRUE
)
@@ -257,7 +254,7 @@ if(NOT EMSCRIPTEN)
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.25.2
GIT_TAG v2.23.0
EXCLUDE_FROM_ALL TRUE
)
@@ -272,7 +269,7 @@ if(NOT EMSCRIPTEN)
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.16
GIT_TAG v1.15
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
@@ -290,7 +287,7 @@ if(NOT EMSCRIPTEN)
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_20_0
GIT_TAG curl-8_19_0
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"

View File

@@ -0,0 +1,49 @@
TRACY_PUBLIC := ../../public
NVCC := nvcc
CXX := g++
CUPTI_INC := /usr/local/cuda/include
CUPTI_LIB := /usr/local/cuda/lib64
TRACY_SRCS := $(TRACY_PUBLIC)/TracyClient.cpp
INCLUDES := -I$(TRACY_PUBLIC) -I$(CUPTI_INC)
LIBS := -L$(CUPTI_LIB) -lcuda -lcupti -lpthread -ldl
CXXFLAGS_REL := -O2 -DTRACY_ENABLE
CXXFLAGS_DBG := -g -O0 -DTRACY_ENABLE
NVCCFLAGS_REL := -arch=native -O2 -DTRACY_ENABLE
NVCCFLAGS_DBG := -arch=native -g -O0 -DTRACY_ENABLE
.PHONY: all debug investigate investigate2 clean
all: repro
debug: repro_debug
investigate: test_corr_reuse
investigate2: test_graphid_recycle
# Release build
repro: repro.cu tracy_client.o
$(NVCC) $(NVCCFLAGS_REL) $(INCLUDES) -o $@ $< tracy_client.o $(LIBS)
tracy_client.o: $(TRACY_SRCS)
$(CXX) $(CXXFLAGS_REL) $(INCLUDES) -c -o $@ $<
# Debug build (asserts enabled, no NDEBUG)
repro_debug: repro.cu tracy_client_debug.o
$(NVCC) $(NVCCFLAGS_DBG) $(INCLUDES) -o $@ $< tracy_client_debug.o $(LIBS)
tracy_client_debug.o: $(TRACY_SRCS)
$(CXX) $(CXXFLAGS_DBG) $(INCLUDES) -c -o $@ $<
# Investigation: correlationId uniqueness per graph launch (no Tracy dependency)
test_corr_reuse: test_corr_reuse.cu
$(NVCC) $(NVCCFLAGS_REL) $(INCLUDES) -o $@ $< $(LIBS)
# Investigation: does CUPTI recycle graphId values after cudaGraphExecDestroy?
test_graphid_recycle: test_graphid_recycle.cu
$(NVCC) $(NVCCFLAGS_REL) $(INCLUDES) -o $@ $< $(LIBS)
clean:
rm -f repro repro_debug test_corr_reuse test_graphid_recycle tracy_client.o tracy_client_debug.o

View File

@@ -14,9 +14,8 @@ drops every GPU zone.
## Build and run
```bash
cmake -S . -B ./build
cmake --build ./build --parallel --config Release
ctest --test-dir ./build -C Release -R repro
make
./repro
```
## What to expect

View File

@@ -1,57 +0,0 @@
// Template implementations of the tracy::Platform* hooks. Pair with the
// platform header (see CustomPlatform.h) and link this into your final
// binary.
#include <stdlib.h>
#include <string.h>
#include "CustomPlatform.h"
namespace tracy
{
uint32_t PlatformGetThreadId()
{
return 0;
}
void PlatformGetHostname( char* buf, size_t size )
{
const char* placeholder = "(?)";
if( size == 0 ) return;
const size_t n = strlen( placeholder );
const size_t copy = n < size - 1 ? n : size - 1;
memcpy( buf, placeholder, copy );
buf[copy] = '\0';
}
const char* PlatformGetUserLogin()
{
return "(?)";
}
const char* PlatformGetUserFullName()
{
return nullptr;
}
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size )
{
// Stub: report failure so Tracy skips the snapshot. Real impls use SEH
// on Win32, pipe(2) on POSIX, or an equivalent probe-and-copy primitive.
(void)dst; (void)src; (void)size;
return false;
}
// Stubs forward to the C runtime. Swap in the allocator you actually want.
void* PlatformMalloc( size_t size ) { return malloc( size ); }
void PlatformFree( void* ptr ) { free( ptr ); }
void* PlatformRealloc( void* ptr, size_t size ) { return realloc( ptr, size ); }
void PlatformAllocatorInit() {}
void PlatformAllocatorThreadInit() {}
void PlatformAllocatorFinalize() {}
void PlatformAllocatorThreadFinalize(){}
}

View File

@@ -1,73 +0,0 @@
// Template platform header for unsupported targets.
//
// Copy into your project, fill in the sections you need, and point Tracy at
// it via -DTRACY_PLATFORM_HEADER="\"my_platform.h\"". Provide the
// implementations in any TU linked into your final binary (see
// CustomPlatform.cpp).
//
// Use this only for the TRACY_HAS_CUSTOM_* hooks and matching Platform*
// declarations — don't set unrelated TRACY_* options here. Some are checked
// before this header is included, so the result would depend on which TU
// consulted them; set those at the build system level instead.
//
// For platform-specific features without a custom hook (call stacks,
// context switches, crash handling, system tracing, etc.), disable them at
// the build system level with the matching TRACY_NO_* macro.
#ifndef __MY_TRACY_PLATFORM_H__
#define __MY_TRACY_PLATFORM_H__
#include <stddef.h>
#include <stdint.h>
namespace tracy
{
// --- Thread id --------------------------------------------------------------
//
// Required if defaults in TracySystem.cpp do not matches your platform.
// Note pthread_self() is NOT suitable, it returns a library handle, not a kernel id.
//#define TRACY_HAS_CUSTOM_THREAD_ID
uint32_t PlatformGetThreadId();
// --- User info --------------------------------------------------------------
//
// Identifies the machine and user in the trace header. Return placeholder
// strings (e.g. "(?)") from any of these if your platform has no equivalent
// notion.
//#define TRACY_HAS_CUSTOM_USER_INFO
void PlatformGetHostname( char* buf, size_t size );
const char* PlatformGetUserLogin();
const char* PlatformGetUserFullName();
// --- Safe memory copy -------------------------------------------------------
//
// Tracy uses this to snapshot potentially-unmapped memory during sampling.
// Must not crash on unreadable input — return false instead. Plain memcpy()
// is NOT a valid implementation.
//#define TRACY_HAS_CUSTOM_SAFE_COPY
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size );
// --- Allocator --------------------------------------------------------------
//
// Replaces Tracy's internal allocator. Drop in the system allocator, an
// in-house one, or any third-party allocator you like. Malloc/Free/Realloc
// must be thread-safe; ThreadInit is an optional prime, not a precondition.
// Finalize must also tear down the calling thread's per-thread state, the
// way rpmalloc_finalize() does — Tracy does not call ThreadFinalize for the
// shutdown thread before Finalize.
//#define TRACY_HAS_CUSTOM_ALLOCATOR
void* PlatformMalloc( size_t size );
void PlatformFree( void* ptr );
void* PlatformRealloc( void* ptr, size_t size );
void PlatformAllocatorInit();
void PlatformAllocatorThreadInit();
void PlatformAllocatorFinalize();
void PlatformAllocatorThreadFinalize();
}
#endif

View File

@@ -1,39 +0,0 @@
cmake_minimum_required(VERSION 3.18)
project(CUDAGraphDemo LANGUAGES CXX CUDA)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CUDA_STANDARD 17)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
set(CMAKE_CUDA_ARCHITECTURES native)
endif()
set(TRACY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../.."
CACHE PATH "Root of the Tracy repository")
set(TRACY_PUBLIC "${TRACY_PATH}/public")
find_package(CUDAToolkit REQUIRED)
find_package(Threads REQUIRED)
# cuda-graph-demo.cu embeds Tracy via #include <TracyClient.cpp> (unity build),
# so no separate TracyClient library is needed — just expose the public headers.
add_executable(cuda-graph-demo cuda-graph-demo.cu)
target_include_directories(cuda-graph-demo PRIVATE ${TRACY_PUBLIC})
target_link_libraries(cuda-graph-demo PRIVATE
CUDA::cupti CUDA::cuda_driver Threads::Threads ${CMAKE_DL_LIBS})
# ctest-related integration below
# to run the binaries via ctest:
# ctest --test-dir <cmake-build-dir> -R <binary-name> -C <build-config>
enable_testing()
add_test(NAME cuda-graph-demo COMMAND cuda-graph-demo)
# On Windows, CUPTI's DLL must be on PATH at runtime.
if(WIN32)
set(_cupti_dir "$<TARGET_FILE_DIR:CUDA::cupti>")
set_target_properties(cuda-graph-demo PROPERTIES
VS_DEBUGGER_ENVIRONMENT "PATH=${_cupti_dir};$ENV{PATH}")
set_tests_properties(cuda-graph-demo PROPERTIES
ENVIRONMENT "PATH=${_cupti_dir};$ENV{PATH}")
endif()

View File

@@ -1,11 +0,0 @@
TRACY_PATH=<path-to-tracy>
CUDA_TOOLKIT_PATH=/usr/local/cuda
CUDA_CUPTI_PATH=${CUDA_TOOLKIT_PATH}/extras/CUPTI
# pass -v to nvcc for verbose build information
nvcc -O2 -std=c++17 cuda-graph-demo.cu \
-o cuda-graph-demo \
-I "${TRACY_PATH}/public" \
-I "${CUDA_CUPTI_PATH}/include" -I "${CUDA_TOOLKIT_PATH}/include" \
-L "${CUDA_CUPTI_PATH}/lib64" -L "${CUDA_TOOLKIT_PATH}/lib64" \
-lcupti -lcuda

View File

@@ -1,146 +0,0 @@
#include <cuda_runtime.h>
// WARN: for simplicity, we enable and "embed" the Tracy client directly into the code
#define TRACY_ENABLE
#include <TracyClient.cpp>
#include <tracy/Tracy.hpp>
#include <tracy/TracyCUDA.hpp>
#include <cstdio>
#include <cstdlib>
#include <vector>
#define CUDA_CHECK(call) \
do { \
cudaError_t err__ = (call); \
if (err__ != cudaSuccess) { \
std::fprintf(stderr, "CUDA error %s at %s:%d: %s\n", \
cudaGetErrorName(err__), __FILE__, __LINE__, \
cudaGetErrorString(err__)); \
std::exit(EXIT_FAILURE); \
} \
} while (0)
__global__ void saxpy(float a, const float* x, float* y, int n)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) y[i] = a * x[i] + y[i];
}
int main()
{
// CUPTI-backed Tracy context. Auto-captures all CUDA activity from the
// point StartProfiling() is called until StopProfiling(). The background
// collector thread flushes activity into Tracy; the explicit Collect()
// calls below just force a flush at known phase boundaries.
auto* cudaCtx = TracyCUDAContext();
{
constexpr char ctxName[] = "CUDA Graph Demo";
TracyCUDAContextName(cudaCtx, ctxName, sizeof(ctxName) - 1);
}
TracyCUDAStartProfiling(cudaCtx);
constexpr int N = 1 << 16; // small N => kernel is short => launch overhead dominates
constexpr int KERNELS_PER_GRAPH = 32; // chain length captured into the graph
constexpr int OUTER_ITERS = 2000; // how many times we replay the chain
// allocate device buffers
float *dX = nullptr, *dY = nullptr;
CUDA_CHECK(cudaMalloc(&dX, N * sizeof(float)));
CUDA_CHECK(cudaMalloc(&dY, N * sizeof(float)));
std::vector<float> hX(N, 1.0f);
CUDA_CHECK(cudaMemcpy(dX, hX.data(), N * sizeof(float), cudaMemcpyHostToDevice));
cudaStream_t stream = nullptr;
CUDA_CHECK(cudaStreamCreate(&stream));
const dim3 block(256);
const dim3 grid((N + block.x - 1) / block.x);
cudaEvent_t evStart, evStop;
CUDA_CHECK(cudaEventCreate(&evStart));
CUDA_CHECK(cudaEventCreate(&evStop));
// warm-up (so first-launch lazy-init and/or JIT doesn't bias the measurement)
saxpy<<<grid, block, 0, stream>>>(0.0f, dX, dY, N);
CUDA_CHECK(cudaStreamSynchronize(stream));
// baseline: launch each kernel directly on the stream
float msStream = 0.0f;
{
ZoneScopedN("stream-launches");
CUDA_CHECK(cudaMemsetAsync(dY, 0, N * sizeof(float), stream));
CUDA_CHECK(cudaEventRecord(evStart, stream));
for (int outer = 0; outer < OUTER_ITERS; ++outer) {
for (int k = 0; k < KERNELS_PER_GRAPH; ++k) {
saxpy<<<grid, block, 0, stream>>>(1.0e-6f, dX, dY, N);
}
}
CUDA_CHECK(cudaEventRecord(evStop, stream));
CUDA_CHECK(cudaEventSynchronize(evStop));
CUDA_CHECK(cudaEventElapsedTime(&msStream, evStart, evStop));
TracyCUDACollect(cudaCtx);
}
// capture: record the same kernel chain into a graph
cudaGraph_t graph = nullptr;
cudaGraphExec_t graphExec = nullptr;
{
ZoneScopedN("graph-capture");
// cudaStreamCaptureModeRelaxed allows the calling thread to perform
// unrelated CUDA work during capture; ThreadLocal is stricter if you need
// isolation. Most short, single-stream captures work fine in either mode.
CUDA_CHECK(cudaStreamBeginCapture(stream, cudaStreamCaptureModeRelaxed));
for (int k = 0; k < KERNELS_PER_GRAPH; ++k) {
saxpy<<<grid, block, 0, stream>>>(1.0e-6f, dX, dY, N);
}
CUDA_CHECK(cudaStreamEndCapture(stream, &graph));
// Instantiate once -> reusable executable graph.
CUDA_CHECK(cudaGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
// The template graph isn't needed once instantiated.
CUDA_CHECK(cudaGraphDestroy(graph));
}
// replay: launch the instantiated graph OUTER_ITERS times
float msGraph = 0.0f;
{
ZoneScopedN("graph-launches");
CUDA_CHECK(cudaMemsetAsync(dY, 0, N * sizeof(float), stream));
CUDA_CHECK(cudaEventRecord(evStart, stream));
for (int outer = 0; outer < OUTER_ITERS; ++outer) {
CUDA_CHECK(cudaGraphLaunch(graphExec, stream));
}
CUDA_CHECK(cudaEventRecord(evStop, stream));
CUDA_CHECK(cudaEventSynchronize(evStop));
CUDA_CHECK(cudaEventElapsedTime(&msGraph, evStart, evStop));
TracyCUDACollect(cudaCtx);
}
// sanity check: y[i] = OUTER_ITERS * KERNELS_PER_GRAPH * 1e-6 * x[i]
std::vector<float> hY(N);
CUDA_CHECK(cudaMemcpy(hY.data(), dY, N * sizeof(float), cudaMemcpyDeviceToHost));
const float expected = float(OUTER_ITERS) * float(KERNELS_PER_GRAPH) * 1.0e-6f;
std::printf("Stream launches: %8.3f ms (%d kernels)\n",
msStream, OUTER_ITERS * KERNELS_PER_GRAPH);
std::printf("Graph launches: %8.3f ms (%d graph launches x %d kernels)\n",
msGraph, OUTER_ITERS, KERNELS_PER_GRAPH);
std::printf("Speedup : %8.2fx\n", msStream / msGraph);
std::printf("hY[0] = %.6e (expected %.6e)\n", hY[0], expected);
// shutdown
CUDA_CHECK(cudaGraphExecDestroy(graphExec));
CUDA_CHECK(cudaEventDestroy(evStart));
CUDA_CHECK(cudaEventDestroy(evStop));
CUDA_CHECK(cudaStreamDestroy(stream));
CUDA_CHECK(cudaFree(dX));
CUDA_CHECK(cudaFree(dY));
TracyCUDAStopProfiling(cudaCtx);
TracyCUDAContextDestroy(cudaCtx);
return 0;
}

View File

@@ -1,63 +0,0 @@
cmake_minimum_required(VERSION 3.29)
project(dyna LANGUAGES C CXX)
option(TRACY_ENABLE "Enable Tracy" ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_COLOR_DIAGNOSTICS ON)
include(cmake/CPM.cmake)
CPMAddPackage(
NAME glad
VERSION 2.0.8
GIT_REPOSITORY https://github.com/Dav1dde/glad.git
GIT_TAG glad2
)
add_subdirectory(${glad_SOURCE_DIR}/cmake ${CMAKE_CURRENT_BINARY_DIR}/glad)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. client/)
glad_add_library(glad_gl_core_33 STATIC API gl:core=3.3)
find_package(SDL3 REQUIRED)
find_package(SDL3_image REQUIRED)
add_executable(dyna
src/main.cpp
src/datapath.cpp
src/timer.cpp
src/gfx.cpp
src/texture.cpp
src/entity.cpp
src/world.cpp
src/map.cpp
src/player.cpp
src/monster.cpp
src/bomb.cpp
src/bonus.cpp
src/game.cpp
)
target_link_libraries(dyna
PRIVATE
glad_gl_core_33
SDL3::SDL3
SDL3_image::SDL3_image
Tracy::TracyClient
)
target_include_directories(dyna PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
# Mirror the data/ tree next to the executable so the game finds its assets
# when launched from the build directory (paths are resolved via SDL_GetBasePath).
add_custom_command(TARGET dyna POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/data
$<TARGET_FILE_DIR:dyna>/data
COMMENT "Copying data/ next to dyna executable"
)
file(GENERATE OUTPUT .gitignore CONTENT "*")

View File

@@ -1,7 +0,0 @@
Dyna.net copyright 2005 by Bartosz Taudul and Ralf Wrześniewski.
This program (including source code and the asset it uses) is NOT licensed
for any use other than being an example of how to integrate Tracy Profiler.
The license terms written in other parts of this repository DO NOT apply
here.

View File

@@ -1,24 +0,0 @@
# SPDX-License-Identifier: MIT
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.42.3)
set(CPM_HASH_SUM "a609e875fd532b067174250f6abbc3dac22fe2d64869783fb1e80bda1625c844")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
)
include(${CPM_DOWNLOAD_LOCATION})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

View File

@@ -1,12 +0,0 @@
10 1 0 0
@............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -1,12 +0,0 @@
20 4 0 0
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#@#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -1,12 +0,0 @@
40 3 2 0
@............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -1,12 +0,0 @@
40 3 3 0
@............
.###.#.#.###.
.............
.#.#.#.#.#.#.
.............
.###.#.#.###.
.............
.#.#.#.#.#.#.
.............
.###.#.#.###.
.............

View File

@@ -1,12 +0,0 @@
40 2 4 1
@............
.###.#.#.###.
.#.........#.
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.#.........#.
.###.#.#.###.
.............

View File

@@ -1,12 +0,0 @@
50 2 2 3
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.....#.#.....
.#.###.###.#.
.............
.#.###.###.#.
.....#.#.....
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#@

View File

@@ -1,12 +0,0 @@
60 3 3 3
@............
.#.#.###.#.#.
.............
.###.#.#.###.
.............
.#.#.###.#.#.
.............
.###.#.#.###.
.............
.#.#.###.#.#.
.............

View File

@@ -1,12 +0,0 @@
60 5 3 3
@............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -1,12 +0,0 @@
90 5 5 5
@............
.#.........#.
.............
.............
.............
.............
.............
.............
.............
.#.........#.
.............

View File

@@ -1,12 +0,0 @@
30 4 4 4
@............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -1,143 +0,0 @@
#include "bomb.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
Bomb::Bomb( int x_, int y_ )
: x( x_ )
, y( y_ )
, left( 9 )
{
}
void Bomb::draw()
{
ZoneScoped;
if( stage == Stage::exploding )
return;
if( stage == Stage::appear )
{
Textures::bomb_appear.bind( 9 - left );
}
else
{
int frame = static_cast<int>( ( time - left ) / static_cast<float>( time ) * 8 );
if( Timer::get_timestamp() / 100 % 2 == 0 )
frame++;
Textures::bomb.bind( frame );
}
Gfx::draw_square( x, y );
}
void Bomb::tick( World& world )
{
ZoneScoped;
delta += Timer::delta;
while( delta > 10 )
{
delta -= 10;
if( stage == Stage::appear )
{
if( left > 0 )
{
delta -= 10; // the fade-in advances at double speed
left--;
}
else
{
stage = Stage::ticking;
left = time;
}
}
else if( left > 0 )
{
left--;
}
else if( stage == Stage::ticking )
{
explode( world );
}
else
{
die( world );
}
}
}
void Bomb::explode( World& world )
{
ZoneScoped;
stage = Stage::exploding;
left = 200;
Map& map = world.map();
map.at( x, y ) = Field::explosion( Field::ExplosionType::center );
struct Dir
{
int dx, dy;
Field::ExplosionType through, tip;
};
const Dir dirs[4] = {
{ -1, 0, Field::ExplosionType::horizontal, Field::ExplosionType::left },
{ 1, 0, Field::ExplosionType::horizontal, Field::ExplosionType::right },
{ 0, -1, Field::ExplosionType::vertical, Field::ExplosionType::up },
{ 0, 1, Field::ExplosionType::vertical, Field::ExplosionType::down },
};
for( const Dir& d : dirs )
{
for( int i = 1; i <= maxrange; i++ )
{
int tx = x + d.dx * i;
int ty = y + d.dy * i;
if( tx < 0 || tx > map.getx() - 1 || ty < 0 || ty > map.gety() - 1 )
break;
Destruction destr = map.at( tx, ty ).destructible();
if( destr == Destruction::none )
break;
etiles.emplace_back( tx, ty );
if( map.at( tx, ty ).kind == Field::Kind::crate )
world.crates_left--;
if( i == maxrange || destr == Destruction::single )
{
map.at( tx, ty ) = Field::explosion( d.tip );
break;
}
else
{
map.at( tx, ty ) = Field::explosion( d.through );
}
}
}
}
void Bomb::die( World& world )
{
ZoneScoped;
dead = true;
Map& map = world.map();
map.at( x, y ) = Field::floor();
for( const auto& [tx, ty] : etiles )
map.at( tx, ty ) = Field::floor();
}
}

View File

@@ -1,44 +0,0 @@
#pragma once
#include <utility>
#include <vector>
namespace dyna
{
class World;
// A bomb on the grid: fades in, counts down, then paints a cross-shaped
// explosion onto the map and clears it again. Ported from bomb.cs.
class Bomb
{
public:
Bomb( int x, int y );
void draw();
void tick( World& world );
bool is_dead() const { return dead; }
private:
void explode( World& world );
void die( World& world );
enum class Stage
{
appear,
ticking,
exploding
};
int x, y; // grid coordinates
Stage stage = Stage::appear;
int left;
int delta = 0;
static constexpr int time = 150;
static constexpr int maxrange = 1;
std::vector<std::pair<int, int>> etiles; // tiles to revert to floor
bool dead = false;
};
}

View File

@@ -1,58 +0,0 @@
#include "bonus.hpp"
#include "gfx.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
Vortex::Vortex( int gx, int gy )
{
x = gx; // stored in grid units, drawn via draw_square
y = gy;
set_action( Action::appear );
left = 79;
}
void Vortex::draw()
{
ZoneScoped;
int frame = static_cast<int>( ( Timer::get_timestamp() - action_start ) / 40 );
switch( action )
{
case Action::appear:
Textures::vortex_appear.bind( frame );
break;
case Action::wait:
Textures::vortex.bind( frame );
break;
default:
break;
}
Gfx::draw_square( x, y );
}
void Vortex::tick( World& )
{
ZoneScoped;
delta += Timer::delta;
while( delta > 10 )
{
delta -= 10;
if( left > 0 )
left--;
else if( action == Action::appear )
set_action( Action::wait );
}
}
void Vortex::die( World& ) {}
}

View File

@@ -1,20 +0,0 @@
#pragma once
#include "entity.hpp"
namespace dyna
{
// The level-exit portal. Unlike the other entities its coordinates are stored in
// grid units (it draws via draw_square), matching bonus.cs.
class Vortex : public Entity
{
public:
Vortex( int gx, int gy );
void draw() override;
void tick( World& world ) override;
void die( World& world ) override;
};
}

View File

@@ -1,25 +0,0 @@
#include "datapath.hpp"
#include <SDL3/SDL.h>
#include <tracy/Tracy.hpp>
namespace dyna
{
std::string data_path( const std::string& rel )
{
ZoneScoped;
ZoneText( rel.c_str(), rel.size() );
// SDL_GetBasePath returns the executable's directory (with a trailing
// separator) and is owned by SDL, so cache it for the program's lifetime.
static const std::string base = []
{
const char* p = SDL_GetBasePath();
return std::string( p ? p : "" );
}();
return base + rel;
}
}

View File

@@ -1,14 +0,0 @@
#pragma once
#include <string>
namespace dyna
{
// Resolve a path relative to the directory containing the executable, so the
// game finds its data files regardless of the current working directory (e.g.
// when launched from the build tree). The data/ tree is copied next to the
// binary at build time; see CMakeLists.txt.
std::string data_path( const std::string& rel );
}

View File

@@ -1,39 +0,0 @@
#include "entity.hpp"
#include "map.hpp"
#include "timer.hpp"
namespace dyna
{
void Entity::set_action( Action a )
{
action = a;
action_start = Timer::get_timestamp();
}
bool Entity::can_move( Action a, const Map& map ) const
{
switch( a )
{
case Action::up:
return y > 0 && !map.at( x / 64, y / 64 - 1 ).solid();
case Action::down:
return y / 64 < map.gety() - 1 && !map.at( x / 64, y / 64 + 1 ).solid();
case Action::left:
return x > 0 && !map.at( x / 64 - 1, y / 64 ).solid();
case Action::right:
return x / 64 < map.getx() - 1 && !map.at( x / 64 + 1, y / 64 ).solid();
default:
return true;
}
}
bool Entity::killed( const Map& map ) const
{
int tx = ( x + 32 ) / 64;
int ty = ( y + 32 ) / 64;
return map.at( tx, ty ).kind == Field::Kind::explosion;
}
}

View File

@@ -1,52 +0,0 @@
#pragma once
#include <cstdint>
namespace dyna
{
class Map;
class World;
// Movement/state verbs shared by the player and monsters. In the C# source this
// lived as Entity.Action; promoted to namespace scope so Game can refer to it.
enum class Action
{
wait,
up,
down,
left,
right,
death,
place_bomb,
appear
};
// Base for everything that moves on the grid. Coordinates are in pixels
// (64 per tile) and laid out top-left origin, matching entity.cs.
class Entity
{
public:
virtual ~Entity() = default;
virtual void set_action( Action a );
int getx() const { return x; }
int gety() const { return y; }
virtual void draw() = 0;
virtual void tick( World& world ) = 0;
virtual void die( World& world ) = 0;
protected:
bool can_move( Action a, const Map& map ) const;
virtual bool killed( const Map& map ) const;
int x = 0, y = 0;
std::int64_t action_start = 0;
int delta = 0;
Action action = Action::wait;
int left = 0;
};
}

View File

@@ -1,210 +0,0 @@
#include "game.hpp"
#include "datapath.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "player.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <SDL3/SDL.h>
#include <tracy/Tracy.hpp>
#include <string>
namespace dyna
{
namespace Game
{
namespace
{
struct TracySection
{
explicit TracySection( const char* name ) { Enter( name ); }
~TracySection() { Leave(); }
void Enter( const char* name )
{
idx = TracySectionEnter( "%s", name );
}
void Leave()
{
if( idx > 0 )
{
TracySectionLeave( idx );
idx = 0;
}
}
private:
uint32_t idx;
};
SDL_Keycode key = 0; // most recently pressed movement key
bool help = false;
// Run one level to completion. Returns true if the player asked to quit the
// whole application (window close), false if the level simply ended (death,
// escape, or reaching the exit) and control should return to the caller.
bool level_loop( World& world )
{
TracySection section( ( std::string( "Level " ) + world.name() ).c_str() );
Player* p = world.player();
for( ;; )
{
SDL_Event ev;
while( SDL_PollEvent( &ev ) )
{
if( ev.type == SDL_EVENT_QUIT )
return true;
if( ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat )
{
switch( ev.key.key )
{
case SDLK_ESCAPE:
world.killed = true;
return false;
case SDLK_LEFT:
key = SDLK_LEFT;
p->move( Action::left );
break;
case SDLK_RIGHT:
key = SDLK_RIGHT;
p->move( Action::right );
break;
case SDLK_UP:
key = SDLK_UP;
p->move( Action::up );
break;
case SDLK_DOWN:
key = SDLK_DOWN;
p->move( Action::down );
break;
case SDLK_SPACE:
world.map().place_bomb( ( p->getx() + 32 ) / 64, ( p->gety() + 32 ) / 64 );
break;
default:
break;
}
}
if( ev.type == SDL_EVENT_KEY_UP )
{
switch( ev.key.key )
{
case SDLK_LEFT:
if( key == SDLK_LEFT ) p->move( Action::wait );
break;
case SDLK_RIGHT:
if( key == SDLK_RIGHT ) p->move( Action::wait );
break;
case SDLK_UP:
if( key == SDLK_UP ) p->move( Action::wait );
break;
case SDLK_DOWN:
if( key == SDLK_DOWN ) p->move( Action::wait );
break;
default:
break;
}
}
}
Gfx::clear();
Timer::tick();
world.tick();
world.draw();
Gfx::swap();
if( world.killed || world.next_level )
return false;
}
}
// Play through the levels in order. Returns true if the application should quit.
bool new_game()
{
TracySection section( "In-game" );
int level = 1;
for( ;; )
{
World world( data_path( "data/levels/" + std::to_string( level ) ), true );
if( level_loop( world ) )
return true; // window closed
if( world.killed )
return false; // died or escaped to the menu
if( ++level >= 10 )
return false; // cleared the last level
}
}
} // namespace
void menu_loop()
{
constexpr const char* sectionName = "Main menu";
TracySection section( sectionName );
World world( data_path( "data/levels/menu" ), false );
for( ;; )
{
SDL_Event ev;
while( SDL_PollEvent( &ev ) )
{
if( ev.type == SDL_EVENT_QUIT )
return;
if( ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat )
{
switch( ev.key.key )
{
case SDLK_ESCAPE:
return;
case SDLK_SPACE:
section.Leave();
if( new_game() )
return; // window closed during play
section.Enter( sectionName );
break;
case SDLK_H:
help = !help;
break;
default:
break;
}
}
}
Gfx::clear();
Timer::tick();
world.tick();
world.draw();
if( help )
Gfx::show_help();
else
Gfx::show_menu();
Gfx::swap();
}
}
} // namespace Game
}

View File

@@ -1,14 +0,0 @@
#pragma once
namespace dyna
{
// Top-level game flow, ported from game.cs. The C# original kept the running
// game's state (player, map, win/lose flags) in static fields; that state now
// lives in a World object owned by the loops below, so nothing leaks out here.
namespace Game
{
void menu_loop();
}
}

View File

@@ -1,517 +0,0 @@
#include "gfx.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include <SDL3/SDL.h>
#include <tracy/Tracy.hpp>
#include <cassert>
#include <cstdio>
#include <vector>
namespace dyna
{
namespace
{
SDL_Window* g_window = nullptr;
SDL_GLContext g_gl_context = nullptr;
GLuint g_program = 0;
GLuint g_vao = 0;
GLuint g_vbo = 0;
// Current draw state, applied to every quad appended to the batch.
GLuint g_current_tex = 0;
int g_current_layer = 0;
float g_alpha = 1.0f;
// One vertex of the streaming batch: screen position, atlas-array texcoord,
// the array layer to sample and a per-vertex alpha multiplier.
struct GlVert
{
float px, py, tx, ty, layer, a;
};
// A run of consecutive vertices that share one texture, drawn in a single call.
struct DrawCmd
{
GLuint tex;
GLsizei count;
};
std::vector<GlVert> g_verts;
std::vector<DrawCmd> g_cmds;
const char* VERT_SRC = R"(
#version 330 core
uniform mat4 uProjection;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in float aLayer;
layout(location = 3) in float aAlpha;
out vec3 vTexCoord;
out float vAlpha;
void main() {
gl_Position = uProjection * vec4(aPosition, 0.0, 1.0);
vTexCoord = vec3(aTexCoord, aLayer);
vAlpha = aAlpha;
}
)";
const char* FRAG_SRC = R"(
#version 330 core
uniform sampler2DArray uTexture;
in vec3 vTexCoord;
in float vAlpha;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, vTexCoord) * vec4(1.0, 1.0, 1.0, vAlpha);
}
)";
GLuint compile_shader( GLenum type, const char* src )
{
ZoneScoped;
GLuint s = glCreateShader( type );
glShaderSource( s, 1, &src, nullptr );
glCompileShader( s );
GLint ok = 0;
glGetShaderiv( s, GL_COMPILE_STATUS, &ok );
if( !ok )
{
char log[512];
glGetShaderInfoLog( s, 512, nullptr, log );
std::fprintf( stderr, "Shader compile error: %s\n", log );
glDeleteShader( s );
return 0;
}
return s;
}
bool init_shaders()
{
ZoneScoped;
GLuint vs = compile_shader( GL_VERTEX_SHADER, VERT_SRC );
if( !vs ) return false;
GLuint fs = compile_shader( GL_FRAGMENT_SHADER, FRAG_SRC );
if( !fs )
{
glDeleteShader( vs );
return false;
}
g_program = glCreateProgram();
glAttachShader( g_program, vs );
glAttachShader( g_program, fs );
glLinkProgram( g_program );
glDeleteShader( vs );
glDeleteShader( fs );
GLint ok = 0;
glGetProgramiv( g_program, GL_LINK_STATUS, &ok );
if( !ok )
{
char log[512];
glGetProgramInfoLog( g_program, 512, nullptr, log );
std::fprintf( stderr, "Program link error: %s\n", log );
glDeleteProgram( g_program );
g_program = 0;
return false;
}
// Bottom-left origin orthographic projection, matching the original
// gluOrtho2D(0, w, 0, h) so the ported draw code carries over verbatim.
float l = 0.0f, r = static_cast<float>( Gfx::w );
float b = 0.0f, t = static_cast<float>( Gfx::h );
float proj[16] = {
2.0f / ( r - l ), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / ( t - b ), 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
-( r + l ) / ( r - l ), -( t + b ) / ( t - b ), 0.0f, 1.0f };
glUseProgram( g_program );
glUniformMatrix4fv( glGetUniformLocation( g_program, "uProjection" ), 1, GL_FALSE, proj );
glUniform1i( glGetUniformLocation( g_program, "uTexture" ), 0 );
glUseProgram( 0 );
return true;
}
void init_quad_vao()
{
ZoneScoped;
glGenVertexArrays( 1, &g_vao );
glGenBuffers( 1, &g_vbo );
glBindVertexArray( g_vao );
glBindBuffer( GL_ARRAY_BUFFER, g_vbo );
const GLsizei stride = sizeof( GlVert );
glEnableVertexAttribArray( 0 );
glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, stride, (void*)0 );
glEnableVertexAttribArray( 1 );
glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, stride, (void*)8 );
glEnableVertexAttribArray( 2 );
glVertexAttribPointer( 2, 1, GL_FLOAT, GL_FALSE, stride, (void*)16 );
glEnableVertexAttribArray( 3 );
glVertexAttribPointer( 3, 1, GL_FLOAT, GL_FALSE, stride, (void*)20 );
glBindVertexArray( 0 );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
}
// Draw and clear everything accumulated since the last flush, in submission
// order. Consecutive quads that share a texture collapse into one draw call.
void flush_batch()
{
ZoneScoped;
if( g_verts.empty() )
return;
glBindBuffer( GL_ARRAY_BUFFER, g_vbo );
glBufferData( GL_ARRAY_BUFFER,
static_cast<GLsizeiptr>( g_verts.size() * sizeof( GlVert ) ),
g_verts.data(), GL_STREAM_DRAW );
glUseProgram( g_program );
glBindVertexArray( g_vao );
GLint offset = 0;
for( const DrawCmd& cmd : g_cmds )
{
glBindTexture( GL_TEXTURE_2D_ARRAY, cmd.tex );
glDrawArrays( GL_TRIANGLES, offset, cmd.count );
offset += cmd.count;
}
glBindVertexArray( 0 );
glUseProgram( 0 );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
g_verts.clear();
g_cmds.clear();
}
// Frame image capture, following the OpenGL example in the Tracy manual. The
// backbuffer is downscaled on the GPU to a small fixed size and read back
// asynchronously, so a screenshot can be attached to every frame without
// stalling the CPU on the GPU. Several buffer sets are cycled because rendering
// runs a few frames ahead of the GPU.
// Half the render resolution, preserving its aspect ratio; both dimensions
// stay divisible by 4 as FrameImage requires.
constexpr int FI_W = Gfx::w / 2;
constexpr int FI_H = Gfx::h / 2;
constexpr int FI_COUNT = 4;
GLuint g_fi_texture[FI_COUNT];
GLuint g_fi_framebuffer[FI_COUNT];
GLuint g_fi_pbo[FI_COUNT];
GLsync g_fi_fence[FI_COUNT] = {};
int g_fi_idx = 0;
std::vector<int> g_fi_queue;
void init_frame_images()
{
ZoneScoped;
glGenTextures( FI_COUNT, g_fi_texture );
glGenFramebuffers( FI_COUNT, g_fi_framebuffer );
glGenBuffers( FI_COUNT, g_fi_pbo );
for( int i = 0; i < FI_COUNT; i++ )
{
glBindTexture( GL_TEXTURE_2D, g_fi_texture[i] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, FI_W, FI_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
glBindFramebuffer( GL_FRAMEBUFFER, g_fi_framebuffer[i] );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_fi_texture[i], 0 );
glBindBuffer( GL_PIXEL_PACK_BUFFER, g_fi_pbo[i] );
glBufferData( GL_PIXEL_PACK_BUFFER, FI_W * FI_H * 4, nullptr, GL_STREAM_READ );
}
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
glBindBuffer( GL_PIXEL_PACK_BUFFER, 0 );
}
void shutdown_frame_images()
{
ZoneScoped;
glDeleteTextures( FI_COUNT, g_fi_texture );
glDeleteFramebuffers( FI_COUNT, g_fi_framebuffer );
glDeleteBuffers( FI_COUNT, g_fi_pbo );
}
// Send any captures the GPU has already finished, then queue a capture of the
// frame just rendered. Call after the batch is flushed but before swapping.
void capture_frame_image()
{
ZoneScoped;
// Hand finished captures from earlier frames to the profiler. The queue
// size is the number of frames we are still ahead of the GPU, which is the
// frame lag Tracy needs as the FrameImage offset.
while( !g_fi_queue.empty() )
{
const int idx = g_fi_queue.front();
if( glClientWaitSync( g_fi_fence[idx], 0, 0 ) == GL_TIMEOUT_EXPIRED ) break;
glDeleteSync( g_fi_fence[idx] );
glBindBuffer( GL_PIXEL_PACK_BUFFER, g_fi_pbo[idx] );
void* ptr = glMapBufferRange( GL_PIXEL_PACK_BUFFER, 0, FI_W * FI_H * 4, GL_MAP_READ_BIT );
FrameImage( ptr, FI_W, FI_H, g_fi_queue.size(), true );
glUnmapBuffer( GL_PIXEL_PACK_BUFFER );
g_fi_queue.erase( g_fi_queue.begin() );
}
// Downscale the current backbuffer into the next buffer set and start an
// asynchronous read-back, signalled by a fence.
assert( g_fi_queue.empty() || g_fi_queue.front() != g_fi_idx ); // buffer overrun
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, g_fi_framebuffer[g_fi_idx] );
glBlitFramebuffer( 0, 0, Gfx::w, Gfx::h, 0, 0, FI_W, FI_H, GL_COLOR_BUFFER_BIT, GL_LINEAR );
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
glBindFramebuffer( GL_READ_FRAMEBUFFER, g_fi_framebuffer[g_fi_idx] );
glBindBuffer( GL_PIXEL_PACK_BUFFER, g_fi_pbo[g_fi_idx] );
glReadPixels( 0, 0, FI_W, FI_H, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
g_fi_fence[g_fi_idx] = glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 );
g_fi_queue.emplace_back( g_fi_idx );
g_fi_idx = ( g_fi_idx + 1 ) % FI_COUNT;
}
} // namespace
namespace Render
{
bool init()
{
ZoneScoped;
if( !init_shaders() ) return false;
init_quad_vao();
init_frame_images();
return true;
}
void shutdown()
{
ZoneScoped;
shutdown_frame_images();
if( g_vbo ) glDeleteBuffers( 1, &g_vbo );
if( g_vao ) glDeleteVertexArrays( 1, &g_vao );
if( g_program ) glDeleteProgram( g_program );
g_vbo = g_vao = g_program = 0;
}
void use_texture( GLuint tex, int layer )
{
g_current_tex = tex;
g_current_layer = layer;
}
GLuint make_texture( int w, int h, int layers, const void* rgba )
{
ZoneScoped;
GLuint tex = 0;
glGenTextures( 1, &tex );
glBindTexture( GL_TEXTURE_2D_ARRAY, tex );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glTexImage3D( GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba );
return tex;
}
} // namespace Render
namespace Gfx
{
void clear()
{
glClear( GL_COLOR_BUFFER_BIT );
}
void swap()
{
ZoneScoped;
flush_batch();
capture_frame_image();
SDL_GL_SwapWindow( g_window );
FrameMark;
}
void alpha( float a )
{
g_alpha = a;
}
void draw_quad( const Vertex corners[4] )
{
ZoneScoped;
// Two triangles, vertices appended in submission order so painter ordering
// (and the transient per-monster alpha) is preserved by the batch.
const int idx[6] = { 0, 1, 2, 0, 2, 3 };
for( int i : idx )
{
const Vertex& c = corners[i];
g_verts.push_back( { c.x, c.y, c.u, c.v,
static_cast<float>( g_current_layer ), g_alpha } );
}
if( !g_cmds.empty() && g_cmds.back().tex == g_current_tex )
g_cmds.back().count += 6;
else
g_cmds.push_back( { g_current_tex, 6 } );
}
void draw_sprite( int x, int y )
{
ZoneScoped;
float fx = static_cast<float>( x );
float fy = static_cast<float>( y );
float top = static_cast<float>( h ) - fy;
float bottom = static_cast<float>( h ) - ( fy + 64.0f );
Vertex corners[4] = {
{ fx, top, 0.0f, 0.0f },
{ fx + 64.0f, top, 1.0f, 0.0f },
{ fx + 64.0f, bottom, 1.0f, 1.0f },
{ fx, bottom, 0.0f, 1.0f },
};
draw_quad( corners );
}
void draw_square( int x, int y )
{
draw_sprite( x * 64, y * 64 );
}
void show_help()
{
ZoneScoped;
Textures::menu.bind();
const float fw = static_cast<float>( w );
const float fh = static_cast<float>( h );
Vertex bg[4] = {
{ 0.0f, fh, 0.0f, 0.0f },
{ fw, fh, 832.0f / 1024, 0.0f },
{ fw, 0.0f, 832.0f / 1024, 704.0f / 1024 },
{ 0.0f, 0.0f, 0.0f, 704.0f / 1024 },
};
draw_quad( bg );
int t = static_cast<int>( Timer::get_timestamp() / 40 );
Textures::p_r.bind( t );
draw_sprite( 150, 85 );
Textures::m1_r.bind( t );
draw_sprite( 75, 160 );
Textures::m2_r.bind( t );
draw_sprite( 150, 160 );
Textures::m3_r.bind( t );
draw_sprite( 225, 160 );
Textures::bomb.bind( static_cast<int>( Timer::get_timestamp() / 100 % 2 ) );
draw_sprite( 150, 235 );
Textures::wall.bind();
draw_sprite( 150, 310 );
Textures::crate.bind();
draw_sprite( 150, 385 );
Textures::vortex.bind( t );
draw_sprite( 150, 460 );
Textures::bonus1.bind( t );
draw_sprite( 112, 535 );
Textures::bonus2.bind( t );
draw_sprite( 187, 535 );
}
void show_menu()
{
ZoneScoped;
Textures::menu.bind();
Vertex logo[4] = {
{ float( ( w - 594 ) / 2 ), float( h - 50 ), 1.0f, 0.0f },
{ float( ( w + 594 ) / 2 ), float( h - 50 ), 1.0f, 594.0f / 1024 },
{ float( ( w + 594 ) / 2 ), float( h - 50 - 180 ), 1.0f - 180.0f / 1024, 594.0f / 1024 },
{ float( ( w - 594 ) / 2 ), float( h - 50 - 180 ), 1.0f - 180.0f / 1024, 0.0f },
};
draw_quad( logo );
Vertex prompt[4] = {
{ float( ( w - 527 ) / 2 ), 335.0f, 0.0f, 704.0f / 1024 },
{ float( ( w + 527 ) / 2 ), 335.0f, 527.0f / 1024, 704.0f / 1024 },
{ float( ( w + 527 ) / 2 ), 20.0f, 527.0f / 1024, 1019.0f / 1024 },
{ float( ( w - 527 ) / 2 ), 20.0f, 0.0f, 1019.0f / 1024 },
};
draw_quad( prompt );
}
} // namespace Gfx
namespace Init
{
bool all()
{
ZoneScoped;
if( !SDL_Init( SDL_INIT_VIDEO ) )
{
std::fprintf( stderr, "SDL_Init failed: %s\n", SDL_GetError() );
return false;
}
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
g_window = SDL_CreateWindow( "Dyna.net", Gfx::w, Gfx::h, SDL_WINDOW_OPENGL );
if( !g_window )
{
std::fprintf( stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError() );
return false;
}
g_gl_context = SDL_GL_CreateContext( g_window );
if( !g_gl_context )
{
std::fprintf( stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
return false;
}
int version = gladLoadGL( (GLADloadfunc)SDL_GL_GetProcAddress );
if( version == 0 )
{
std::fprintf( stderr, "gladLoadGL failed\n" );
return false;
}
SDL_GL_SetSwapInterval( 1 ); // vsync; the game is time-based so speed is unaffected
glViewport( 0, 0, Gfx::w, Gfx::h );
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
if( !Render::init() ) return false;
Timer::reset();
Textures::preload();
return true;
}
void shutdown()
{
ZoneScoped;
Render::shutdown();
if( g_gl_context ) SDL_GL_DestroyContext( g_gl_context );
if( g_window ) SDL_DestroyWindow( g_window );
SDL_Quit();
}
} // namespace Init
}

View File

@@ -1,59 +0,0 @@
#pragma once
#include <glad/gl.h>
namespace dyna
{
// Screen dimensions, matching the original 13x11 grid of 64px tiles.
namespace Gfx
{
constexpr int w = 832;
constexpr int h = 704;
void clear();
void swap();
// Drawing primitives ported from gfx.cs. They render with the currently
// bound texture (see Texture::bind) and the current alpha. The coordinate
// system is bottom-left origin with y growing upward, exactly as the C#
// gluOrtho2D setup; draw_sprite/draw_square take y measured from the top
// and flip internally, so game-side coordinates stay top-left based.
void alpha( float a );
void draw_sprite( int x, int y ); // pixel position of the top-left corner
void draw_square( int x, int y ); // grid position (multiplied by 64)
// A single textured quad given four explicit (position, texcoord) corners,
// used by the menu/help screens which sample rotated regions of the atlas.
struct Vertex
{
float x, y, u, v;
};
void draw_quad( const Vertex corners[4] );
void show_help();
void show_menu();
}
// Renderer back end shared by the texture loaders.
namespace Render
{
bool init(); // shaders + streaming VBO/VAO
void shutdown(); // delete the program and buffers
// Select the array texture (and layer within it) used by subsequent draws.
void use_texture( GLuint tex, int layer );
// Upload `layers` tightly packed RGBA8 images of size w*h as one
// GL_TEXTURE_2D_ARRAY and return its name (0 on failure).
GLuint make_texture( int w, int h, int layers, const void* rgba );
}
// One-time startup/shutdown, ported from the Init class in gfx.cs.
namespace Init
{
bool all(); // SDL, GL context, renderer, textures, timer
void shutdown();
}
}

View File

@@ -1,38 +0,0 @@
#include "game.hpp"
#include "gfx.hpp"
#include <SDL3/SDL_main.h>
#include <tracy/Tracy.hpp>
#include <cstdlib>
#include <new>
// Route every heap allocation through Tracy so the profiler can track memory
// usage. The default array forms (operator new[]/delete[]) and the nothrow
// forms forward to these, so overriding the scalar operators covers them too.
void* operator new( std::size_t count )
{
void* ptr = std::malloc( count );
if( !ptr ) throw std::bad_alloc();
TracyAlloc( ptr, count );
return ptr;
}
void operator delete( void* ptr ) noexcept
{
TracyFree( ptr );
std::free( ptr );
}
int main( int /*argc*/, char* /*argv*/[] )
{
TracyNoop;
if( !dyna::Init::all() )
return 1;
dyna::Game::menu_loop();
dyna::Init::shutdown();
return 0;
}

View File

@@ -1,334 +0,0 @@
#include "map.hpp"
#include "bomb.hpp"
#include "bonus.hpp"
#include "gfx.hpp"
#include "monster.hpp"
#include "player.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <tracy/Tracy.hpp>
namespace dyna
{
// ---- Field --------------------------------------------------------------
Field Field::explosion( ExplosionType t )
{
Field f;
f.kind = Kind::explosion;
f.etype = t;
f.tstart = Timer::get_timestamp();
return f;
}
bool Field::solid() const
{
switch( kind )
{
case Kind::wall:
case Kind::crate:
case Kind::bomb:
return true;
default:
return false;
}
}
Destruction Field::destructible() const
{
switch( kind )
{
case Kind::floor:
return Destruction::multi;
case Kind::crate:
return Destruction::single;
default:
return Destruction::none;
}
}
void Field::draw( int x, int y ) const
{
switch( kind )
{
case Kind::wall:
Textures::wall.bind();
Gfx::draw_square( x, y );
break;
case Kind::crate:
Textures::sand.bind();
Gfx::draw_square( x, y );
Textures::crate.bind();
Gfx::draw_square( x, y );
break;
case Kind::explosion: {
Textures::sand.bind();
Gfx::draw_square( x, y );
int frame = static_cast<int>( ( Timer::get_timestamp() - tstart ) / 40 % 8 );
if( frame > 4 ) frame = 8 - frame;
switch( etype )
{
case ExplosionType::center: Textures::e_c.bind( frame ); break;
case ExplosionType::vertical: Textures::e_v.bind( frame ); break;
case ExplosionType::horizontal: Textures::e_h.bind( frame ); break;
case ExplosionType::left: Textures::e_le.bind( frame ); break;
case ExplosionType::right: Textures::e_re.bind( frame ); break;
case ExplosionType::up: Textures::e_ue.bind( frame ); break;
case ExplosionType::down: Textures::e_de.bind( frame ); break;
}
Gfx::draw_square( x, y );
break;
}
// floor, bomb and vortex tiles all show plain sand; the bomb and vortex
// sprites themselves are drawn by their entities.
case Kind::floor:
case Kind::bomb:
case Kind::vortex:
default:
Textures::sand.bind();
Gfx::draw_square( x, y );
break;
}
}
// ---- Map ----------------------------------------------------------------
Map::Map( const std::string& fn )
{
ZoneScoped;
ZoneText( fn.c_str(), fn.size() );
load( fn );
generate_destructibles();
populate_map();
}
Map::~Map() = default;
void Map::load( const std::string& fn )
{
ZoneScoped;
std::ifstream f( fn );
if( !f )
{
std::fprintf( stderr, "Cannot open level %s\n", fn.c_str() );
grid.assign( X * Y, Field::floor() );
return;
}
std::stringstream buf;
buf << f.rdbuf();
std::string content = buf.str();
size_t nl = content.find( '\n' );
std::string header = ( nl == std::string::npos ) ? content : content.substr( 0, nl );
std::sscanf( header.c_str(), "%d %d %d %d", &destructibles, &m1, &m2, &m3 );
grid.assign( X * Y, Field::floor() );
px = -1;
size_t p = ( nl == std::string::npos ) ? content.size() : nl + 1;
for( int ry = 0; ry < Y; ry++ )
{
for( int rx = 0; rx < X; rx++ )
{
char c = ( p < content.size() ) ? content[p++] : '\0';
switch( c )
{
case '.':
at( rx, ry ) = Field::floor();
break;
case '#':
at( rx, ry ) = Field::wall();
break;
case '@':
at( rx, ry ) = Field::floor();
px = rx;
py = ry;
break;
case '\n':
rx--; // newlines don't consume a grid cell
break;
default:
break;
}
}
}
}
bool Map::monster_ok( int rx, int ry, int pxx, int pyy, int r ) const
{
const Field& f = at( rx, ry );
return f.is_floor_family() && f.kind != Field::Kind::crate &&
( std::abs( rx - pxx ) > r || std::abs( ry - pyy ) > r );
}
void Map::generate_destructibles()
{
ZoneScoped;
int i = destructibles;
while( i != 0 )
{
int rx = RNG::next( X );
int ry = RNG::next( Y );
if( monster_ok( rx, ry, px, py, 1 ) )
{
at( rx, ry ) = Field::crate();
i--;
}
}
}
void Map::populate_map()
{
ZoneScoped;
for( int type = 1; type <= 3; type++ )
{
int count = ( type == 1 ) ? m1 : ( type == 2 ) ? m2
: m3;
while( count != 0 )
{
int rx = RNG::next( X );
int ry = RNG::next( Y );
if( monster_ok( rx, ry, px, py, 2 ) )
{
monsters.push_back( std::make_unique<Monster>( type, rx, ry ) );
count--;
}
}
}
}
void Map::draw()
{
ZoneScoped;
for( int ry = 0; ry < Y; ry++ )
for( int rx = 0; rx < X; rx++ )
at( rx, ry ).draw( rx, ry );
for( auto& b : bombs ) b->draw();
for( auto& e : monsters ) e->draw();
for( auto& e : bonuses ) e->draw();
}
void Map::tick( World& world )
{
ZoneScoped;
// Bombs.
for( auto& b : bombs ) b->tick( world );
bombs.erase( std::remove_if( bombs.begin(), bombs.end(),
[]( const std::unique_ptr<Bomb>& b ) { return b->is_dead(); } ),
bombs.end() );
// Monsters: tick, then retire the dead and queue their respawn timers.
for( auto& e : monsters ) e->tick( world );
for( auto& e : monsters )
{
if( e->is_dead() )
{
int delay = ( e->type() == 1 ) ? 10000 : ( e->type() == 2 ) ? 20000
: 30000;
mwait.push_back( { e->type(), Timer::get_timestamp() + delay } );
}
}
monsters.erase( std::remove_if( monsters.begin(), monsters.end(),
[]( const std::unique_ptr<Monster>& e ) { return e->is_dead(); } ),
monsters.end() );
// The respawn and exit-portal placement below need the player's position;
// they only fire during gameplay (a monster died, or every crate is gone),
// never on the player-less menu screen.
Player* player = world.player();
// Respawn monsters whose wait has elapsed.
std::int64_t now = Timer::get_timestamp();
std::vector<MWait> still_waiting;
for( const MWait& m : mwait )
{
if( m.time < now && player )
{
int rx = 0, ry = 0;
bool ok = false;
while( !ok )
{
rx = RNG::next( X );
ry = RNG::next( Y );
if( monster_ok( rx, ry, player->getx() / 64, player->gety() / 64, 3 ) )
ok = true;
}
auto monster = std::make_unique<Monster>( m.type, rx, ry );
monster->set_action( Action::appear );
monsters.push_back( std::move( monster ) );
}
else
{
still_waiting.push_back( m );
}
}
mwait = std::move( still_waiting );
// Bonuses.
for( auto& e : bonuses ) e->tick( world );
// Once every crate is gone, open the exit portal somewhere clear.
if( world.crates_left == 0 && player )
{
world.crates_left--;
int rx = 0, ry = 0;
bool ok = false;
while( !ok )
{
rx = RNG::next( X );
ry = RNG::next( Y );
if( monster_ok( rx, ry, player->getx() / 64, player->gety() / 64, 4 ) )
ok = true;
}
at( rx, ry ) = Field::vortex();
bonuses.push_back( std::make_unique<Vortex>( rx, ry ) );
}
}
std::unique_ptr<Player> Map::create_player() const
{
return std::make_unique<Player>( px, py );
}
void Map::place_bomb( int x, int y )
{
Field& f = at( x, y );
if( f.is_floor_family() && f.kind != Field::Kind::bomb )
{
f = Field::bomb();
bombs.push_back( std::make_unique<Bomb>( x, y ) );
}
}
bool Map::monster_collide( int tx, int ty ) const
{
for( const auto& e : monsters )
{
if( ( e->getx() + 32 ) / 64 == ( tx + 32 ) / 64 &&
( e->gety() + 32 ) / 64 == ( ty + 32 ) / 64 )
return true;
}
return false;
}
}

View File

@@ -1,120 +0,0 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace dyna
{
class Player;
class Bomb;
class Monster;
class Vortex;
class World;
// How a tile reacts to an explosion sweeping through it.
enum class Destruction
{
none, // blocks the blast (wall, bomb, existing explosion, vortex)
single, // destroyed and stops the blast (crate)
multi // passable, blast continues (floor)
};
// A grid cell. The C# version used a small class hierarchy rooted at a Field
// interface; since the variants differ only in a couple of flags and how they
// draw, this collapses them into one value type tagged by Kind. Note that in
// the original everything except Wall derived from Floor, so the "is Floor"
// checks there map to "kind != Wall" here.
struct Field
{
enum class Kind
{
floor,
wall,
crate,
bomb, // tile occupied by a live bomb (solid, indestructible)
explosion, // transient blast tile
vortex // level exit portal
};
enum class ExplosionType
{
center,
vertical,
horizontal,
left,
right,
down,
up
};
Kind kind = Kind::floor;
ExplosionType etype = ExplosionType::center;
std::int64_t tstart = 0; // explosion animation start, set on creation
static Field floor() { return Field{}; }
static Field wall() { return Field{ Kind::wall, {}, 0 }; }
static Field crate() { return Field{ Kind::crate, {}, 0 }; }
static Field bomb() { return Field{ Kind::bomb, {}, 0 }; }
static Field vortex() { return Field{ Kind::vortex, {}, 0 }; }
static Field explosion( ExplosionType t );
bool solid() const;
Destruction destructible() const;
void draw( int x, int y ) const;
bool is_floor_family() const { return kind != Kind::wall; }
};
class Map
{
public:
explicit Map( const std::string& fn );
~Map(); // defined in map.cpp where the entity types are complete
Field& at( int x, int y ) { return grid[index( x, y )]; }
const Field& at( int x, int y ) const { return grid[index( x, y )]; }
void draw();
void tick( World& world );
int getx() const { return X; }
int gety() const { return Y; }
int get_crates() const { return destructibles; }
std::unique_ptr<Player> create_player() const;
void place_bomb( int x, int y );
bool monster_collide( int tx, int ty ) const;
private:
static constexpr int X = 13, Y = 11;
// Deferred monster respawn timer, mirroring Map.MWait.
struct MWait
{
int type; // 1, 2 or 3
std::int64_t time; // timestamp at which it respawns
};
static int index( int x, int y ) { return x * Y + y; }
void load( const std::string& fn );
void generate_destructibles();
void populate_map();
bool monster_ok( int rx, int ry, int px, int py, int r ) const;
std::vector<Field> grid;
int px = -10, py = -10;
int destructibles = 0;
int m1 = 0, m2 = 0, m3 = 0;
std::vector<std::unique_ptr<Bomb>> bombs;
std::vector<std::unique_ptr<Monster>> monsters;
std::vector<std::unique_ptr<Vortex>> bonuses;
std::vector<MWait> mwait;
};
}

View File

@@ -1,227 +0,0 @@
#include "monster.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
namespace
{
bool is_opposite( Action a, Action b )
{
return ( a == Action::up && b == Action::down ) ||
( a == Action::down && b == Action::up ) ||
( a == Action::left && b == Action::right ) ||
( a == Action::right && b == Action::left );
}
} // namespace
Monster::Monster( int type, int gx, int gy )
: mtype( type )
, t( type == 1 ? 14 : type == 2 ? 11
: 7 )
{
x = gx * 64;
y = gy * 64;
}
void Monster::set_action( Action a )
{
Entity::set_action( a );
if( action == Action::appear )
left = 200;
}
std::vector<Action> Monster::possible_dirs( const Map& map ) const
{
std::vector<Action> dirs;
if( x > 0 && !map.at( x / 64 - 1, y / 64 ).solid() )
dirs.push_back( Action::left );
if( x / 64 < map.getx() - 1 && !map.at( x / 64 + 1, y / 64 ).solid() )
dirs.push_back( Action::right );
if( y > 0 && !map.at( x / 64, y / 64 - 1 ).solid() )
dirs.push_back( Action::up );
if( y / 64 < map.gety() - 1 && !map.at( x / 64, y / 64 + 1 ).solid() )
dirs.push_back( Action::down );
return dirs;
}
bool Monster::straight( const std::vector<Action>& dirs )
{
return is_opposite( dirs[0], dirs[1] );
}
Action Monster::any_dir( const Map& map )
{
std::vector<Action> dirs = possible_dirs( map );
if( dirs.empty() )
return Action::wait;
return dirs[RNG::next( static_cast<int>( dirs.size() ) )];
}
Action Monster::rand_dir( const Map& map )
{
Action tmp = any_dir( map );
if( is_opposite( action, tmp ) )
tmp = any_dir( map );
return tmp;
}
void Monster::think( const Map& map )
{
ZoneScoped;
if( action == Action::wait || action == Action::appear )
{
set_action( rand_dir( map ) );
return;
}
std::vector<Action> dirs = possible_dirs( map );
if( dirs.size() == 2 && straight( dirs ) )
{
left = 64;
}
else
{
Action tmp = rand_dir( map );
if( tmp == action )
{
left = 64;
}
else
{
set_action( tmp );
if( tmp != Action::wait )
left = 64;
}
}
}
void Monster::tick( World& world )
{
ZoneScoped;
Map& map = world.map();
delta += Timer::delta;
while( delta > t )
{
delta -= t;
if( action == Action::wait )
{
think( map );
}
else if( left > 0 )
{
left--;
switch( action )
{
case Action::down: y++; break;
case Action::up: y--; break;
case Action::left: x--; break;
case Action::right: x++; break;
default: break;
}
}
else
{
if( action == Action::death )
die( world );
else
think( map );
}
if( action != Action::death && killed( map ) )
{
set_action( Action::death );
left = 790 / t;
}
}
}
void Monster::die( World& )
{
dead = true;
}
const AnimTexture& Monster::texture_for( Action a ) const
{
struct Set
{
const AnimTexture* wait;
const AnimTexture* up;
const AnimTexture* down;
const AnimTexture* left;
const AnimTexture* right;
const AnimTexture* death;
};
Set s;
if( mtype == 1 )
s = { &Textures::m1_d, &Textures::m1_u, &Textures::m1_d, &Textures::m1_l, &Textures::m1_r, &Textures::m1_death };
else if( mtype == 2 )
s = { &Textures::m2_d, &Textures::m2_u, &Textures::m2_d, &Textures::m2_l, &Textures::m2_r, &Textures::m2_death };
else
s = { &Textures::m3_d, &Textures::m3_u, &Textures::m3_d, &Textures::m3_l, &Textures::m3_r, &Textures::m3_death };
switch( a )
{
case Action::up: return *s.up;
case Action::down: return *s.down;
case Action::left: return *s.left;
case Action::right: return *s.right;
case Action::death: return *s.death;
case Action::wait:
case Action::appear:
default: return *s.wait; // wait/appear use the "down" sprite
}
}
void Monster::draw()
{
ZoneScoped;
// The original returns without drawing for unexpected actions; monsters only
// ever hold the actions handled by texture_for, so always draw.
generic_draw( texture_for( action ) );
}
void Monster::generic_draw( const AnimTexture& tex )
{
int frame;
if( action == Action::wait )
{
frame = 0;
}
else if( action == Action::appear )
{
frame = 0;
Gfx::alpha( static_cast<float>( 200 - left ) / 200.0f );
}
else
{
frame = static_cast<int>( ( Timer::get_timestamp() - action_start ) / 40 );
}
tex.bind( frame );
Gfx::draw_sprite( x, y );
if( action == Action::appear )
Gfx::alpha( 1.0f );
}
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include "entity.hpp"
#include <vector>
namespace dyna
{
class AnimTexture;
// The three monster variants from monster.cs differed only in speed, sprite set
// and respawn delay, so they fold into one class parameterised by `type` (1-3).
class Monster : public Entity
{
public:
Monster( int type, int gx, int gy );
void set_action( Action a ) override;
void tick( World& world ) override;
void draw() override;
void die( World& world ) override;
bool is_dead() const { return dead; }
int type() const { return mtype; }
private:
std::vector<Action> possible_dirs( const Map& map ) const;
static bool straight( const std::vector<Action>& dirs );
Action rand_dir( const Map& map );
Action any_dir( const Map& map ); // __rand_dir in the original
void think( const Map& map );
void generic_draw( const AnimTexture& tex );
const AnimTexture& texture_for( Action a ) const;
int mtype; // 1, 2 or 3
int t; // ms per movement sub-step (per-type speed)
bool dead = false;
};
}

View File

@@ -1,127 +0,0 @@
#include "player.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
Player::Player( int gx, int gy )
{
x = gx * 64;
y = gy * 64;
set_action( Action::wait );
queue = Action::wait;
}
void Player::tick( World& world )
{
ZoneScoped;
Map& map = world.map();
delta += Timer::delta;
while( delta > t )
{
delta -= t;
if( left > 0 )
{
left--;
switch( action )
{
case Action::down: y++; break;
case Action::up: y--; break;
case Action::left: x--; break;
case Action::right: x++; break;
case Action::place_bomb:
if( left == 0 )
map.place_bomb( x / 64, y / 64 );
break;
default:
break;
}
}
else
{
if( action == Action::death )
{
die( world );
return;
}
if( map.at( x / 64, y / 64 ).kind == Field::Kind::vortex )
{
world.next_level = true;
return;
}
if( !can_move( queue, map ) )
queue = Action::wait;
if( action != queue )
set_action( queue );
if( action != Action::wait )
left = 64;
if( action == Action::place_bomb )
left = 32;
}
if( action != Action::death && killed( map ) )
{
set_action( Action::death );
left = 1140 / t;
}
}
}
void Player::draw()
{
ZoneScoped;
const AnimTexture* tex = nullptr;
switch( action )
{
case Action::wait: tex = &Textures::p_wait; break;
case Action::up: tex = &Textures::p_u; break;
case Action::down: tex = &Textures::p_d; break;
case Action::left: tex = &Textures::p_l; break;
case Action::right: tex = &Textures::p_r; break;
case Action::death: tex = &Textures::p_death; break;
case Action::place_bomb: tex = &Textures::p_wait; break;
default:
return;
}
int frame = static_cast<int>( Timer::get_timestamp() - action_start );
frame /= ( action == Action::death ) ? 60 : 40;
tex->bind( frame );
Gfx::draw_sprite( x, y );
}
void Player::move( Action a )
{
queue = a;
}
void Player::die( World& world )
{
world.killed = true;
}
bool Player::killed( const Map& map ) const
{
if( Entity::killed( map ) )
return true;
if( map.monster_collide( x, y ) )
return true;
return false;
}
}

View File

@@ -1,27 +0,0 @@
#pragma once
#include "entity.hpp"
namespace dyna
{
class Player : public Entity
{
public:
Player( int gx, int gy );
void tick( World& world ) override;
void draw() override;
void die( World& world ) override;
void move( Action a ); // queues the next direction; applied between tiles
protected:
bool killed( const Map& map ) const override;
private:
static constexpr int t = 6; // ms per movement sub-step
Action queue = Action::wait;
};
}

View File

@@ -1,221 +0,0 @@
#include "texture.hpp"
#include "datapath.hpp"
#include "gfx.hpp"
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <tracy/Tracy.hpp>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
namespace dyna
{
void GlTexture::reset()
{
if( id_ )
{
// The texture globals outlive main(), so their destructors can run after
// the GL context is already gone (which frees its textures anyway). Only
// call into GL while a context is current; otherwise just drop the name.
if( SDL_GL_GetCurrentContext() )
glDeleteTextures( 1, &id_ );
id_ = 0;
}
}
namespace
{
struct SurfaceDeleter
{
void operator()( SDL_Surface* s ) const { SDL_DestroySurface( s ); }
};
using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
// Convert an arbitrary surface to tightly addressable RGBA8. Returns null on
// failure; the result owns its pixels.
SurfacePtr to_rgba( SDL_Surface* src )
{
ZoneScoped;
if( !src ) return nullptr;
return SurfacePtr{ SDL_ConvertSurface( src, SDL_PIXELFORMAT_RGBA32 ) };
}
} // namespace
bool Texture::load( const char* fn )
{
ZoneScoped;
ZoneText( fn, strlen( fn ) );
SurfacePtr image{ IMG_Load( fn ) };
if( !image )
{
std::fprintf( stderr, "Cannot open texture %s: %s\n", fn, SDL_GetError() );
return false;
}
SurfacePtr rgba = to_rgba( image.get() );
if( !rgba )
{
std::fprintf( stderr, "Cannot convert texture %s: %s\n", fn, SDL_GetError() );
return false;
}
// Pack the surface into a tight RGBA8 block, skipping any per-row padding.
const int w = rgba->w, h = rgba->h;
std::vector<std::uint8_t> packed( static_cast<size_t>( w ) * h * 4 );
const auto* pixels = static_cast<const std::uint8_t*>( rgba->pixels );
for( int row = 0; row < h; row++ )
{
std::memcpy( &packed[static_cast<size_t>( row ) * w * 4],
pixels + static_cast<size_t>( row ) * rgba->pitch,
static_cast<size_t>( w ) * 4 );
}
tex_ = GlTexture{ Render::make_texture( w, h, 1, packed.data() ) };
return static_cast<bool>( tex_ );
}
void Texture::bind() const
{
Render::use_texture( tex_.get(), 0 );
}
void AnimTexture::load( SDL_Surface* sheet, int tilex, int tiley, int n )
{
ZoneScoped;
SurfacePtr rgba = to_rgba( sheet );
if( !rgba )
{
std::fprintf( stderr, "Cannot convert sprite sheet: %s\n", SDL_GetError() );
return;
}
const auto* pixels = static_cast<const std::uint8_t*>( rgba->pixels );
const int pitch = rgba->pitch;
// Lay the n frames out back to back as the layers of an array texture.
constexpr int frame_bytes = 64 * 64 * 4;
std::vector<std::uint8_t> frames( static_cast<size_t>( n ) * frame_bytes );
for( int i = 0; i < n; i++ )
{
for( int fy = 0; fy < 64; fy++ )
{
int srcy = 64 * ( tiley + i ) + fy;
int srcx = 64 * tilex;
std::memcpy( &frames[static_cast<size_t>( i ) * frame_bytes + static_cast<size_t>( fy ) * 64 * 4],
pixels + static_cast<size_t>( srcy ) * pitch + static_cast<size_t>( srcx ) * 4,
static_cast<size_t>( 64 ) * 4 );
}
}
tex_ = GlTexture{ Render::make_texture( 64, 64, n, frames.data() ) };
frames_ = n;
}
void AnimTexture::bind( int frame ) const
{
if( frames_ <= 0 ) return;
int layer = frame % frames_;
if( layer < 0 ) layer += frames_;
Render::use_texture( tex_.get(), layer );
}
namespace Textures
{
Texture menu, sand, wall, crate;
AnimTexture p_wait, p_u, p_d, p_l, p_r, p_death;
AnimTexture bomb, bomb_appear, e_c, e_h, e_v, e_le, e_re, e_de, e_ue;
AnimTexture m1_death, m1_l, m1_r, m1_d, m1_u;
AnimTexture m2_death, m2_l, m2_r, m2_d, m2_u;
AnimTexture m3_death, m3_l, m3_r, m3_d, m3_u;
AnimTexture bonus1, bonus2;
AnimTexture vortex_appear, vortex;
void preload()
{
ZoneScoped;
menu.load( data_path( "data/gfx/menu.png" ).c_str() );
sand.load( data_path( "data/gfx/sand.png" ).c_str() );
wall.load( data_path( "data/gfx/wall.png" ).c_str() );
crate.load( data_path( "data/gfx/crate.png" ).c_str() );
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/Player.png" ).c_str() ) };
p_wait.load( img.get(), 0, 0, 20 );
p_d.load( img.get(), 1, 0, 20 );
p_u.load( img.get(), 2, 0, 20 );
p_l.load( img.get(), 3, 0, 20 );
p_r.load( img.get(), 4, 0, 20 );
p_death.load( img.get(), 5, 0, 20 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/Bomb.png" ).c_str() ) };
bomb.load( img.get(), 0, 0, 10 );
bomb_appear.load( img.get(), 5, 0, 10 );
e_c.load( img.get(), 1, 0, 5 );
e_h.load( img.get(), 2, 0, 5 );
e_v.load( img.get(), 1, 5, 5 );
e_le.load( img.get(), 3, 0, 5 );
e_re.load( img.get(), 2, 5, 5 );
e_de.load( img.get(), 4, 0, 5 );
e_ue.load( img.get(), 3, 5, 5 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/monster1.png" ).c_str() ) };
m1_death.load( img.get(), 0, 0, 20 );
m1_u.load( img.get(), 1, 0, 10 );
m1_l.load( img.get(), 2, 0, 10 );
m1_d.load( img.get(), 1, 10, 10 );
m1_r.load( img.get(), 2, 10, 10 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/monster2.png" ).c_str() ) };
m2_death.load( img.get(), 0, 0, 20 );
m2_d.load( img.get(), 1, 0, 20 );
m2_u.load( img.get(), 2, 0, 20 );
m2_l.load( img.get(), 3, 0, 20 );
m2_r.load( img.get(), 4, 0, 20 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/monster3.png" ).c_str() ) };
m3_death.load( img.get(), 0, 0, 20 );
m3_d.load( img.get(), 1, 0, 9 );
m3_u.load( img.get(), 2, 0, 9 );
m3_l.load( img.get(), 1, 10, 9 );
m3_r.load( img.get(), 2, 10, 9 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/bonusy.png" ).c_str() ) };
bonus1.load( img.get(), 0, 0, 20 );
bonus2.load( img.get(), 1, 0, 20 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/portal.png" ).c_str() ) };
vortex_appear.load( img.get(), 0, 0, 20 );
vortex.load( img.get(), 1, 0, 20 );
}
}
}
}

View File

@@ -1,91 +0,0 @@
#pragma once
#include <glad/gl.h>
struct SDL_Surface;
namespace dyna
{
// Move-only RAII owner of a GL texture name. Every texture in the game is a
// GL_TEXTURE_2D_ARRAY (static images use a single layer, animations use one
// layer per frame) so the renderer only ever has to deal with one sampler type.
class GlTexture
{
public:
GlTexture() = default;
explicit GlTexture( GLuint id ) noexcept : id_( id ) {}
~GlTexture() { reset(); }
GlTexture( GlTexture&& o ) noexcept : id_( o.id_ ) { o.id_ = 0; }
GlTexture& operator=( GlTexture&& o ) noexcept
{
if( this != &o )
{
reset();
id_ = o.id_;
o.id_ = 0;
}
return *this;
}
GlTexture( const GlTexture& ) = delete;
GlTexture& operator=( const GlTexture& ) = delete;
GLuint get() const { return id_; }
explicit operator bool() const { return id_ != 0; }
void reset(); // glDeleteTextures; safe on an empty handle
private:
GLuint id_ = 0;
};
// A single static texture loaded from a whole image file. Ported from
// texture.cs; binding just records the texture for the next draw call.
class Texture
{
public:
bool load( const char* fn );
void bind() const;
private:
GlTexture tex_;
};
// A vertical strip of 64x64 animation frames cut out of a sprite sheet, stored
// as the layers of one array texture. Mirrors AnimTexture in texture.cs.
class AnimTexture
{
public:
// Extract n frames from column `tilex`, starting at row `tiley`, where each
// coordinate is in 64px tile units. Mirrors AnimTexture.load in texture.cs.
void load( SDL_Surface* sheet, int tilex, int tiley, int n );
void bind( int frame ) const; // frame is taken modulo the frame count
private:
GlTexture tex_;
int frames_ = 0;
};
// All game textures, loaded once at startup. Mirrors the Textures class.
namespace Textures
{
extern Texture menu, sand, wall, crate;
extern AnimTexture p_wait, p_u, p_d, p_l, p_r, p_death;
extern AnimTexture bomb, bomb_appear, e_c, e_h, e_v, e_le, e_re, e_de, e_ue;
extern AnimTexture m1_death, m1_l, m1_r, m1_d, m1_u;
extern AnimTexture m2_death, m2_l, m2_r, m2_d, m2_u;
extern AnimTexture m3_death, m3_l, m3_r, m3_d, m3_u;
extern AnimTexture bonus1, bonus2;
extern AnimTexture vortex_appear, vortex;
void preload();
}
}

View File

@@ -1,51 +0,0 @@
#include "timer.hpp"
#include <SDL3/SDL.h>
#include <random>
namespace dyna
{
namespace Timer
{
int delta = 0;
static std::int64_t timestamp = 0;
void reset()
{
delta = 0;
timestamp = static_cast<std::int64_t>( SDL_GetTicks() );
}
int tick()
{
std::int64_t tmp = timestamp;
timestamp = static_cast<std::int64_t>( SDL_GetTicks() );
delta = static_cast<int>( timestamp - tmp );
return delta;
}
std::int64_t get_timestamp()
{
return timestamp;
}
}
namespace RNG
{
static std::mt19937& engine()
{
static std::mt19937 e{ std::random_device{}() };
return e;
}
int next( int n )
{
if( n <= 0 ) return 0;
std::uniform_int_distribution<int> dist( 0, n - 1 );
return dist( engine() );
}
}
}

View File

@@ -1,25 +0,0 @@
#pragma once
#include <cstdint>
namespace dyna
{
// Frame timing, ported from timer.cs. Timestamps are milliseconds since
// Timer::reset(); kept 64-bit so the modulo arithmetic the animation code
// relies on never overflows during a session.
namespace Timer
{
void reset();
int tick(); // advances the clock, returns delta in ms
std::int64_t get_timestamp();
extern int delta; // ms elapsed during the last tick()
}
// Thin wrapper over a single global PRNG, mirroring the C# RNG helper.
namespace RNG
{
int next( int n ); // uniform in [0, n)
}
}

View File

@@ -1,40 +0,0 @@
#include "world.hpp"
#include "map.hpp"
#include "player.hpp"
namespace dyna
{
World::World( const std::string& level_fn, bool with_player )
: map_( std::make_unique<Map>( level_fn ) )
, name_( level_fn.substr( level_fn.rfind( '/' ) + 1 ) )
{
if( with_player )
{
player_ = map_->create_player();
crates_left = map_->get_crates();
}
else
{
crates_left = -1; // the menu never opens an exit portal
}
}
World::~World() = default;
void World::tick()
{
map_->tick( *this );
if( player_ )
player_->tick( *this );
}
void World::draw()
{
map_->draw();
if( player_ )
player_->draw();
}
}

View File

@@ -1,46 +0,0 @@
#pragma once
#include <memory>
#include <string>
namespace dyna
{
class Map;
class Player;
// Owns the state for one running level: the map, the player (absent on the
// menu screen), and the flags the gameplay code used to reach through global
// variables. Passing a World& into the tick path replaces the old Game::p /
// Game::current_map / Game::killed globals, so there are no non-owning pointers
// to outlive the objects they point at.
class World
{
public:
// Loads `level_fn`; spawns a player from the map's '@' marker when
// with_player is set (gameplay) and leaves it null otherwise (menu).
World( const std::string& level_fn, bool with_player );
~World();
World( const World& ) = delete;
World& operator=( const World& ) = delete;
Map& map() { return *map_; }
const Map& map() const { return *map_; }
Player* player() { return player_.get(); } // null on the menu screen
const std::string& name() const { return name_; }
void tick();
void draw();
bool killed = false;
bool next_level = false;
int crates_left = 0;
private:
std::unique_ptr<Map> map_;
std::unique_ptr<Player> player_;
std::string name_;
};
}

View File

@@ -1,83 +0,0 @@
# CMakeLists.txt — OpenGL spinning triangle demo
#
# macOS:
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
# cmake --build build/ninja
#
# Linux (requires libsdl3-dev libgl1-mesa-dev):
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
# cmake --build build/ninja
#
# Windows:
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
# cmake --build build/ninja
cmake_minimum_required(VERSION 3.16)
project(gl_spinning_triangle LANGUAGES C CXX)
# ---------------------------------------------------------------------------
# Tracy root — defaults to three directories above this CMakeLists.txt.
# ---------------------------------------------------------------------------
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
option(TRACY_ENABLE "Enable Tracy profiling" ON)
# ---------------------------------------------------------------------------
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
# ---------------------------------------------------------------------------
find_package(SDL3 REQUIRED)
# ---------------------------------------------------------------------------
# GL extension loader — GLEW (Windows + Linux, fetched automatically)
# ---------------------------------------------------------------------------
if(NOT APPLE)
include(FetchContent)
set(glew-cmake_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(ONLY_LIBS ON CACHE BOOL "" FORCE)
FetchContent_Declare(glew
GIT_REPOSITORY https://github.com/Perlmint/glew-cmake.git
GIT_TAG master # pin to a specific commit for reproducible builds
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glew)
endif()
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
if(APPLE)
set(PLATFORM_LIBS SDL3::SDL3 "-framework OpenGL")
elseif(WIN32)
set(PLATFORM_LIBS SDL3::SDL3 opengl32 libglew_static)
else()
set(PLATFORM_LIBS SDL3::SDL3 GL libglew_static)
endif()
# ---------------------------------------------------------------------------
# Target
# ---------------------------------------------------------------------------
add_executable(gl_spinning_triangle
spinning_triangle.cpp
"${TRACY_DIR}/public/TracyClient.cpp"
${PLATFORM_SOURCES}
)
# Suppress upstream warnings from TracyClient.cpp
if(MSVC)
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "/w"
)
else()
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "-w"
)
endif()
target_compile_features(gl_spinning_triangle PRIVATE cxx_std_17)
if(TRACY_ENABLE)
target_compile_definitions(gl_spinning_triangle PRIVATE TRACY_ENABLE)
endif()
target_include_directories(gl_spinning_triangle PRIVATE
"${TRACY_DIR}/public"
)
target_link_libraries(gl_spinning_triangle PRIVATE ${PLATFORM_LIBS})

View File

@@ -1,37 +0,0 @@
// platform.h — interface between platform-agnostic code and platform backends
//
// Each platform_*.mm / platform_*.cpp file implements these four functions.
// Exactly one backend must be linked into the final binary.
#pragma once
#ifdef __APPLE__
# include <OpenGL/gl3.h>
#else
# include <GL/glew.h>
#endif
// Initialize the windowing system, create a window, and make an OpenGL 3.3
// Core Profile context current on the calling thread.
// Returns true on success.
bool platformInit(int width, int height, const char* title);
// Load OpenGL function pointers (no-op on macOS where the framework exports them directly).
// Must be called after platformInit() while the GL context is current.
// Returns true on success.
bool platformInitGL();
// Elapsed wall-clock time in seconds since platformInit().
double platformGetTime();
// Swap front and back buffers (present the rendered frame).
void platformSwapBuffers();
// Pixel scaling factor relative to the logical window size (1.0 on non-HiDPI displays).
// Must be called after platformInit().
void platformGetPixelDensityScale(float* x, float* y);
// Enter the platform event/render loop.
// Calls render() each frame at ~60 fps.
// Calls shutdown() exactly once before returning.
void platformRunLoop(void (*render)(), void (*shutdown)());

View File

@@ -1,85 +0,0 @@
// platform_sdl3.cpp — SDL3 windowing backend (cross-platform)
#include "platform.h" // GL headers first (gl3.h / glew.h) so SDL sees guards set
#define SDL_MAIN_HANDLED // we don't want SDL_main
#include <SDL3/SDL.h>
#include <chrono>
#include <cstdio>
static SDL_Window* sWin = nullptr;
static SDL_GLContext sCtx = nullptr;
static std::chrono::steady_clock::time_point sStartTime;
bool platformInit(int width, int height, const char* title) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
return false;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
sWin = SDL_CreateWindow(title, width, height, SDL_WINDOW_OPENGL);
if (!sWin) {
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
return false;
}
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
sCtx = SDL_GL_CreateContext(sWin);
if (!sCtx) {
fprintf(stderr, "ERROR: SDL_GL_CreateContext failed: %s\n", SDL_GetError());
SDL_DestroyWindow(sWin);
SDL_Quit();
return false;
}
SDL_GL_SetSwapInterval(1);
sStartTime = std::chrono::steady_clock::now();
return true;
}
bool platformInitGL() {
#ifndef __APPLE__
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
return false;
}
#endif
return true;
}
double platformGetTime() {
return std::chrono::duration<double>(
std::chrono::steady_clock::now() - sStartTime).count();
}
void platformSwapBuffers() { SDL_GL_SwapWindow(sWin); }
void platformGetPixelDensityScale(float* x, float* y) {
int pw, ph, ww, wh;
SDL_GetWindowSizeInPixels(sWin, &pw, &ph);
SDL_GetWindowSize(sWin, &ww, &wh);
*x = (ww > 0) ? (float)pw / (float)ww : 1.0f;
*y = (wh > 0) ? (float)ph / (float)wh : 1.0f;
}
void platformRunLoop(void (*render)(), void (*shutdown)()) {
bool running = true;
while (running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) running = false;
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
}
if (running) render();
}
shutdown();
SDL_GL_DestroyContext(sCtx);
SDL_DestroyWindow(sWin);
SDL_Quit();
}

View File

@@ -1,145 +0,0 @@
// spinning_triangle.cpp — OpenGL spinning triangle demo with Tracy GPU profiling.
#ifdef __APPLE__
// NOTE: OpenGL is only available on MacOS (no iOS support)
// Including and using anything related to OpenGL on Apple (like <OpenGL/gl3.h>)
// will emit deprecation warnings, unless GL_SILENCE_DEPRECATION is defined
#define GL_SILENCE_DEPRECATION
// NOTE: TracyOpenGL.hpp will not work as expected even on Apple devices that
// support OpenGL, because the OpenGL drivers do not implement ARB_timer_query
// properly (querying GL_TIMESTAMP always resolves to 0). TracyOpenGL.hpp will
// emit a compiler warning, and a Tracy message to the trace/profiler, but the
// program will still run.
#endif
#include "platform/platform.h" // also includes OpenGL headers
#include <tracy/Tracy.hpp>
// NOTE: opt-in toggle for periodic recalibrations during Collect()
#define TRACY_OPENGL_AUTO_CALIBRATION
#include <tracy/TracyOpenGL.hpp>
static const int kWidth = 800;
static const int kHeight = 600;
static GLuint gProgram = 0;
static GLuint gVao = 0;
static GLint gAngleLoc = -1;
// Vertex colors and positions are baked in; rotation is driven by a uniform.
static const char* kVertSrc = R"(
#version 150 core
uniform float uAngle;
const vec2 kPos[3] = vec2[3](
vec2( 0.0, 0.5 ),
vec2(-0.433, -0.25 ),
vec2( 0.433, -0.25 )
);
const vec3 kCol[3] = vec3[3](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
out vec3 vColor;
void main() {
float c = cos(uAngle);
float s = sin(uAngle);
vec2 p = kPos[gl_VertexID];
gl_Position = vec4(p.x*c - p.y*s, p.x*s + p.y*c, 0.0, 1.0);
vColor = kCol[gl_VertexID];
}
)";
static const char* kFragSrc = R"(
#version 150 core
in vec3 vColor;
out vec4 fragColor;
void main() { fragColor = vec4(vColor, 1.0); }
)";
static GLuint compileShader(GLenum type, const char* src) {
GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
fprintf(stderr, "Shader compile error: %s\n", log);
glDeleteShader(s);
return 0;
}
return s;
}
static int initGL() {
if (!platformInitGL()) return 1;
TracyGpuContext;
TracyGpuContextName("OpenGL", 6);
GLuint vert = compileShader(GL_VERTEX_SHADER, kVertSrc);
GLuint frag = compileShader(GL_FRAGMENT_SHADER, kFragSrc);
if (!vert || !frag) return 1;
gProgram = glCreateProgram();
glAttachShader(gProgram, vert);
glAttachShader(gProgram, frag);
glLinkProgram(gProgram);
glDeleteShader(vert);
glDeleteShader(frag);
GLint ok = 0;
glGetProgramiv(gProgram, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(gProgram, sizeof(log), nullptr, log);
fprintf(stderr, "Program link error: %s\n", log);
return 1;
}
gAngleLoc = glGetUniformLocation(gProgram, "uAngle");
// Core profile requires a bound VAO even with no vertex attributes.
glGenVertexArrays(1, &gVao);
glBindVertexArray(gVao);
glClearColor(0.05f, 0.05f, 0.08f, 1.0f);
float scaleX, scaleY;
platformGetPixelDensityScale(&scaleX, &scaleY);
glViewport(0, 0, (int)(kWidth * scaleX), (int)(kHeight * scaleY));
return 0;
}
static void renderFrame() {
ZoneScoped;
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(gProgram);
{
TracyGpuZone("triangle draw");
glUniform1f(gAngleLoc, (float)platformGetTime());
glDrawArrays(GL_TRIANGLES, 0, 3);
}
platformSwapBuffers();
TracyGpuCollect;
}
static void shutdown() {
fprintf(stderr, "application is shutting down...\n");
glDeleteVertexArrays(1, &gVao);
glDeleteProgram(gProgram);
}
int main() {
if (!platformInit(kWidth, kHeight, "OpenGL Spinning Triangle"))
return 1;
if (initGL() != 0)
return 2;
platformRunLoop(renderFrame, shutdown);
return 0;
}

View File

@@ -1,157 +0,0 @@
# CMakeLists.txt — WebGPU spinning triangle demo
#
# macOS:
# clang++ -std=c++17 -ObjC++ spinning_triangle.cpp platform/platform_macos.mm \
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
# -Wl,-rpath,@executable_path \
# -framework Cocoa -framework Metal -framework QuartzCore \
# -framework Foundation -framework IOKit -framework IOSurface \
# -o spinning_triangle
#
# Windows (MSVC):
# cl /std:c++17 spinning_triangle.cpp platform/platform_windows.cpp \
# /I\path\to\wgpu\include \path\to\wgpu\lib\wgpu_native.lib \
# user32.lib gdi32.lib /Fe:spinning_triangle.exe
#
# Linux (requires libsdl3-dev):
# g++ -std=c++17 spinning_triangle.cpp platform/platform_wayland.cpp \
# xdg-shell-protocol.c \
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
# -lwayland-client -o spinning_triangle
cmake_minimum_required(VERSION 3.16)
project(spinning_triangle LANGUAGES C CXX)
# ---------------------------------------------------------------------------
# WebGPU backend — set WGPU_PATH to your wgpu-native or Dawn installation.
# The library name differs between backends:
# wgpu-native → wgpu_native
# Dawn → webgpu_dawn
# ---------------------------------------------------------------------------
set(WGPU_PATH "" CACHE PATH "Root of the WebGPU native installation (contains include/ and lib/)")
set(WGPU_LIB "" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty")
if(NOT WGPU_PATH)
message(FATAL_ERROR "Set WGPU_PATH to the root of your WebGPU native installation.")
endif()
# When WGPU_PATH changes, discard any previously auto-detected WGPU_LIB so
# detection re-runs against the new path.
if(NOT "${WGPU_PATH}" STREQUAL "${_WGPU_PATH_LAST}")
unset(WGPU_LIB CACHE)
set(WGPU_LIB "" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty")
endif()
set(_WGPU_PATH_LAST "${WGPU_PATH}" CACHE INTERNAL "")
if(NOT WGPU_LIB)
unset(_WGPU_NATIVE_LIB CACHE)
unset(_WEBGPU_DAWN_LIB CACHE)
find_library(_WGPU_NATIVE_LIB NAMES wgpu_native wgpu_native.dll PATHS "${WGPU_PATH}/lib" NO_DEFAULT_PATH)
find_library(_WEBGPU_DAWN_LIB NAMES webgpu_dawn PATHS "${WGPU_PATH}/lib" NO_DEFAULT_PATH)
if(_WGPU_NATIVE_LIB)
set(WGPU_LIB "wgpu_native" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty" FORCE)
elseif(_WEBGPU_DAWN_LIB)
set(WGPU_LIB "webgpu_dawn" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty" FORCE)
else()
message(FATAL_ERROR "Could not detect a WebGPU library in ${WGPU_PATH}/lib. Set WGPU_LIB explicitly (wgpu_native or webgpu_dawn).")
endif()
message(STATUS "WebGPU library auto-detected: ${WGPU_LIB}")
endif()
# ---------------------------------------------------------------------------
# Tracy root — defaults to two directories above this CMakeLists.txt.
# ---------------------------------------------------------------------------
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
option(TRACY_ENABLE "Enable Tracy profiling" ON)
# ---------------------------------------------------------------------------
# macOS quarantine — pre-built WebGPU binaries downloaded from the internet
# carry a com.apple.quarantine extended attribute that prevents dyld from
# loading them ("damaged or incomplete" / Gatekeeper block). Strip it once
# at configure time so the linker and the runtime loader can both access the
# library directory without further user intervention.
# ---------------------------------------------------------------------------
if(APPLE)
execute_process(
COMMAND xattr -dr com.apple.quarantine "${WGPU_PATH}/lib"
)
endif()
# ---------------------------------------------------------------------------
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
# ---------------------------------------------------------------------------
find_package(SDL3 REQUIRED)
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
if(APPLE)
set(PLATFORM_LIBS
SDL3::SDL3
"-framework Cocoa"
"-framework Metal"
"-framework QuartzCore"
"-framework Foundation"
"-framework IOKit"
"-framework IOSurface"
)
elseif(WIN32)
# wgpu-native (Rust stdlib) pull-ins: NtReadFile, GetUserProfileDirectoryW, ...
set(WGPU_NATIVE_WIN32_LIBS ntdll userenv)
# Dawn pull-ins: WKPDID_D3DDebugObjectName GUID, CompareObjectHandles, ...
set(WEBGPU_DAWN_WIN32_LIBS dxguid onecore)
set(PLATFORM_LIBS SDL3::SDL3 ${WGPU_NATIVE_WIN32_LIBS} ${WEBGPU_DAWN_WIN32_LIBS})
else()
set(PLATFORM_LIBS SDL3::SDL3)
endif()
# ---------------------------------------------------------------------------
# Target
# ---------------------------------------------------------------------------
add_executable(spinning_triangle
spinning_triangle.cpp
"${TRACY_DIR}/public/TracyClient.cpp"
${PLATFORM_SOURCES}
)
# Treat TracyClient.cpp as third-party code — suppress all warnings so that
# upstream changes don't pollute our build output.
if(MSVC)
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "/w"
)
else()
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "-w"
)
endif()
target_compile_features(spinning_triangle PRIVATE cxx_std_17)
if(TRACY_ENABLE)
target_compile_definitions(spinning_triangle PRIVATE TRACY_ENABLE)
endif()
target_include_directories(spinning_triangle PRIVATE
"${WGPU_PATH}/include"
"${TRACY_DIR}/public"
)
target_link_directories(spinning_triangle PRIVATE "${WGPU_PATH}/lib")
target_link_libraries(spinning_triangle PRIVATE
${WGPU_LIB}
${PLATFORM_LIBS}
)
# Embed the rpath so the binary finds the WebGPU dylib/so next to itself.
if(APPLE)
set_target_properties(spinning_triangle PROPERTIES
BUILD_RPATH "${WGPU_PATH}/lib"
INSTALL_RPATH "@executable_path"
)
elseif(UNIX)
set_target_properties(spinning_triangle PROPERTIES
BUILD_RPATH "${WGPU_PATH}/lib"
INSTALL_RPATH "$ORIGIN"
)
endif()

View File

@@ -1,23 +0,0 @@
// platform.h — interface between platform-agnostic code and platform backends
//
// Each platform_*.mm / platform_*.cpp file implements these five functions.
// Exactly one backend must be linked into the final binary.
#pragma once
#include <webgpu/webgpu.h>
// Initialize the windowing system and create a window of the given dimensions.
// Returns true on success.
bool platformInit(int width, int height, const char* title);
// Create a WebGPU surface backed by the platform window.
// Must be called after wgpuCreateInstance() and platformInit().
WGPUSurface platformCreateSurface(WGPUInstance instance);
// Elapsed wall-clock time in seconds since platformInit().
double platformGetTime();
// Enter the platform event/render loop.
// Calls render() each frame at ~60 fps.
// Calls shutdown() exactly once before returning.
void platformRunLoop(void (*render)(), void (*shutdown)());

View File

@@ -1,95 +0,0 @@
// platform_sdl3.cpp — SDL3 windowing backend for the WebGPU example
#include "platform.h" // webgpu/webgpu.h first
#define SDL_MAIN_HANDLED // we don't want SDL_main
#include <SDL3/SDL.h>
#ifdef __APPLE__
# include <SDL3/SDL_metal.h>
#endif
#include <chrono>
#include <cstdio>
static SDL_Window* sWin = nullptr;
static std::chrono::steady_clock::time_point sStartTime;
#ifdef __APPLE__
static SDL_MetalView sMetalView = nullptr;
#endif
bool platformInit(int width, int height, const char* title) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
return false;
}
SDL_WindowFlags flags = 0;
#ifdef __APPLE__
flags |= SDL_WINDOW_METAL;
#endif
sWin = SDL_CreateWindow(title, width, height, flags);
if (!sWin) {
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
return false;
}
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
sStartTime = std::chrono::steady_clock::now();
return true;
}
WGPUSurface platformCreateSurface(WGPUInstance instance) {
WGPUSurfaceDescriptor desc = {};
SDL_PropertiesID props = SDL_GetWindowProperties(sWin);
#if defined(__APPLE__)
sMetalView = SDL_Metal_CreateView(sWin);
if (!sMetalView) {
fprintf(stderr, "ERROR: SDL_Metal_CreateView failed\n");
return nullptr;
}
WGPUSurfaceSourceMetalLayer metalDesc = {};
metalDesc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
metalDesc.layer = SDL_Metal_GetLayer(sMetalView);
desc.nextInChain = &metalDesc.chain;
#elif defined(_WIN32)
WGPUSurfaceSourceWindowsHWND hwndDesc = {};
hwndDesc.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
hwndDesc.hinstance = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, nullptr);
hwndDesc.hwnd = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
desc.nextInChain = &hwndDesc.chain;
#else // Linux / X11
WGPUSurfaceSourceXlibWindow x11Desc = {};
x11Desc.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
x11Desc.display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
x11Desc.window = (uint32_t)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
desc.nextInChain = &x11Desc.chain;
#endif
return wgpuInstanceCreateSurface(instance, &desc);
}
double platformGetTime() {
return std::chrono::duration<double>(
std::chrono::steady_clock::now() - sStartTime).count();
}
void platformRunLoop(void (*render)(), void (*shutdown)()) {
bool running = true;
while (running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) running = false;
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
}
if (running) render();
}
shutdown();
#ifdef __APPLE__
SDL_Metal_DestroyView(sMetalView);
#endif
SDL_DestroyWindow(sWin);
SDL_Quit();
}

View File

@@ -1,352 +0,0 @@
// spinning_triangle.cpp — platform-agnostic WebGPU spinning triangle demo.
#include "platform/platform.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <webgpu/webgpu.h>
#include <tracy/Tracy.hpp>
#include <tracy/TracyWebGPU.hpp>
// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------
static const int kWidth = 800;
static const int kHeight = 600;
static WGPUInstance gInstance = nullptr;
static WGPUSurface gSurface = nullptr;
static WGPUAdapter gAdapter = nullptr;
static WGPUDevice gDevice = nullptr;
static WGPUQueue gQueue = nullptr;
static WGPURenderPipeline gPipeline = nullptr;
static WGPUBuffer gUniformBuf = nullptr;
static WGPUBindGroup gBindGroup = nullptr;
static TracyWebGPUCtx gTracyCtx = nullptr;
static WGPUTextureFormat gSurfaceFormat = WGPUTextureFormat_BGRA8Unorm;
// TODO: this can become platformError() instead
int error(int code, const char* message) {
fprintf(stderr, "ERROR: %s (code: %d)\n", message, code);
return code;
}
// ---------------------------------------------------------------------------
// WGSL shader — vertex colours baked in, rotation via a uniform float.
// ---------------------------------------------------------------------------
static const char* kShaderSource = R"(
struct Uniforms {
angle: f32,
};
@group(0) @binding(0) var<uniform> u: Uniforms;
struct VSOut {
@builtin(position) pos: vec4f,
@location(0) color: vec3f,
};
@vertex
fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {
var positions = array<vec2f, 3>(
vec2f( 0.0, 0.5),
vec2f(-0.433, -0.25),
vec2f( 0.433, -0.25),
);
var colors = array<vec3f, 3>(
vec3f(1.0, 0.0, 0.0),
vec3f(0.0, 1.0, 0.0),
vec3f(0.0, 0.0, 1.0),
);
let c = cos(u.angle);
let s = sin(u.angle);
let p = positions[vi];
let rotated = vec2f(p.x * c - p.y * s, p.x * s + p.y * c);
var out: VSOut;
out.pos = vec4f(rotated, 0.0, 1.0);
out.color = colors[vi];
return out;
}
@fragment
fn fs_main(@location(0) color: vec3f) -> @location(0) vec4f {
return vec4f(color, 1.0);
}
)";
// ---------------------------------------------------------------------------
// Adapter / Device request callbacks (current wgpu-native API)
// ---------------------------------------------------------------------------
static void onAdapterReady(WGPURequestAdapterStatus status,
WGPUAdapter adapter,
WGPUStringView message,
void* userdata1, void* /*userdata2*/) {
if (status == WGPURequestAdapterStatus_Success) {
*(WGPUAdapter*)userdata1 = adapter;
} else {
fprintf(stderr, "Adapter request failed: %.*s\n",
(int)message.length, message.data);
}
}
static void onDeviceReady(WGPURequestDeviceStatus status,
WGPUDevice device,
WGPUStringView message,
void* userdata1, void* /*userdata2*/) {
if (status == WGPURequestDeviceStatus_Success) {
*(WGPUDevice*)userdata1 = device;
} else {
fprintf(stderr, "Device request failed: %.*s\n",
(int)message.length, message.data);
}
}
// ---------------------------------------------------------------------------
// WebGPU init
// ---------------------------------------------------------------------------
static int initWebGPU() {
// Adapter
WGPURequestAdapterOptions adapterOpts = {};
adapterOpts.compatibleSurface = gSurface;
WGPURequestAdapterCallbackInfo adapterCB = {};
adapterCB.mode = WGPUCallbackMode_AllowProcessEvents;
adapterCB.callback = onAdapterReady;
adapterCB.userdata1 = &gAdapter;
wgpuInstanceRequestAdapter(gInstance, &adapterOpts, adapterCB);
while (!gAdapter) { wgpuInstanceProcessEvents(gInstance); }
if (!gAdapter) return error(11, "No adapter");
WGPUUncapturedErrorCallbackInfo errorCB = {};
errorCB.callback = [](WGPUDevice const*, WGPUErrorType type,
WGPUStringView message, void*, void*) {
fprintf(stderr, "[WGPU ERROR] type=%d %.*s\n",
(int)type, (int)message.length, message.data);
};
WGPUDeviceDescriptor deviceDesc = {};
deviceDesc.uncapturedErrorCallbackInfo = errorCB;
TracyWebGPUSetupDeviceDescriptor(deviceDesc);
WGPURequestDeviceCallbackInfo deviceCB = {};
deviceCB.mode = WGPUCallbackMode_AllowProcessEvents;
deviceCB.callback = onDeviceReady;
deviceCB.userdata1 = &gDevice;
wgpuAdapterRequestDevice(gAdapter, &deviceDesc, deviceCB);
while (!gDevice) { wgpuInstanceProcessEvents(gInstance); }
if (!gDevice) return error(12, "No device");
gQueue = wgpuDeviceGetQueue(gDevice);
gTracyCtx = TracyWebGPUContext(gInstance, gDevice, gQueue);
TracyWebGPUContextName(gTracyCtx, "WebGPU", 6);
// Configure surface
WGPUSurfaceConfiguration config = {};
config.device = gDevice;
config.format = gSurfaceFormat;
config.usage = WGPUTextureUsage_RenderAttachment;
config.alphaMode = WGPUCompositeAlphaMode_Opaque;
config.width = kWidth;
config.height = kHeight;
config.presentMode = WGPUPresentMode_Fifo;
wgpuSurfaceConfigure(gSurface, &config);
// Shader module
WGPUShaderSourceWGSL wgslSrc = {};
wgslSrc.chain.sType = WGPUSType_ShaderSourceWGSL;
wgslSrc.code = { kShaderSource, WGPU_STRLEN };
WGPUShaderModuleDescriptor smDesc = {};
smDesc.nextInChain = (WGPUChainedStruct*)&wgslSrc;
WGPUShaderModule shaderMod = wgpuDeviceCreateShaderModule(gDevice, &smDesc);
// Uniform buffer (one f32 for rotation angle)
WGPUBufferDescriptor bufDesc = {};
bufDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
bufDesc.size = sizeof(float);
gUniformBuf = wgpuDeviceCreateBuffer(gDevice, &bufDesc);
// Bind group layout + bind group
WGPUBindGroupLayoutEntry bglEntry = {};
bglEntry.binding = 0;
bglEntry.visibility = WGPUShaderStage_Vertex;
bglEntry.buffer.type = WGPUBufferBindingType_Uniform;
bglEntry.buffer.minBindingSize = sizeof(float);
WGPUBindGroupLayoutDescriptor bglDesc = {};
bglDesc.entryCount = 1;
bglDesc.entries = &bglEntry;
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(gDevice, &bglDesc);
WGPUBindGroupEntry bgEntry = {};
bgEntry.binding = 0;
bgEntry.buffer = gUniformBuf;
bgEntry.size = sizeof(float);
WGPUBindGroupDescriptor bgDesc = {};
bgDesc.layout = bgl;
bgDesc.entryCount = 1;
bgDesc.entries = &bgEntry;
gBindGroup = wgpuDeviceCreateBindGroup(gDevice, &bgDesc);
// Pipeline layout
WGPUPipelineLayoutDescriptor plDesc = {};
plDesc.bindGroupLayoutCount = 1;
plDesc.bindGroupLayouts = &bgl;
WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(gDevice, &plDesc);
// Render pipeline
WGPUColorTargetState colorTarget = {};
colorTarget.format = gSurfaceFormat;
colorTarget.writeMask = WGPUColorWriteMask_All;
WGPUFragmentState fragState = {};
fragState.module = shaderMod;
fragState.entryPoint = { "fs_main", WGPU_STRLEN };
fragState.targetCount = 1;
fragState.targets = &colorTarget;
WGPURenderPipelineDescriptor rpDesc = {};
rpDesc.layout = pipelineLayout;
rpDesc.vertex.module = shaderMod;
rpDesc.vertex.entryPoint = { "vs_main", WGPU_STRLEN };
rpDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
rpDesc.multisample.count = 1;
rpDesc.multisample.mask = 0xFFFFFFFF;
rpDesc.fragment = &fragState;
gPipeline = wgpuDeviceCreateRenderPipeline(gDevice, &rpDesc);
// Cleanup intermediates
wgpuShaderModuleRelease(shaderMod);
wgpuPipelineLayoutRelease(pipelineLayout);
wgpuBindGroupLayoutRelease(bgl);
return 0;
}
// ---------------------------------------------------------------------------
// Frame rendering
// ---------------------------------------------------------------------------
// Returns the surface texture for the current frame, or {.texture=nullptr} on
// a skippable condition (timeout, occlusion) or an error.
static WGPUSurfaceTexture getWindowSurface() {
WGPUSurfaceTexture surfTex = {};
wgpuSurfaceGetCurrentTexture(gSurface, &surfTex);
if (surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal ||
surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal)
return surfTex;
// Timeout and Occluded are normal OS events (window covered / on a different Space).
bool silent = surfTex.status == WGPUSurfaceGetCurrentTextureStatus_Timeout;
#ifdef WGPU_H_
silent = silent || surfTex.status == (WGPUSurfaceGetCurrentTextureStatus)WGPUSurfaceGetCurrentTextureStatus_Occluded;
#endif
if (!silent)
fprintf(stderr, "Failed to get surface texture (status %d)\n", surfTex.status);
if (surfTex.texture) wgpuTextureRelease(surfTex.texture);
surfTex.texture = nullptr;
return surfTex;
}
static void renderFrame() {
ZoneScoped;
// Update rotation angle
float angle = (float)platformGetTime();
wgpuQueueWriteBuffer(gQueue, gUniformBuf, 0, &angle, sizeof(float));
WGPUSurfaceTexture surfTex = getWindowSurface();
if (!surfTex.texture) return;
WGPUTextureView view = wgpuTextureCreateView(surfTex.texture, nullptr);
// Command encoder
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(gDevice, nullptr);
// Render pass
WGPURenderPassColorAttachment colorAtt = {};
colorAtt.view = view;
colorAtt.loadOp = WGPULoadOp_Clear;
colorAtt.storeOp = WGPUStoreOp_Store;
colorAtt.clearValue = { 0.05, 0.05, 0.08, 1.0 };
colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
WGPURenderPassDescriptor passDesc = {};
passDesc.colorAttachmentCount = 1;
passDesc.colorAttachments = &colorAtt;
{
ZoneScopedN("render-pass");
TracyWebGPUNamedZone(gTracyCtx, tracyZone, encoder, passDesc, "triangle draw", true);
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc);
wgpuRenderPassEncoderSetPipeline(pass, gPipeline);
wgpuRenderPassEncoderSetBindGroup(pass, 0, gBindGroup, 0, nullptr);
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
}
// Submit
WGPUCommandBuffer cmdBuf = wgpuCommandEncoderFinish(encoder, nullptr);
wgpuQueueSubmit(gQueue, 1, &cmdBuf);
// Present
wgpuSurfacePresent(gSurface);
// Process Events
wgpuInstanceProcessEvents(gInstance);
TracyWebGPUCollect(gTracyCtx);
// Cleanup
wgpuCommandBufferRelease(cmdBuf);
wgpuCommandEncoderRelease(encoder);
wgpuTextureViewRelease(view);
wgpuTextureRelease(surfTex.texture);
}
// ---------------------------------------------------------------------------
// Shutdown
// ---------------------------------------------------------------------------
static void shutdown() {
fprintf(stderr, "application is shutting down...\n");
TracyWebGPUDestroy(gTracyCtx);
if (gBindGroup) wgpuBindGroupRelease(gBindGroup);
if (gUniformBuf) wgpuBufferRelease(gUniformBuf);
if (gPipeline) wgpuRenderPipelineRelease(gPipeline);
if (gQueue) wgpuQueueRelease(gQueue);
if (gDevice) wgpuDeviceRelease(gDevice);
if (gAdapter) wgpuAdapterRelease(gAdapter);
if (gSurface) wgpuSurfaceRelease(gSurface);
if (gInstance) wgpuInstanceRelease(gInstance);
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[]) {
if (!platformInit(kWidth, kHeight, "WebGPU Spinning Triangle"))
return 1;
gInstance = wgpuCreateInstance(nullptr);
if (!gInstance) return error(2, "Failed to create WebGPU instance.");
gSurface = platformCreateSurface(gInstance);
if (!gSurface) return error(3, "Failed to create surface.");
if (initWebGPU() != 0) return 4;
platformRunLoop(renderFrame, shutdown);
return 0;
}

View File

@@ -1,4 +1,4 @@
// g++ identify.cpp -lpthread ../public/common/tracy_lz4.cpp -lzstd
// g++ identify.cpp -lpthread ../public/common/tracy_lz4.cpp ../zstd/common/*.c ../zstd/decompress/*.c ../zstd/decompress/huf_decompress_amd64.S
#include <memory>
#include <stdint.h>

26
extra/make-build.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/sh
rm -rf tracy-build
mkdir tracy-build
./update-meson-version.sh
if [ ! -f vswhere.exe ]; then
wget https://github.com/microsoft/vswhere/releases/download/2.8.4/vswhere.exe
fi
MSVC=`./vswhere.exe -property installationPath -version '[17.0,17.999]' | head -n 1`
MSVC=`wslpath "$MSVC" | tr -d '\r'`
MSBUILD=$MSVC/MSBuild/Current/Bin/MSBuild.exe
for i in capture csvexport import-chrome update; do
echo $i...
"$MSBUILD" ../$i/build/win32/$i.sln /t:Clean /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
"$MSBUILD" ../$i/build/win32/$i.sln /t:Build /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
cp ../$i/build/win32/x64/Release/$i.exe tracy-build/
done
echo profiler...
"$MSBUILD" ../profiler/build/win32/Tracy.sln /t:Clean /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
"$MSBUILD" ../profiler/build/win32/Tracy.sln /t:Build /p:Configuration=Release /p:Platform=x64 /noconsolelogger /nologo -m
cp ../profiler/build/win32/x64/Release/Tracy.exe tracy-build/

View File

@@ -1,7 +1,7 @@
# Tracy MCP eval guide
This document covers the bindings-layer detail that the analysis
guidance (`tracy://prompt`) does not.
This document covers the bindings-layer detail that the curated catalog
(`tracy://catalog`) and analysis guidance (`tracy://prompt`) do not.
## ctx
@@ -21,9 +21,6 @@ data surface. Common entry points:
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
- Sections: `get_sections()` — timed code sections from
`TracySectionEnter`/`TracySectionLeave` instrumentation. Returns a list of
`{start, end, text}` dicts (start/end in ns).
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
@@ -44,23 +41,24 @@ Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
- Source-location IDs from `get_all_zone_source_locations()` are the join key
between zone-name lookups and per-callsite queries.
## Common query patterns
## Translating catalog entries to ctx Python
Small Python snippets for the queries you'll reach for most often:
The catalog (`tracy://catalog`) lists curated queries. Each maps to a small
Python snippet:
```python
# top 10 hottest zones by total time
# zone_list — top 10 hottest zones by total time
top = sorted(ctx.get_all_zone_stats().items(),
key=lambda kv: kv[1].total, reverse=True)[:10]
for k, v in top:
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
# primary frame set timing
# frame_list — primary frame set timing
times = ctx.get_frame_times() # ns per frame
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
# stats for a named zone — find the srcloc id, then drill in
# zone_stats for a named zone — find the srcloc id, then drill in
import re
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))

View File

@@ -30,7 +30,6 @@ _HERE = os.path.dirname(os.path.abspath(__file__))
_PORT_FILE = os.path.join(_HERE, "tracy_mcp.port")
_PID_FILE = os.path.join(_HERE, "tracy_mcp.pid")
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
_TRANSPORT = os.environ.get("TRACY_MCP_TRANSPORT", "streamable-http").strip().lower()
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
# system prompt; exposing it as an MCP resource keeps analysis guidance in
@@ -259,7 +258,8 @@ def _prompt_resource() -> str:
@mcp_server.resource("tracy://eval-guide")
def _eval_guide_resource() -> str:
"""Bindings-layer guide for the eval tool: ctx object model, time units,
source-location ID semantics, and worked examples of common ctx queries."""
source-location ID semantics, and worked examples translating catalog
entries into ctx Python."""
return _read_text(_EVAL_GUIDE_PATH)
@@ -436,110 +436,6 @@ async def load_capture(path: str, alias: str | None = None) -> str:
return f"Failed to load: {str(e)}"
@mcp_server.tool()
async def save_trace(
instance_id: str,
path: str,
level: int = 3,
streams: int = 4,
fi_dict: bool = False,
overwrite: bool = False,
async_mode: bool = True,
) -> object:
"""
Snapshot a Tracy instance (live or loaded) to a .tracy file.
Wraps `Worker::Write` under the main-thread data lock — safe for live
instances; the receive thread yields cooperatively for the duration of
the write. Concurrent `eval` calls against the same live instance may
stall until the save completes.
Parameters:
instance_id — name returned by live_connect / load_capture.
path — output file. Absolute paths are used as-is; a bare
filename is resolved under TRACY_CAPTURES_DIR if set.
On Windows use backslashes (e.g. 'E:\\\\traces\\\\a.tracy').
level — Zstd compression level (default 3, matches capture tool).
streams — number of compression streams (default 4).
fi_dict — rebuild frame-image dedup dictionary on save. Only
meaningful for traces containing screenshots; default
False matches the capture tool and GUI default.
overwrite — refuse to clobber an existing file unless True.
async_mode — default True; large traces take seconds-to-minutes.
Returns {task_id, status: "running"}; poll with `task`.
On success returns a dict with path, uncompressed_bytes, compressed_bytes,
ratio, and elapsed_ms — the same numbers the capture tool prints.
"""
if instance_id not in instances:
return f"Error: Instance '{instance_id}' not found. Use list_instances to find valid IDs."
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
if not os.path.isabs(path):
if captures_dir and os.path.basename(path) == path:
path = os.path.join(captures_dir, path)
else:
return (
f"Error: '{path}' is not absolute. Pass a full path, or a bare "
f"filename with TRACY_CAPTURES_DIR set (currently "
f"{captures_dir!r})."
)
if not path.endswith(".tracy"):
path += ".tracy"
if os.path.exists(path) and not overwrite:
return (
f"Error: '{path}' already exists. Pass overwrite=True to clobber, "
f"or choose a different path."
)
if not async_mode:
return await _execute_save(instance.worker, path, level, streams, fi_dict)
task_id = str(uuid.uuid4())
t = Task(task_id, f"save_trace({instance_id} -> {path})")
tasks[task_id] = t
asyncio.get_running_loop().run_in_executor(
executor, _run_save_task_sync, t, instance.worker, path, level, streams, fi_dict
)
return {"task_id": task_id, "status": "running"}
def _save_worker_sync(worker: object, path: str, level: int, streams: int, fi_dict: bool) -> dict:
t0 = time.time()
uncompressed, compressed = tracy_server.save_worker(
worker, path, level, streams, fi_dict
)
elapsed_ms = int((time.time() - t0) * 1000)
ratio = (compressed / uncompressed) if uncompressed else 0.0
return {
"path": path,
"uncompressed_bytes": uncompressed,
"compressed_bytes": compressed,
"ratio": ratio,
"elapsed_ms": elapsed_ms,
}
def _run_save_task_sync(t: Task, worker: object, path: str, level: int, streams: int, fi_dict: bool) -> None:
t.status = "running"
try:
t.result = _save_worker_sync(worker, path, level, streams, fi_dict)
t.status = "completed"
except Exception as e:
t.error = str(e)
t.status = "failed"
finally:
t.end_time = time.time()
async def _execute_save(worker: object, path: str, level: int, streams: int, fi_dict: bool) -> dict:
return await asyncio.get_running_loop().run_in_executor(
executor, _save_worker_sync, worker, path, level, streams, fi_dict
)
@mcp_server.tool()
async def unload_capture(instance_id: str) -> str:
"""Unload a Tracy instance and release its memory."""
@@ -677,13 +573,6 @@ async def shutdown_server() -> str:
if __name__ == "__main__":
atexit.register(_cleanup_pid_files)
if _TRANSPORT not in ("sse", "streamable-http"):
print(
"TRACY_MCP_TRANSPORT must be 'sse' or 'streamable-http'.",
file=sys.stderr,
)
sys.exit(1)
running, existing_port = _is_our_server_running()
if running:
print(
@@ -696,17 +585,12 @@ if __name__ == "__main__":
port = _find_free_port()
_write_pid_and_port(port)
path = (
mcp_server.settings.sse_path
if _TRANSPORT == "sse"
else mcp_server.settings.streamable_http_path
)
print(f"Tracy MCP listening on http://127.0.0.1:{port}{path}", file=sys.stderr)
print(f"Tracy MCP listening on http://127.0.0.1:{port}/sse", file=sys.stderr)
mcp_server.settings.host = "127.0.0.1"
mcp_server.settings.port = port
try:
mcp_server.run(transport=_TRANSPORT)
mcp_server.run(transport="sse")
except KeyboardInterrupt:
print("\nTracy MCP server stopped.", file=sys.stderr)
sys.exit(0)

View File

@@ -1 +0,0 @@
The LaTeX source file (tracy.tex) and the resulting PDF file (tracy.pdf) are the only authorative version of the user manual. Do NOT modify the Markdown user manual (tracy.md) by hand. It is only meant to be updated via the latex2md.sh script.

View File

@@ -1,35 +0,0 @@
/\\begin\{bclogo\}\[/ {
in_bclogo = 1
bclogo_type = ""
next
}
in_bclogo && /logo=/ {
if (/\\bcbombe/) bclogo_type = "bcbombe"
else if (/\\bcattention/) bclogo_type = "bcattention"
else if (/\\bclampe/) bclogo_type = "bclampe"
else if (/\\bcquestion/) bclogo_type = "bcquestion"
next
}
in_bclogo && /noborder|couleur/ {
next
}
in_bclogo {
line = $0
sub(/^[ \t]*\]?\{/, "", line)
sub(/\}.*$/, "", line)
bclogo_title = line
if (bclogo_type == "bcbombe") prefix = "IMPORTANT"
else if (bclogo_type == "bcattention") prefix = "CAUTION"
else if (bclogo_type == "bclampe") prefix = "TIP"
else prefix = "NOTE"
printf "\\begin{quote}\\textbf{%s:%s}\\par\n", prefix, bclogo_title
in_bclogo = 0
next
}
/\\end\{bclogo\}/ {
printf "\\end{quote}\n"
next
}
{ print }

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""Replace Font Awesome icon macros in LaTeX with Unicode codepoints."""
import re
import sys
def pascal_to_snake(name):
"""Convert PascalCase to UPPER_SNAKE_CASE."""
result = name[0]
for i in range(1, len(name)):
if name[i].isupper() and name[i - 1].islower():
result += '_'
result += name[i]
return result.upper()
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <header_path> <tex_path>", file=sys.stderr)
sys.exit(1)
header_path = sys.argv[1]
tex_path = sys.argv[2]
# Parse header: ICON_FA_SNAKE_CASE -> Unicode char
icons = {}
with open(header_path) as f:
for line in f:
m = re.match(
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
)
if m:
snake = m.group(1)
parts = snake.split('_')
pascal = ''.join(p.capitalize() for p in parts)
codepoint = int(m.group(3), 16)
icons[pascal] = chr(codepoint)
# Read tex file
with open(tex_path) as f:
text = f.read()
# Find all \faXxx used in the text (uppercase first letter excludes \fancyhead etc.)
used = set()
for m in re.finditer(r'\\fa([A-Z][a-zA-Z0-9]*)', text):
used.add(m.group(1))
# Replace each used icon, longest names first to avoid prefix conflicts
for name in sorted(used, key=lambda n: (-len(n), n)):
if name not in icons:
print(f"Warning: \\fa{name} not found in header", file=sys.stderr)
continue
char = icons[name]
# Order matters: more specific patterns first
text = text.replace(f'\\fa{name}{{}}~', f'{char} ')
text = text.replace(f'\\fa{name}{{}}', char)
text = text.replace(f'\\fa{name}~', f'{char} ')
text = text.replace(f'\\fa{name}', char)
# Write back
with open(tex_path, 'w') as f:
f.write(text)
if __name__ == '__main__':
main()

View File

@@ -3,151 +3,3 @@ function Link(el)
el.attributes['reference'] = nil
return el
end
-- Drop Div wrappers (e.g. table/titlepage containers), keeping their content.
function Div(el)
return el.content
end
-- ---------------------------------------------------------------------------
-- LaTeX math -> plain-text approximation.
--
-- The target Markdown renderer has no math support, so a raw "$\frac{1}{2}$"
-- would show verbatim. We turn each math node into the closest Unicode/ASCII
-- equivalent: fractions become "a/b", \times becomes "x", super/subscripts use
-- Unicode digits, and the one multi-line display equation becomes a fenced
-- code block (Markdown collapses plain newlines, a code block keeps them).
-- ---------------------------------------------------------------------------
local sup = {['0']='',['1']='¹',['2']='²',['3']='³',['4']='',['5']='',
['6']='',['7']='',['8']='',['9']='',['+']='',['-']='',
['=']='',['(']='',[')']=''}
local sub = {['0']='',['1']='',['2']='',['3']='',['4']='',['5']='',
['6']='',['7']='',['8']='',['9']='',['+']='',['-']='',
['=']='',['(']='',[')']=''}
-- Symbol replacements, applied as literal substitutions. Longer commands must
-- precede those that are a prefix of them (e.g. \rightarrow before \right).
local symbols = {
{'\\leftrightarrow',''}, {'\\rightarrow',''}, {'\\leftarrow',''},
{'\\Rightarrow',''}, {'\\Leftarrow',''}, {'\\to',''}, {'\\mapsto',''},
{'\\times','×'}, {'\\cdot','·'}, {'\\div','÷'}, {'\\ast','*'}, {'\\star','*'},
{'\\leq',''}, {'\\geq',''}, {'\\neq',''}, {'\\approx',''}, {'\\equiv',''},
{'\\ll','«'}, {'\\gg','»'}, {'\\le',''}, {'\\ge',''},
{'\\ldots',''}, {'\\cdots',''}, {'\\dots',''}, {'\\infty',''},
{'\\pm','±'}, {'\\mp',''}, {'\\propto',''}, {'\\sum','Σ'}, {'\\prod','Π'},
{'\\alpha','α'}, {'\\beta','β'}, {'\\gamma','γ'}, {'\\delta','δ'}, {'\\Delta','Δ'},
{'\\mu','µ'}, {'\\sigma','σ'}, {'\\pi','π'}, {'\\lambda','λ'}, {'\\theta','θ'},
{'\\left',''}, {'\\right',''},
{'\\qquad',' '}, {'\\quad',' '}, {'\\,',' '}, {'\\;',' '}, {'\\:',' '},
{'\\ ',' '}, {'\\!',''},
{'\\%','%'}, {'\\#','#'}, {'\\&','&'}, {'\\_','_'}, {'\\{','{'}, {'\\}','}'},
{'\\$','$'},
}
-- Literal (non-pattern) string replacement; avoids Lua pattern magic in keys.
local function lit_replace(s, a, b)
local out, i = {}, 1
while true do
local p = s:find(a, i, true)
if not p then out[#out + 1] = s:sub(i); break end
out[#out + 1] = s:sub(i, p - 1)
out[#out + 1] = b
i = p + #a
end
return table.concat(out)
end
-- Strip the outer braces of a "%b{}" capture.
local function grp(b) return b:sub(2, #b - 1) end
-- Map a string to Unicode super/subscript, or nil if any char is unsupported.
local function map_script(txt, map)
local res = {}
for i = 1, #txt do
local c = txt:sub(i, i)
if not map[c] then return nil end
res[#res + 1] = map[c]
end
return table.concat(res)
end
local function convert(s)
-- Text/font wrappers: keep the content, recurse to handle nesting.
for _, cmd in ipairs({'text', 'mathrm', 'mathit', 'mathbf', 'mathbb',
'mathsf', 'mathtt', 'mathcal', 'operatorname',
'textbf', 'textit', 'textrm'}) do
s = s:gsub('\\' .. cmd .. '(%b{})', function(b) return convert(grp(b)) end)
end
-- Fractions -> "num/den" (spaced when either side has spaces).
local function frac(a, b)
local n, d = convert(grp(a)), convert(grp(b))
local sep = (n:find(' ', 1, true) or d:find(' ', 1, true)) and ' / ' or '/'
return n .. sep .. d
end
s = s:gsub('\\frac(%b{})(%b{})', frac)
s = s:gsub('\\dfrac(%b{})(%b{})', frac)
s = s:gsub('\\tfrac(%b{})(%b{})', frac)
s = s:gsub('\\sfrac(%b{})(%b{})', frac)
-- Roots.
s = s:gsub('\\sqrt(%b{})', function(b) return '√(' .. convert(grp(b)) .. ')' end)
-- Single-char scripts first, so the braced fallback (e.g. "_native") below
-- is not re-scanned and mangled into Unicode subscripts.
s = s:gsub('%^([%w])', function(c) return sup[c] or ('^' .. c) end)
s = s:gsub('_([%w])', function(c) return sub[c] or ('_' .. c) end)
-- Braced scripts: Unicode when the content is all digits/signs, else keep
-- a readable "^(...)" / "_..." form.
s = s:gsub('%^(%b{})', function(b)
local inner = convert(grp(b))
return map_script(inner, sup) or ('^(' .. inner .. ')')
end)
s = s:gsub('_(%b{})', function(b)
local inner = convert(grp(b))
return map_script(inner, sub) or ('_' .. inner)
end)
-- Remaining symbols.
for _, pair in ipairs(symbols) do s = lit_replace(s, pair[1], pair[2]) end
return s
end
-- Convert a display equation, preserving its line structure for a code block.
local function convert_display(s)
s = convert(s)
for _, env in ipairs({'cases', 'aligned', 'align', 'array', 'matrix',
'gathered', 'split'}) do
s = lit_replace(s, '\\begin{' .. env .. '}', '')
s = lit_replace(s, '\\end{' .. env .. '}', '')
end
s = lit_replace(s, '\\\\', '\n') -- row break
s = s:gsub('%s*&%s*', ' ') -- column separator -> spacing
local lines = {}
for line in (s .. '\n'):gmatch('(.-)\n') do
line = line:gsub('^%s+', ''):gsub('%s+$', '')
if line ~= '' then lines[#lines + 1] = line end
end
for i = 2, #lines do lines[i] = ' ' .. lines[i] end -- indent continuations
return table.concat(lines, '\n')
end
function Math(el)
if el.mathtype == 'DisplayMath' then
return el -- handled at block level by Para, to emit a code block
end
return pandoc.Str(convert(el.text))
end
-- A paragraph that is solely a display equation becomes a fenced code block.
function Para(el)
local maths, only_math = {}, true
for _, x in ipairs(el.content) do
if x.t == 'Math' and x.mathtype == 'DisplayMath' then
maths[#maths + 1] = x
elseif x.t ~= 'Space' and x.t ~= 'SoftBreak' and x.t ~= 'LineBreak' then
only_math = false
end
end
if #maths == 0 or not only_math then return nil end
local parts = {}
for _, m in ipairs(maths) do parts[#parts + 1] = convert_display(m.text) end
return pandoc.CodeBlock(table.concat(parts, '\n\n'))
end

View File

@@ -1,77 +0,0 @@
#!/usr/bin/env python3
"""Append icon legend blocks to each markdown section containing Font Awesome icons."""
import re
import sys
def _extract_icons(lines):
"""Return deduplicated icon chars from lines, in order of first appearance."""
seen = set()
icons = []
for line in lines:
for ch in line:
cp = ord(ch)
if 0xE000 <= cp <= 0xF8FF and ch not in seen:
seen.add(ch)
icons.append(ch)
return icons
def _append_legend(result_lines, icons, icon_names):
"""Append a legend block for the given icons."""
result_lines.append('')
result_lines.append('-----')
result_lines.append('')
for ch in icons:
name = icon_names.get(ch, f'Unknown(U+{ord(ch):04X})')
result_lines.append(f'{ch} - {name} icon')
result_lines.append('')
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <header_path> <md_path>", file=sys.stderr)
sys.exit(1)
header_path = sys.argv[1]
md_path = sys.argv[2]
# Build char -> name mapping from header
icon_names = {}
with open(header_path) as f:
for line in f:
m = re.match(
r'#define\s+ICON_FA_(\w+)\s+.*?//\s*(U\+([0-9a-fA-F]+))', line
)
if m:
snake = m.group(1)
parts = snake.split('_')
pascal = ' '.join(p.capitalize() for p in parts)
codepoint = int(m.group(3), 16)
icon_names[chr(codepoint)] = pascal
with open(md_path, encoding='utf-8') as f:
lines = f.read().split('\n')
# Build chunk boundaries: header lines and EOF
chunk_starts = [i for i, line in enumerate(lines) if line.startswith('#')]
# Also add index 0 as a chunk start if there's pre-header content
if chunk_starts and chunk_starts[0] > 0:
chunk_starts.insert(0, 0)
result_lines = []
for ci, start in enumerate(chunk_starts):
end = chunk_starts[ci + 1] if ci + 1 < len(chunk_starts) else len(lines)
icons = _extract_icons(lines[start:end])
result_lines.extend(lines[start:end])
if icons:
_append_legend(result_lines, icons, icon_names)
with open(md_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(result_lines))
if __name__ == '__main__':
main()

View File

@@ -7,46 +7,20 @@ sed -i -e 's@\\ctrl@Ctrl@g' _tmp.tex
sed -i -e 's@\\shift@Shift@g' _tmp.tex
sed -i -e 's@\\Alt@Alt@g' _tmp.tex
sed -i -e 's@\\del@Delete@g' _tmp.tex
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome7.h _tmp.tex
sed -i -e 's@\\fa\([a-zA-Z]*\)@(\1~icon)@g' _tmp.tex
sed -i -e 's@\\LMB{}~@@g' _tmp.tex
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
sed -i -e 's@\\textsigma@σ@g' _tmp.tex
# Resolve \circled{} markers and lstlisting escapeinside (@...@) snippets, which
# pandoc would otherwise emit verbatim or drop, to their Unicode equivalents.
sed -i -e 's|@\\circled{a}@|(a)|g' -e 's|@\\circled{b}@|(b)|g' -e 's|@\\circled{c}@|(c)|g' _tmp.tex
sed -i -e 's|\\circled{a}|(a)|g' -e 's|\\circled{b}|(b)|g' -e 's|\\circled{c}|(c)|g' _tmp.tex
sed -i -e 's|@\\ldots@|…|g' _tmp.tex
sed -i -e 's@\\nameref{quicklook}@A quick look at Tracy Profiler@g' _tmp.tex
sed -i -e 's@\\nameref{firststeps}@First steps@g' _tmp.tex
sed -i -e 's@\\nameref{client}@Client markup@g' _tmp.tex
sed -i -e 's@\\nameref{capturing}@Capturing the data@g' _tmp.tex
sed -i -e 's@\\nameref{analyzingdata}@Analyzing captured data@g' _tmp.tex
sed -i -e 's@\\nameref{tracyassist}@Tracy Assist@g' _tmp.tex
sed -i -e 's@\\nameref{csvexport}@Exporting zone statistics to CSV@g' _tmp.tex
sed -i -e 's@\\nameref{importingdata}@Importing external profiling data@g' _tmp.tex
sed -i -e 's@\\nameref{configurationfiles}@Configuration files@g' _tmp.tex
awk -f bclogo2quote.awk _tmp.tex > _tmp_quoted.tex
mv _tmp_quoted.tex _tmp.tex
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -t 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables' -s _tmp.tex -o tracy.md
awk -f tablecaption.awk tracy.md > _tmp_caption.md
mv _tmp_caption.md tracy.md
sed -i -e 's/^> \*\*IMPORTANT:\([^*]*\)\*\*/> [!IMPORTANT]\
> **\1**/' tracy.md
sed -i -e 's/^> \*\*TIP:\([^*]*\)\*\*/> [!TIP]\
> **\1**/' tracy.md
sed -i -e 's/^> \*\*CAUTION:\([^*]*\)\*\*/> [!CAUTION]\
> **\1**/' tracy.md
sed -i -e 's/^> \*\*NOTE:\([^*]*\)\*\*/> [!NOTE]\
> **\1**/' tracy.md
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome7.h tracy.md
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -s _tmp.tex -o tracy.md
rm -f _tmp.tex

View File

@@ -1,16 +0,0 @@
# Pandoc emits table captions as a line beginning with ": ", which GitHub
# renders literally instead of as a caption. Strip the marker and italicize
# the caption instead. Captions may span several physical lines when they
# contain a hard line break (a trailing backslash). Underscores are used for
# the emphasis so captions that already contain "*...*" markup are left intact.
!incap && /^: / {
incap = 1
$0 = "_" substr($0, 3)
}
incap && !/\\$/ {
print $0 "_"
incap = 0
next
}
incap { print; next }
{ print }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,10 +21,6 @@ if get_option('callstack') > 0
tracy_common_args += ['-DTRACY_CALLSTACK='+get_option('callstack').to_string()]
endif
if get_option('platform_header') != ''
tracy_common_args += ['-DTRACY_PLATFORM_HEADER="'+get_option('platform_header')+'"']
endif
if get_option('no_callstack')
tracy_common_args += ['-DTRACY_NO_CALLSTACK']
endif
@@ -135,10 +131,6 @@ if get_option('ignore_memory_faults')
tracy_common_args += ['-DTRACY_IGNORE_MEMORY_FAULTS']
endif
if get_option('opengl_auto_calibration')
tracy_common_args += ['-DTRACY_OPENGL_AUTO_CALIBRATION']
endif
tracy_shared_libs = get_option('default_library') == 'shared'
if tracy_shared_libs

View File

@@ -1,7 +1,6 @@
option('tracy_enable', type : 'boolean', value : false, description : 'Enable profiling', yield: true)
option('on_demand', type : 'boolean', value : false, description : 'On-demand profiling')
option('callstack', type : 'integer', value : 0, description : 'Enforce callstack collection for tracy zones x frames deep')
option('platform_header', type : 'string', value : '', description : 'Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform')
option('no_callstack', type : 'boolean', value : false, description : 'Disable all callstack related functionality')
option('no_callstack_inlines', type : 'boolean', value : false, description : 'Disables the inline functions in callstacks')
option('only_localhost', type : 'boolean', value : false, description : 'Only listen on the localhost interface')
@@ -29,4 +28,3 @@ option('verbose', type : 'boolean', value : false, description : 'Enable verbose
option('no_internal_message', type : 'boolean', value : false, description : 'Prevent the profiler from logging messages')
option('debuginfod', type : 'boolean', value : false, description : 'Enable debuginfod support')
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation')
option('opengl_auto_calibration', type : 'boolean', value : false, description : 'Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)')

View File

@@ -5,11 +5,10 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/options.cmake)
set_option(NO_FILESELECTOR "Disable the file selector" OFF)
set_option(GTK_FILESELECTOR "Use the GTK file selector on Linux instead of the xdg-portal one" OFF)
set_option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
set_option(NO_STATISTICS "Disable calculation of statistics" OFF)
set_option(SELF_PROFILE "Enable self-profiling" OFF)
set_option_value(SANITIZE "Sanitizer parameters" "")
set(NO_STATISTICS OFF)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
set(CMAKE_CXX_STANDARD 20)
@@ -44,16 +43,10 @@ ExternalProject_Add(embed
)
function(Embed LIST NAME FILE)
cmake_parse_arguments(EMBED "TEXT" "" "" ${ARGN})
if(EMBED_TEXT)
set(EMBED_FLAGS -t)
else()
set(EMBED_FLAGS)
endif()
add_custom_command(
OUTPUT data/${NAME}.cpp data/${NAME}.hpp
COMMAND ${CMAKE_COMMAND} -E make_directory data
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${EMBED_FLAGS} ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
DEPENDS embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
)
list(APPEND ${LIST} data/${NAME}.cpp)
@@ -66,7 +59,6 @@ set(SERVER_FILES
TracyBadVersion.cpp
TracyColor.cpp
TracyConfig.cpp
TracyDisassembly.cpp
TracyEmbed.cpp
TracyEventDebug.cpp
TracyFileselector.cpp
@@ -76,7 +68,6 @@ set(SERVER_FILES
TracyMarkdown.cpp
TracyMicroArchitecture.cpp
TracyMouse.cpp
TracyNameGen.cpp
TracyProtoHistory.cpp
TracySourceContents.cpp
TracySourceTokenizer.cpp
@@ -101,7 +92,6 @@ set(SERVER_FILES
TracyView_FindZone.cpp
TracyView_FlameGraph.cpp
TracyView_FrameOverview.cpp
TracyView_FrameStatistics.cpp
TracyView_FrameTimeline.cpp
TracyView_FrameTree.cpp
TracyView_GpuTimeline.cpp
@@ -153,33 +143,18 @@ set(PROFILER_FILES
src/winmainArchDiscovery.cpp
)
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md TEXT)
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md TEXT)
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md TEXT)
Embed(PROFILER_FILES ToolsJson src/llm/tools.json TEXT)
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md)
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md)
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md)
Embed(PROFILER_FILES ToolsJson src/llm/tools.json)
Embed(PROFILER_FILES FontFixed src/font/FiraCode-Retina.ttf)
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 7\ Free-Solid-900.otf)
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 6\ Free-Solid-900.otf)
Embed(PROFILER_FILES FontNormal src/font/Roboto-Regular.ttf)
Embed(PROFILER_FILES FontBold src/font/Roboto-Bold.ttf)
Embed(PROFILER_FILES FontItalic src/font/Roboto-Italic.ttf)
Embed(PROFILER_FILES FontBoldItalic src/font/Roboto-BoldItalic.ttf)
Embed(PROFILER_FILES FontEmoji src/font/NotoEmoji-Regular.ttf)
Embed(PROFILER_FILES Manual ../manual/tracy.md TEXT)
Embed(PROFILER_FILES Text100Million src/achievements/100Million.md TEXT)
Embed(PROFILER_FILES TextConnectToClient src/achievements/ConnectToClient.md TEXT)
Embed(PROFILER_FILES TextFindZone src/achievements/FindZone.md TEXT)
Embed(PROFILER_FILES TextFrameImages src/achievements/FrameImages.md TEXT)
Embed(PROFILER_FILES TextGlobalSettings src/achievements/GlobalSettings.md TEXT)
Embed(PROFILER_FILES TextInstrumentationIntro src/achievements/InstrumentationIntro.md TEXT)
Embed(PROFILER_FILES TextInstrumentationStatistics src/achievements/InstrumentationStatistics.md TEXT)
Embed(PROFILER_FILES TextInstrumentFrames src/achievements/InstrumentFrames.md TEXT)
Embed(PROFILER_FILES TextIntro src/achievements/Intro.md TEXT)
Embed(PROFILER_FILES TextLoadTrace src/achievements/LoadTrace.md TEXT)
Embed(PROFILER_FILES TextSamplingIntro src/achievements/SamplingIntro.md TEXT)
Embed(PROFILER_FILES TextSaveTrace src/achievements/SaveTrace.md TEXT)
Embed(PROFILER_FILES Manual ../manual/tracy.md)
set(INCLUDES "${CMAKE_CURRENT_BINARY_DIR}")
set(LIBS "")
@@ -301,19 +276,7 @@ if(NOT EMSCRIPTEN)
endif()
if(EMSCRIPTEN)
target_link_options(${PROJECT_NAME} PRIVATE
-pthread
-sASSERTIONS=0
-sINITIAL_MEMORY=384mb
-sALLOW_MEMORY_GROWTH=1
-sMAXIMUM_MEMORY=4gb
-sSTACK_SIZE=1048576
-sPTHREAD_POOL_SIZE=8
-sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
-sEXPORTED_RUNTIME_METHODS=ccall
-sENVIRONMENT=web,worker
--preload-file embed.tracy
)
target_link_options(${PROJECT_NAME} PRIVATE -pthread -sASSERTIONS=0 -sINITIAL_MEMORY=384mb -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sSTACK_SIZE=1048576 -sWASM_BIGINT=1 -sPTHREAD_POOL_SIZE=8 -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard -sEXPORTED_RUNTIME_METHODS=ccall -sENVIRONMENT=web,worker --preload-file embed.tracy)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -1,27 +1,17 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include "../../public/common/tracy_lz4hc.hpp"
static void Usage()
{
fprintf( stderr, "Usage: embed [-t] <objectName> <source> <destination>\n" );
fprintf( stderr, "Usage: embed <objectName> <source> <destination>\n" );
fprintf( stderr, " destination should be without extension, will create cpp, hpp pair\n" );
fprintf( stderr, " -t: treat source as text, convert line endings to unix\n" );
}
int main( int argc, char** argv )
{
bool text = false;
if( argc >= 2 && strcmp( argv[1], "-t" ) == 0 )
{
text = true;
argc--;
argv++;
}
if( argc < 4 )
{
Usage();
@@ -48,16 +38,6 @@ int main( int argc, char** argv )
fread( data, 1, sz, src );
fclose( src );
if( text )
{
size_t pos = 0;
for( size_t i=0; i<sz; i++ )
{
if( data[i] != '\r' ) data[pos++] = data[i];
}
sz = pos;
}
const auto lz4szMax = tracy::LZ4_compressBound( sz );
auto lz4data = new uint8_t[lz4szMax];
const auto lz4sz = tracy::LZ4_compress_HC( (const char*)data, (char*)lz4data, sz, lz4szMax, 6 );

View File

@@ -162,15 +162,6 @@ static ImGuiKey TranslateKeyCode( const char* code )
return ImGuiKey_None;
}
static void UpdateKeyModifiers( const EmscriptenKeyboardEvent* e )
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent( ImGuiMod_Ctrl, e->ctrlKey );
io.AddKeyEvent( ImGuiMod_Shift, e->shiftKey );
io.AddKeyEvent( ImGuiMod_Alt, e->altKey );
io.AddKeyEvent( ImGuiMod_Super, e->metaKey );
}
Backend::Backend( const char* title, const std::function<void()>& redraw, const std::function<void(float)>& scaleChanged, const std::function<int(void)>& isBusy, RunQueue* mainThreadTasks )
{
constexpr EGLint eglConfigAttrib[] = {
@@ -252,7 +243,6 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
return EM_TRUE;
} );
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
UpdateKeyModifiers( e );
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, true );
@@ -260,7 +250,6 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
return EM_TRUE;
} );
emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
UpdateKeyModifiers( e );
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, false );
@@ -310,12 +299,8 @@ void Backend::NewFrame( int& w, int& h )
if( s_width != w || s_height != h )
{
EM_ASM( {
Module.canvas.style.width = ($0 / $2) + 'px';
Module.canvas.style.height = ($1 / $2) + 'px';
Module.canvas.width = $0;
Module.canvas.height = $1;
}, w, h, double( scale ) );
EM_ASM( Module.canvas.style.width = window.innerWidth + 'px'; Module.canvas.style.height = window.innerHeight + 'px' );
EM_ASM( Module.canvas.width = $0; Module.canvas.height = $1, w, h );
s_width = w;
s_height = h;

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