Compare commits

..

1 Commits

Author SHA1 Message Date
Clément Grégoire
1c26daa3ac Detect unreliable TSC on Linux via perf timestamp probe.
Linux exposes time_zero/time_mult/time_shift on the perf_event mmap
page for converting sample timestamps back to TSC cycles, gated by
cap_user_time_zero. That flag only states the conversion math is
valid; on Ryzen the kernel can leave it set while TSC silently drifts
between cores, before the clocksource watchdog catches it. We've had
user reports of this surfacing as garbage timestamps in traces.

CheckLinuxPerfTscConsistency() first looks at the current clocksource
in sysfs, then opens a dummy PERF_COUNT_SW_DUMMY event and verifies
that a PR_SET_NAME-triggered PERF_RECORD_COMM timestamp inverts back
into the rdtsc-sandwiched window with ~1 ms of slack. Modeled on
tools/perf/tests/perf-time-to-tsc.c.

Wired into CheckHardwareSupportsInvariantTSC() so a failing probe
calls InitFailure in non-fallback builds and routes GetTime() to
clock_gettime in TRACY_TIMER_FALLBACK builds. Inconclusive cases
(perf blocked, no COMM record, etc) return true to avoid penalising
sandboxed environments. Gated on TRACY_HAS_SYSTEM_TRACING so we only
probe when Tracy would actually open perf events.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 16:11:39 +02:00
310 changed files with 9957 additions and 20360 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_MANUAL_LIFETIME
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -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

@@ -1,49 +0,0 @@
name: android
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-android:
runs-on: ubuntu-latest
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git ninja nodejs unzip
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r29
- run: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Library v7
run: |
cmake -B build-v7 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a
cmake --build build-v7
- name: Library v8
run: |
cmake -B build-v8 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a
cmake --build build-v8

View File

@@ -1,61 +0,0 @@
name: appimage
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: appimage-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-appimage:
runs-on: ubuntu-latest
container: ubuntu:24.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake ninja-build meson pkg-config git ca-certificates \
curl file desktop-file-utils nodejs patchelf \
libegl-dev libxkbcommon-dev libdbus-1-dev libssl-dev \
libffi-dev libexpat1-dev
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Cache wayland
uses: actions/cache@v4
with:
path: profiler/build-appimage/wayland
key: ${{ runner.os }}-wayland-${{ hashFiles('profiler/appimage/build.sh') }}
- name: Build AppImage
run: profiler/appimage/build.sh
env:
GIT_REV: ${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: appimage
path: profiler/build-appimage/tracy-profiler-x86_64.AppImage

View File

@@ -7,16 +7,8 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: emscripten-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-emscripten:
@@ -28,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
@@ -38,11 +30,6 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Profiler GUI
run: |
cmake -G Ninja -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=MinSizeRel -DGIT_REV=${{ github.sha }} -DCMAKE_TOOLCHAIN_FILE=${{env.EMSDK}}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake

View File

@@ -8,13 +8,6 @@ on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: latex-${{ github.ref }}
cancel-in-progress: true
jobs:
build-manual:

View File

@@ -1,79 +0,0 @@
name: linux-cli
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: linux-cli-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-linux-cli:
runs-on: ubuntu-latest
container: ubuntu:24.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git ca-certificates curl nodejs
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel
- name: Build capture
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel
- name: Build csvexport
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel
- name: Build import
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel
- name: Build merge
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel
- name: Build monitor
run: |
cmake -B monitor/build -S monitor -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build monitor/build --parallel
- name: Stage release files
run: |
mkdir -p linux-cli
cp update/build/tracy-update linux-cli/
cp capture/build/tracy-capture linux-cli/
cp capture/build/tracy-capture-daemon linux-cli/
cp csvexport/build/tracy-csvexport linux-cli/
cp import/build/tracy-import-chrome linux-cli/
cp import/build/tracy-import-fuchsia linux-cli/
cp merge/build/tracy-merge linux-cli/
cp monitor/build/tracy-monitor linux-cli/
strip linux-cli/tracy-*
- uses: actions/upload-artifact@v4
with:
name: linux-cli
path: linux-cli

View File

@@ -7,16 +7,8 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: linux-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-linux:
@@ -24,7 +16,7 @@ jobs:
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua clang sdl3_image python-jinja
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
@@ -34,58 +26,39 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Determine build parallelism
run: |
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
echo "CMAKE_PARALLEL=--parallel 2" >> "$GITHUB_ENV"
else
echo "CMAKE_PARALLEL=--parallel" >> "$GITHUB_ENV"
fi
- name: Dyna example / clang
run: |
CC=clang CXX=clang++ cmake -B examples/dyna/build-clang -S examples/dyna -DCMAKE_BUILD_TYPE=Release
cmake --build examples/dyna/build-clang $CMAKE_PARALLEL
- name: Dyna example / gcc
run: |
CC=gcc CXX=g++ cmake -B examples/dyna/build-gcc -S examples/dyna -DCMAKE_BUILD_TYPE=Release
cmake --build examples/dyna/build-gcc $CMAKE_PARALLEL
- name: Profiler GUI
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build $CMAKE_PARALLEL
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
cmake --build profiler/build
else
cmake --build profiler/build --parallel
fi
- name: Update utility
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build update/build $CMAKE_PARALLEL
cmake --build update/build --parallel
- name: Capture utility
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build capture/build $CMAKE_PARALLEL
cmake --build capture/build --parallel
- name: Csvexport utility
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build $CMAKE_PARALLEL
cmake --build csvexport/build --parallel
- name: Import utilities
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build import/build $CMAKE_PARALLEL
cmake --build import/build --parallel
- name: Merge utility
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build $CMAKE_PARALLEL
- name: Monitor utility
run: |
cmake -B monitor/build -S monitor -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build monitor/build $CMAKE_PARALLEL
cmake --build merge/build --parallel
- name: Library (cmake)
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
cmake --build build $CMAKE_PARALLEL
cmake --build build
cmake --install build
env:
CMAKE_INSTALL_PREFIX: ${{ github.workspace }}/bin
- name: Library (meson)
@@ -93,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: |
@@ -101,12 +94,10 @@ jobs:
cp profiler/build/tracy-profiler bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp capture/build/tracy-capture-daemon bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin
cp merge/build/tracy-merge bin
cp monitor/build/tracy-monitor bin
strip bin/tracy-*
- uses: actions/upload-artifact@v4
with:

View File

@@ -6,22 +6,13 @@ on:
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: macos-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-macos:
runs-on: macos-latest
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Cache CPM packages
@@ -30,23 +21,14 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- name: Install dependencies
run: brew install pkg-config meson
run: brew install pkg-config glfw meson
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Build library
run: meson setup -Dtracy_enable=true build && meson compile -C build
- name: Test application
uses: ./.github/actions/test-tracy
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DBUNDLE=ON -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel 2 --config Release
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
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 }}
@@ -67,19 +49,18 @@ jobs:
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
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: Package artifacts
run: |
mkdir -p bin
cp -r profiler/build/tracy-profiler.app bin
cp profiler/build/tracy-profiler bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp capture/build/tracy-capture-daemon bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin
cp merge/build/tracy-merge bin
strip bin/tracy-profiler.app/Contents/MacOS/tracy-profiler
strip bin/tracy-update bin/tracy-capture bin/tracy-capture-daemon bin/tracy-csvexport bin/tracy-import-chrome bin/tracy-import-fuchsia bin/tracy-merge
- uses: actions/upload-artifact@v4
with:
name: macos

View File

@@ -7,13 +7,6 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: mingw-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
@@ -60,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

@@ -4,62 +4,26 @@ on:
release:
types: [published]
permissions:
contents: read
concurrency:
group: release-${{ github.event.release.tag_name }}
cancel-in-progress: false
jobs:
release-build-windows:
uses: ./.github/workflows/windows.yml
release-build-macos:
uses: ./.github/workflows/macos.yml
release-build-manual:
uses: ./.github/workflows/latex.yml
release-build-appimage:
uses: ./.github/workflows/appimage.yml
release-build-linux-cli:
uses: ./.github/workflows/linux-cli.yml
attach-to-release:
needs: [release-build-windows, release-build-macos, release-build-manual, release-build-appimage, release-build-linux-cli]
needs: [release-build-windows, release-build-manual]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
- name: Create versioned Windows zip
- name: Create versioned zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd windows
zip -r ../windows-$VERSION_NO_V.zip .
- name: Create versioned macOS zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd macos
chmod +x tracy-* tracy-profiler.app/Contents/MacOS/*
zip -r ../macos-$VERSION_NO_V.zip .
- name: Create versioned Linux zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
mkdir linux
cp appimage/* linux/
cp linux-cli/* linux/
cd linux
zip -r ../linux-$VERSION_NO_V.zip .
- uses: softprops/action-gh-release@v2
with:
files: |
windows-*.zip
macos-*.zip
linux-*.zip
manual/tracy.pdf

View File

@@ -8,16 +8,8 @@ on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: windows-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
PANDOC_CACHE_DIR: ${{ github.workspace }}/pandoc-cache
jobs:
build-windows:
@@ -30,32 +22,17 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache pandoc
uses: actions/cache@v4
with:
path: ${{ env.PANDOC_CACHE_DIR }}
key: ${{ runner.os }}-pandoc-${{ hashFiles('cmake/manual.cmake') }}
- uses: microsoft/setup-msbuild@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- run: pip install meson ninja
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Install meson
run: python -m pip install --upgrade meson
- name: Library (meson)
env:
CCACHE_DISABLE: '1'
run: |
$vs = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
foreach ($line in & cmd /c "`"$vs\VC\Auxiliary\Build\vcvarsall.bat`" x64 && set") {
if ($line -match '^([^=]+)=(.*)$') { [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process') }
}
meson setup -Dtracy_enable=true build-meson
meson compile -C build-meson
- name: Test application
uses: ./.github/actions/test-tracy
- 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 }}
@@ -82,7 +59,6 @@ jobs:
copy profiler\build\Release\tracy-profiler.exe bin
copy update\build\Release\tracy-update.exe bin
copy capture\build\Release\tracy-capture.exe bin
copy capture\build\Release\tracy-capture-daemon.exe bin
copy csvexport\build\Release\tracy-csvexport.exe bin
copy import\build\Release\tracy-import-chrome.exe bin
copy import\build\Release\tracy-import-fuchsia.exe bin

14
.gitignore vendored
View File

@@ -1,12 +1,16 @@
.vs
_build
_compiler
tools/*
*.d
*.o
*.so
*.swp
*.obj
imgui.ini
test/tracy_test
test/tracy_test.exe
*/build/unix/*-*
manual/t*.aux
manual/t*.log
manual/t*.out
@@ -17,17 +21,19 @@ manual/t*.bbl
manual/t*.blg
manual/t*.fdb_latexmk
manual/t*.fls
manual/tracy.md
profiler/build-appimage
profiler/build/win32/packages
profiler/build/win32/Tracy.aps
.deps/
.dirstamp
/_*/**
/**/__pycache__/**
extra/vswhere.exe
extra/tracy-build
.cache
.uv-cache/
.venv/
compile_commands.json
profiler/build/wasm/Tracy-release.*
profiler/build/wasm/Tracy-debug.*
profiler/build/wasm/embed.tracy
examples/ToyPathTracer/Windows/TestCpu
examples/ToyPathTracer/Windows/x64
*.user

View File

@@ -1,341 +0,0 @@
---
name: tracy-gui-verify
description: Build, serve, and drive the Tracy Profiler's emscripten (web) GUI in a headless browser with verified mouse/keyboard semantics. Use when developing or testing the profiler GUI. No X11/Wayland/DISPLAY required — the browser is the display server.
---
# Test the Tracy web GUI in a browser
Build the emscripten profiler GUI into an instrumented temporary workspace,
serve it, and drive it with the browser tool. The harness exports
(`debug_snapshot`/`debug_clear`) expose the app-side mouse/ID state — they
are the ground truth for targeting and verification, not screenshots.
Applies to this repo's emscripten build with ImGui 1.92.9b-docking and
Emscripten 5.0.7. Constants marked M are workstation-dependent — re-verify
them. Everything else is stable; do not re-derive it.
## Setup
```sh
sh <this-skill-dir>/setup.sh # workspace=/tmp/tracy-protocol
# or: TRACY_WEB_WS=/elsewhere TRACY_WEB_REPO=<repo> sh setup.sh
```
`<this-skill-dir>` is the project-scoped `.omp/skills/tracy-gui-verify/`
under the active project root — not a fixed home directory. If multiple
copies exist (one per project that has used this skill), use the copy
inside the repo being tested; `TRACY_WEB_REPO` controls which repo is
copied into the workspace.
The script: fresh source copy (excludes `.git` and build dirs) → applies
`harness.patch` (adds `debug_snapshot`/`debug_clear` exports + event/frame
recording to the emscripten backend, in the COPY only) → configures → builds
Release with the emsdk at `$EMSDK_DIR` (default `~/emsdk`) and the CPM cache at
`~/.cache/cpm`.
**Pitfall:** if the workspace is deleted while a `tracy-web` server is running,
stop the hub process first — a stale server keeps the *deleted* cwd and silently
returns empty responses while the port still accepts connections.
**Pitfall:** a `tracy-web` that died in an earlier session leaves the daemon in
`failed` state: `stop` only reports the old failure, and `start` keeps returning
"Daemon tracy-web has unacknowledged completion notifications" while the port
stays free. Drain the daemon with `hub op=logs name=tracy-web` (the log also
shows the cause), then relaunch with `hub op=restart name=tracy-web` (retained
spec). If the port is NOT free, a stale process owns it — often one serving an
earlier (now-stale) build directory: the new `httpd.py` dies on the bind and
your requests silently hit the old directory. Check `ss -tlnp | grep 8000` and
kill the owner. After any start/restart, verify the server is actually
serving: `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/index.html`
must return 200 — the daemon message is not proof of a server.
**After editing the GUI in the repo:** copy the changed files into
`$WS/src/` (same relative path), re-run `cmake --build $WS/build -j$(nproc)`,
reload the page. If a change touches `profiler/src/BackendEmscripten.cpp` or
`profiler/CMakeLists.txt`, the harness patch may no longer apply — re-apply it
manually (keep the `Dbg*` harness section, the `DbgPushEvent`/`DbgPushFrame`
calls in the mouse callbacks + `NewFrame`, and the two `debug_*` exports in
`-sEXPORTED_FUNCTIONS`).
## Serve + open + ready
Server (long-running → `hub`):
```
hub op=start name=tracy-web application=python3 args=["$WS/build/httpd.py"]
cwd=$WS/build ready={port:8000} persist=true
```
`httpd.py` sends the COOP/COEP headers required for pthreads/SharedArrayBuffer —
use it, not a bare http.server.
Browser:
```
browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
```
**Ready signal** (in a `run` cell; `run` code executes in Node scope — page JS
goes through `tab.evaluate`):
```js
let title = null;
for (let i = 0; i < 360; i++) {
title = await tab.evaluate(() => document.title);
if (/ - Tracy Profiler/.test(title)) break;
await new Promise(r => setTimeout(r, 250));
}
```
Title is `Tracy Profiler X.Y.Z` until the preloaded `embed.tracy` (DarkRL
capture) finishes loading, then `<trace> (embed.tracy) - Tracy Profiler X.Y.Z`
(`profiler/src/main.cpp` `SetWindowTitleCallback`). Wait ~1.5 s after, then
confirm the app loop lives: `snap().frames.at(-1).tick > 0`.
## Harness API (ground truth)
```js
const snap = () => tab.evaluate(() => JSON.parse(Module.ccall('debug_snapshot','string',[],[])));
const clear = () => tab.evaluate(() => Module.ccall('debug_clear','void',[],[]));
```
`snap()``{ dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH,
title, cursor, frames:[…64], events:[…64] }` (newest last).
- `frames[]`: one sample **per rAF tick, including idle ticks**. `tick` = main-loop
count (always advances), `f` = `ImGui::GetFrameCount()` (advances only when a
frame actually renders — the idle gate, §Idle), `mx/my` = app mouse position,
`md[3]` = mouse button states, `q` = pending input events, `wa` =
`tracy::s_wasActive` at tick start, `hi`/`ai` = ImGui `HoveredId`/`ActiveId`.
- `events[]`: raw callback data as the app received it. `type` 0=move 1=down
2=up 3=enter 4=leave 5=wheel; `tx/ty` = Emscripten int `targetX/Y`; `ax/ay` =
position passed to ImGui (`-1` for non-move); `t` = DOMHighResTimeStamp.
## Coordinate mapping
```
appX = floor( trunc(cssX - rectL) * dpr ) // device px, what ImGui hit-tests
cssX = appX / dpr // inverse for targeting
screenshot_px = cssX * dpr
```
Chain: DOM `clientX` → Emscripten `targetX = int(clientX - (rect.left|0))`
(`src/lib/libhtml5.js`, `EmscriptenMouseEvent.targetX` is an `int`) → Tracy
`AddMousePosEvent(targetX * dpr, …)` (`BackendEmscripten.cpp`) → ImGui `ImFloor`
(`imgui.cpp` `AddMousePosEvent`). Canvas fills the viewport, so `rectL/rectT`
are 0 (re-read anyway).
**M: `dpr` on this workstation's headless browser is 1.25** (canvas buffer
2000×1125 for a 1600×900 viewport; screenshots are captured at device px).
Never assume 1. App positions come in multiples of `dpr`; verify targeting
within ±1 px via `frames[].mx/my`.
## Click protocol
Click = move, press, refresh through the hold, release, verify — all
against rendered frames. The idle gate (§Idle) renders only 3 frames per
wake, and a hold with no mouse events at all refreshes nothing, so a
fixed-wait down/up from a cold idle can be swallowed (both events received,
zero frames rendered) or released after the wake budget expires. Issuing
mouse moves while the button is down keeps the budget alive: a same-coord
re-move is delivered (verified: it reaches the canvas handler, wakes the
app, and a same-coord hold click straddles cleanly) and is the safest
refresh — zero hover drift; a ±`dx` px wiggle works too, but must stay
inside the hit area:
```js
async function click(cssX, cssY, holdMs = 400) {
await page.mouse.move(cssX, cssY); // 1. ALWAYS move first
await new Promise(r => setTimeout(r, 150)); // 2. >= 1 rendered frame
const pre = await snap();
const pf = pre.frames.at(-1);
await clear();
await page.mouse.down(); // 3. press
let df = null, hiDuring = null;
const t0 = Date.now();
while (Date.now() - t0 < holdMs) {
await new Promise(r => setTimeout(r, 70));
await page.mouse.move(cssX, cssY); // 4. same-coord re-move refreshes the wake budget
const f = (await snap()).frames.at(-1);
if (f.md[0] === 1) { df ??= f.f; hiDuring = f.hi; }
}
await page.mouse.up(); // 5. release
let uf = null;
for (let i = 0; i < 40; i++) {
await new Promise(r => setTimeout(r, 40));
const f = (await snap()).frames.at(-1);
if (f.md[0] === 0) { uf = f.f; break; }
}
await new Promise(r => setTimeout(r, 400));
const s = await snap();
const d = s.dpr, ex = Math.floor(Math.trunc(cssX - s.rectL) * d), ey = Math.floor(Math.trunc(cssY - s.rectT) * d);
return { hoverID: pf.hi, hiDuring, appPos: [pf.mx, pf.my], expected: [ex, ey],
straddle: { df, uf },
ok: df !== null && uf !== null && uf > df && hiDuring === pf.hi &&
Math.abs(pf.mx - ex) <= 1 && Math.abs(pf.my - ey) <= 1 };
}
```
Rules:
1. **Move before every click.** The emscripten mousemove handler is the only
thing that sets the app mouse position; mousedown/mouseup only queue button
state. Fresh page position is invalid (`-FLT_MAX`) — a click without a prior
move activates nothing.
2. **Press and release must each render** in separate frames with the item
hovered (ImGui's `PressedOnClickRelease` — doc table at
`imgui_widgets.cpp:489-497`; input trickling,
`ConfigInputTrickleEventQueue`, defers a same-frame release). The
same-coord refresh is what guarantees rendered frames across the hold
from a cold idle.
3. **Verify every click:** `ok: true` (position landed, press and release each
rendered, hover held — `hiDuring === hoverID`) plus a screenshot diff.
Non-ImGui targets (timeline zone bars) have `hi = 0` by design — for those
verify by screenshot, not hover.
4. **Double-click trap:** two same-position clicks <300 ms apart = double-click
(`MouseDoubleClickTime = 0.3 s`) → zoom/thread-info actions fire. Wait
≥350 ms between repeated clicks at one spot.
5. **Drag:** move → down → (move + wait 70 ms)×n → up. **Wheel:**
`page.mouse.wheel({ deltaY: ±500 })` after positioning. **Keys:**
`page.keyboard.type(..., { delay: 50 })` after focusing the input by click;
`page.keyboard.press('Enter')`.
6. **Danger:** toolbar device x 1032 (css 826) = red power button (leftmost
toolbar icon, hover ID 3313137574), closes the trace view (back to "Get
started"). Never click it unless intended.
## Idle and power saving
`main.cpp DrawContents` keeps an `activeFrames = 3` budget refreshed on any
input event (`tracy::s_wasActive`), a connected client, view animation, or a
queued input; exhausted → `sleep 16 ms` with **no `ImGui::NewFrame` and no GL
draw** (canvas keeps the last frame). Expect ~97 % of ticks to skip rendering
while idle.
- Clicks from a cold idle land only via the click protocol's hold refresh
(§Click protocol) — a fixed-wait down/up can be swallowed by the idle gate
(both events received, zero frames rendered). **Before interacting, assert
`tick`/`f` is advancing**; a stalled main loop (initial trace parse, hidden
tab) renders nothing and drops everything. During the initial load the
cursor style is `wait`.
- `focusLostLimit` (reduce render rate on focus loss) is desktop-only; it does
not exist in the emscripten path.
- `canvas.style.cursor` is **not** a hover oracle: this ImGui build sets the
Hand cursor only in `TextLink`, not on buttons.
## Targeting
Screenshots never resolve targeting. Full-page shots arrive downscaled JPEG
(1024 px wide for a 2000-px device buffer; the factor varies), and
`page.screenshot({ clip })` returns a PNG at device scale — a W×H CSS clip
returns W·dpr × H·dpr px (CSS = clip offset + px/dpr). Icon buttons are
1525 device px wide, so visual estimates carry ±20 px error, enough to hit
the neighboring button or a gap. Target from the app's own hit-testing —
sweep the row and click the run's center:
```js
async function sweepRow(dpr, cssY, devFrom, devTo, step = 4) {
const ranges = []; let cur = null;
for (let dev = devFrom; dev <= devTo; dev += step) {
await page.mouse.move(dev / dpr, cssY);
await new Promise(r => setTimeout(r, 50));
const s = await snap();
const hi = s.frames[s.frames.length - 1].hi;
if (cur && hi === cur.id) cur.end = dev;
else { if (cur) ranges.push(cur); cur = { id: hi, start: dev, end: dev }; }
}
ranges.push(cur);
return ranges.filter(r => r.id !== 0 && r.end - r.start >= 4);
}
```
- Same non-zero `hi` across consecutive steps = one widget; `hi = 0` = gap.
Click the run's center: `click((r.start + r.end) / 2 / dpr, cssY)`.
- **Layout is state-dependent:** docked panels (Statistics, Flame, …) resize the
main window and shift the toolbar — **re-sweep after any state change.**
- **Close popups before sweeping:** while a popup is open, hovers outside the
popup can read `hi = 0` even over live widgets (verified with ZoomPopup over
the toolbar). A % click in ZoomPopup does not close the popup — click an
empty area first.
- Hover IDs are stable across sessions (label hashes) — the map below holds for
the default state (fresh load, no panels, 1600×900, dpr 1.25).
- Zoomed region shots (for correlating IDs with icons): `page.screenshot({clip})`
returns a Buffer — write it with Node `fs`, then `read` the file:
```js
const fs = require('fs');
const buf = await page.screenshot({ clip: { x: 0, y: 0, width: 720, height: 28 } });
fs.writeFileSync('/tmp/toolbar-zoom.png', buf);
```
### Toolbar map — default state (device px; css = device/1.25; row y css 15)
| device x | hover ID | function |
|---|---|---|
| 1032 | 3313137574 | ⏻ power (red, leftmost) — **closes the trace view (DANGER)** |
| 4468 | 608241489 | ⚙ Options gear — opens the Options window (clickable; do not confuse with the power button) |
| 81185 | 2246346107 | 💬 Messages panel toggle |
| 199259 | 3177774389 | 🔍 Find zone toggle (search input hover ID 3637961452, css ~x 100440, y ~90) |
| 271367 | 1419322276 | 📊 Statistics toggle |
| 383451 | 322727105 | 🔥 Flame graph toggle |
| 463551 | 37993378 | 💾 Memory graph toggle |
| 567667 | 2133895361 | ⚖ Compare toggle |
| 679735 | 1808024465 | ⌗ Info (trace information) toggle |
| 751775 | 2535445525 | 🛠 wrench → ToolsPopup (Playback, CPU data, Annotations, Limits, Wait stacks, Frame statistics) |
| 791811 | 3101356487 | 📖 book → User manual toggle |
| 827851 | 2335673581 | 🔍+ → ZoomPopup (50300 % user scale) |
| 864888 | 1263603982 | ◀ previous frame — focuses the timeline on the previous frame (view range shrinks to the frame's timespan) |
| ~8901010 | 0 | frame set name + count text ("Frames: N") — LMB jumps to a specified frame (custom hit-test, like timeline zones) |
| 10121036 | 2142681495 | ▶ next frame — focuses the timeline on the next frame (verified: view range 25.82 s → 203.38 ms) |
| 10521072 | 2961929287 | ▼ frame set selection (switch the active frame set) |
Button names and functions: the user manual, *Control menu* (`manual/tracy.md`,
generated into the profiler build directory by the build, e.g. `build/profiler/manual/tracy.md`). The
web build omits *Connection* (live capture only) and *Tracy Assist* (desktop only).
## User scale (DPI zoom)
The 🔍+ button (hover ID 2335673581) — *Display scale* in the user manual —
opens a ZoomPopup with 50300 % steps.
In the web build this scales the UI **layout only**: fonts,
`Style::ScaleAllSizes`, and window sizes (`main.cpp SetupDPIScale`,
`scale = devicePixelRatio × userScale`). The canvas buffer and the mouse→app
mapping stay at `devicePixelRatio` (`BackendEmscripten.cpp`) — hit regions
become physically larger in the same coordinate space, targeting precision is
unchanged, and the standard click protocol works as-is.
Use it when small buttons are hard to hit or details are hard to read in
screenshots. At 200 % every toolbar hit region is roughly 2× (verified: power
16→40 px, gear 25→50 px, Messages 105→190 px wide; row height 24→46 device
px). The layout **reflows completely** at any scale — re-sweep after changing
it and after setting it back. `userScale` is session-only unless Options →
"Save UI scale" is on; a fresh page load starts at 100 %.
## Limitations
- A *connected* live-streaming client cannot be tested in the web build (the
browser GUI cannot open a TCP server). The embedded-trace GUI is the
live-app proxy: real trace data, full GUI.
- `embed.tracy` (10 MB) is downloaded from share.nereid.pl at configure time —
network needed once per fresh workspace; the file then lives in `$WS/build`.
## References
Sources for the behavior the rules above encode — consult them when re-verifying
a constant or a rule fails:
- `profiler/src/BackendEmscripten.cpp` — emscripten input handlers, canvas/dpr
sizing, cursor mapping.
- `profiler/src/main.cpp:540-574` — idle gate (`activeFrames` budget).
- `profiler/src/profiler/TracyMouse.cpp` — per-frame click state cache.
- `profiler/CMakeLists.txt:334-353` — emscripten link options.
- ImGui 1.92.9b (vendored copy in the CPM cache, `~/.cache/cpm/imgui/`):
`imgui_widgets.cpp:483-545` (click semantics table), `imgui.cpp:11278-11403`
(event trickling), `imgui.cpp:1736` (trickle default on), `imgui.cpp:1992-2016`
(AddMousePosEvent floor/dedup).
- Emscripten 5.0.7: `src/lib/libhtml5.js` (`fillMouseEventData`,
`targetX = e.clientX - (rect.left|0)` into HEAP32), `system/include/emscripten/html5.h`
(`EmscriptenMouseEvent.targetX` is `int`; `EmscriptenWheelEvent` embeds a
`mouse` sub-struct).
- Upstream: imgui issue #4921 (new input event API — events spread over
multiple frames), PR #2525 (same-frame click/release), issue #1992 (rapid
down/up "soft clicks" dropped).

View File

@@ -1,221 +0,0 @@
diff -r a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt
--- a/profiler/CMakeLists.txt
+++ b/profiler/CMakeLists.txt
@@ -340,7 +340,7 @@
-sMAXIMUM_MEMORY=4gb
-sSTACK_SIZE=1048576
-sPTHREAD_POOL_SIZE=8
- -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
+ -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard,_debug_snapshot,_debug_clear
-sEXPORTED_RUNTIME_METHODS=ccall
-sENVIRONMENT=web,worker
--preload-file embed.tracy
diff -r a/profiler/src/BackendEmscripten.cpp b/profiler/src/BackendEmscripten.cpp
--- a/profiler/src/BackendEmscripten.cpp
+++ b/profiler/src/BackendEmscripten.cpp
@@ -1,6 +1,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <cstdarg>
+#include <algorithm>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
@@ -10,7 +12,146 @@
#include "Backend.hpp"
#include "RunQueue.hpp"
#include "profiler/TracyImGui.hpp"
+#include "imgui_internal.h"
+// ================= DEBUG HARNESS (temporary copy, not for upstream) =================
+struct DbgEvent
+{
+ int type; // 0=move 1=down 2=up 3=enter 4=leave 5=wheel
+ int button;
+ int targetX;
+ int targetY;
+ double appX; // what was passed to ImGui (targetX * dpr), -1 if n/a
+ double appY;
+ double t; // DOMHighResTimeStamp
+ int wheelX;
+ int wheelY;
+};
+struct DbgFrame
+{
+ double t;
+ int tick; // emscripten main-loop tick count (rAF)
+ int imguiFrame; // ImGui::GetFrameCount()
+ float mx, my; // io.MousePos
+ int md0, md1, md2;
+ int queue; // pending ImGui input events
+ int wasActive; // tracy::s_wasActive at tick start
+ unsigned hi; // ImGui hovered item ID
+ unsigned ai; // ImGui active item ID
+};
+static DbgEvent s_dbgEvents[512];
+static int s_dbgEventHead = 0, s_dbgEventN = 0;
+static DbgFrame s_dbgFrames[512];
+static int s_dbgFrameHead = 0, s_dbgFrameN = 0;
+static int s_dbgTick = 0;
+
+static void DbgPushEvent( int type, int button, int tx, int ty, double ax, double ay, double t, int wx = 0, int wy = 0 )
+{
+ auto& e = s_dbgEvents[s_dbgEventHead];
+ e.type = type; e.button = button; e.targetX = tx; e.targetY = ty;
+ e.appX = ax; e.appY = ay; e.t = t; e.wheelX = wx; e.wheelY = wy;
+ s_dbgEventHead = ( s_dbgEventHead + 1 ) % 512;
+ if ( s_dbgEventN < 512 ) s_dbgEventN++;
+}
+
+static void DbgPushFrame()
+{
+ auto& f = s_dbgFrames[s_dbgFrameHead];
+ auto& io = ImGui::GetIO();
+ f.t = EM_ASM_DOUBLE( { return performance.now(); } );
+ f.tick = s_dbgTick;
+ f.imguiFrame = ImGui::GetFrameCount();
+ f.mx = io.MousePos.x; f.my = io.MousePos.y;
+ f.md0 = io.MouseDown[0]; f.md1 = io.MouseDown[1]; f.md2 = io.MouseDown[2];
+ auto ctx = ImGui::GetCurrentContext();
+ f.queue = ctx ? int( ctx->InputEventsQueue.Size ) : -1;
+ f.wasActive = tracy::s_wasActive;
+ f.hi = ctx ? ctx->HoveredId : 0;
+ f.ai = ctx ? ctx->ActiveId : 0;
+ s_dbgFrameHead = ( s_dbgFrameHead + 1 ) % 512;
+ if ( s_dbgFrameN < 512 ) s_dbgFrameN++;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE void debug_clear()
+{
+ s_dbgEventHead = s_dbgEventN = s_dbgFrameHead = s_dbgFrameN = 0;
+}
+
+// Escape a C string for embedding in a JSON string literal (", \, control chars).
+static void JsonEscape( char* out, size_t outsz, const char* in )
+{
+ size_t o = 0;
+ for( const unsigned char* p = ( const unsigned char* )in; *p; p++ )
+ {
+ if ( *p == '"' || *p == '\\' )
+ {
+ if ( o + 2 >= outsz ) break;
+ out[o++] = '\\';
+ out[o++] = char( *p );
+ }
+ else if ( *p < 0x20 )
+ {
+ if ( o + 6 >= outsz ) break;
+ o += size_t( snprintf( out + o, outsz - o, "\\u%04x", *p ) );
+ }
+ else
+ {
+ if ( o + 1 >= outsz ) break;
+ out[o++] = char( *p );
+ }
+ }
+ out[o] = 0;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE const char* debug_snapshot()
+{
+ static char buf[512 * 1024];
+ size_t off = 0;
+ auto put = [ & ]( const char* fmt, ... )
+ {
+ if ( off + 1 >= sizeof( buf ) ) return;
+ va_list args;
+ va_start( args, fmt );
+ int n = vsnprintf( buf + off, sizeof( buf ) - off - 1, fmt, args );
+ va_end( args );
+ if ( n > 0 ) off = std::min( off + size_t( n ), sizeof( buf ) - 1 );
+ };
+ const auto dpr = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
+ const auto innerW = EM_ASM_INT( { return window.innerWidth; } );
+ const auto innerH = EM_ASM_INT( { return window.innerHeight; } );
+ const auto bufW = EM_ASM_INT( { return document.getElementById( 'canvas' ).width; } );
+ const auto bufH = EM_ASM_INT( { return document.getElementById( 'canvas' ).height; } );
+ const auto rectL = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.left; } );
+ const auto rectT = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.top; } );
+ const auto rectW = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.width; } );
+ const auto rectH = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.height; } );
+ char title[256];
+ EM_ASM( { stringToUTF8( document.title, $0, 256 ); }, title );
+ char cursor[64];
+ EM_ASM( { stringToUTF8( document.getElementById( 'canvas' ).style.cursor || "", $0, 64 ); }, cursor );
+ char title_e[1536];
+ char cursor_e[384];
+ JsonEscape( title_e, sizeof( title_e ), title );
+ JsonEscape( cursor_e, sizeof( cursor_e ), cursor );
+ put( "{\"dpr\":%.6f,\"innerW\":%d,\"innerH\":%d,\"bufW\":%d,\"bufH\":%d,\"rectL\":%.3f,\"rectT\":%.3f,\"rectW\":%.3f,\"rectH\":%.3f,\"title\":\"%s\",\"cursor\":\"%s\",\"frames\":[", dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH, title_e, cursor_e );
+ const int fn = std::min( s_dbgFrameN, 64 );
+ for ( int i = 0; i < fn; i++ )
+ {
+ auto& f = s_dbgFrames[ ( s_dbgFrameHead - fn + i + 512 ) % 512 ];
+ put( "%s{\"t\":%.3f,\"tick\":%d,\"f\":%d,\"mx\":%.1f,\"my\":%.1f,\"md\":[%d,%d,%d],\"q\":%d,\"wa\":%d,\"hi\":%u,\"ai\":%u}", i ? "," : "", f.t, f.tick, f.imguiFrame, f.mx, f.my, f.md0, f.md1, f.md2, f.queue, f.wasActive, f.hi, f.ai );
+ }
+ put( "],\"events\":[");
+ const int en = std::min( s_dbgEventN, 64 );
+ for ( int i = 0; i < en; i++ )
+ {
+ auto& e = s_dbgEvents[ ( s_dbgEventHead - en + i + 512 ) % 512 ];
+ put( "%s{\"type\":%d,\"btn\":%d,\"tx\":%d,\"ty\":%d,\"ax\":%.3f,\"ay\":%.3f,\"t\":%.3f,\"wx\":%d,\"wy\":%d}", i ? "," : "", e.type, e.button, e.targetX, e.targetY, e.appX, e.appY, e.t, e.wheelX, e.wheelY );
+ }
+ put( "]}");
+ buf[off] = 0;
+ return buf;
+}
+
static std::function<void()> s_redraw;
static std::function<void(float)> s_scaleChanged;
static std::function<int(void)> s_isBusy;
@@ -223,32 +364,38 @@
emscripten_set_mousedown_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, true );
tracy::s_wasActive = true;
+ DbgPushEvent( 1, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_mouseup_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, false );
tracy::s_wasActive = true;
+ DbgPushEvent( 2, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_mousemove_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
const auto scale = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
ImGui::GetIO().AddMousePosEvent( e->targetX * scale, e->targetY * scale );
tracy::s_wasActive = true;
+ DbgPushEvent( 0, -1, e->targetX, e->targetY, e->targetX * scale, e->targetY * scale, e->timestamp );
return EM_TRUE;
} );
- emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
+ emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( false );
tracy::s_wasActive = true;
+ DbgPushEvent( 4, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
- emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
+ emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( true );
tracy::s_wasActive = true;
+ DbgPushEvent( 3, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_wheel_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenWheelEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseWheelEvent( e->deltaX * -0.05, e->deltaY * -0.05 );
tracy::s_wasActive = true;
+ DbgPushEvent( 5, -1, e->mouse.targetX, e->mouse.targetY, -1, -1, e->mouse.timestamp, int( e->deltaX ), int( e->deltaY ) );
return EM_TRUE;
} );
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
@@ -298,6 +445,8 @@
void Backend::NewFrame( int& w, int& h )
{
+ s_dbgTick++;
+ DbgPushFrame();
const auto scale = GetDpiScale();
if( scale != s_prevScale )
{

View File

@@ -1,102 +0,0 @@
#!/bin/sh
# setup.sh - build the instrumented Tracy web GUI workspace (see SKILL.md).
#
# Usage:
# sh setup.sh
# TRACY_WEB_WS=/custom/ws TRACY_WEB_REPO=<repo> sh setup.sh
#
# Idempotent: reuses an existing source copy and build dir, skips
# already-applied harness patch hunks, rebuilds.
set -e
WS="${TRACY_WEB_WS:-/tmp/tracy-protocol}"
# The skill is project-scoped: <repo>/.omp/skills/tracy-gui-verify/
SKILL_DIR=$(cd "$(dirname "$0")" && pwd)
if [ -n "$TRACY_WEB_REPO" ]; then
REPO="$TRACY_WEB_REPO"
else
REPO=$(cd "$SKILL_DIR/../../.." 2>/dev/null && pwd) || REPO=""
fi
EMSDK="${EMSDK_DIR:-$HOME/emsdk}"
[ -x "$EMSDK/emsdk" ] || { echo "error: emsdk not found at $EMSDK (set EMSDK_DIR)" >&2; exit 1; }
[ -f "$REPO/profiler/CMakeLists.txt" ] || { echo "error: not a Tracy repo: '$REPO' (set TRACY_WEB_REPO)" >&2; exit 1; }
. "$EMSDK/emsdk_env.sh" >/dev/null 2>&1 || true
command -v emcc >/dev/null || { echo "error: emcc not on PATH after emsdk_env.sh" >&2; exit 1; }
if [ ! -f "$WS/src/profiler/CMakeLists.txt" ]; then
echo "[setup] fresh source copy -> $WS/src (from $REPO)"
mkdir -p "$WS/src"
# -C applies to both archive creation and extraction; exclude all build dirs
tar -C "$REPO" \
--exclude=.git \
--exclude='*/build*' \
-cf - . | tar -xf - -C "$WS/src"
else
echo "[setup] reusing source copy in $WS/src (copy changed repo files into it first)"
fi
# The harness is fully in place iff each patch hunk's signature line is present.
# patch --forward skips a whole file once its first hunk is detected as already
# applied, so a failed later hunk is invisible in patch's output and exit code —
# verify the content instead of trusting either.
harness_complete()
{
grep -q '_debug_snapshot' "$WS/src/profiler/CMakeLists.txt" \
&& grep -qF 'EMSCRIPTEN_KEEPALIVE const char* debug_snapshot' "$WS/src/profiler/src/BackendEmscripten.cpp" \
&& grep -qF 'DbgPushEvent( 1,' "$WS/src/profiler/src/BackendEmscripten.cpp" \
&& grep -qF 's_dbgTick++;' "$WS/src/profiler/src/BackendEmscripten.cpp"
}
echo "[setup] applying instrumentation harness"
if harness_complete; then
echo "[setup] harness already applied"
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
else
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
patch -p1 -d "$WS/src" --forward --no-backup-if-mismatch < "$SKILL_DIR/harness.patch" \
|| {
if harness_complete; then
# mixed run: some hunks already present (skipped), the rest applied
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
else
rej=$(ls "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej" 2>/dev/null || true)
if [ -n "$rej" ]; then
kept="The unapplied hunks are kept in:
$(printf '%s\n' $rej | sed 's/^/ /')"
else
kept="No .rej files were produced (fatal patch error); the patch output above is the only evidence."
fi
cat >&2 <<EOF
error: harness patch did not fully apply — $WS/src has drifted from the repo.
$kept
Do not build from this workspace. Either re-apply the harness by hand
(SKILL.md, "After editing the GUI in the repo"), or start fresh:
rm -rf $WS && sh <this-skill-dir>/setup.sh
EOF
exit 1
fi
}
fi
if [ ! -f "$WS/build/CMakeCache.txt" ]; then
echo "[setup] configuring"
cmake -S "$WS/src/profiler" -B "$WS/build" \
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCPM_SOURCE_CACHE="$HOME/.cache/cpm"
fi
echo "[setup] building (first full build ~3-5 min, incremental ~30 s)"
cmake --build "$WS/build" -j "$(nproc)"
cat <<EOF
[setup] done.
workspace : $WS
repo : $REPO
emcc : $(emcc --version | sed -n 1p)
serve : hub op=start name=tracy-web application=python3 args=[$WS/build/httpd.py] cwd=$WS/build ready={port:8000} persist=true
open : browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
ready : document.title matches / - Tracy Profiler/ (see SKILL.md)
EOF

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,50 +108,67 @@ 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_WAIT_STACKS "Disable capture of context switch call stacks" 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_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, or from repeated allocations of the same address" 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()
# handle incompatible combinations
if(TRACY_MANUAL_LIFETIME AND NOT TRACY_DELAYED_INIT)
message(FATAL_ERROR "TRACY_MANUAL_LIFETIME can not be activated with disabled TRACY_DELAYED_INIT")
endif()
if(TRACY_DISALLOW_HW_TIMER AND NOT TRACY_TIMER_FALLBACK)
message(FATAL_ERROR "TRACY_DISALLOW_HW_TIMER can not be activated with disabled TRACY_TIMER_FALLBACK")
endif()
@@ -179,8 +196,7 @@ set(tracy_includes
${TRACY_PUBLIC_DIR}/tracy/TracyMetal.hmm
${TRACY_PUBLIC_DIR}/tracy/TracyOpenCL.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyOpenGL.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyWebGPU.hpp)
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp)
set(client_includes
${TRACY_PUBLIC_DIR}/client/tracy_concurrentqueue.h
@@ -211,19 +227,15 @@ set(common_includes
${TRACY_PUBLIC_DIR}/common/TracyAlign.hpp
${TRACY_PUBLIC_DIR}/common/TracyAlloc.hpp
${TRACY_PUBLIC_DIR}/common/TracyApi.h
${TRACY_PUBLIC_DIR}/common/TracyAssert.hpp
${TRACY_PUBLIC_DIR}/common/TracyColor.hpp
${TRACY_PUBLIC_DIR}/common/TracyForceInline.hpp
${TRACY_PUBLIC_DIR}/common/TracyFormat.h
${TRACY_PUBLIC_DIR}/common/TracyMutex.hpp
${TRACY_PUBLIC_DIR}/common/TracyProtocol.hpp
${TRACY_PUBLIC_DIR}/common/TracyQueue.hpp
${TRACY_PUBLIC_DIR}/common/TracySocket.hpp
${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp
${TRACY_PUBLIC_DIR}/common/TracyString.hpp
${TRACY_PUBLIC_DIR}/common/TracySystem.hpp
${TRACY_PUBLIC_DIR}/common/TracyTaggedUserlandAddress.hpp
${TRACY_PUBLIC_DIR}/common/TracyVersion.hpp
${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp
${TRACY_PUBLIC_DIR}/common/TracyYield.hpp)
@@ -276,11 +288,6 @@ configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TracyConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
COMPATIBILITY SameMinorVersion)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
option(TRACY_CLIENT_PYTHON "Whether to build Tracy python client library" OFF)
@@ -291,7 +298,3 @@ if(TRACY_CLIENT_PYTHON)
add_subdirectory(python)
endif()
if(PROJECT_IS_TOP_LEVEL)
set(CMAKE_COLOR_DIAGNOSTICS ON)
endif()

View File

@@ -1,7 +1,5 @@
@PACKAGE_INIT@
set(TRACY_VERSION_STRING "@TRACY_VERSION_STRING@")
include(CMakeFindDependencyMacro)
find_dependency(Threads REQUIRED)

204
NEWS
View File

@@ -2,69 +2,12 @@ Note: There is no guarantee that version mismatched client and server will
be able to talk with each other. Network protocol breakages won't be listed
here.
v0.14.1 (2026-08-22)
vx.xx.x (2026-xx-xx)
--------------------
- Dropped support for loading pre-0.11 traces.
- Silenced MSVC warning C4366: The result of the unary '&' operator may be
unaligned.
- Fixed section setup in on-demand mode.
- Hardened string length encoding in protocol.
- Fixed compilation failure related to VSync when targeting 32-bit on
Windows.
- Added TRACY_NO_WAIT_STACKS compile time macro.
- Improved documentation of requirements in edge cases:
- Memory allocation reporting needs to be done under a lock.
- Manual lifetime management requires shutdown of the profiler.
- Documented how zones behave when crossing the on-demand connection
boundary.
- Mention that statistics time range limit also affects source view.
- Memory events are now attributed to virtual fiber threads.
- Added TRACY_ASSERT macro allowing user-defined handling of asserts in the
profiler.
- Fixed csvexport options: -t, --sep, --filter.
- Configuration of plot color now matches the documented RGB channels order.
- Link profiler executable with libdl (required by old glibc versions).
- Improve coverage of link-time macro mismatch detection.
- Removed TRACY_DELAYED_INIT option. It was no-op since 2020.
- Fixed illegal instruction in rpmalloc on ARMv6 and older.
- Fixed handling of source code queries in no-callstack builds. Previously
the profiling session would hang at exit.
- Hardened Lua on-demand handling.
- Fixed system tracing on Linux kernels older than 4.1.
- Linux system tracing setup failures are now reported via Tracy debug
message functionality.
- Improved version handling in CMake / Meson / Python build systems.
- Fixed D3D12 readback buffer error.
- Child zone list in zone info window can be now sorted by name or by time.
- File dialog failures (e.g. related to the XDG portal) are now reported
in the modal dialog.
- Fixed NO_FILESELECTOR builds.
- Fixed possible profiler hang when there was not enough space to draw
a plot on the timeline.
- Update check can be now interrupted, e.g. when exiting the profiler.
- Patched the file selector library to use modern Wayland protocol for
setting parent-child window relation.
v0.14.0 (2026-08-09)
--------------------
- 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 printf-style variants of the C API zone text and name macros:
TracyCZoneTextF and TracyCZoneNameF now accept a format string and
arguments, instead of a fixed-length text buffer.
- 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.
- Binary releases are now also provided for macOS (needs manual quarantine
handling) and Linux (AppImage with Ubuntu 24.04 base, requires Wayland).
- 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
@@ -72,44 +15,9 @@ v0.14.0 (2026-08-09)
- Flame graph window.
- Call stack window.
- Statistics window (sampling mode).
- Wait stacks have a separate setting for external frames, as you would
typically want to know what caused the wait state, which can be inferred
from the external code.
- External frames are now dimmed out in call stacks in various parts of UI.
- Sample markers on the timeline are now colored according to sample type:
- Green for samples in the profiled program,
- Blue for samples in external code,
- Red for samples in kernel.
- External frames are now dimmed out in call stacks.
- Single-line call stacks now have ellipsis at the end, if there are frames
remaining.
- Sample entry stacks window can now also show the stacks by which a symbol
was reached, i.e. it was present anywhere on the call stack, not only when
it was executing. Recursive re-entries can be either ignored or counted
separately.
- Entry stacks are now also available for symbols which were only reached,
but never directly sampled, e.g. in the assembly view of a call site with
purely child sample costs, or in the statistics window for symbols which
only have inclusive counts.
- Fixed inclusive sample counts of symbols with inline functions. Each
sample was counted multiple times: once for the symbol itself and once for
every of its inline functions present on the call stack.
- Fixed context switch samples leaking into per-symbol sample lists and
child sample data on trace load. This inflated range-limited sample counts
and child call costs, and made loaded traces disagree with live sessions,
where such samples are excluded from sampling statistics.
- Fixed wrong results of range-limited child call queries during live
capture, when some samples had to wait for context switch data to arrive.
- Fixed crashes when opening the sample entry stacks window for symbols
without any sampling data.
- Sampling statistics percentages are now calculated against the number of
samples that were actually collected. Previously, enabling the range
filter switched the calculation to a theoretical single-thread estimate,
which changed both the meaning and the scale of the displayed values.
- Find zone sample count percentages are now relative to the matched zones,
consistent with the time percentages in the same list, instead of being
relative to the whole trace.
- Context switch samples are now consistently excluded from all sampling
statistics views.
- System tracing on Windows has been refactored to be more robust.
- Tracing on Arm macOS will now have more precise timer readings.
- Extended CUDA support to track some previously missing memory operations.
@@ -121,8 +29,7 @@ v0.14.0 (2026-08-09)
- Added inline call stack list to the zone information window.
- The "call stack" button for opening the call stack in a separate window
is still there.
- Call stacks are now also displayed in zone and memory allocation tooltips
(single-line).
- Call stacks are now also displayed in zone tooltips (single-line).
- Implemented heuristic reconstruction of zone call stacks for zones that
were captured without call stacks.
- Requires sampling to be enabled, and at least one sample in the zone
@@ -135,15 +42,10 @@ v0.14.0 (2026-08-09)
- 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.
@@ -164,16 +66,13 @@ v0.14.0 (2026-08-09)
- 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.
- It is now possible to alter the assistant personality.
- 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.
@@ -197,7 +96,6 @@ v0.14.0 (2026-08-09)
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
@@ -209,8 +107,6 @@ v0.14.0 (2026-08-09)
- Added TRACY_DISALLOW_HW_TIMER define for virtualized environments and WSL2,
which may not have reliable access to hardware timer registers. Falls back
to standard library timer with reduced resolution.
- Fixed TRACY_NO_CODE_TRANSFER define. It didn't prevent source code transfer
before.
- Fixed DPI scaling on macOS.
- Thread names are no longer truncated to 15 characters on Apple.
- Executable path is now inspected when looking for PDB files on Windows.
@@ -240,20 +136,9 @@ v0.14.0 (2026-08-09)
- Mismatched versions will break at linking.
- As a reminder, Tracy *always* required using the same set of compilation
options for the entire program.
- Message windows were improved.
- Full message will be now displayed in a tooltip for multi-line messages.
- Added right click context menu with option to copy message to clipboard.
- 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.
- Message windows will now properly show full message in a tooltip for
multi-line messages.
- 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
@@ -268,83 +153,6 @@ v0.14.0 (2026-08-09)
- 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.
- Flame graph improvements.
- The flame graph view now supports zooming in and panning with the mouse.
- Sampling flame graph items can be now also grouped by name, in addition
to symbol address.
- Sampling flame graph no longer omits display of unknown symbols. These
are now aggregated into blocks named after the executable image.
- 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.
- It is now possible to create annotations from time range limits.
- 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.
- Sections can be assigned to user-defined categories.
- Can be used to set time range limits.
- Exposed via Lua bindings.
- 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.
- Added a new time range limit for frame statistics. This accompanies the
previously existing "limit to view" option.
- More robust statistics for functions that recursively call themselves.
- General uplift of the compare traces window.
- Added all the statistical parameters displayed in find zone window that
were missing here.
- Frame comparison now also displays FPS values.
- The "more" and "less" indicators are now color coded to show their
importance. The amount of color these keywords are printed with is now
dependant on how divergent the numeric values are.
- It is now possible to limit time range for both traces that are compared
to their respective annotations or sections.
- Frame image playback improvements.
- Playback can be looped.
- The range of played frame images can be limited, either manually, or by
copying annotation, section, or time range limit. There's an "require
frame coverage" option to only include frames that are at least 75%
within the source region.
- Most windows now have a minimum width, in order to make sure all controls
provided in the window are visible. Previously it was easy to oversee some
button that was out of the window draw area.
- The ignore memory faults functionality (enabled by default if profiling
programs on Apple platforms) will now also ignore allocating memory at the
same location twice, without an intermediate free. Thanks, Tim Apple!
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,31 +0,0 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -137,7 +137,6 @@ int main( int argc, char** argv )
{
worker.Disconnect();
s_disconnect.store(false, std::memory_order_relaxed );
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
break;
}

View File

@@ -168,13 +168,7 @@ void CaptureThread( ClientSession* session, const std::string& address, uint16_t
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
if( worker.IsConnected() )
{
worker.Disconnect();
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
printf( "\nSaving %s...", outputFile.c_str() );
fflush( stdout );

View File

@@ -11,7 +11,7 @@ function(add_git_ref target)
if(Git_FOUND)
add_custom_target(git-ref
COMMAND ${CMAKE_COMMAND} -E echo "#pragma once" > GitRef.hpp.tmp
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} log -1 "--format=namespace tracy { static inline const char* GitRef = %x22%h%x22; }" ${GIT_REV} >> GitRef.hpp.tmp || ${CMAKE_COMMAND} -E echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp.tmp
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} log -1 "--format=namespace tracy { static inline const char* GitRef = %x22%h%x22; }" ${GIT_REV} >> GitRef.hpp.tmp || echo "namespace tracy { static inline const char* GitRef = \"unknown\"; }" >> GitRef.hpp.tmp
COMMAND ${CMAKE_COMMAND} -E copy_if_different GitRef.hpp.tmp GitRef.hpp
BYPRODUCTS GitRef.hpp GitRef.hpp.tmp
VERBATIM

View File

@@ -1,13 +0,0 @@
diff --git i/imconfig.h w/imconfig.h
index b40db3898..561ab7782 100644
--- i/imconfig.h
+++ w/imconfig.h
@@ -42,7 +42,7 @@
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
-//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
+#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_TIME_FUNCTIONS // Don't setup default platform_io.Platform_SessionDate value using time(), localtime_r().
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)

View File

@@ -1,13 +0,0 @@
diff --git i/imconfig.h w/imconfig.h
index b40db3898..f7f864394 100644
--- i/imconfig.h
+++ w/imconfig.h
@@ -50,7 +50,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
-//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
+#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT_BITMAP // Disable default embedded bitmap font (ProggyClean). Remove ~9 KB from output binary. AddFontDefaultBitmap() will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT_VECTOR // Disable default embedded vector font (ProggyForever), Remove ~14 KB from output binary. AddFontDefaultVector() will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available

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,14 +0,0 @@
diff --git a/imgui_tables.cpp b/imgui_tables.cpp
--- a/imgui_tables.cpp
+++ b/imgui_tables.cpp
@@ -4082,7 +4082,9 @@
}
if (load_flags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
- else
+ else if (column_settings->Index != -1)
+ // An Index of -1 means this column had no "Column N" line in the .ini file (columns without saved
+ // data are not written). Keep the display order as initialized (identity for non-reorderable tables). (#9519)
column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults()
if ((load_flags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1);

View File

@@ -1,88 +0,0 @@
set(PANDOC_VERSION 3.9.0.2)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
find_program(TRACY_PANDOC pandoc)
if(TRACY_PANDOC)
execute_process(
COMMAND ${TRACY_PANDOC} --version
OUTPUT_VARIABLE _pandoc_version
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _pandoc_rc
ERROR_QUIET)
string(REGEX MATCH "pandoc [0-9.]+" _pandoc_version "${_pandoc_version}")
if(NOT _pandoc_rc EQUAL 0 OR NOT _pandoc_version STREQUAL "pandoc ${PANDOC_VERSION}")
set(TRACY_PANDOC "")
endif()
endif()
if(TRACY_PANDOC)
message(STATUS "Using system pandoc ${PANDOC_VERSION}: ${TRACY_PANDOC}")
else()
if(WIN32)
if(NOT CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
endif()
set(_pandoc_asset pandoc-${PANDOC_VERSION}-windows-x86_64.zip)
set(_pandoc_hash c97542f2800f446e788d9f74237856d995421ad1bb3cc8324286840c5f272d3a)
set(_pandoc_exe pandoc-${PANDOC_VERSION}/pandoc.exe)
elseif(APPLE)
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-arm64-macOS.zip)
set(_pandoc_hash 6e9eca844076bcbb599bbeebbba78a70f93b5307782b85c2c272872812c88875)
set(_pandoc_exe pandoc-${PANDOC_VERSION}-arm64/bin/pandoc)
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-x86_64-macOS.zip)
set(_pandoc_hash b9fbceabccbc8f34ac021a50483fc32f8160568d0b4b2c22d81bb29e3054fd82)
set(_pandoc_exe pandoc-${PANDOC_VERSION}-x86_64/bin/pandoc)
else()
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
endif()
else()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-linux-arm64.tar.gz)
set(_pandoc_hash b6d21e8f9c3b15744f5a7ab40248019157ed7793875dbe0383d4c82ff572b528)
set(_pandoc_exe pandoc-${PANDOC_VERSION}/bin/pandoc)
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
set(_pandoc_asset pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz)
set(_pandoc_hash a69abfababda8a56969a254b09f9553a7be89ddec00d4e0fe9fd585d71a67508)
set(_pandoc_exe pandoc-${PANDOC_VERSION}/bin/pandoc)
else()
message(FATAL_ERROR "No pinned pandoc ${PANDOC_VERSION} binary for ${CMAKE_HOST_SYSTEM_PROCESSOR}; install pandoc ${PANDOC_VERSION}")
endif()
endif()
# Shared cache: $PANDOC_CACHE_DIR, else the platform user cache directory.
if(DEFINED ENV{PANDOC_CACHE_DIR})
set(_pandoc_cache $ENV{PANDOC_CACHE_DIR})
elseif(WIN32)
set(_pandoc_cache $ENV{LOCALAPPDATA}/tracy/pandoc)
elseif(APPLE)
set(_pandoc_cache $ENV{HOME}/Library/Caches/tracy/pandoc)
elseif(DEFINED ENV{XDG_CACHE_HOME})
set(_pandoc_cache $ENV{XDG_CACHE_HOME}/tracy/pandoc)
else()
set(_pandoc_cache $ENV{HOME}/.cache/tracy/pandoc)
endif()
set(TRACY_PANDOC ${_pandoc_cache}/${_pandoc_exe})
if(NOT EXISTS ${TRACY_PANDOC})
# Concurrent configures on a cold cache can collide; worst case the
# hash check fails and a rerun succeeds.
message(STATUS "Downloading pandoc ${PANDOC_VERSION} (${_pandoc_asset})")
file(MAKE_DIRECTORY ${_pandoc_cache})
file(DOWNLOAD
https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/${_pandoc_asset}
${_pandoc_cache}/pandoc.archive
EXPECTED_HASH SHA256=${_pandoc_hash}
STATUS _pandoc_download)
list(GET _pandoc_download 0 _pandoc_download_rc)
if(NOT _pandoc_download_rc STREQUAL "0")
file(REMOVE ${_pandoc_cache}/pandoc.archive)
message(FATAL_ERROR "Failed to download pandoc ${PANDOC_VERSION}: ${_pandoc_download}")
endif()
file(ARCHIVE_EXTRACT
INPUT ${_pandoc_cache}/pandoc.archive
DESTINATION ${_pandoc_cache})
file(REMOVE ${_pandoc_cache}/pandoc.archive)
endif()
endif()

View File

@@ -1,203 +0,0 @@
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d6a9edc..abef8d7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,18 +32,18 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
if(NFD_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED wayland-client)
message(STATUS "Using Wayland version: ${WAYLAND_VERSION}")
- set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml)
+ set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml)
add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
- COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
+ COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
- COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
+ COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
- list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c)
+ list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c)
endif()
endif()
diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp
index 76fdcd7..cec691d 100644
--- a/src/nfd_gtk.cpp
+++ b/src/nfd_gtk.cpp
@@ -433,17 +433,17 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
#if defined(NFD_WAYLAND)
void DestroyXdgExported(void* context) {
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
}
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
GdkWindow* childWindow = static_cast<GdkWindow*>(context);
// GDK doesn't modify the string, even though it takes char*, so we can cast away the constness
gdk_wayland_window_set_transient_for_exported(childWindow, const_cast<char*>(handle));
}
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
- &zxdg_exported_v1_handle};
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
+ &zxdg_exported_v2_handle};
#endif
// This is an RAII class that wraps the parenting of a GtkWidget (the file dialog).
@@ -645,17 +645,17 @@ struct NativeWindowParenter {
#if defined(NFD_WAYLAND)
void SetParentWayland(GdkWindow* childWindow) {
- if (wayland_display && wayland_xdg_exporter_v1) {
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindowHandle));
+ if (wayland_display && wayland_xdg_exporter_v2) {
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindowHandle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
return;
}
- zxdg_exported_v1_add_listener(
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(childWindow));
+ zxdg_exported_v2_add_listener(
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(childWindow));
wl_display_roundtrip(wayland_display);
- zxdg_exported_v1_set_user_data(exported, nullptr);
+ zxdg_exported_v2_set_user_data(exported, nullptr);
destroy.fn = &DestroyXdgExported;
destroy.context = static_cast<void*>(exported);
}
diff --git a/src/nfd_linux_shared.hpp b/src/nfd_linux_shared.hpp
index 1413bac..e826f1e 100644
--- a/src/nfd_linux_shared.hpp
+++ b/src/nfd_linux_shared.hpp
@@ -14,7 +14,7 @@
#ifdef NFD_WAYLAND
#include <wayland-client.h>
-#include "xdg-foreign-unstable-v1.h"
+#include "xdg-foreign-unstable-v2.h"
#endif
namespace {
@@ -81,9 +81,9 @@ nfdnchar_t* emit_case_insensitive_glob(const nfdnchar_t* begin,
#ifdef NFD_WAYLAND
struct wl_display* wayland_display;
struct wl_registry* wayland_registry;
-uint32_t wayland_xdg_exporter_v1_name;
-struct zxdg_exporter_v1* wayland_xdg_exporter_v1;
-constexpr const char* XDG_EXPORTER_V1 = "zxdg_exporter_v1";
+uint32_t wayland_xdg_exporter_v2_name;
+struct zxdg_exporter_v2* wayland_xdg_exporter_v2;
+constexpr const char* XDG_EXPORTER_V2 = "zxdg_exporter_v2";
#endif
void EmptyFn(void*) {}
@@ -103,19 +103,19 @@ void registry_handle_global(void* context,
uint32_t version) {
(void)context;
(void)version;
- if (strcmp(interface, XDG_EXPORTER_V1) == 0) {
- wayland_xdg_exporter_v1_name = name;
- wayland_xdg_exporter_v1 = static_cast<struct zxdg_exporter_v1*>(wl_registry_bind(
- registry, name, &zxdg_exporter_v1_interface, zxdg_exporter_v1_interface.version));
+ if (strcmp(interface, XDG_EXPORTER_V2) == 0) {
+ wayland_xdg_exporter_v2_name = name;
+ wayland_xdg_exporter_v2 = static_cast<struct zxdg_exporter_v2*>(wl_registry_bind(
+ registry, name, &zxdg_exporter_v2_interface, zxdg_exporter_v2_interface.version));
}
}
void registry_handle_global_remove(void* context, struct wl_registry* registry, uint32_t name) {
(void)context;
(void)registry;
- if (wayland_xdg_exporter_v1 && name == wayland_xdg_exporter_v1_name) {
- zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
- wayland_xdg_exporter_v1 = nullptr;
+ if (wayland_xdg_exporter_v2 && name == wayland_xdg_exporter_v2_name) {
+ zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
+ wayland_xdg_exporter_v2 = nullptr;
}
}
@@ -128,7 +128,7 @@ void NFD_Wayland_Init(void) {
void NFD_Wayland_Quit(void) {
if (wayland_display) {
- if (wayland_xdg_exporter_v1) zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
+ if (wayland_xdg_exporter_v2) zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
wl_registry_destroy(wayland_registry);
}
}
@@ -143,7 +143,7 @@ nfdresult_t NFD_SetWaylandDisplay(wl_display* display) {
wayland_display = display;
if (wayland_display) {
wayland_registry = wl_display_get_registry(wayland_display);
- wayland_xdg_exporter_v1 = nullptr;
+ wayland_xdg_exporter_v2 = nullptr;
// seems like registry can't be null
wl_registry_add_listener(wayland_registry, &wayland_registry_listener, nullptr);
wl_display_roundtrip(wayland_display);
diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp
index df840e0..e66ca7f 100644
--- a/src/nfd_portal.cpp
+++ b/src/nfd_portal.cpp
@@ -148,10 +148,10 @@ constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";
constexpr const char* WAYLAND_PREFIX = "wayland:";
void DestroyXdgExported(void* context) {
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
}
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
DBusMessageIter& iter = *static_cast<DBusMessageIter*>(context);
const size_t handle_len = strlen(handle);
const size_t prefix_len = strlen(WAYLAND_PREFIX);
@@ -163,8 +163,8 @@ void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char
NFDi_Free(buf);
}
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
- &zxdg_exported_v1_handle};
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
+ &zxdg_exported_v2_handle};
#endif
void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
@@ -194,18 +194,18 @@ void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
#endif
#ifdef NFD_WAYLAND
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
- if (wayland_display && wayland_xdg_exporter_v1) {
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindow.handle));
+ if (wayland_display && wayland_xdg_exporter_v2) {
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindow.handle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
return;
}
- zxdg_exported_v1_add_listener(
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(&iter));
+ zxdg_exported_v2_add_listener(
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(&iter));
wl_display_roundtrip(wayland_display);
- zxdg_exported_v1_set_user_data(exported, nullptr);
+ zxdg_exported_v2_set_user_data(exported, nullptr);
destroy.fn = &DestroyXdgExported;
destroy.context = static_cast<void*>(exported);
return;

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

@@ -8,20 +8,11 @@ set (ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../")
include(FindPkgConfig)
include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake)
if(APPLE AND BUNDLE)
set(DLOPT ON)
else()
set(DLOPT OFF)
endif()
option(DOWNLOAD_CAPSTONE "Force download capstone" ON)
if(VENDOR_GUI)
option(DOWNLOAD_GLFW "Force download glfw" ${DLOPT})
option(DOWNLOAD_FREETYPE "Force download freetype" ${DLOPT})
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
option(DOWNLOAD_PUGIXML "Force download pugixml" ${DLOPT})
endif()
option(DOWNLOAD_GLFW "Force download glfw" OFF)
option(DOWNLOAD_FREETYPE "Force download freetype" OFF)
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
option(DOWNLOAD_PUGIXML "Force download pugixml" OFF)
# capstone
@@ -35,7 +26,7 @@ else()
CPMAddPackage(
NAME capstone
GITHUB_REPOSITORY capstone-engine/capstone
GIT_TAG 6.0.0-Alpha10
GIT_TAG 6.0.0-Alpha7
OPTIONS
"CAPSTONE_X86_ATT_DISABLE ON"
"CAPSTONE_ALPHA_SUPPORT OFF"
@@ -67,6 +58,52 @@ else()
target_link_libraries(TracyCapstone INTERFACE capstone_static)
endif()
# GLFW
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
pkg_check_modules(GLFW glfw3)
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
add_library(TracyGlfw3 INTERFACE)
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.4
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyGlfw3 INTERFACE)
target_link_libraries(TracyGlfw3 INTERFACE glfw)
endif()
endif()
# freetype
pkg_check_modules(FREETYPE freetype2)
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
add_library(TracyFreetype INTERFACE)
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-3
OPTIONS
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyFreetype INTERFACE)
target_link_libraries(TracyFreetype INTERFACE freetype)
endif()
# Zstd
CPMAddPackage(
@@ -95,6 +132,65 @@ set(GETOPT_HEADERS ${GETOPT_DIR}/getopt.h)
add_library(TracyGetOpt STATIC EXCLUDE_FROM_ALL ${GETOPT_SOURCES} ${GETOPT_HEADERS})
target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
# ImGui
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.8-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
)
set(IMGUI_SOURCES
imgui_widgets.cpp
imgui_draw.cpp
imgui_demo.cpp
imgui.cpp
imgui_tables.cpp
misc/freetype/imgui_freetype.cpp
backends/imgui_impl_opengl3.cpp
)
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
target_link_libraries(TracyImGui PUBLIC TracyFreetype)
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
find_package(X11 REQUIRED)
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
endif()
# NFD
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
if(GTK_FILESELECTOR)
set(NFD_PORTAL OFF)
else()
set(NFD_PORTAL ON)
endif()
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG v1.3.0
EXCLUDE_FROM_ALL TRUE
OPTIONS
"NFD_PORTAL ${NFD_PORTAL}"
)
endif()
# PPQSort
CPMAddPackage(
@@ -116,226 +212,93 @@ CPMAddPackage(
EXCLUDE_FROM_ALL TRUE
)
if(VENDOR_GUI)
# md4c
# GLFW
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG release-0.5.2
EXCLUDE_FROM_ALL TRUE
)
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
pkg_check_modules(GLFW glfw3)
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
add_library(TracyGlfw3 INTERFACE)
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.4
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyGlfw3 INTERFACE)
target_link_libraries(TracyGlfw3 INTERFACE glfw)
endif()
endif()
if(NOT EMSCRIPTEN)
# freetype
pkg_check_modules(FREETYPE freetype2)
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
add_library(TracyFreetype INTERFACE)
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-3
OPTIONS
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
"FT_DISABLE_ZLIB ON"
"FT_DISABLE_BZIP2 ON"
"FT_DISABLE_PNG ON"
"FT_DISABLE_BROTLI ON"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyFreetype INTERFACE)
target_link_libraries(TracyFreetype INTERFACE freetype)
endif()
# ImGui
# base64
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.9b-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"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-default-font.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-macos-clipboard.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-phantom-column.patch"
)
set(IMGUI_SOURCES
imgui_widgets.cpp
imgui_draw.cpp
imgui_demo.cpp
imgui.cpp
imgui_tables.cpp
misc/freetype/imgui_freetype.cpp
backends/imgui_impl_opengl3.cpp
)
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
target_link_libraries(TracyImGui PUBLIC
TracyFreetype
${CMAKE_DL_LIBS}
)
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
find_package(X11 REQUIRED)
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
endif()
if(APPLE)
target_link_libraries(TracyImGui PUBLIC "-framework ApplicationServices")
endif()
# NFD
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
if(GTK_FILESELECTOR)
set(NFD_PORTAL OFF)
else()
set(NFD_PORTAL ON)
endif()
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG 3cd252a8f7ca32419b1ca235c2990ba6a0ecba7c
EXCLUDE_FROM_ALL TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/nfd-xdg-foreign-v2.patch"
OPTIONS
"BUILD_SHARED_LIBS OFF"
"NFD_PORTAL ${NFD_PORTAL}"
)
endif()
# md4c
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 65c6c9d72cebd9a731aaa5597414ce04d9ea5de3
NAME base64
GITHUB_REPOSITORY aklomp/base64
GIT_TAG v0.5.2
OPTIONS
"BUILD_SHARED_LIBS OFF"
"BASE64_BUILD_CLI OFF"
"BASE64_WITH_OpenMP OFF"
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
# tidy
CPMAddPackage(
NAME tidy
GITHUB_REPOSITORY htacg/tidy-html5
GIT_TAG 5.8.0
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
EXCLUDE_FROM_ALL TRUE
)
if(NOT EMSCRIPTEN)
# usearch
# base64
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.23.0
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
# pugixml
pkg_check_modules(PUGIXML pugixml)
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
add_library(TracyPugixml INTERFACE)
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME base64
GITHUB_REPOSITORY aklomp/base64
GIT_TAG v0.5.2
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.15
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
target_link_libraries(TracyPugixml INTERFACE pugixml)
endif()
# libcurl
pkg_check_modules(LIBCURL libcurl>=7.87.0)
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
add_library(TracyLibcurl INTERFACE)
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_19_0
OPTIONS
"BASE64_BUILD_CLI OFF"
"BASE64_WITH_OpenMP OFF"
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"
"HTTP_ONLY ON"
"CURL_ZSTD OFF"
"CURL_USE_LIBPSL OFF"
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
# tidy
CPMAddPackage(
NAME tidy
GITHUB_REPOSITORY htacg/tidy-html5
GIT_TAG 5.8.0
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
EXCLUDE_FROM_ALL TRUE
)
# usearch
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.26.0
EXCLUDE_FROM_ALL TRUE
)
# pugixml
pkg_check_modules(PUGIXML pugixml)
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
add_library(TracyPugixml INTERFACE)
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.16
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
target_link_libraries(TracyPugixml INTERFACE pugixml)
endif()
# libcurl
pkg_check_modules(LIBCURL libcurl>=7.87.0)
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
add_library(TracyLibcurl INTERFACE)
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_21_0
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"
"HTTP_ONLY ON"
"CURL_ZSTD OFF"
"CURL_USE_LIBPSL OFF"
"CURL_USE_LIBSSH2 OFF"
"CURL_BROTLI OFF"
"USE_NGHTTP2 OFF"
"USE_LIBIDN2 OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyLibcurl INTERFACE)
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
endif()
add_library(TracyLibcurl INTERFACE)
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
endif()
endif()

View File

@@ -1,31 +0,0 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -26,17 +26,17 @@ void print_usage_exit(int e)
fprintf(stderr, "Usage:\n");
fprintf(stderr, " extract [OPTION...] <trace file>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -h, --help Print usage\n");
fprintf(stderr, " -V, --version Show version information\n");
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
fprintf(stderr, " -c, --case Case sensitive filtering\n");
fprintf(stderr, " -e, --self Get self times\n");
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
fprintf(stderr, " -m, --messages Report only messages\n");
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
fprintf(stderr, " -t, --truncated_mean[=arg] Report truncated mean (arg is the percentile. Default is 90)\n");
fprintf(stderr, " -h, --help Print usage\n");
fprintf(stderr, " -V, --version Show version information\n");
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
fprintf(stderr, " -c, --case Case sensitive filtering\n");
fprintf(stderr, " -e, --self Get self times\n");
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
fprintf(stderr, " -m, --messages Report only messages\n");
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
fprintf(stderr, " -t, --truncated_mean arg Report truncated mean (arg is the percentile. Default is 90)\n");
exit(e);
}
@@ -66,8 +66,8 @@ Args parse_args(int argc, char** argv)
struct option long_opts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "filter", required_argument, NULL, 'f' },
{ "sep", required_argument, NULL, 's' },
{ "filter", optional_argument, NULL, 'f' },
{ "sep", optional_argument, NULL, 's' },
{ "case", no_argument, NULL, 'c' },
{ "self", no_argument, NULL, 'e' },
{ "unwrap", no_argument, NULL, 'u' },
@@ -79,7 +79,7 @@ Args parse_args(int argc, char** argv)
};
int c;
while ((c = getopt_long(argc, argv, "hf:s:t:ceugmpV", long_opts, NULL)) != -1)
while ((c = getopt_long(argc, argv, "hf:s:ceugmpV", long_opts, NULL)) != -1)
{
switch (c)
{

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,71 +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)
if(TRACY_ENABLE)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
add_compile_options(-fno-omit-frame-pointer)
else()
message(WARNING "Tracy builds should be RelWithDebInfo!")
endif()
endif()
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,211 +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 ); }
explicit TracySection( const char* name, uint16_t category ) : idx( TracySectionEnterCategory( category, "%s", 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(), 1 );
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,41 +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;
TracySectionSetup( 0, "Game state" );
TracySectionSetup( 1, "Level progression" );
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)());

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