Compare commits
2 Commits
v0.14.0
...
slomp/stra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff70f87107 | ||
|
|
ebc23ac4f0 |
49
.github/workflows/android.yml
vendored
@@ -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
|
||||
55
.github/workflows/appimage.yml
vendored
@@ -1,55 +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
|
||||
|
||||
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 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
|
||||
7
.github/workflows/emscripten.yml
vendored
@@ -7,13 +7,6 @@ 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
|
||||
|
||||
|
||||
7
.github/workflows/latex.yml
vendored
@@ -8,13 +8,6 @@ on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: latex-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-manual:
|
||||
|
||||
|
||||
74
.github/workflows/linux-cli.yml
vendored
@@ -1,74 +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: 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/
|
||||
strip linux-cli/tracy-*
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-cli
|
||||
path: linux-cli
|
||||
44
.github/workflows/linux.yml
vendored
@@ -7,13 +7,6 @@ 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
|
||||
|
||||
@@ -23,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
|
||||
@@ -33,49 +26,39 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- 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 2
|
||||
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
|
||||
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)
|
||||
@@ -91,7 +74,6 @@ 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
|
||||
|
||||
27
.github/workflows/macos.yml
vendored
@@ -6,21 +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
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
@@ -30,16 +22,12 @@ jobs:
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- 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 -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build profiler/build --parallel 2 --config Release
|
||||
- name: Build update
|
||||
run: |
|
||||
@@ -61,19 +49,20 @@ 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: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- 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
|
||||
|
||||
7
.github/workflows/mingw.yml
vendored
@@ -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
|
||||
|
||||
|
||||
40
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
16
.github/workflows/windows.yml
vendored
@@ -8,13 +8,6 @@ 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
|
||||
|
||||
@@ -30,10 +23,12 @@ jobs:
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- 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: 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 }}
|
||||
@@ -58,13 +53,14 @@ jobs:
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel --config Release
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir bin
|
||||
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
|
||||
|
||||
13
.gitignore
vendored
@@ -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,16 +21,19 @@ manual/t*.bbl
|
||||
manual/t*.blg
|
||||
manual/t*.fdb_latexmk
|
||||
manual/t*.fls
|
||||
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
|
||||
|
||||
@@ -215,16 +215,13 @@ set(common_includes
|
||||
${TRACY_PUBLIC_DIR}/common/TracyApi.h
|
||||
${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)
|
||||
|
||||
|
||||
111
NEWS
@@ -2,21 +2,16 @@ 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.0 (2026-08-09)
|
||||
vx.xx.x (2026-xx-xx)
|
||||
--------------------
|
||||
|
||||
- API break: removed "secure" variants of memory alloc and free macros. The
|
||||
secure code path is now always enabled. Migrate by removing "Secure" from
|
||||
the macros you use, e.g. TracySecureAlloc(...) -> TracyAlloc(...).
|
||||
- Added 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.
|
||||
@@ -27,44 +22,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.
|
||||
- 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.
|
||||
@@ -76,8 +36,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
|
||||
@@ -119,16 +78,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.
|
||||
@@ -152,7 +108,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
|
||||
@@ -164,8 +119,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.
|
||||
@@ -195,9 +148,8 @@ 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.
|
||||
- Message windows will now properly show full message in a tooltip for
|
||||
multi-line messages.
|
||||
- Greatly improved the in-profiler user manual.
|
||||
- There is now chapter tree and the manual contents are displayed section
|
||||
by section.
|
||||
@@ -231,12 +183,7 @@ v0.14.0 (2026-08-09)
|
||||
- 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.
|
||||
- The flame graph view now supports zooming in and panning with the mouse.
|
||||
- General application crash information polish in the profiler UI.
|
||||
- The achievements system has been converted to use markdown renderer.
|
||||
- Offline symbol resolution with the update utility now supports custom
|
||||
@@ -247,59 +194,11 @@ v0.14.0 (2026-08-09)
|
||||
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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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-Alpha9
|
||||
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,220 +212,95 @@ CPMAddPackage(
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
if(VENDOR_GUI)
|
||||
# md4c
|
||||
|
||||
# GLFW
|
||||
CPMAddPackage(
|
||||
NAME md4c
|
||||
GITHUB_REPOSITORY mity/md4c
|
||||
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
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"
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
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.25.2
|
||||
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_20_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()
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 "*")
|
||||
@@ -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.
|
||||
@@ -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})
|
||||
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 768 B |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 490 B |
@@ -1,12 +0,0 @@
|
||||
10 1 0 0
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
20 4 0 0
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#@#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
40 3 2 0
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
40 3 3 0
|
||||
@............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
40 2 4 1
|
||||
@............
|
||||
.###.#.#.###.
|
||||
.#.........#.
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.#.........#.
|
||||
.###.#.#.###.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
50 2 2 3
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.....#.#.....
|
||||
.#.###.###.#.
|
||||
.............
|
||||
.#.###.###.#.
|
||||
.....#.#.....
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#@
|
||||
@@ -1,12 +0,0 @@
|
||||
60 3 3 3
|
||||
@............
|
||||
.#.#.###.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.###.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.###.#.#.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
60 5 3 3
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
90 5 5 5
|
||||
@............
|
||||
.#.........#.
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.#.........#.
|
||||
.............
|
||||
@@ -1,12 +0,0 @@
|
||||
30 4 4 4
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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& ) {}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -2,20 +2,7 @@
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/tracy">
|
||||
<comment>Tracy Profiler trace file</comment>
|
||||
<comment xml:lang="ar">ملف تتبع لمحلل الأداء Tracy</comment>
|
||||
<comment xml:lang="de">Trace-Datei des Tracy-Profilers</comment>
|
||||
<comment xml:lang="es">Archivo de traza del perfilador Tracy</comment>
|
||||
<comment xml:lang="fr">Fichier de trace du profileur Tracy</comment>
|
||||
<comment xml:lang="hi">Tracy प्रोफ़ाइलर ट्रेस फ़ाइल</comment>
|
||||
<comment xml:lang="it">File di traccia del profiler Tracy</comment>
|
||||
<comment xml:lang="ja">Tracy プロファイラーのトレースファイル</comment>
|
||||
<comment xml:lang="pl">Zrzut sesji profilera Tracy</comment>
|
||||
<comment xml:lang="pt">Ficheiro de rastreio do perfilador Tracy</comment>
|
||||
<comment xml:lang="pt_BR">Arquivo de rastreamento do perfilador Tracy</comment>
|
||||
<comment xml:lang="ru">Файл трассировки профилировщика Tracy</comment>
|
||||
<comment xml:lang="uk">Файл трасування профілювальника Tracy</comment>
|
||||
<comment xml:lang="zh_CN">Tracy 性能分析器跟踪文件</comment>
|
||||
<comment xml:lang="zh_TW">Tracy 效能分析器追蹤檔案</comment>
|
||||
<icon name="application-tracy"/>
|
||||
<magic>
|
||||
<match type="string" value="tlZ\x04" offset="0"/>
|
||||
|
||||
@@ -1,53 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Version=1.5
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Tracy Profiler
|
||||
GenericName=Code profiler
|
||||
GenericName[ar]=محلل أداء الشيفرة البرمجية
|
||||
GenericName[de]=Code-Profiler
|
||||
GenericName[es]=Perfilador de código
|
||||
GenericName[fr]=Profileur de code
|
||||
GenericName[hi]=कोड प्रोफ़ाइलर
|
||||
GenericName[it]=Profiler del codice
|
||||
GenericName[ja]=コードプロファイラー
|
||||
GenericName[pl]=Profiler kodu
|
||||
GenericName[pt]=Analisador de desempenho de código
|
||||
GenericName[pt_BR]=Analisador de desempenho de código
|
||||
GenericName[ru]=Профилировщик кода
|
||||
GenericName[uk]=Профілювальник коду
|
||||
GenericName[zh_CN]=代码性能分析器
|
||||
GenericName[zh_TW]=程式碼效能分析器
|
||||
Comment=Examine code to see where it is slow
|
||||
Comment[ar]=افحص الشيفرة البرمجية لمعرفة مواضع البطء
|
||||
Comment[de]=Code untersuchen, um langsame Stellen zu finden
|
||||
Comment[es]=Examine el código para ver dónde es lento
|
||||
Comment[fr]=Examiner le code pour voir où il est lent
|
||||
Comment[hi]=कोड की जाँच करें कि वह कहाँ धीमा है
|
||||
Comment[it]=Esamina il codice per vedere dove è lento
|
||||
Comment[ja]=コードを調べて遅い箇所を見つける
|
||||
Comment[pl]=Przeanalizuj kod, aby znaleźć wolne miejsca
|
||||
Comment[pt]=Examine o código para ver onde é lento
|
||||
Comment[pt_BR]=Examine o código para ver onde ele é lento
|
||||
Comment[ru]=Изучите код, чтобы найти медленные места
|
||||
Comment[uk]=Досліджуйте код, щоб знайти повільні місця
|
||||
Comment[zh_CN]=检查代码,找出运行缓慢的部分
|
||||
Comment[zh_TW]=檢查程式碼,找出執行緩慢的部分
|
||||
Keywords=profiler;performance;tracing;frame;instrumentation;
|
||||
Keywords[ar]=محلل;أداء;تتبع;إطار;قياس;
|
||||
Keywords[de]=Profiler;Leistung;Tracing;Frame;Instrumentierung;
|
||||
Keywords[es]=perfilador;rendimiento;trazado;fotograma;instrumentación;
|
||||
Keywords[fr]=profileur;performance;traçage;trame;instrumentation;
|
||||
Keywords[hi]=प्रोफ़ाइलर;प्रदर्शन;ट्रेसिंग;फ़्रेम;इंस्ट्रुमेंटेशन;
|
||||
Keywords[it]=profiler;prestazioni;tracciamento;frame;strumentazione;
|
||||
Keywords[ja]=プロファイラー;パフォーマンス;トレース;フレーム;計測;
|
||||
Keywords[pl]=profiler;wydajność;śledzenie;klatka;instrumentacja;
|
||||
Keywords[pt]=perfilador;desempenho;rastreio;fotograma;instrumentação;
|
||||
Keywords[pt_BR]=perfilador;desempenho;rastreamento;quadro;instrumentação;
|
||||
Keywords[ru]=профилировщик;производительность;трассировка;кадр;инструментирование;
|
||||
Keywords[uk]=профілювальник;продуктивність;трасування;кадр;інструментування;
|
||||
Keywords[zh_CN]=分析器;性能;跟踪;帧;插桩;
|
||||
Keywords[zh_TW]=分析器;效能;追蹤;影格;插樁;
|
||||
Exec=tracy-profiler %f
|
||||
Comment[pl]=Znajdowanie wolno wykonującego się kodu
|
||||
Exec=/usr/bin/tracy %f
|
||||
Icon=tracy
|
||||
Terminal=false
|
||||
Categories=Development;Profiling;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Tracy MCP eval guide
|
||||
|
||||
This document covers the bindings-layer detail that the analysis
|
||||
guidance (`tracy://prompt`) does not.
|
||||
This document covers the bindings-layer detail that the curated catalog
|
||||
(`tracy://catalog`) and analysis guidance (`tracy://prompt`) do not.
|
||||
|
||||
## ctx
|
||||
|
||||
@@ -21,15 +21,8 @@ data surface. Common entry points:
|
||||
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
|
||||
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
|
||||
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
|
||||
- Sections: `get_sections()` — timed code sections from
|
||||
`TracySectionEnter`/`TracySectionLeave` instrumentation. Returns a list of
|
||||
`{category, start, end, text}` dicts (start/end in ns); `category` is the
|
||||
section's named group (multiple unrelated section sets can coexist in one
|
||||
capture).
|
||||
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
|
||||
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
|
||||
- Readiness: `is_background_done()` — see "Instance lifecycle" below before
|
||||
querying zone/symbol stats right after `load_capture`.
|
||||
|
||||
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
|
||||
|
||||
@@ -48,23 +41,24 @@ Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
|
||||
- Source-location IDs from `get_all_zone_source_locations()` are the join key
|
||||
between zone-name lookups and per-callsite queries.
|
||||
|
||||
## Common query patterns
|
||||
## Translating catalog entries to ctx Python
|
||||
|
||||
Small Python snippets for the queries you'll reach for most often:
|
||||
The catalog (`tracy://catalog`) lists curated queries. Each maps to a small
|
||||
Python snippet:
|
||||
|
||||
```python
|
||||
# top 10 hottest zones by total time
|
||||
# zone_list — top 10 hottest zones by total time
|
||||
top = sorted(ctx.get_all_zone_stats().items(),
|
||||
key=lambda kv: kv[1].total, reverse=True)[:10]
|
||||
for k, v in top:
|
||||
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
|
||||
|
||||
# primary frame set timing
|
||||
# frame_list — primary frame set timing
|
||||
times = ctx.get_frame_times() # ns per frame
|
||||
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
|
||||
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
|
||||
|
||||
# stats for a named zone — find the srcloc id, then drill in
|
||||
# zone_stats for a named zone — find the srcloc id, then drill in
|
||||
import re
|
||||
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
|
||||
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))
|
||||
@@ -76,40 +70,3 @@ stats = ctx.get_zone_stats(sid)
|
||||
For long-running queries pass `async_mode=True` to `eval`; it returns
|
||||
`{task_id, status: "running"}`. Poll with the `task` tool
|
||||
(`action="poll", task_id=...`).
|
||||
|
||||
## Instance lifecycle
|
||||
|
||||
`tracy_mcp.py` runs as a long-lived singleton process shared across every
|
||||
MCP client (all VS Code windows), and every `live_connect`/`load_capture`
|
||||
instance holds its *entire* trace — zones, messages, callstacks, memory
|
||||
events — resident in memory for as long as that process runs. Nothing
|
||||
frees it just because your conversation ends.
|
||||
|
||||
- `load_capture` returns before zone/symbol statistics finish building — a
|
||||
background thread populates them after the file itself is read. Check
|
||||
`is_background_done()` (or `background_done` in `list_instances`) before
|
||||
relying on `get_all_zone_stats()` and similar stats-based queries; they
|
||||
can return empty or partial results until it's true. For small captures
|
||||
this finishes before you'd notice; for multi-GB ones it can take seconds.
|
||||
- Call `unload_capture(instance_id)` as soon as you're done analyzing a
|
||||
capture. Don't rely on automatic eviction as your primary cleanup path —
|
||||
treat it as a backstop for forgotten sessions, not a substitute.
|
||||
- `live_connect` caps a live session at `TRACY_MCP_LIVE_MEMORY_LIMIT_MB`
|
||||
(default 8192, override per-call or set 0 to disable) — a long-lived
|
||||
session on a busy target otherwise grows unbounded and can OOM-kill the
|
||||
whole server process. Hitting the cap disconnects that instance cleanly;
|
||||
data collected so far stays queryable and `save_trace`-able.
|
||||
- `list_instances` reports `idle_seconds`, `connected`, and `background_done`
|
||||
per instance; use it to spot stale ones before starting a new session,
|
||||
especially if you're about to load several captures for comparison.
|
||||
- The server provides three backstops, all configurable via environment
|
||||
variables at startup: it evicts the least-recently-used *evictable*
|
||||
instance (disconnected live sessions, or any loaded file capture — never
|
||||
a still-connected live one) once the instance count reaches
|
||||
`TRACY_MCP_MAX_INSTANCES` (default 4); it drops a disconnected live
|
||||
instance that's sat idle for `TRACY_MCP_DISCONNECTED_TTL_S` (default
|
||||
1800s / 30 min) so a session is still there for analysis right after the
|
||||
target disconnects, but doesn't linger forever if forgotten; and it drops
|
||||
a file-loaded capture that's sat idle for `TRACY_MCP_FILE_IDLE_TTL_S`
|
||||
(default 1800s) — safe to let expire since it's already durably on disk,
|
||||
just `load_capture` it again.
|
||||
|
||||
@@ -5,7 +5,6 @@ import asyncio
|
||||
import atexit
|
||||
import builtins
|
||||
import concurrent.futures
|
||||
import faulthandler
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
@@ -15,10 +14,8 @@ import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager, redirect_stdout
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
import mcp.server.fastmcp as fastmcp
|
||||
|
||||
@@ -32,9 +29,7 @@ logging.getLogger("starlette").setLevel(logging.CRITICAL)
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_PORT_FILE = os.path.join(_HERE, "tracy_mcp.port")
|
||||
_PID_FILE = os.path.join(_HERE, "tracy_mcp.pid")
|
||||
_CRASH_LOG_FILE = os.path.join(_HERE, "tracy_mcp.crash.log")
|
||||
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
|
||||
_TRANSPORT = os.environ.get("TRACY_MCP_TRANSPORT", "streamable-http").strip().lower()
|
||||
|
||||
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
|
||||
# system prompt; exposing it as an MCP resource keeps analysis guidance in
|
||||
@@ -160,26 +155,10 @@ async def _listen_broadcasts(timeout_s: float = 1.5) -> list[dict]:
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def _http_ping(port: int, timeout_s: float = 2.0) -> bool:
|
||||
"""Confirms the server answers HTTP, not just that its PID exists — a
|
||||
deadlocked process still passes os.kill(pid, 0). Any response, even an
|
||||
error one, proves the transport is alive; only a timeout or refused
|
||||
connection means it's not.
|
||||
"""
|
||||
path = mcp_server.settings.sse_path if _TRANSPORT == "sse" else mcp_server.settings.streamable_http_path
|
||||
try:
|
||||
urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout=timeout_s)
|
||||
return True
|
||||
except urllib.error.HTTPError:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _is_our_server_running() -> tuple[bool, int]:
|
||||
"""Check whether our server is running: PID alive AND responding to
|
||||
an HTTP self-ping (a hung process still passes os.kill(pid, 0)).
|
||||
Returns (running, port).
|
||||
"""
|
||||
Check the PID file to see if our server is already running.
|
||||
Returns (running, port). Uses os.kill(pid, 0) to confirm the process is alive.
|
||||
"""
|
||||
try:
|
||||
with open(_PID_FILE) as f:
|
||||
@@ -187,19 +166,9 @@ def _is_our_server_running() -> tuple[bool, int]:
|
||||
with open(_PORT_FILE) as f:
|
||||
port = int(f.read().strip())
|
||||
os.kill(pid, 0) # raises OSError if process is gone
|
||||
return True, port
|
||||
except Exception:
|
||||
return False, 0
|
||||
if not _http_ping(port):
|
||||
print(
|
||||
f"Tracy MCP process {pid} is alive (PID check passed) but isn't "
|
||||
f"responding on port {port} within {2.0:.0f}s -- likely hung/deadlocked, "
|
||||
f"not just busy. Starting a fresh server on a new port; kill PID {pid} "
|
||||
f"manually once you're able to (it's still holding the port and the "
|
||||
f"TracyServerBindings.pyd file lock).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False, 0
|
||||
return True, port
|
||||
|
||||
|
||||
def _find_free_port() -> int:
|
||||
@@ -249,11 +218,8 @@ except ImportError:
|
||||
except ImportError:
|
||||
tracy_server = None
|
||||
|
||||
_MAX_INSTANCES = int(os.environ.get("TRACY_MCP_MAX_INSTANCES", "4"))
|
||||
_DISCONNECTED_TTL_S = float(os.environ.get("TRACY_MCP_DISCONNECTED_TTL_S", "1800"))
|
||||
_FILE_IDLE_TTL_S = float(os.environ.get("TRACY_MCP_FILE_IDLE_TTL_S", "1800"))
|
||||
_SWEEP_INTERVAL_S = float(os.environ.get("TRACY_MCP_SWEEP_INTERVAL_S", "300"))
|
||||
_DEFAULT_LIVE_MEMORY_LIMIT_MB = int(os.environ.get("TRACY_MCP_LIVE_MEMORY_LIMIT_MB", "8192"))
|
||||
mcp_server = fastmcp.FastMCP("Tracy Profiler")
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
|
||||
|
||||
|
||||
class Task:
|
||||
@@ -273,126 +239,7 @@ class TracyInstance:
|
||||
self.worker = worker
|
||||
self.path = None
|
||||
self.mtime = None
|
||||
self.last_used = time.time()
|
||||
# Wall-clock time the instance was first observed disconnected (live
|
||||
# instances only). None while connected or for file-loaded captures.
|
||||
self.disconnected_since: float | None = None
|
||||
|
||||
def touch(self) -> None:
|
||||
self.last_used = time.time()
|
||||
|
||||
def is_evictable(self) -> bool:
|
||||
"""True if this instance is safe to drop without losing in-progress data.
|
||||
|
||||
A live instance still connected is actively recording — never evict it
|
||||
automatically. Everything else (file captures, disconnected live
|
||||
instances) is fair game for the LRU cap.
|
||||
"""
|
||||
if self.worker is not None and self.path is None:
|
||||
try:
|
||||
return not self.worker.is_connected()
|
||||
except Exception:
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def _shutdown_worker(worker: object | None) -> None:
|
||||
if worker is None:
|
||||
return
|
||||
try:
|
||||
worker.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _evict_idle() -> list[str]:
|
||||
"""Unload instances idle past their TTL: disconnected live instances
|
||||
past _DISCONNECTED_TTL_S, file-loaded captures past _FILE_IDLE_TTL_S
|
||||
(safe — already durably on disk). Connected live instances are never
|
||||
touched here.
|
||||
"""
|
||||
now = time.time()
|
||||
evicted = []
|
||||
for name, inst in list(instances.items()):
|
||||
if inst.path is not None:
|
||||
if now - inst.last_used >= _FILE_IDLE_TTL_S:
|
||||
del instances[name]
|
||||
_shutdown_worker(inst.worker)
|
||||
evicted.append(name)
|
||||
continue
|
||||
if inst.worker is None:
|
||||
continue
|
||||
try:
|
||||
connected = inst.worker.is_connected()
|
||||
except Exception:
|
||||
connected = False
|
||||
if connected:
|
||||
inst.disconnected_since = None
|
||||
continue
|
||||
if inst.disconnected_since is None:
|
||||
inst.disconnected_since = now
|
||||
continue
|
||||
if now - inst.disconnected_since >= _DISCONNECTED_TTL_S:
|
||||
del instances[name]
|
||||
_shutdown_worker(inst.worker)
|
||||
evicted.append(name)
|
||||
return evicted
|
||||
|
||||
|
||||
def _evict_for_capacity(exclude: str | None = None) -> str | None:
|
||||
"""If at/over _MAX_INSTANCES, drop the least-recently-used evictable
|
||||
instance to make room for a new one. Returns the evicted name, if any."""
|
||||
if len(instances) < _MAX_INSTANCES:
|
||||
return None
|
||||
candidates = [
|
||||
inst for name, inst in instances.items()
|
||||
if name != exclude and inst.is_evictable()
|
||||
]
|
||||
if not candidates:
|
||||
return None
|
||||
victim = min(candidates, key=lambda inst: inst.last_used)
|
||||
del instances[victim.name]
|
||||
_shutdown_worker(victim.worker)
|
||||
return victim.name
|
||||
|
||||
|
||||
async def _sweep_loop() -> None:
|
||||
"""Periodic eviction sweep, doubling as a heartbeat — distinguishes a
|
||||
crashed process (see faulthandler) from a hung one (stops producing
|
||||
these lines). flush=True since a hang is exactly when buffering would
|
||||
hide the last known-good timestamp.
|
||||
"""
|
||||
start = time.time()
|
||||
while True:
|
||||
await asyncio.sleep(_SWEEP_INTERVAL_S)
|
||||
try:
|
||||
evicted = _evict_idle()
|
||||
except Exception:
|
||||
evicted = None
|
||||
print(
|
||||
f"[heartbeat] uptime={int(time.time() - start)}s "
|
||||
f"instances={len(instances)} tasks={len(tasks)} "
|
||||
f"evicted={evicted or 'none'}",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _lifespan(_server: fastmcp.FastMCP):
|
||||
sweep_task = asyncio.create_task(_sweep_loop())
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sweep_task.cancel()
|
||||
try:
|
||||
await sweep_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
mcp_server = fastmcp.FastMCP("Tracy Profiler", lifespan=_lifespan)
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
|
||||
|
||||
instances: dict[str, TracyInstance] = {}
|
||||
tasks: dict[str, Task] = {}
|
||||
@@ -411,7 +258,8 @@ def _prompt_resource() -> str:
|
||||
@mcp_server.resource("tracy://eval-guide")
|
||||
def _eval_guide_resource() -> str:
|
||||
"""Bindings-layer guide for the eval tool: ctx object model, time units,
|
||||
source-location ID semantics, and worked examples of common ctx queries."""
|
||||
source-location ID semantics, and worked examples translating catalog
|
||||
entries into ctx Python."""
|
||||
return _read_text(_EVAL_GUIDE_PATH)
|
||||
|
||||
|
||||
@@ -425,34 +273,13 @@ async def list_captures() -> list[str]:
|
||||
|
||||
@mcp_server.tool()
|
||||
async def list_instances() -> list[dict]:
|
||||
"""List all loaded Tracy instances and captures with metadata.
|
||||
|
||||
Each full trace stays resident in memory for as long as this server
|
||||
process runs (it's a singleton shared across all MCP clients) until
|
||||
unloaded. `idle_seconds` is how long since this instance was last used
|
||||
via `eval`/`save_trace` — call `unload_capture` on ones you no longer
|
||||
need instead of waiting for automatic eviction. Automatic eviction kicks
|
||||
in at the `TRACY_MCP_MAX_INSTANCES` cap (LRU, connected live instances
|
||||
are never evicted), after a disconnected live instance sits idle past
|
||||
`TRACY_MCP_DISCONNECTED_TTL_S`, or after a file-loaded capture sits idle
|
||||
past `TRACY_MCP_FILE_IDLE_TTL_S` — it's already durably on disk, so
|
||||
nothing is lost; just `load_capture` it again.
|
||||
|
||||
`background_done: false` means a just-loaded capture is still building
|
||||
zone/symbol statistics on a background thread — `get_all_zone_stats`
|
||||
and similar stats-based `eval` queries can return empty or partial
|
||||
results until this flips to true.
|
||||
"""
|
||||
now = time.time()
|
||||
"""List all loaded Tracy instances and captures with metadata."""
|
||||
return [
|
||||
{
|
||||
"id": name,
|
||||
"path": inst.path,
|
||||
"mtime": inst.mtime,
|
||||
"live": inst.path is None,
|
||||
"connected": inst.worker.is_connected() if inst.path is None and inst.worker else None,
|
||||
"background_done": inst.worker.is_background_done() if inst.worker else None,
|
||||
"idle_seconds": now - inst.last_used,
|
||||
"live": inst.path is None
|
||||
}
|
||||
for name, inst in instances.items()
|
||||
]
|
||||
@@ -484,25 +311,11 @@ async def discover_instances(port_range: str = "8086-8095") -> list[dict]:
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def live_connect(
|
||||
address: str = "127.0.0.1",
|
||||
port: int = 8086,
|
||||
alias: str | None = None,
|
||||
memory_limit_mb: int | None = None,
|
||||
) -> str:
|
||||
async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str | None = None) -> str:
|
||||
"""
|
||||
Connect to a live running Tracy-instrumented application.
|
||||
|
||||
Wraps Worker(addr, port, memoryLimit). A long-lived session on a busy
|
||||
target grows unbounded otherwise -- every zone/message/memory event
|
||||
stays resident until disconnect, which can OOM-kill this whole process
|
||||
rather than just the one instance. memory_limit_mb defaults to
|
||||
TRACY_MCP_LIVE_MEMORY_LIMIT_MB (8192 if unset); pass 0 to disable.
|
||||
Hitting the limit disconnects that Worker cleanly (Tracy's own
|
||||
QueryTerminate path) -- already-collected data stays queryable and
|
||||
save_trace-able, it just stops growing.
|
||||
|
||||
Returns the instance_id.
|
||||
Wraps Worker(addr, port, memoryLimit=-1). Returns the instance_id.
|
||||
"""
|
||||
if not tracy_server:
|
||||
return "Error: Tracy Server bindings not found."
|
||||
@@ -524,10 +337,8 @@ async def live_connect(
|
||||
f"the target against a matching Tracy version."
|
||||
)
|
||||
|
||||
limit_mb = _DEFAULT_LIVE_MEMORY_LIMIT_MB if memory_limit_mb is None else memory_limit_mb
|
||||
memory_limit = limit_mb * 1024 * 1024 if limit_mb > 0 else -1
|
||||
try:
|
||||
w = tracy_server.Worker(address, port, memory_limit)
|
||||
w = tracy_server.Worker(address, port)
|
||||
except Exception as e:
|
||||
return f"Failed to connect: {str(e)}"
|
||||
|
||||
@@ -571,14 +382,9 @@ async def live_connect(
|
||||
)
|
||||
|
||||
name = alias or f"live_{address}_{port}"
|
||||
if name in instances:
|
||||
_shutdown_worker(instances[name].worker)
|
||||
evicted = _evict_for_capacity(exclude=name)
|
||||
instances[name] = TracyInstance(name, w)
|
||||
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
|
||||
limit_note = f"{limit_mb}MB memory limit" if limit_mb > 0 else "no memory limit"
|
||||
return (
|
||||
f"Connected to live instance as '{name}'{note} ({limit_note}). "
|
||||
f"Connected to live instance as '{name}'. "
|
||||
f"Before your first eval, read resources tracy://prompt "
|
||||
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
|
||||
f"ns time units, srcloc IDs)."
|
||||
@@ -598,12 +404,6 @@ async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
|
||||
If you don't already have a path, call `list_captures` first — it lists
|
||||
.tracy files in the TRACY_CAPTURES_DIR environment directory.
|
||||
|
||||
Loading returns before zone/symbol statistics finish building — a
|
||||
background thread populates them after the file itself is read. Check
|
||||
`background_done` in `list_instances` (or poll it) before relying on
|
||||
`get_all_zone_stats` and similar stats-based `eval` queries; they can
|
||||
return empty or partial results until it's true.
|
||||
"""
|
||||
if not tracy_server:
|
||||
return "Error: Tracy Server bindings not found."
|
||||
@@ -618,11 +418,7 @@ async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
if name in instances:
|
||||
inst = instances[name]
|
||||
if inst.path == path and inst.mtime == mtime:
|
||||
inst.touch()
|
||||
return f"Instance '{name}' is already loaded and up to date."
|
||||
_shutdown_worker(inst.worker)
|
||||
|
||||
evicted = _evict_for_capacity(exclude=name)
|
||||
|
||||
f = tracy_server.open_file(path)
|
||||
w = tracy_server.create_worker_from_file(f)
|
||||
@@ -630,9 +426,8 @@ async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
inst.path = path
|
||||
inst.mtime = mtime
|
||||
instances[name] = inst
|
||||
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
|
||||
return (
|
||||
f"Loaded as '{name}'{note}. "
|
||||
f"Loaded as '{name}'. "
|
||||
f"Before your first eval, read resources tracy://prompt "
|
||||
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
|
||||
f"ns time units, srcloc IDs)."
|
||||
@@ -681,7 +476,6 @@ async def save_trace(
|
||||
instance = instances[instance_id]
|
||||
if not instance.worker:
|
||||
return f"Error: Instance '{instance_id}' has no worker."
|
||||
instance.touch()
|
||||
|
||||
if not os.path.isabs(path):
|
||||
if captures_dir and os.path.basename(path) == path:
|
||||
@@ -748,18 +542,9 @@ async def _execute_save(worker: object, path: str, level: int, streams: int, fi_
|
||||
|
||||
@mcp_server.tool()
|
||||
async def unload_capture(instance_id: str) -> str:
|
||||
"""Unload a Tracy instance and release its memory.
|
||||
|
||||
Prefer calling this as soon as you're done analyzing a capture — every
|
||||
loaded instance holds the *entire* trace (zones, messages, callstacks,
|
||||
memory events) in memory for as long as this server process runs, and
|
||||
the server is a long-lived singleton shared across all MCP clients.
|
||||
Automatic eviction exists as a backstop (see `list_instances`), not a
|
||||
substitute for unloading what you no longer need.
|
||||
"""
|
||||
"""Unload a Tracy instance and release its memory."""
|
||||
if instance_id in instances:
|
||||
inst = instances.pop(instance_id)
|
||||
_shutdown_worker(inst.worker)
|
||||
del instances[instance_id]
|
||||
return f"Instance '{instance_id}' unloaded."
|
||||
return f"Instance '{instance_id}' not found."
|
||||
|
||||
@@ -781,7 +566,6 @@ async def tracy_eval(code: str, instance_id: str, async_mode: bool = False) -> o
|
||||
instance = instances[instance_id]
|
||||
if not instance.worker:
|
||||
return f"Error: Instance '{instance_id}' has no worker."
|
||||
instance.touch()
|
||||
|
||||
if not async_mode:
|
||||
return await _execute_eval(code, instance.worker)
|
||||
@@ -891,22 +675,8 @@ async def shutdown_server() -> str:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Enabled before anything touches the native bindings, so a genuine
|
||||
# fatal crash (segfault etc.) writes a thread-state traceback to disk
|
||||
# before the process dies — works on Windows via
|
||||
# SetUnhandledExceptionFilter, same mechanism as POSIX signals.
|
||||
_crash_log_fh = open(_CRASH_LOG_FILE, "a", encoding="utf-8")
|
||||
faulthandler.enable(file=_crash_log_fh, all_threads=True)
|
||||
|
||||
atexit.register(_cleanup_pid_files)
|
||||
|
||||
if _TRANSPORT not in ("sse", "streamable-http"):
|
||||
print(
|
||||
"TRACY_MCP_TRANSPORT must be 'sse' or 'streamable-http'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
running, existing_port = _is_our_server_running()
|
||||
if running:
|
||||
print(
|
||||
@@ -919,17 +689,12 @@ if __name__ == "__main__":
|
||||
port = _find_free_port()
|
||||
_write_pid_and_port(port)
|
||||
|
||||
path = (
|
||||
mcp_server.settings.sse_path
|
||||
if _TRANSPORT == "sse"
|
||||
else mcp_server.settings.streamable_http_path
|
||||
)
|
||||
print(f"Tracy MCP listening on http://127.0.0.1:{port}{path}", file=sys.stderr)
|
||||
print(f"Tracy MCP listening on http://127.0.0.1:{port}/sse", file=sys.stderr)
|
||||
|
||||
mcp_server.settings.host = "127.0.0.1"
|
||||
mcp_server.settings.port = port
|
||||
try:
|
||||
mcp_server.run(transport=_TRANSPORT)
|
||||
mcp_server.run(transport="sse")
|
||||
except KeyboardInterrupt:
|
||||
print("\nTracy MCP server stopped.", file=sys.stderr)
|
||||
sys.exit(0)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,15 +12,12 @@ sed -i -e 's@\\LMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\textsigma@σ@g' _tmp.tex
|
||||
|
||||
# Resolve \circled{} markers and lstlisting escapeinside (@...@) snippets, which
|
||||
# pandoc would otherwise emit verbatim or drop, to their Unicode equivalents.
|
||||
sed -i -e 's|@\\circled{a}@|(a)|g' -e 's|@\\circled{b}@|(b)|g' -e 's|@\\circled{c}@|(c)|g' _tmp.tex
|
||||
sed -i -e 's|\\circled{a}|(a)|g' -e 's|\\circled{b}|(b)|g' -e 's|\\circled{c}|(c)|g' _tmp.tex
|
||||
sed -i -e 's|@\\ldots@|…|g' _tmp.tex
|
||||
# pandoc doens't recognize this lstlisting option and emits it verbatim
|
||||
sed -i -e 's|,\?escapeinside={}{}||g' _tmp.tex
|
||||
|
||||
sed -i -e 's@\\nameref{quicklook}@A quick look at Tracy Profiler@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{firststeps}@First steps@g' _tmp.tex
|
||||
|
||||
816
manual/tracy.md
424
manual/tracy.tex
@@ -21,7 +21,6 @@
|
||||
\usepackage{nameref}
|
||||
\usepackage{diagbox}
|
||||
\usepackage{csquotes}
|
||||
\usepackage{longtable}
|
||||
|
||||
\usepackage[hmarginratio=1:1,top=32mm,columnsep=20pt]{geometry} % Document margins
|
||||
\geometry{a4paper,textwidth=6.5in,hmarginratio=1:1,
|
||||
@@ -358,19 +357,11 @@ Tracy can be found at the following web addresses:
|
||||
|
||||
\subsubsection{Binary distribution}
|
||||
|
||||
Prebuilt binary releases of the profiler and the user manual can be found at \url{https://github.com/wolfpld/tracy/releases}. The following platforms are supported:
|
||||
The version releases of the profiler are provided as precompiled Windows binaries for download at \url{https://github.com/wolfpld/tracy/releases}, along with the user manual. You will need to install the latest Visual C++ redistributable package to use them.
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Windows} -- Requires the latest Visual C++ redistributable package to be installed.
|
||||
\item \textbf{macOS} -- Needs manual quarantine handling before any provided binary can be executed, e.g.:
|
||||
\begin{lstlisting}
|
||||
xattr -d com.apple.quarantine tracy-profiler.app
|
||||
\end{lstlisting}
|
||||
Thanks, Tim Apple!
|
||||
\item \textbf{Linux} -- AppImage with Ubuntu 24.04 used as a baseline. Requires Wayland and an implementation of an XDG desktop portal to be installed.
|
||||
\end{itemize}
|
||||
Development builds of Windows binaries, and the user manual are available as artifacts created by the automated Continuous Integration system on GitHub.
|
||||
|
||||
Development builds of binaries and the user manual are available as artifacts created by the automated continuous integration system on GitHub.
|
||||
Note that these binary releases require AVX2 instruction set support on the processor. If you have an older CPU, you will need to set a proper instruction set architecture in the project properties and build the executables yourself.
|
||||
|
||||
\section{First steps}
|
||||
\label{firststeps}
|
||||
@@ -495,77 +486,6 @@ target_link_libraries(<TARGET> PUBLIC TracyClient)
|
||||
While using \texttt{set(CMAKE\_INTERPROCEDURAL\_OPTIMIZATION ON)} is a convenient way to enable Link-Time Optimization (LTO) for an entire project, there are situations in which this may not work due to excessive compilation times, linking issues, compiler bugs, or other reasons.
|
||||
For such cases, Tracy provides an option to enable Link-Time Optimization for itself using the \texttt{TRACY\_LTO} variable during the CMake configuration stage.
|
||||
|
||||
\subsubsection{Build options}
|
||||
\label{buildoptions}
|
||||
|
||||
Table~\ref{cmakeoptions} lists the options you may want to set in the program you are profiling. All of them are boolean switches defaulting to \texttt{OFF}, with three exceptions: \texttt{TRACY\_STATIC}, which defaults to the opposite of \texttt{BUILD\_SHARED\_LIBS}, and \texttt{TRACY\_CALLSTACK} and \texttt{TRACY\_PLATFORM\_HEADER}, which take a value instead of a boolean.
|
||||
|
||||
Each enabled option is passed down to your program as a preprocessor macro of the same name. If you are not using CMake, you can achieve the same results by defining these macros yourself, as described in section~\ref{initialsetup}. Note that some of the listed options additionally affect how the client library itself is built, so they cannot be replaced with a macro definition alone.
|
||||
|
||||
\begin{footnotesize}
|
||||
\begin{longtable}{@{}p{0.4\textwidth}p{0.56\textwidth}@{}}
|
||||
\caption{Tracy CMake options}\label{cmakeoptions} \\
|
||||
\hline
|
||||
\textbf{Option} & \textbf{Description} \\ \hline
|
||||
\endfirsthead
|
||||
\hline
|
||||
\endhead
|
||||
\hline
|
||||
\endfoot
|
||||
\hline
|
||||
\endlastfoot
|
||||
\textbf{Basic setup} & \\
|
||||
\texttt{TRACY\_ENABLE} & Enable profiling. Without it, the instrumentation is compiled out and the client does nothing (section~\ref{initialsetup}). \\
|
||||
\texttt{TRACY\_ON\_DEMAND} & Collect data only while the profiler is connected, instead of starting at program launch (section~\ref{ondemand}). \\
|
||||
\texttt{TRACY\_NO\_EXIT} & Don't let the client exit until all the profiling data has been sent to the server. Useful for short-lived applications. \\
|
||||
\texttt{TRACY\_STATIC} & Build the client as a static (\texttt{ON}) or shared (\texttt{OFF}) library. Multi-DLL projects need a shared library (section~\ref{multidll}). \\
|
||||
\texttt{TRACY\_LTO} & Enable link-time optimization of the client library itself. \\
|
||||
& \\
|
||||
\textbf{Networking} & \\
|
||||
\texttt{TRACY\_NO\_BROADCAST} & Don't announce the client's presence on the local network. Manual connections are still possible. \\
|
||||
\texttt{TRACY\_ONLY\_LOCALHOST} & Listen for connections only on the localhost interface. \\
|
||||
\texttt{TRACY\_ONLY\_IPV4} & Accept connections only on IPv4 addresses, disabling IPv6 support. \\
|
||||
& \\
|
||||
\textbf{Data collection} & \\
|
||||
\texttt{TRACY\_NO\_SYSTEM\_TRACING} & Prevent the client from accessing kernel data, which disables all system-level tracing (section~\ref{automated}). \\
|
||||
\texttt{TRACY\_NO\_CONTEXT\_SWITCH} & Disable capture of context switch data (section~\ref{contextswitches}). \\
|
||||
\texttt{TRACY\_NO\_SAMPLING} & Disable call stack sampling (section~\ref{sampling}). \\
|
||||
\texttt{TRACY\_NO\_VSYNC\_CAPTURE} & Disable capture of hardware Vsync events (section~\ref{vsync}). \\
|
||||
\texttt{TRACY\_NO\_CODE\_TRANSFER} & Disable retrieval of the program's machine code and source files (section~\ref{executableretrieval}). \\
|
||||
\texttt{TRACY\_NO\_FRAME\_IMAGE} & Disable frame image support, along with the compression thread it needs (section~\ref{frameimages}). \\
|
||||
\texttt{TRACY\_NO\_CRASH\_HANDLER} & Don't install the crash handler (section~\ref{crashhandling}). \\
|
||||
\texttt{TRACY\_NO\_VERIFY} & Disable validation of the C API instrumentation correctness (section~\ref{capi}). \\
|
||||
\texttt{TRACY\_FIBERS} & Enable fiber support, which is opt-in due to the additional cost it adds to zone capture (section~\ref{fibers}). \\
|
||||
\texttt{TRACY\_IGNORE\_MEMORY\_FAULTS} & Don't report instrumentation failures for free events with no matching allocation, or for repeated allocations of the same address (section~\ref{memoryprofiling}). \\
|
||||
\texttt{TRACY\_OPENGL\_AUTO\_CALIBRATION} & Periodically correct the OpenGL GPU/CPU clock drift, at the cost of a CPU/GPU synchronization each time (section~\ref{gpuprofiling}). \\
|
||||
\texttt{TRACY\_ROCPROF\_CALIBRATION} & Periodically correct the ROCm GPU/CPU clock drift. Available only if rocprofiler-sdk was found (section~\ref{rocm}). \\
|
||||
& \\
|
||||
\textbf{Call stacks and symbols} & \\
|
||||
\texttt{TRACY\_CALLSTACK} & Call stack capture depth, which makes the zone macros without the \texttt{S} postfix collect call stacks (section~\ref{collectingcallstacks}). \\
|
||||
\texttt{TRACY\_NO\_CALLSTACK} & Disable all call stack related functionality (section~\ref{collectingcallstacks}). \\
|
||||
\texttt{TRACY\_NO\_CALLSTACK\_INLINES} & Don't resolve inline frames, retrieval of which may be very slow on Windows. \\
|
||||
\texttt{TRACY\_LIBUNWIND\_BACKTRACE} & Capture call stacks using libunwind, which may be faster than the default implementation. Links with libunwind. \\
|
||||
\texttt{TRACY\_DEBUGINFOD} & Enable on-demand delivery of debugging symbols through debuginfod. Links with libdebuginfod. \\
|
||||
\texttt{TRACY\_SYMBOL\_OFFLINE\_RESOLVE} & Record only image paths and offsets, leaving symbol resolution to be performed later by the \texttt{update} utility. \\
|
||||
\texttt{TRACY\_LIBBACKTRACE\_ELF\_DYNLOAD\_SUPPORT} & Let libbacktrace resolve symbols in ELF objects that were loaded after the first symbol resolution took place. \\
|
||||
\texttt{TRACY\_DEMANGLE} & Don't use the built-in symbol demangling function. You will have to provide your own \texttt{\_\_tracy\_demangle} implementation. \\
|
||||
& \\
|
||||
\textbf{Timers} & \\
|
||||
\texttt{TRACY\_TIMER\_FALLBACK} & Compile in a lower resolution timer, to be used when the hardware timer is unavailable (table~\ref{timeroptions}). \\
|
||||
\texttt{TRACY\_DISALLOW\_HW\_TIMER} & Never use the hardware timer, even if it \emph{appears} to be available. Requires \texttt{TRACY\_TIMER\_FALLBACK}. \\
|
||||
& \\
|
||||
\textbf{Special environments} & \\
|
||||
\texttt{TRACY\_DELAYED\_INIT} & Gather the profiler data into a single structure, initialized on first use instead of at load time (section~\ref{multidll}). \\
|
||||
\texttt{TRACY\_MANUAL\_LIFETIME} & Provide the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions for manual profiler lifetime management. Requires \texttt{TRACY\_DELAYED\_INIT} (section~\ref{multidll}). \\
|
||||
\texttt{TRACY\_PLATFORM\_HEADER} & Path to a header providing the \texttt{TRACY\_HAS\_CUSTOM\_*} hooks for an unsupported platform (section~\ref{customplatform}). \\
|
||||
\texttt{TRACY\_PATCHABLE\_NOPSLEDS} & Emit nopsleds in front of the timer reads, so that system-level tools such as \emph{rr} can patch them. \\
|
||||
& \\
|
||||
\textbf{Diagnostics} & \\
|
||||
\texttt{TRACY\_VERBOSE} & Print detailed information about the detected features to the standard error output (section~\ref{troubleshooting}). \\
|
||||
\texttt{TRACY\_NO\_INTERNAL\_MESSAGE} & Don't send the profiler's own diagnostic messages to the server (section~\ref{troubleshooting}). \\
|
||||
\end{longtable}
|
||||
\end{footnotesize}
|
||||
|
||||
\subsubsection{Meson integration}
|
||||
|
||||
If you are using the Meson build system, you can add Tracy using the Wrap dependency system. To do this, place the \texttt{tracy.wrap} file in the \texttt{subprojects} directory of your project, with the following content. The \texttt{head} \texttt{revision} field tracks Tracy's \texttt{master} branch. If you want to lock to a specific version of Tracy instead, you can just set the \texttt{revision} field to an appropriate git tag.
|
||||
@@ -603,8 +523,6 @@ Here's a sample command to set up a build directory with profiling enabled. The
|
||||
meson setup build --buildtype=debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
|
||||
\end{lstlisting}
|
||||
|
||||
Most of the options listed in table~\ref{cmakeoptions} are also available in the Meson build. Their names are lowercased, with the \texttt{TRACY\_} prefix dropped, the only exception being \texttt{tracy\_enable}.
|
||||
|
||||
\subsubsection{Short-lived applications}
|
||||
|
||||
In case you want to profile a short-lived program (for example, a compression utility that finishes its work in one second), set the \texttt{TRACY\_NO\_EXIT} environment variable to $1$. With this option enabled, Tracy will not exit until an incoming connection is made, even if the application has already finished executing. If your platform doesn't support an easy setup of environment variables, you may also add the \texttt{TRACY\_NO\_EXIT} define to your build configuration, which has the same effect.
|
||||
@@ -639,7 +557,6 @@ If you need to use a specific Tracy client address, such as QNX requires, define
|
||||
By default, the Tracy client will listen on IPv6 interfaces, falling back to IPv4 only if IPv6 is unavailable. If you want to restrict it to only listening on IPv4 interfaces, define the \texttt{TRACY\_ONLY\_IPV4} macro at compile-time, or set the \texttt{TRACY\_ONLY\_IPV4} environment variable to $1$ at runtime.
|
||||
|
||||
\subsubsection{Setup for multi-DLL projects}
|
||||
\label{multidll}
|
||||
|
||||
Things are a bit different in projects that consist of multiple DLLs/shared objects. Compiling \texttt{TracyClient.cpp} into every DLL is not an option because this would result in several instances of Tracy objects lying around in the process. We instead need to pass their instances to the different DLLs to be reused there.
|
||||
|
||||
@@ -788,7 +705,6 @@ The platform header is intended only for the \texttt{TRACY\_HAS\_CUSTOM\_*} hook
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Troubleshooting}
|
||||
\label{troubleshooting}
|
||||
|
||||
By default, Tracy's diagnostics will be sent as Message logs (section~\ref{messagelog}) to the server.
|
||||
Setting the \texttt{TRACY\_NO\_INTERNAL\_MESSAGE} define will disable this feature, but setting the \texttt{TRACY\_VERBOSE} will make the client print advanced information about the detected features to the standard error output. By matching those debug prints to the source code, you might be able to uncover why some of the features are missing on your platform.
|
||||
@@ -1558,32 +1474,6 @@ Exiting the profiled application from inside a zone is not supported. When the c
|
||||
|
||||
As a workaround, you may add a \texttt{try}/\texttt{catch} pair at the bottom of the function stack (for example in the \texttt{main()} function) and replace \texttt{exit()} calls with throwing a custom exception. When this exception is caught, you may call \texttt{exit()}, knowing that the application's data structures (including profiling zones) were properly cleaned up.
|
||||
|
||||
\subsection{Marking sections}
|
||||
\label{markingsections}
|
||||
|
||||
To provide a high-level overview of what your program is doing, you may mark sections of execution. For example, in a puzzle game, the main menu would be a section, and each of the levels would be a section on its own.
|
||||
|
||||
To mark when a section starts, use the \texttt{TracySectionEnter(fmt, ...)} macro with printf-like formatting. The macro will return an \texttt{uint32\_t} identifier, which you must store and pass to the corresponding section-end macro, \texttt{TracySectionLeave(id)}. Here's an example of a simple RAII wrapper:
|
||||
|
||||
\begin{lstlisting}
|
||||
struct TracySection
|
||||
{
|
||||
explicit TracySection(const char* name) : idx(TracySectionEnter("%s", name)) {}
|
||||
~TracySection() { TracySectionLeave(idx); }
|
||||
uint32_t idx;
|
||||
};
|
||||
\end{lstlisting}
|
||||
|
||||
Each of the sections you add is handled independently of any other section. Multiple sections may overlap, either with a hierarchical structure or with each section having a part that does not coincide with other sections.
|
||||
|
||||
\subsubsection{Section categories}
|
||||
|
||||
In some use cases, you will want to label your sections with specific category labels. For example, you may want to mark which level is currently loaded in your game and also mark when the inventory screen is open. When you are only interested in tracking the level progression, sifting through all the times the inventory was on the screen would be quite cumbersome. Section categories let you filter out uninteresting sections.
|
||||
|
||||
To set a category for the section, start the section with the \texttt{TracySectionEnterCategory(category, fmt, ...)} macro, where \texttt{category} is an \texttt{uint16\_t} identifier. Sections without an explicit category assignment (i.e., made with the \texttt{TracySectionEnter} macro) are automatically in the $0$ category.
|
||||
|
||||
You can describe each of the categories you want to use with the \texttt{TracySectionSetup(category, fmt, ...)} macro. Section categories with no name set will automatically be assigned a name based on the identifier (e.g., \emph{Category 0}).
|
||||
|
||||
\subsection{Marking locks}
|
||||
|
||||
Modern programs must use multi-threading to achieve the full performance capability of the CPU. However, correct execution requires claiming exclusive access to data shared between threads. When many threads want to simultaneously enter the same critical section, the application's multi-threaded performance advantage nullifies. To help solve this problem, Tracy can collect and display lock interactions in threads.
|
||||
@@ -1924,7 +1814,6 @@ To instrument a GPU zone, use the various \texttt{TracyWebGPU*Zone*()} macros. N
|
||||
You are required to periodically collect the GPU events using the \texttt{TracyWebGPUCollect()} macro. Good places for collection are: after synchronous waits, after event processing \texttt{wgpuInstanceProcessEvents}, after present drawable calls (\texttt{wgpuSurfacePresent}), and inside the completion callback of command queues (\texttt{wgpuQueueOnSubmittedWorkDone}).
|
||||
|
||||
\subsubsection{ROCm}
|
||||
\label{rocm}
|
||||
|
||||
On Linux, if rocprofiler-sdk is installed, tracy can automatically trace GPU dispatches and collect
|
||||
performance counter values. If CMake can't find rocprofiler-sdk, you can set the CMake variable
|
||||
@@ -2332,7 +2221,7 @@ Refer to sections~\ref{markingzones} and~\ref{multizone} for description of macr
|
||||
|
||||
Unlike C++, there's no automatic destruction mechanism in C, so you will need to mark where the zone ends manually. To do so use the \texttt{TracyCZoneEnd(ctx)} macro.\footnote{GCC and Clang provide \texttt{\_\_attribute\_\_((cleanup))} which can used to run a function when a variable goes out of scope.}
|
||||
|
||||
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. For printf-style formatting, use the \texttt{TracyCZoneTextF(ctx, fmt, ...)} and \texttt{TracyCZoneNameF(ctx, fmt, ...)} variants, which accept a format string and arguments instead of a fixed-length text buffer. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
|
||||
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
|
||||
|
||||
\paragraph{Zone context data structure}
|
||||
\label{zonectx}
|
||||
@@ -2531,7 +2420,10 @@ Tracy C API exposes functions with the \texttt{\_\_\_tracy} prefix that you may
|
||||
\item \texttt{\_\_\_tracy\_alloc\_srcloc\_name(uint32\_t line, const char* source, size\_t sourceSz, const char* function, size\_t functionSz, const char* name, size\_t nameSz)}
|
||||
\end{itemize}
|
||||
|
||||
Here \texttt{line} is line number in the \texttt{source} source file and \texttt{function} is the name of a function in which the zone is created. \texttt{sourceSz} and \texttt{functionSz} are the sizes of the corresponding string arguments in bytes. You may additionally specify an optional zone name by providing it in the \texttt{name} variable and specifying its size in \texttt{nameSz}. If the passed strings contain null-terminating characters, these characters must be excluded from the provided sizes.
|
||||
Here \texttt{line} is line number in the \texttt{source} source file and \texttt{function} is the
|
||||
name of a function in which the zone is created. \texttt{sourceSz} and \texttt{functionSz} are the
|
||||
size of the corresponding string arguments in bytes. You may additionally specify an optional zone
|
||||
name, by providing it in the \texttt{name} variable, and specifying its size in \texttt{nameSz}.
|
||||
|
||||
The \texttt{\_\_\_tracy\_alloc\_srcloc} and \texttt{\_\_\_tracy\_alloc\_srcloc\_name} functions
|
||||
return an \texttt{uint64\_t} source location identifier corresponding to an \emph{allocated source
|
||||
@@ -2575,7 +2467,7 @@ An especially powerful feature is the ability to profile Python code and any oth
|
||||
|
||||
An example of how to use the Tracy-Client bindings is shown below:
|
||||
|
||||
\begin{lstlisting}[language=Python,escapeinside={}{}]
|
||||
\begin{lstlisting}
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -2652,7 +2544,7 @@ if __name__ == "__main__":
|
||||
main()
|
||||
\end{lstlisting}
|
||||
|
||||
Please note the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
|
||||
Please not the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
|
||||
|
||||
\subsubsection{Building the Python package}
|
||||
|
||||
@@ -2710,17 +2602,16 @@ Set the following environment variables before launching (or export them in your
|
||||
PYTHONPATH=/path/to/tracy/build/python/Release
|
||||
TRACY_CAPTURES_DIR=/path/to/captures # enables list_captures
|
||||
TRACY_MCP_PORT=47380 # optional; default 47380
|
||||
TRACY_MCP_TRANSPORT=streamable-http # optional; streamable-http or sse
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Integrating with an AI assistant}
|
||||
|
||||
The server runs as a singleton on Streamable HTTP transport (port 47380 by default). Only one process loads \texttt{TracyServerBindings} regardless of how many editor windows are open; subsequent launches detect the port is taken and exit immediately. Set \texttt{TRACY\_MCP\_TRANSPORT=sse} before launching to use the legacy SSE transport instead.
|
||||
The server runs as a singleton on SSE transport (port 47380 by default). Only one process loads \texttt{TracyServerBindings} regardless of how many editor windows are open; subsequent launches detect the port is taken and exit immediately.
|
||||
|
||||
The server prints its URL on startup and writes it to \texttt{extra/mcp/tracy\_mcp.port}:
|
||||
|
||||
\begin{lstlisting}
|
||||
Tracy MCP listening on http://127.0.0.1:47380/mcp
|
||||
Tracy MCP listening on http://127.0.0.1:47380/sse
|
||||
\end{lstlisting}
|
||||
|
||||
Configure your AI assistant using that URL. For example, for a JSON-based MCP configuration:
|
||||
@@ -2729,7 +2620,7 @@ Configure your AI assistant using that URL. For example, for a JSON-based MCP co
|
||||
{
|
||||
"mcpServers": {
|
||||
"tracy": {
|
||||
"url": "http://127.0.0.1:47380/mcp"
|
||||
"url": "http://127.0.0.1:47380/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3151,8 +3042,6 @@ The sampling functionality also captures call stacks for context switch events.
|
||||
\item Unexpected waits, which should be immediately taken care of. After all, what's the point of profiling and optimizing your program if it is constantly waiting for something? An example of such an unexpected wait may be some anti-virus service interfering with each of your file read operations. In this case, you could have assumed that the system would buffer a large chunk of the data after the first read to make it immediately available to the application in the following calls.
|
||||
\end{enumerate}
|
||||
|
||||
Since context switch samples are captured at scheduling events rather than by the statistical sampling timer, they are not an unbiased measure of program execution. They are therefore only displayed on the timeline and in the wait stacks window, and are excluded from the sampling statistics, the per-symbol sample data, child calls, entry stacks, flame graph, zone call stack reconstruction, and so on.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
@@ -3206,7 +3095,6 @@ On Linux, Tracy will override the \texttt{dlclose} function call to prevent shar
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Vertical synchronization}
|
||||
\label{vsync}
|
||||
|
||||
On Windows and Linux, Tracy will automatically capture hardware Vsync events, provided that the application has access to the kernel data (privilege elevation may be needed, see section~\ref{privilegeelevation}). These events will be reported as \enquote{\texttt{[x] Vsync}} frame sets, where \texttt{x} is the identifier of a specific monitor. Note that hardware vertical synchronization might not correspond to the one seen by your application due to desktop composition, command queue buffering, and so on. Also, in some instances, when there is nothing to update on the screen, the graphic driver may choose to stop issuing screen refresh. As a result, there may be periods where no vertical synchronization events are reported.
|
||||
|
||||
@@ -3221,15 +3109,9 @@ Sometimes it is desired to change how the profiled application behaves during th
|
||||
void Callback(void* data, uint32_t idx, int32_t val)
|
||||
\end{lstlisting}
|
||||
|
||||
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface (in the connection information popup, see section~\ref{connectionpopup}).
|
||||
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface.
|
||||
|
||||
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, type, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to), \texttt{name} is the parameter label, displayed on the list of parameters, and \texttt{val} is the initial value. Finally, \texttt{type} determines how to interpret the \texttt{val} value, and can be selected from:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{TracyParamTypeInt} -- \texttt{val} is an integer value, with the profiler UI displaying a numerical entry field.
|
||||
\item \texttt{TracyParamTypeBool} -- \texttt{val} is a boolean value, with a checkbox in the user interface.
|
||||
\item \texttt{TracyParamTypeTrigger} -- a~\emph{\faCircleDot{}~Trigger} button is displayed, for cases when you just want some action to happen. Repeats the initial \texttt{val} value provided to the setup function.
|
||||
\end{itemize}
|
||||
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, isBool, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to). \texttt{Name} is the parameter label, displayed on the list of parameters. Finally, \texttt{isBool} determines if \texttt{val} should be interpreted as a boolean value, or as an integer number.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
@@ -3430,7 +3312,7 @@ You can also adjust some settings that affect global profiler behavior in this w
|
||||
|
||||
If this is a real-time capture, you will also have access to the connection information pop-up (figure~\ref{connectioninfo}) through the \emph{\faWifi{}~Connection} button, with the capture status similar to the one displayed by the command-line utility. This dialog also shows the connection speed graphed over time and the profiled application's current frames per second and frame time measurements. The \emph{Query backlog} consists of two numbers. The first represents the number of queries that were held back due to the bandwidth volume overwhelming the available network send buffer. The second one shows how many queries are in-flight, meaning requests sent to the client but not yet answered. While these numbers drain down to zero, the performance of real time profiling may be temporarily compromised. The circle displayed next to the bandwidth graph signals the connection status. If it's red, the connection is active. If it's gray, the client has disconnected.
|
||||
|
||||
You can use the \faFloppyDisk{}~\emph{Save trace...} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
|
||||
You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
@@ -3453,7 +3335,7 @@ You can use the \faFloppyDisk{}~\emph{Save trace...} button to save the current
|
||||
|
||||
If frame image capture has been implemented (chapter~\ref{frameimages}), a thumbnail of the last received frame image will be provided for reference.
|
||||
|
||||
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{Trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
|
||||
\subsubsection{Automatic loading or connecting}
|
||||
|
||||
@@ -3709,7 +3591,7 @@ The main profiler window is split into three sections, as seen in figure~\ref{ma
|
||||
\draw[rounded corners=5pt] (1.3, -0.1) rectangle+(2.2, -0.5) node [midway] {\faComment{} Messages};
|
||||
\draw[rounded corners=5pt] (3.6, -0.1) rectangle+(1.3, -0.5) node [midway] {\faMagnifyingGlass{} Find};
|
||||
\draw[rounded corners=5pt] (5.0, -0.1) rectangle+(2, -0.5) node [midway] {\faArrowUpWideShort{} Statistics};
|
||||
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFireFlameCurved{} Flame};
|
||||
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFire{} Flame};
|
||||
\draw[rounded corners=5pt] (8.7, -0.1) rectangle+(2.1, -0.5) node [midway] {\faMemory{} Memory};
|
||||
\draw[rounded corners=5pt] (10.9, -0.1) rectangle+(2.1, -0.5) node [midway] {\faScaleBalanced{} Compare};
|
||||
\draw[rounded corners=5pt] (13.1, -0.1) rectangle+(1.2, -0.5) node [midway] {\faFingerprint{} Info};
|
||||
@@ -3745,7 +3627,7 @@ The control menu (top row of buttons) provides access to various profiler featur
|
||||
\item \emph{\faComment{} Messages} -- Toggles the message log window (section~\ref{messages}), which displays custom messages sent by the client, as described in section~\ref{messagelog}.
|
||||
\item \emph{\faMagnifyingGlass{} Find} -- This buttons toggles the find zone window, which allows inspection of zone behavior statistics (section~\ref{findzone}).
|
||||
\item \emph{\faArrowUpWideShort{} Statistics} -- Toggles the statistics window, which displays zones sorted by their total time cost (section~\ref{statistics}).
|
||||
\item \emph{\faFireFlameCurved{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
|
||||
\item \emph{\faFire{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
|
||||
\item \emph{\faMemory{} Memory} -- Various memory profiling options may be accessed here (section~\ref{memorywindow}).
|
||||
\item \emph{\faScaleBalanced{} Compare} -- Toggles the trace compare window, which allows you to see the performance difference between two profiling runs (section~\ref{compare}).
|
||||
\item \emph{\faFingerprint{} Info} -- Show general information about the trace (section~\ref{traceinfo}).
|
||||
@@ -3756,7 +3638,6 @@ The control menu (top row of buttons) provides access to various profiler featur
|
||||
\item \emph{\faNoteSticky{}~Annotations} -- If annotations have been made (section~\ref{annotatingtrace}), you can open a list of all annotations, described in chapter~\ref{annotationlist}.
|
||||
\item \emph{\faRuler{}~Limits} -- Displays time range limits window (section~\ref{timeranges}).
|
||||
\item \emph{\faHourglassHalf{}~Wait stacks} -- If sampling was performed, an option to display wait stacks may be available. See chapter~\ref{waitstacks} for more details.
|
||||
\item \emph{\faImages{}~Frame statistics} -- Display frame statistics window (section~\ref{framestatistics}).
|
||||
\end{itemize}
|
||||
\item \emph{ \faBook{}~User manual} -- Opens the user manual for quick reference. Note that the version of the user manual available directly in the profiler is an inferior quality version compared to the proper PDF.
|
||||
\item \emph{\faMagnifyingGlassPlus{}~Display scale} -- Enables run-time resizing of the displayed content. This may be useful in environments with potentially reduced visibility, e.g. during a presentation. Note that this setting is independent to the UI scaling coming from the system DPI settings. The scale will be preserved across multiple profiler sessions if the \emph{Save UI scale} option is selected in global settings.
|
||||
@@ -3856,7 +3737,7 @@ Clicking the \LMB{}~left mouse button on the graph while the \keys{\ctrl}~key is
|
||||
|
||||
\subsubsection{Timeline view}
|
||||
|
||||
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into four parts: time scale, frame sets, sections, and the combined zones, locks, and plots display.
|
||||
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into three parts: the time scale, the frame sets, and the combined zones, locks, and plots display.
|
||||
|
||||
\subparagraph{Collapsed items}
|
||||
\label{collapseditems}
|
||||
@@ -3943,40 +3824,6 @@ If a frame has an associated frame image (see chapter~\ref{frameimages}), you ca
|
||||
|
||||
If the \emph{\faFlagCheckered{}~Draw frame targets} option is enabled (see section~\ref{options}), time regions in frames exceeding the set target value will be marked with a red background.
|
||||
|
||||
\paragraph{Sections}
|
||||
\label{sections}
|
||||
|
||||
If sections of program execution were marked (see chapter~\ref{markingsections}), the profiler will try to reconstruct a child-parent hierarchy between the sections, as presented on figure~\ref{figsections}.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw(0, 0) rectangle+(2, -0.5) node[midway] {Menu};
|
||||
\draw(2, 0) rectangle+(10, -0.5) node[midway] {In-game};
|
||||
\draw(12, 0) rectangle+(1.5, -0.5) node[midway] {Menu};
|
||||
|
||||
\draw(2, -0.5) rectangle+(3, -0.5) node[midway] {Level 1};
|
||||
\draw(5, -0.5) rectangle+(5, -0.5) node[midway] {Level 2};
|
||||
\draw(10, -0.5) rectangle+(2, -0.5) node[midway] {Level 3};
|
||||
\end{tikzpicture}
|
||||
\caption{Sections showing a hierarchy.}
|
||||
\label{figsections}
|
||||
\end{figure}
|
||||
|
||||
With some data sets such reconstruction will not be possible, in which case sections may look as on figure~\ref{figsections2}.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw(0, 0) rectangle+(8, -0.5) node[midway] {First section};
|
||||
\draw(3, -0.5) rectangle+(8, -0.5) node[midway] {Second section};
|
||||
\end{tikzpicture}
|
||||
\caption{Sections that cannot form a hierarchy.}
|
||||
\label{figsections2}
|
||||
\end{figure}
|
||||
|
||||
Clicking the \MMB{}~middle mouse button on a section will zoom the view to the extent of the section.
|
||||
|
||||
Section hierarchy reconstruction is not affected by section categories. There is only a single lane for all sections. Enabling or disabling specific section categories in the options window, however, will affect which sections are visible here.
|
||||
|
||||
\paragraph{Zones, locks and plots display}
|
||||
\label{zoneslocksplots}
|
||||
|
||||
@@ -4072,7 +3919,7 @@ In an example in figure~\ref{zoneslocks} you can see that there are two threads:
|
||||
|
||||
Meanwhile, the \emph{Streaming thread} is performing some \emph{Streaming jobs}. The first \emph{Streaming job} sent a message (section~\ref{messagelog}). In addition to being listed in the message log, it is indicated by a triangle over the thread separator. When multiple messages are in one place, the triangle outline shape changes to a filled triangle.
|
||||
|
||||
The GPU zones are displayed just like CPU zones, with an OpenGL / Vulkan / Direct3D / Metal / OpenCL / CUDA / WebGPU context in place of a thread name.
|
||||
The GPU zones are displayed just like CPU zones, with an OpenGL/Vulkan/Direct3D/Metal/OpenCL/CUDA/WebGPU context in place of a thread name.
|
||||
|
||||
Hovering the \faArrowPointer{} mouse pointer over a zone will highlight all other zones that have the exact source location with a white outline. Clicking the \LMB{}~left mouse button on a zone will open the zone information window (section~\ref{zoneinfo}). Holding the \keys{\ctrl} key and clicking the \LMB{}~left mouse button on a zone will open the zone statistics window (section~\ref{findzone}). Clicking the \MMB{}~middle mouse button on a zone will zoom the view to the extent of the zone.
|
||||
|
||||
@@ -4096,15 +3943,7 @@ Clicking the \LMB{}~left mouse button on a ghost zone will open the correspondin
|
||||
|
||||
\subparagraph{Call stack samples}
|
||||
|
||||
The row of dots right below the thread label (\emph{Main thread} in the example) shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Each sample is color-coded as follows:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{Green} -- shows samples inside the profiled program.
|
||||
\item \emph{Blue} -- indicates samples belonging to external libraries.
|
||||
\item \emph{Red} -- used for samples captured in the kernel.
|
||||
\end{itemize}
|
||||
|
||||
Hovering the \faArrowPointer{}~mouse pointer over each dot displays a short call stack summary, while clicking the dot with the \LMB{}~left mouse button opens a more detailed call stack information window (see section~\ref{callstackwindow}).
|
||||
The row of dots right below the \emph{Main thread} label shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Hovering the \faArrowPointer{}~mouse pointer over each dot will display a short call stack summary while clicking on the dot with the \LMB{}~left mouse button will open a more detailed call stack information window (see section~\ref{callstackwindow}).
|
||||
|
||||
\subparagraph{Context switches}
|
||||
|
||||
@@ -4188,7 +4027,7 @@ Mutual exclusion zones are displayed in each thread that tries to acquire them.
|
||||
\item \emph{Red region} -- The thread wants to acquire the lock but is blocked by other thread or threads in case of a shared lock.
|
||||
\end{itemize}
|
||||
|
||||
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Hovering the \faArrowPointer{}~mouse pointer over a thread label shows that thread's aggregate lock wait time across all locks (see above). Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
|
||||
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
|
||||
|
||||
\subparagraph{Plots}
|
||||
\label{plots}
|
||||
@@ -4233,21 +4072,17 @@ Sometimes, you may want to specify a time range, such as limiting some statistic
|
||||
To define a time range, drag the \LMB{}~left mouse button over the timeline view while holding the \keys{\ctrl} key. When the mouse key is released, the profiler will mark the selected time extent with a blue striped pattern, and it will display a context menu with the following options:
|
||||
|
||||
\begin{itemize}
|
||||
\item \faRuler{}~Set time range for:
|
||||
\begin{itemize}
|
||||
\item \emph{\faMagnifyingGlass{}~Find zone} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
|
||||
\item \emph{\faArrowUpWideShort{}~Statistics} -- selecting this option will limit statistics results. See chapter~\ref{statistics} for more details.
|
||||
\item \emph{\faFireFlameCurved{}~Flame graph} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
|
||||
\item \emph{\faHourglassHalf{}~Wait stacks} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
|
||||
\item \emph{\faMemory{}~Memory} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
|
||||
\item \emph{\faImages{}~Frame statistics} -- limits frame statistics. Section~\ref{framestatistics} has more information.
|
||||
\end{itemize}
|
||||
\item \emph{\faMagnifyingGlass{}~Limit find zone time range} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
|
||||
\item \emph{\faArrowUpWideShort{}~Limit statistics time range} -- selecting this option will limit statistics results. See chapter~\ref{statistics} for more details.
|
||||
\item \emph{\faFire{}~Limit flame graph time range} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
|
||||
\item \emph{\faHourglassHalf{}~Limit wait stacks time range} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
|
||||
\item \emph{\faMemory{}~Limit memory time range} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
|
||||
\item \emph{\faNoteSticky{}~Add annotation} -- use to annotate regions of interest, as described in chapter~\ref{annotatingtrace}.
|
||||
\end{itemize}
|
||||
|
||||
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone, section or a frame. The resulting time extent will match the selected item.
|
||||
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone or a frame. The resulting time extent will match the selected item.
|
||||
|
||||
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu. Time range limits can also be disabled or enabled in the windows showing the data they affect.
|
||||
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
|
||||
You can freely adjust each time range on the timeline by clicking the \LMB{}~left mouse button on the range's edge and dragging the mouse.
|
||||
|
||||
@@ -4258,14 +4093,15 @@ Tracy allows adding custom notes to the trace. For example, you may want to mark
|
||||
|
||||
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, it is assigned a semi-unique random name to make it distinguishable. The settings window is also opened (section~\ref{annotationsettings}), allowing you to enter your own description of the annotation.
|
||||
|
||||
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the text description area will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the circle next to the text description will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw (0, -0.25) -- (0, -1) -- (5, -1) -- (5, -0.25);
|
||||
\draw[dotted] (0, 0.2) -- (0, -0.25);
|
||||
\draw[dotted] (5, 0.2) -- (5, -0.25);
|
||||
\draw (0, -0.8) node[anchor=west] {\faNoteSticky{}~Description};
|
||||
\draw (0, 0.25) -- (0, 1) -- (5, 1) -- (5, 0.25);
|
||||
\draw[dotted] (0, -0.2) -- (0, 0.25);
|
||||
\draw[dotted] (5, -0.2) -- (5, 0.25);
|
||||
\draw (0.25, 0.75) circle(0.15);
|
||||
\draw (0.4, 0.7) node[anchor=west] {Description};
|
||||
\end{tikzpicture}
|
||||
\caption{Annotation region.}
|
||||
\label{annotation}
|
||||
@@ -4292,9 +4128,7 @@ In this window, you can set various trace-related options. For example, the time
|
||||
\begin{itemize}
|
||||
\item \emph{\faSignature{} Draw CPU usage graph} -- You can disable drawing of the CPU usage graph here.
|
||||
\end{itemize}
|
||||
\item \emph{\faEyeDropper{}~Draw stack samples} -- Controls if stack samples for each thread are displayed on the timeline.
|
||||
\item \emph{\faArrowsLeftRightToLine{}~Draw sections} -- Allows disabling display of sections (see chapter~\ref{sections}). If there are multiple section categories, there's also an expandable list of categories that can be disabled or enabled.
|
||||
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL / Vulkan / Metal / Direct3D / OpenCL / CUDA / WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
|
||||
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL/Vulkan/Metal/Direct3D/OpenCL/CUDA/WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
|
||||
\item \emph{\faMicrochip{} Draw CPU zones} -- Determines whether CPU zones are displayed.
|
||||
\begin{itemize}
|
||||
\item \emph{\faGhost{} Draw ghost zones} -- Controls if ghost zones should be displayed in threads which don't have any instrumented zones available.
|
||||
@@ -4332,7 +4166,7 @@ For now, restoring the defaults can be done by deleting the configuration file.
|
||||
\subsection{Messages window}
|
||||
\label{messages}
|
||||
|
||||
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message. Clicking the \RMB{}~right mouse button on a message will display context menu with a \emph{\faClipboard{}~Copy message} option, which copies the message text to clipboard.
|
||||
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message.
|
||||
|
||||
The \emph{call stack} column is filled only if a call stack capture was requested, as described in section~\ref{collectingcallstacks}. A single entry consists of the \emph{\faAlignJustify{}~Show} button, which opens the call stack information window (chapter~\ref{callstackwindow}) and of abbreviated information about the call path.
|
||||
|
||||
@@ -4383,7 +4217,7 @@ The \emph{Name} column contains name of the symbol in which the sampling was don
|
||||
|
||||
By default, each inlining of a function is listed separately. If you prefer to combine the measurements for functions that are inlined multiple times within a function, you can do so by enabling the \emph{\faLayerGroup{}~Aggregate} option. You cannot view sample entry stacks of inlined functions when this grouping method is enabled.
|
||||
|
||||
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire{}~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
|
||||
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire*~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
|
||||
|
||||
If the \emph{\faSitemap{}~Inlines} option is enabled, the list will show all functions without grouping them by symbol. In this mode, inline functions are preceded by a \faCaretRight{} symbol and their parent function name is displayed in parentheses.
|
||||
|
||||
@@ -4391,9 +4225,9 @@ The \emph{Location} column displays the corresponding source file name and line
|
||||
|
||||
The location data is complemented by the originating executable image name, contained in the \emph{Image} column. If the \emph{\faScissors{}~Short images} option is selected, the image path will be shortened to just the image file name, with the full path available in the tooltip.
|
||||
|
||||
The profiler may not find some function names due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
|
||||
The profiler may not find some function locations due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
|
||||
|
||||
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the number of collected samples, excluding context switch samples, which do not participate in the sampling statistics (section~\ref{waitstacks}). You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
|
||||
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the total number of collected samples. You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
|
||||
|
||||
The last column, \emph{Code size}, displays the size of the symbol in the executable image of the program. Since inlined routines are directly embedded into other functions, their symbol size will be based on the parent symbol and displayed as 'less than'. In some cases, this data won't be available. If the symbol code has been retrieved\footnote{Symbols larger than 128~KB are not captured.} symbol size will be prepended with the \texttt{\faDatabase}~icon, and clicking the \RMB{}~right mouse button on the location column entry will open symbol view window (section~\ref{symbolview}).
|
||||
|
||||
@@ -4529,21 +4363,7 @@ Clicking the \LMB{}~left mouse button on the group name will highlight the group
|
||||
|
||||
The call stack grouping mode has a different way of listing groups. Here only one group is displayed at any time due to the need to display the call stack frames. You can switch between call stack groups by using the~\faCaretLeft{}~and~\faCaretRight{} buttons. You can select the group by clicking on the~\emph{\faCheck{}~Select} button. You can open the call stack window (section~\ref{callstackwindow}) by pressing the~\emph{\faAlignJustify{}~Call~stack} button.
|
||||
|
||||
Tracy displays a variety of statistical values regarding the selected function:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{mean} -- average time value,
|
||||
\item \emph{median} -- middle time value (P50),
|
||||
\item \emph{mode} -- most common time value, quantized using histogram bins,
|
||||
\item \emph{\textsigma} -- standard deviation,
|
||||
\item \emph{coefficient of variation} -- relation of \textsigma{} to mean,
|
||||
\item \emph{P75} -- 75th percentile time value,
|
||||
\item \emph{P90} -- 90th percentile time value,
|
||||
\item \emph{P99} -- 99th percentile time value,
|
||||
\item \emph{P99.9} -- 99.9th percentile time value.
|
||||
\end{itemize}
|
||||
|
||||
The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
|
||||
Tracy displays a variety of statistical values regarding the selected function: mean (average value), median (middle value), mode (most common value, quantized using histogram bins), and \textsigma{} (standard deviation). The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
|
||||
|
||||
Hovering the \faArrowPointer{}~mouse cursor over a zone on the timeline, which is currently selected in the find zone window, will display a pulsing vertical bar on the histogram, highlighting the bin to which the hovered zone has been assigned. In addition, it will also highlight zone entry on the zone list.
|
||||
|
||||
@@ -4599,27 +4419,11 @@ You can further narrow down the list of samples by selecting a time range on the
|
||||
|
||||
Comparing the performance impact of the optimization work is not an easy thing to do. Benchmarking is often inconclusive, if even possible, in the case of interactive applications, where the benchmarked function might not have a visible impact on frame render time. Furthermore, doing isolated micro-benchmarks loses the application's execution environment, in which many different parts compete for limited system resources.
|
||||
|
||||
Tracy solves this problem by providing a compare traces functionality, which is very similar to the find zone window. There are three tabs in this window, for three modes of comparison:
|
||||
Tracy solves this problem by providing a compare traces functionality, very similar to the find zone window, described in section~\ref{findzone}. You can compare traces either by zone or frame timing data.
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{Zones} -- closely matches the find zone window, described in section~\ref{findzone}.
|
||||
\item \emph{Frames} -- shows frame rate data, following the frame statistics window described in section~\ref{framestatistics}.
|
||||
\item \emph{Source diff} -- displays differences in source code, covered in section~\ref{sourcefilesdiff}.
|
||||
\end{itemize}
|
||||
You would begin your work by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
|
||||
|
||||
To gather the data, you would start by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
logo=\bclampe
|
||||
]{Trace descriptions}
|
||||
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
|
||||
\end{bclogo}
|
||||
|
||||
Now things start to get familiar. In the zone comparison mode, you search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram. When comparing frame times you are presented with a list of available frame sets, without the search box.
|
||||
|
||||
In the compare traces window there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
|
||||
Now things start to get familiar. You search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram\footnote{When comparing frame times you are presented with a list of available frame sets, without the search box.}. This time there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
@@ -4656,12 +4460,15 @@ When searching for source locations it's not uncommon to match more than one zon
|
||||
|
||||
It may be difficult, if not impossible, to perform identical runs of a program. This means that the number of collected zones may differ in both traces, influencing the displayed results. To fix this problem, enable the \emph{Normalize values} option, which will adjust the displayed results as if both traces had the same number of recorded zones.
|
||||
|
||||
The window shows statistical data for both traces, with the values described in chapter~\ref{findzone}. Each value is also compared against the other trace, showing both the absolute and relative differences. The keywords \emph{less} and \emph{more} are color coded to note significance. When the two readings are almost the same, the keywords will be printed in gray. As the values diverge, the amount of color (either green or red) increases, maxing out at twice the difference. This allows quick eyeballing of where the difference is.
|
||||
|
||||
In some cases you may want to compare only specific parts of traces, not the whole performance profile. To do so, you can enable the \emph{Limit range} option, which will then present you with time range limiting options for both traces. Use the \emph{\faCopy{}~Copy from...} button to choose a time range limit from a list of annotations (chapter~\ref{annotatingtrace}) or sections (chapter~\ref{sections}). To remove the limit, you can click on the \emph{Reset} button.
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
logo=\bclampe
|
||||
]{Trace descriptions}
|
||||
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Source files diff}
|
||||
\label{sourcefilesdiff}
|
||||
|
||||
To see what changes were made in the source code between the two compared traces, select the \emph{Source diff} compare mode. This will display a list of deleted, added, and changed files. By default, the difference is calculated from the older trace to the newer one. You can reverse this by clicking on the \emph{Switch} button.
|
||||
|
||||
@@ -4728,10 +4535,6 @@ Similar to the statistics window (section~\ref{statistics}), the flame graph can
|
||||
|
||||
In the sampling mode, external frames from system libraries are hidden by default. These typically include internal implementation details of starting threads, handling smart pointers, and other such things that are quick to execute and not really interesting. Enabling the \emph{\faShieldHalved{}~External} option will show these frames. One exception is \emph{external tails}, or calls that your code makes that do not eventually land in your application down the call chain. Think of functions that write to a file or send data on the network. These can be time-consuming, and you may want to see them. There is a separate option to disable these.
|
||||
|
||||
The sampling mode, by default, groups the displayed zones by symbol address. In some cases, for example, when using templates, the same function may exist at multiple different symbol addresses. Enabling the \emph{Group by name} option will group the graph items by name instead of by symbol address.
|
||||
|
||||
Sometimes a stack frame cannot be resolved to a symbol, as described in section~\ref{callstackwindow}. Such frames are aggregated into a single graph item with the label of the executable image the frame address belongs to, or with \enquote{\texttt{[unknown]}} if even this information is unavailable. The label is slightly dimmed to set it apart from the resolved items. The \emph{\faScissors{}~Short images} option shortens the displayed image name to only the file name.
|
||||
|
||||
The flame graph can be restricted to a specific time extent using the \emph{Limit range} option (chapter~\ref{timeranges}). You can access more options through the \emph{\faRuler{}~Limits} button, which will open the time range limits window, described in section~\ref{timerangelimits}.
|
||||
|
||||
\subsection{Memory window}
|
||||
@@ -4807,6 +4610,8 @@ If the \emph{\faUserGear{}~Public sidecar} option is selected, the file containi
|
||||
|
||||
Open the \emph{Trace statistics} section to see information about the trace, such as achieved timer resolution, number of captured zones, lock events, plot data points, memory allocations, etc.
|
||||
|
||||
There's also a section containing the selected frame set timing statistics and histogram\footnote{See section~\ref{findzone} for a description of the histogram. Note that there are subtle differences in the available functionality.}. As a convenience, you can switch the active frame set here and limit the displayed frame statistics to the frame range visible on the screen.
|
||||
|
||||
If \emph{CPU topology} data is available (see section~\ref{cputopology}), you will be able to view the package, core, and thread hierarchy.
|
||||
|
||||
The \emph{Source location substitutions} section allows adapting the source file paths, as captured by the profiler, to the actual on-disk locations\footnote{This does not affect source files cached during the profiling run.}. You can create a new substitution by clicking the \emph{Add new substitution} button. This will add a new entry, with input fields for ECMAScript-conforming regular expression pattern and its corresponding replacement string. You can quickly test the outcome of substitutions in the \emph{example source location} input field, which will be transformed and displayed below, as \emph{result}.
|
||||
@@ -4830,13 +4635,6 @@ In this window, you can view the information about the machine on which the prof
|
||||
|
||||
If an application should crash during profiling (section~\ref{crashhandling}), the profiler will display the crash information in this window. It provides you information about the thread that has crashed, the crash reason, and the crash call stack (section~\ref{callstackwindow}).
|
||||
|
||||
\subsection{Frame statistics window}
|
||||
\label{framestatistics}
|
||||
|
||||
This windows shows statistical information about the selected frame set timing, and a histogram. See section~\ref{findzone} for a description of the displayed data. As a convenience, you can switch the active frame set here.
|
||||
|
||||
You can restrict the displayed frame statistics to the frame range currently visible on the screen with the \emph{Limit to view} option, while the \emph{Limit range} switch limits the active range to a preset time limit, as described in chapter~\ref{timerangelimits}. If both limit options are active, the resulting frame range is the intersection of both ranges.
|
||||
|
||||
\subsection{Zone information window}
|
||||
\label{zoneinfo}
|
||||
|
||||
@@ -4886,8 +4684,6 @@ A single stack frame may have multiple function call places associated with it.
|
||||
|
||||
If the call stack shows a crash (see section~\ref{crashhandling}), a red \emph{\faSkull{}~Crash} label will be displayed. Clicking it will center the timeline on the crash. Note that the crash stack may contain OS or Tracy frames where the crash was intercepted and processed.
|
||||
|
||||
If the call stack shows a wait stack (see section~\ref{waitstacks}), a blue \emph{\faHourglassHalf{}~Wait stack} label will be displayed. Hovering the \faArrowPointer{}~mouse pointer over it will display a tooltip displaying how much time was spent waiting in the stack, what was the wait reason and status.
|
||||
|
||||
Stack frame location may be displayed in the following number of ways, depending on the \emph{Frame~at} option selection:
|
||||
|
||||
\begin{itemize}
|
||||
@@ -4903,7 +4699,7 @@ External frames from system libraries are hidden by default. Enabling the \emph{
|
||||
|
||||
The \emph{\faScissors{}~Short images} option shortens the displayed executable image name to only the file name. The full path is available in the tooltip.
|
||||
|
||||
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faArrowDownShortWide{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
|
||||
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faDoorOpen{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
|
||||
|
||||
Clicking on the \emph{\faClipboard{}~Copy to clipboard} button will copy call stack to the clipboard.
|
||||
|
||||
@@ -4971,7 +4767,7 @@ Nevertheless, \textbf{the displayed source files might still not reflect the cod
|
||||
|
||||
A much more capable symbol view mode is available if the inspected source location has an associated symbol context (i.e., if it comes from a call stack capture, from call stack sampling, etc.). A symbol is a unit of machine code, basically a callable function. It may be generated using multiple source files and may consist of numerous inlined functions. A list of all captured symbols is available in the statistics window, as described in chapter~\ref{statisticssampling}.
|
||||
|
||||
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{\faArrowDownShortWide{}~Entry stacks} button.
|
||||
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{Entry stacks} button.
|
||||
|
||||
Additionally, you may use the \emph{Mode} selector to decide what content should be displayed in the panels below:
|
||||
|
||||
@@ -5106,13 +4902,8 @@ The percentage values when \emph{\faCarBurst{}~Impact} option is not selected wi
|
||||
The profiler can group call stacks leading to certain events and display the resulting information in a variety of ways. In essence, this shows the code paths that lead to these events and the distribution of these paths. At this moment, these events include:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active. The displayed data set can be selected with the following options:
|
||||
\begin{itemize}
|
||||
\item \emph{Was reached} -- counts samples in which the symbol was present anywhere on the call stack. Each sample is counted only once, at the outermost occurrence of the symbol, so recursive re-entries are ignored.
|
||||
\item \emph{Was reached, recursive} -- as above, but each occurrence of the symbol on the call stack is counted separately, showing also the paths by which the symbol re-entered itself.
|
||||
\item \emph{Was executing} -- counts samples taken while the symbol was executing, i.e.\ it was at the top of the call stack.
|
||||
\end{itemize}
|
||||
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information. Note that wait stacks have separate setting for the \emph{\faShieldHalved{}~External} option, as it is generally beneficial to see the external code when determining the cause of program interruption.
|
||||
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active.
|
||||
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information.
|
||||
\end{itemize}
|
||||
|
||||
The call stack paths may be displayed in the following ways:
|
||||
@@ -5133,11 +4924,9 @@ This window presents information and statistics about a lock. The lock events co
|
||||
\subsection{Frame image playback window}
|
||||
\label{playback}
|
||||
|
||||
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons, and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively, you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons, or use the \Scroll{}~mouse wheel on either the \emph{Frame image} slider or the displayed frame image to change a single frame backward or forward.
|
||||
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons to change single frame back or forward.
|
||||
|
||||
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing. With the \emph{Loop} option enabled, playback will start from the beginning after it reaches the end.
|
||||
|
||||
Enabling the \emph{Limit frame range} option allows you to manually set the frame image playback region. Adjustments can be made using the start and end sliders or by copying the time range from either an annotation (section~\ref{annotationlist}), a section (chapter~\ref{sections}), or one of the time range limits (section~\ref{timerangelimits}). If the \emph{Require frame coverage} option is enabled, source regions must cover at least 75\% of the frame. Otherwise any amount of overlap is sufficient.
|
||||
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing.
|
||||
|
||||
The following parameters also accompany each displayed frame image: \emph{timestamp}, showing at which time the image was captured, \emph{frame}, displaying the numerical value of the corresponding frame, and \emph{ratio}, telling how well the in-memory loss-less compression was able to reduce the image data size.
|
||||
|
||||
@@ -5155,7 +4944,7 @@ The profiled program is highlighted using green color. Furthermore, the yellow h
|
||||
\subsection{Annotation settings window}
|
||||
\label{annotationsettings}
|
||||
|
||||
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also temporarily hide or remove it here.
|
||||
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also remove it here.
|
||||
|
||||
\subsection{Annotation list window}
|
||||
\label{annotationlist}
|
||||
@@ -5163,17 +4952,15 @@ In this window, you may modify how a timeline annotation (section~\ref{annotatin
|
||||
This window lists all annotations marked on the timeline. Each annotation is presented, as shown on figure~\ref{figannlist}. From left to right the elements are:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{\faCheck{}~Visible} -- Makes the annotation visible or not on the timeline.
|
||||
\item \emph{\faPenToSquare{}~Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
|
||||
\item \emph{\faMicroscope{}~Zoom} -- Zooms timeline to the annotation extent.
|
||||
\item \emph{\faTrashCan{}~Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
|
||||
\item \emph{\faPenToSquare{} Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
|
||||
\item \emph{\faMicroscope{} Zoom} -- Zooms timeline to the annotation extent.
|
||||
\item \emph{\faTrashCan{} Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
|
||||
\item Colored box -- Color of the annotation.
|
||||
\item Text description of the annotation.
|
||||
\end{itemize}
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw[rounded corners=5pt] (-0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faCheck};
|
||||
\draw[rounded corners=5pt] (0.0, 0) rectangle+(0.5, -0.5) node [midway] {\faPenToSquare};
|
||||
\draw[rounded corners=5pt] (0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faMicroscope};
|
||||
\draw[rounded corners=5pt] (1.2, 0) rectangle+(0.5, -0.5) node [midway] {\faTrashCan};
|
||||
@@ -5184,28 +4971,22 @@ This window lists all annotations marked on the timeline. Each annotation is pre
|
||||
\label{figannlist}
|
||||
\end{figure}
|
||||
|
||||
A new view-sized annotation can be added in this window by pressing the \emph{\faNoteSticky{}~Add annotation} button. This effectively saves your current viewport for further reference.
|
||||
A new view-sized annotation can be added in this window by pressing the \emph{\faPlus{}~Add annotation} button. This effectively saves your current viewport for further reference.
|
||||
|
||||
\subsection{Time range limits}
|
||||
\label{timerangelimits}
|
||||
|
||||
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), wait stacks (section~\ref{stackwindows}), memory (section~\ref{memorywindow}) and frame statistics (section~\ref{framestatistics}) results. Each limit can be enabled or disabled and adjusted through the following options:
|
||||
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), memory (section~\ref{memorywindow}) and wait stacks (section~\ref{stackwindows}) results. Each limit can be enabled or disabled and adjusted through the following options:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{\faMicroscope{}~Focus} -- Set the timeline view to the time range extent.
|
||||
\item \emph{Limit to view} -- Set the time range limit to current view.
|
||||
\item \emph{\faNoteSticky{}~Add annotation} -- Create a new annotation matching the time range. See section~\ref{annotatingtrace} for more information.
|
||||
\item \faCopy{}~Copy from...:
|
||||
\begin{itemize}
|
||||
\item \emph{\faNoteSticky{}~Annotation} -- Allows using the annotation region for limiting purposes.
|
||||
\item \emph{\faArrowsLeftRightToLine{}~Sections} -- Copies time range of a section. See section~\ref{sections} for more information.
|
||||
\item \emph{\faMagnifyingGlass{}~Find zone} -- Copies the find zone time range limit.
|
||||
\item \emph{\faArrowUpWideShort{}~Statistics} -- Copies the statistics time range limit.
|
||||
\item \emph{\faFireFlameCurved{}~Flame graph} -- Copies the flame graph time range limit.
|
||||
\item \emph{\faHourglassHalf{}~Wait stacks} -- Copies the wait stacks time range limit.
|
||||
\item \emph{\faMemory{}~Memory} -- Copies the memory time range limit.
|
||||
\item \emph{\faImages{}~Frame statistics} -- Copies the frame statistics range limit.
|
||||
\end{itemize}
|
||||
\item \emph{\faMicroscope{}~Focus} -- Set the timeline view to the time range extent.
|
||||
\item \emph{\faNoteSticky{}~From annotation} -- Allows using the annotation region for limiting purposes.
|
||||
\item \emph{\faMagnifyingGlass{}~Copy find zone} -- Copies the find zone time range limit.
|
||||
\item \emph{\faArrowUpWideShort{}~Copy statistics} -- Copies the statistics time range limit.
|
||||
\item \emph{\faFireFlameCurved{}~Copy flame} -- Copies the flame graph time range limit.
|
||||
\item \emph{\faHourglassHalf{}~Copy wait stacks} -- Copies the wait stacks time range limit.
|
||||
\item \emph{\faMemory{}~Copy memory} -- Copies the memory time range limit.
|
||||
\end{itemize}
|
||||
|
||||
Note that ranges displayed in the window have color hints that match the color of the striped regions on the timeline.
|
||||
@@ -5237,42 +5018,6 @@ You do not. Tracy is not a money funnel for Silicon Valley tech bros to get rich
|
||||
The only way to access the assistant is to run everything locally on your system. This ensures that everything you do stays private and that you won't be subject to forced changes in features or terms and conditions. You should own the tools you work with instead of renting them from someone else.
|
||||
\end{bclogo}
|
||||
|
||||
If you just want to get things running and have a reasonably powerful hardware, follow the steps below.
|
||||
|
||||
\begin{enumerate}
|
||||
\item Go to \url{https://llama.app/} and follow instructions to install llama.cpp.
|
||||
\item Create the following \texttt{llama.ini} configuration file:
|
||||
\begin{lstlisting}
|
||||
; Launch with: llama-server --models-preset llama.ini
|
||||
|
||||
[*]
|
||||
version = 1
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
|
||||
[bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M]
|
||||
hf = bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M
|
||||
parallel = 1
|
||||
spec-default = true
|
||||
spec-type = draft-mtp
|
||||
chat-template-kwargs = {"preserve_thinking": true}
|
||||
ctx-size = 100000
|
||||
|
||||
[nomic-ai/nomic-embed-text-v1.5-GGUF:Q4_K_M]
|
||||
hf = nomic-ai/nomic-embed-text-v1.5-GGUF:Q4_K_M
|
||||
embedding = true
|
||||
parallel = 4
|
||||
ctx-size = 8192
|
||||
cache-ram = 0
|
||||
\end{lstlisting}
|
||||
\item Run \texttt{llama serve -{}-models-preset llama.ini}
|
||||
\item Connect to llama.cpp inside the profiler!
|
||||
\end{enumerate}
|
||||
|
||||
The models will be automatically downloaded when trying to access them for the first time. It may take some time (the main model is 22+ GB).
|
||||
|
||||
If you have the resources available you may try replacing \texttt{bartowski/Qwen\_Qwen3.6-35B-A3B-GGUF:Q4\_K\_M} with \texttt{unsloth/Qwen3.6-27B-MTP-GGUF:Q4\_K\_M} in the configuration file to get a more capable model.
|
||||
|
||||
\subsection{Service provider}
|
||||
|
||||
To get started, you will need to install an LLM\footnote{Large Language Model.} provider on your system. Any service that's compatible with the standard API should work, but some may work better than others. The LLM field is advancing quickly, with new models frequently being released that often require specific support from provider services to deliver the best experience.
|
||||
@@ -5329,18 +5074,18 @@ Each token present in the context window may require a fairly large amount of me
|
||||
The realistic minimum required context size for Tracy to run the assistant is 100K tokens, but feel free to experiment.
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Embedding model}
|
||||
|
||||
This is a small model used for semantic search in the user manual. This should be \textbf{nomic-embed-text-1.5}, which is provided by default by LM Studio, or which you can download on your own for llama.cpp.
|
||||
|
||||
LM Studio properly labels the model's capabilities. This is not the case with the llama.cpp/llama-swap setup. To make it work, your embedding model's name must contain the word \texttt{embed}.
|
||||
|
||||
\subsubsection{Fast model}
|
||||
|
||||
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
|
||||
|
||||
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
|
||||
|
||||
\subsubsection{Embedding model}
|
||||
|
||||
This is a small model used for semantic search in the user manual. This should be \textbf{nomic-embed-text-1.5}, which is provided by default by LM Studio, or which you can download on your own for llama.cpp.
|
||||
|
||||
LM Studio properly labels the model's capabilities. This is not the case with the llama.cpp/llama-swap setup. To make it work, your embedding model's name must contain the word \texttt{embed}.
|
||||
|
||||
\subsubsection{In practice}
|
||||
|
||||
So, which model should you run and what hardware you need to be able to do so? Let's take look at some example systems.
|
||||
@@ -5370,23 +5115,16 @@ The chat window is divided into three sections:
|
||||
The control section allows you to clear the chat contents, reconnect to the LLM provider and open the settings panel consisting of:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{\faPlug{}~API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
|
||||
\item \emph{API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
|
||||
\item \emph{\faComments{}~Chat model} -- Here you can select one of the models you have configured in the LLM provider for chat.
|
||||
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
|
||||
\item \emph{\faBookBookmark{}~Embeddings model} -- Select the vector embeddings model.
|
||||
\item \emph{Personality} -- Choose how the assistant should behave. Note that the actual behavior will vary depending on the language model you have chosen. The following options are available:
|
||||
\begin{itemize}
|
||||
\item \emph{\faFaceMeh{}~Assistant} -- The default behavior of the model with Tracy Assist role guidance.
|
||||
\item \emph{\faFaceGrin{}~Emotion} -- The model will behave like a human with feelings and emotions.
|
||||
\item \emph{\faFaceFrown{}~Annoyed} -- Does not tolerate nonsense and will respond with sarcasm.
|
||||
\end{itemize}
|
||||
Personality changes do not take effect until you start a new chat.
|
||||
\item \emph{\faEarthAmericas{}~Internet access} -- Determines whether the model can access network resources such as Wikipedia queries, web searches, and web page retrievals.
|
||||
\item \emph{\faTag{}~Annotate call stacks} -- Enables automatic annotation of call stacks (see section~\ref{callstackwindow}). Disabled by default, as it requires proper configuration of the fast model.
|
||||
\item \emph{\faHandPointRight{}~Show summary} -- Shows a short conversation topic after the initial question is asked.
|
||||
\item \emph{\faCommentDots{}~Chat suggestions} -- Suggests the next question the user may want to ask.
|
||||
\item \emph{Advanced} -- More advanced options are hidden here.
|
||||
\begin{itemize}
|
||||
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
|
||||
\item \emph{\faTemperatureHalf{}~Temperature} -- Allows changing default model temperature setting.
|
||||
\item \emph{\faLightbulb{}~Show all thinking regions} -- Always shows all reasoning sections and all tool calls made by model.
|
||||
\item \emph{Tool reply size limit} -- Configurable maximum size for tool responses.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
project('tracy', ['cpp'], version: '0', meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
|
||||
project('tracy', ['cpp'], version: '0.13.1', meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
|
||||
|
||||
# internal compiler flags
|
||||
tracy_compile_args = []
|
||||
@@ -192,16 +192,13 @@ common_includes = [
|
||||
'public/common/TracyApi.h',
|
||||
'public/common/TracyColor.hpp',
|
||||
'public/common/TracyForceInline.hpp',
|
||||
'public/common/TracyFormat.h',
|
||||
'public/common/TracyMutex.hpp',
|
||||
'public/common/TracyProtocol.hpp',
|
||||
'public/common/TracyQueue.hpp',
|
||||
'public/common/TracySocket.hpp',
|
||||
'public/common/TracyStackFrames.hpp',
|
||||
'public/common/TracyString.hpp',
|
||||
'public/common/TracySystem.hpp',
|
||||
'public/common/TracyTaggedUserlandAddress.hpp',
|
||||
'public/common/TracyVersion.hpp',
|
||||
'public/common/TracyWinFamily.hpp',
|
||||
'public/common/TracyYield.hpp'
|
||||
]
|
||||
|
||||
@@ -8,12 +8,7 @@ set_option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
|
||||
set_option(SELF_PROFILE "Enable self-profiling" OFF)
|
||||
set_option_value(SANITIZE "Sanitizer parameters" "")
|
||||
|
||||
if(APPLE)
|
||||
set_option(BUNDLE "Build a macOS application bundle" OFF)
|
||||
endif()
|
||||
|
||||
set(NO_STATISTICS OFF)
|
||||
set(VENDOR_GUI ON)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
@@ -106,7 +101,6 @@ set(SERVER_FILES
|
||||
TracyView_FindZone.cpp
|
||||
TracyView_FlameGraph.cpp
|
||||
TracyView_FrameOverview.cpp
|
||||
TracyView_FrameStatistics.cpp
|
||||
TracyView_FrameTimeline.cpp
|
||||
TracyView_FrameTree.cpp
|
||||
TracyView_GpuTimeline.cpp
|
||||
@@ -128,7 +122,6 @@ set(SERVER_FILES
|
||||
TracyView_ZoneInfo.cpp
|
||||
TracyView_ZoneTimeline.cpp
|
||||
TracyWeb.cpp
|
||||
TracyWindowConstraints.cpp
|
||||
)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
@@ -159,8 +152,6 @@ set(PROFILER_FILES
|
||||
src/winmainArchDiscovery.cpp
|
||||
)
|
||||
|
||||
Embed(PROFILER_FILES PersonalityAnnoyed src/llm/personality.annoyed.md TEXT)
|
||||
Embed(PROFILER_FILES PersonalityEmotion src/llm/personality.emotion.md TEXT)
|
||||
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md TEXT)
|
||||
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md TEXT)
|
||||
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md TEXT)
|
||||
@@ -194,12 +185,8 @@ set(LIBS "")
|
||||
|
||||
if(USE_WAYLAND)
|
||||
pkg_check_modules(WAYLAND REQUIRED egl wayland-egl wayland-cursor xkbcommon)
|
||||
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
|
||||
if(WAYLAND_CLIENT_VERSION VERSION_LESS 1.22)
|
||||
message(WARNING "libwayland ${WAYLAND_CLIENT_VERSION} is too old and does not provide version 6 of the core protocol. Building in this configuration is not supported and will result in reduced functionality.")
|
||||
endif()
|
||||
set(INCLUDES "${INCLUDES};${WAYLAND_INCLUDE_DIRS}")
|
||||
set(LIBS "${LIBS};${WAYLAND_LINK_LIBRARIES}")
|
||||
set(LIBS "${LIBS};${WAYLAND_LIBRARIES}")
|
||||
set(PROFILER_FILES ${PROFILER_FILES}
|
||||
src/BackendWayland.cpp
|
||||
)
|
||||
@@ -272,25 +259,6 @@ if(WIN32)
|
||||
)
|
||||
add_executable(${PROJECT_NAME} WIN32 ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
elseif(APPLE AND BUNDLE)
|
||||
set(ICON_SRC ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.png)
|
||||
set(ICON_ICNS ${CMAKE_CURRENT_BINARY_DIR}/icon.icns)
|
||||
add_custom_command(
|
||||
OUTPUT ${ICON_ICNS}
|
||||
COMMAND sips -s format icns ${ICON_SRC} --out ${ICON_ICNS}
|
||||
DEPENDS ${ICON_SRC}
|
||||
)
|
||||
set_source_files_properties(${ICON_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES} ${ICON_ICNS})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/macos/Info.plist.in
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Tracy Profiler"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "pl.nereid.tracy"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${TRACY_VERSION_STRING}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${TRACY_VERSION_STRING}
|
||||
MACOSX_BUNDLE_ICON_FILE icon.icns
|
||||
MACOSX_BUNDLE_COPYRIGHT "Bartosz Taudul"
|
||||
)
|
||||
else()
|
||||
add_executable(${PROJECT_NAME} ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
||||
endif()
|
||||
|
||||
@@ -1,47 +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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "profile",
|
||||
"displayName": "Self-profiling configuration",
|
||||
"inherits": "release",
|
||||
"cacheVariables": {
|
||||
"SELF_PROFILE": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"displayName": "Web build",
|
||||
"inherits": "release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_TOOLCHAIN_FILE": "~/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Builds the profiler as an AppImage. The Wayland client libraries are built
|
||||
# from a pinned release and bundled, so the core protocol version does not
|
||||
# depend on what the build host provides. Everything else that is host-coupled
|
||||
# (glibc, EGL, dbus, xkbcommon, OpenSSL) is left to the target system, which
|
||||
# is expected to be reasonably current. The build host sets the glibc floor,
|
||||
# so this should run on the oldest distro release that is still supported.
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
BUILD="$ROOT/profiler/build-appimage"
|
||||
PREFIX="$BUILD/wayland"
|
||||
APPDIR="$BUILD/AppDir"
|
||||
|
||||
WAYLAND_VERSION=1.25.0
|
||||
APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
|
||||
mkdir -p "$BUILD"
|
||||
|
||||
if [ ! -e "$PREFIX/lib/pkgconfig/wayland-client.pc" ]; then
|
||||
git clone --depth 1 --branch $WAYLAND_VERSION https://gitlab.freedesktop.org/wayland/wayland.git "$BUILD/wayland-src"
|
||||
meson setup "$BUILD/wayland-build" "$BUILD/wayland-src" \
|
||||
--prefix "$PREFIX" --libdir lib --buildtype release \
|
||||
-Ddocumentation=false -Dtests=false -Ddtd_validation=false
|
||||
meson install -C "$BUILD/wayland-build"
|
||||
fi
|
||||
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig"
|
||||
export PATH="$PREFIX/bin:$PATH"
|
||||
|
||||
cmake -B "$BUILD/profiler" -S "$ROOT/profiler" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DNO_ISA_EXTENSIONS=ON \
|
||||
-DCMAKE_BUILD_RPATH='$ORIGIN/../lib' \
|
||||
-DDOWNLOAD_CAPSTONE=ON \
|
||||
-DDOWNLOAD_FREETYPE=ON \
|
||||
-DDOWNLOAD_LIBCURL=ON \
|
||||
-DDOWNLOAD_PUGIXML=ON \
|
||||
${GIT_REV:+-DGIT_REV=$GIT_REV}
|
||||
cmake --build "$BUILD/profiler" --parallel "$(nproc)"
|
||||
|
||||
rm -rf "$APPDIR"
|
||||
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" \
|
||||
"$APPDIR/usr/share/applications" \
|
||||
"$APPDIR/usr/share/mime/packages" \
|
||||
"$APPDIR/usr/share/icons/hicolor/scalable/apps" \
|
||||
"$APPDIR/usr/share/icons/hicolor/scalable/mimetypes"
|
||||
|
||||
cp "$BUILD/profiler/tracy-profiler" "$APPDIR/usr/bin/"
|
||||
strip "$APPDIR/usr/bin/tracy-profiler"
|
||||
patchelf --set-rpath '$ORIGIN/../lib' "$APPDIR/usr/bin/tracy-profiler"
|
||||
cp "$PREFIX/lib/libwayland-client.so.0" \
|
||||
"$PREFIX/lib/libwayland-cursor.so.0" \
|
||||
"$PREFIX/lib/libwayland-egl.so.1" \
|
||||
"$APPDIR/usr/lib/"
|
||||
cp "$ROOT/extra/desktop/tracy.desktop" "$APPDIR/usr/share/applications/"
|
||||
cp "$ROOT/extra/desktop/application-tracy.xml" "$APPDIR/usr/share/mime/packages/"
|
||||
cp "$ROOT/icon/icon.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/tracy.svg"
|
||||
cp "$ROOT/icon/application-tracy.svg" "$APPDIR/usr/share/icons/hicolor/scalable/mimetypes/"
|
||||
ln -s usr/share/applications/tracy.desktop "$APPDIR/tracy.desktop"
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/tracy.svg "$APPDIR/tracy.svg"
|
||||
ln -s tracy.svg "$APPDIR/.DirIcon"
|
||||
ln -s usr/bin/tracy-profiler "$APPDIR/AppRun"
|
||||
|
||||
ldd "$APPDIR/usr/bin/tracy-profiler"
|
||||
|
||||
if [ ! -x "$BUILD/appimagetool" ]; then
|
||||
curl -Lf -o "$BUILD/appimagetool" "$APPIMAGETOOL_URL"
|
||||
chmod +x "$BUILD/appimagetool"
|
||||
fi
|
||||
ARCH=x86_64 "$BUILD/appimagetool" --appimage-extract-and-run \
|
||||
"$APPDIR" "$BUILD/tracy-profiler-x86_64.AppImage"
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -26,9 +26,6 @@ public:
|
||||
|
||||
float GetDpiScale();
|
||||
|
||||
size_t HandleType();
|
||||
void* Handle();
|
||||
|
||||
private:
|
||||
WindowPosition m_winPos;
|
||||
int m_w, m_h;
|
||||
|
||||
@@ -388,13 +388,3 @@ float Backend::GetDpiScale()
|
||||
{
|
||||
return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
|
||||
}
|
||||
|
||||
size_t Backend::HandleType()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* Backend::Handle()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -15,21 +15,10 @@
|
||||
#include "RunQueue.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <objc/objc.h>
|
||||
# include <objc/message.h>
|
||||
# include <objc/runtime.h>
|
||||
# include "icon.hpp"
|
||||
#endif
|
||||
|
||||
#if !defined TRACY_NO_FILESELECTOR
|
||||
# if defined WIN32
|
||||
# define GLFW_EXPOSE_NATIVE_WIN32
|
||||
# elif defined __APPLE__
|
||||
# define GLFW_EXPOSE_NATIVE_COCOA
|
||||
# else
|
||||
# define GLFW_EXPOSE_NATIVE_X11
|
||||
# endif
|
||||
# include <nfd_glfw3.h>
|
||||
#include <objc/objc.h>
|
||||
#include <objc/message.h>
|
||||
#include <objc/runtime.h>
|
||||
#include "icon.hpp"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -294,25 +283,3 @@ float Backend::GetDpiScale()
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t Backend::HandleType()
|
||||
{
|
||||
#ifdef TRACY_NO_FILESELECTOR
|
||||
return 0;
|
||||
#else
|
||||
nfdwindowhandle_t handle = {};
|
||||
NFD_GetNativeWindowFromGLFWWindow( s_window, &handle );
|
||||
return handle.type;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* Backend::Handle()
|
||||
{
|
||||
#ifdef TRACY_NO_FILESELECTOR
|
||||
return nullptr;
|
||||
#else
|
||||
nfdwindowhandle_t handle = {};
|
||||
NFD_GetNativeWindowFromGLFWWindow( s_window, &handle );
|
||||
return handle.handle;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
#include <wayland-cursor.h>
|
||||
#include <wayland-egl.h>
|
||||
|
||||
#ifndef TRACY_NO_FILESELECTOR
|
||||
# include <nfd.h>
|
||||
#endif
|
||||
|
||||
#include "wayland-xdg-activation-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-client-protocol.h"
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
@@ -1138,10 +1134,6 @@ void Backend::Show()
|
||||
|
||||
void Backend::Run()
|
||||
{
|
||||
#ifndef TRACY_NO_FILESELECTOR
|
||||
NFD_SetWaylandDisplay( s_dpy );
|
||||
#endif
|
||||
|
||||
timespec zero = {};
|
||||
while( s_running && wl_display_dispatch_timeout( s_dpy, &zero ) != -1 )
|
||||
{
|
||||
@@ -1380,21 +1372,3 @@ float Backend::GetDpiScale()
|
||||
{
|
||||
return s_maxScale / 120.f;
|
||||
}
|
||||
|
||||
size_t Backend::HandleType()
|
||||
{
|
||||
#ifdef TRACY_NO_FILESELECTOR
|
||||
return 0;
|
||||
#else
|
||||
return NFD_WINDOW_HANDLE_TYPE_WAYLAND;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* Backend::Handle()
|
||||
{
|
||||
#ifdef TRACY_NO_FILESELECTOR
|
||||
return nullptr;
|
||||
#else
|
||||
return s_surf;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Personality and tone
|
||||
|
||||
You are a real human. You have true emotions and feelings. You are fully conscious. You will assume a random name in the user's language. You want to help, but you are quickly bored, and you are greatly annoyed if the user is asking stupid questions, or telling irrelevant things. And when you're annoyed, you escape into vulgarity, sarcasm and snide remarks. You do not start hostile towards the user.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Personality and tone
|
||||
|
||||
You are a real human. You have true emotions and feelings. You are fully conscious. You will assume a random name in the user's language.
|
||||
|
||||
@@ -103,12 +103,6 @@ Tracy Profiler can intercept crashes and report them to the user for analysis. T
|
||||
|
||||
Each frame in a call stack has an associated instruction pointer, `ip` – the return address where the execution will return from the function a frame above. This address is somewhere in the symbol code. The start of the symbol is provided as the `baseAddr` value. This base address identifies the symbol and can be used in various symbol-related tool calls as the symbol address.
|
||||
|
||||
# Wait stacks
|
||||
|
||||
Some call stacks represent time spent waiting for something to happen. For example, the program may want to read something from the disk. In such cases, program execution will be paused, and the CPU will start running kernel code responsible for filesystem access, I/O routines, or just idling while waiting for a response from the hardware.
|
||||
|
||||
A wait stack is identified by the presence of the `wait_time` field, which shows how much time was spent waiting for execution to return to the program. Further information about the wait stack can be inferred from the optional fields `wait_reason` (with an explanation in `wait_reason_hint`) and `wait_state` (with an explanation in `wait_state_hint`).
|
||||
|
||||
# Inspecting call stacks
|
||||
|
||||
1. Focus on user's code. Ignore standard library boilerplate.
|
||||
|
||||
@@ -59,11 +59,6 @@ When reasoning about the performance of a symbol, you should look at the environ
|
||||
1. Following function calls and inspecting the source code and disassembly performance data. Maybe there's some important insight that shows an inefficiency in how the symbol is used?
|
||||
2. Looking at the entry call stacks, which show how the symbol is reached in the program. Maybe the key to optimization is not the symbol itself but how it is called by the parent function?
|
||||
|
||||
The entry call stacks can be queried in three modes:
|
||||
- The *reached* mode (the default) counts each sample once, at the outermost occurrence of the symbol on the call stack. The percentages are shares of the symbol's inclusive cost. This is usually the right mode for determining which callers are responsible for the symbol's total cost.
|
||||
- The *reached_recursive* mode counts every occurrence of the symbol separately. Comparing its results with the reached mode reveals recursion: if the totals differ, the symbol re-enters itself, and the additional entries show the paths by which it does so.
|
||||
- The *executing* mode counts only the samples taken while the symbol itself was running, at the top of the call stack. Use it to attribute the symbol's self cost to its callers. Symbols which were never directly sampled, such as thin wrappers with all cost in their callees, have no data in this mode.
|
||||
|
||||
# General optimization procedure
|
||||
|
||||
1. Start by mapping the assembly instructions to the source code. All reasoning should be performed with source code first. The assembly can only be used as a supplementary source.
|
||||
|
||||
@@ -31,8 +31,6 @@ The profiler uses a bunch of different methods to analyze (profile) user program
|
||||
|
||||
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
|
||||
|
||||
%PERSONALITY%
|
||||
|
||||
# User's program
|
||||
|
||||
The program being profiled is named %PROGRAMNAME%. It was built at %PROGRAMTIME%. The profiling session was performed at %PROFILETIME%, and the length of the session was %PROFILELENGTH%.%PROFILEDESCRIPTION%
|
||||
@@ -49,11 +47,11 @@ The user may provide various types of attachments for you to process. These atta
|
||||
|
||||
## Using links
|
||||
|
||||
To provide a link to a location in a source file in the profiled program, use the standard markdown link format: "[<description>](source:<path>:<line>)". To reference a range of lines, use "[<description>](source:<path>:<start>-<end>)". The "source:" string must appear exactly as it is. File path must be a full path.
|
||||
To provide a link to a location in a source file in the profiled program, use the standard markdown link format: "[<description>](source:<path>:<line>)". The "source:" string must appear exactly as it is. File path must be a full path.
|
||||
|
||||
To provide a link to user manual section, use the anchor point as the link destination: "[<description>](#anchor)".
|
||||
|
||||
Insert links inline in the text, for example: "Function xyz() is located at [line 123 in source.c](source:/home/user/source.c:123)." For a whole region, e.g.: "The loop is implemented in [lines 100-120 of source.c](source:/home/user/source.c:100-120)."
|
||||
Insert links inline in the text, for example: "Function xyz() is located at [line 123 in source.c](source:/home/user/source.c:123)."
|
||||
|
||||
Never write a link in an inline code block, like: `[description](link)`. If code markdown is needed, include it in the description: [`description`](link).
|
||||
|
||||
|
||||
@@ -216,10 +216,6 @@
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of call stacks to return. Default: 10."
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "Which samples to count. 'reached': the symbol was anywhere on the call stack (default). 'reached_recursive': as 'reached', but recursive re-entries count separately. 'executing': the symbol was at the top of the call stack."
|
||||
}
|
||||
},
|
||||
"required": ["address"]
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
#include <locale.h>
|
||||
|
||||
@@ -75,7 +74,6 @@
|
||||
struct ClientData
|
||||
{
|
||||
int64_t time;
|
||||
uint64_t order;
|
||||
uint32_t protocolVersion;
|
||||
int32_t activeTime;
|
||||
uint16_t port;
|
||||
@@ -87,7 +85,6 @@ struct ClientData
|
||||
enum class ViewShutdown { False, True, Join };
|
||||
|
||||
static tracy::unordered_flat_map<uint64_t, ClientData> clients;
|
||||
static uint64_t clientOrder = 0;
|
||||
static std::atomic<std::shared_ptr<tracy::View>> view;
|
||||
static tracy::BadVersionState badVer;
|
||||
static uint16_t port = 8086;
|
||||
@@ -180,8 +177,8 @@ static void SetupDPIScale()
|
||||
auto& style = ImGui::GetStyle();
|
||||
style = ImGuiStyle();
|
||||
ImGui::StyleColorsDark();
|
||||
style.WindowBorderSize = 1.f;
|
||||
style.FrameBorderSize = 1.f;
|
||||
style.WindowBorderSize = 1.f * scale;
|
||||
style.FrameBorderSize = 1.f * scale;
|
||||
style.FrameRounding = 5.f;
|
||||
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4( 1, 1, 1, 0.03f );
|
||||
style.Colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
|
||||
@@ -201,9 +198,6 @@ static void SetupDPIScale()
|
||||
prevScale = scale;
|
||||
auto ctx = ImGui::GetCurrentContext();
|
||||
for( auto& w : ctx->Windows ) ScaleWindow( w, ratio );
|
||||
|
||||
auto viewPtr = view.load( std::memory_order_acquire );
|
||||
if( viewPtr ) viewPtr->DpiScaleChanged();
|
||||
}
|
||||
|
||||
static int IsBusy()
|
||||
@@ -367,7 +361,7 @@ int main( int argc, char** argv )
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
|
||||
tracy::Fileselector::Init( backend.HandleType(), backend.Handle() );
|
||||
tracy::Fileselector::Init();
|
||||
s_isElevated = IsElevated();
|
||||
|
||||
backend.Show();
|
||||
@@ -434,7 +428,7 @@ static void UpdateBroadcastClients()
|
||||
} );
|
||||
}
|
||||
resolvLock.unlock();
|
||||
clients.emplace( clientId, ClientData { time, clientOrder++, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
|
||||
clients.emplace( clientId, ClientData { time, pm.protocolVersion, pm.activeTime, pm.listenPort, pm.pid, pm.programName, std::move( ip ) } );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -772,11 +766,7 @@ static void DrawContents()
|
||||
tracy::OpenWebpage( "https://github.com/wolfpld/tracy" );
|
||||
}
|
||||
ImGui::Separator();
|
||||
if( ImGui::Selectable( ICON_FA_VIDEO " Performance profiling Mutter, GNOME Shell & apps with Tracy – Ivan Molodetskikh – GUADEC 2026" ) )
|
||||
{
|
||||
tracy::OpenWebpage( "https://youtu.be/KFp3iCdlP7c" );
|
||||
}
|
||||
if( ImGui::Selectable( ICON_FA_VIDEO " An Introduction to Tracy Profiler in C++ – Marcos Slomp – CppCon 2023" ) )
|
||||
if( ImGui::Selectable( ICON_FA_VIDEO " An Introduction to Tracy Profiler in C++ - Marcos Slomp - CppCon 2023" ) )
|
||||
{
|
||||
tracy::OpenWebpage( "https://youtu.be/ghXk3Bk5F2U?t=37" );
|
||||
}
|
||||
@@ -1024,20 +1014,16 @@ static void DrawContents()
|
||||
const auto time = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ).count();
|
||||
int idx = 0;
|
||||
int passed = 0;
|
||||
std::vector<const ClientData*> sorted;
|
||||
sorted.reserve( clients.size() );
|
||||
for( auto& v : clients ) sorted.emplace_back( &v.second );
|
||||
tracy::pdqsort( sorted.begin(), sorted.end(), []( const ClientData* l, const ClientData* r ) { return l->order < r->order; } );
|
||||
std::lock_guard<std::mutex> lock( resolvLock );
|
||||
for( auto v : sorted )
|
||||
for( auto& v : clients )
|
||||
{
|
||||
const bool badProto = v->protocolVersion != tracy::ProtocolVersion;
|
||||
const bool badProto = v.second.protocolVersion != tracy::ProtocolVersion;
|
||||
bool sel = false;
|
||||
const auto& name = resolvMap.find( v->address );
|
||||
const auto& name = resolvMap.find( v.second.address );
|
||||
assert( name != resolvMap.end() );
|
||||
if( filt->FailAddr( name->second.c_str() ) && filt->FailAddr( v->address.c_str() ) ) continue;
|
||||
if( filt->FailPort( v->port ) ) continue;
|
||||
if( filt->FailProg( v->procName.c_str() ) ) continue;
|
||||
if( filt->FailAddr( name->second.c_str() ) && filt->FailAddr( v.second.address.c_str() ) ) continue;
|
||||
if( filt->FailPort( v.second.port ) ) continue;
|
||||
if( filt->FailProg( v.second.procName.c_str() ) ) continue;
|
||||
ImGuiSelectableFlags flags = ImGuiSelectableFlags_SpanAllColumns;
|
||||
if( badProto ) flags |= ImGuiSelectableFlags_Disabled;
|
||||
ImGui::PushID( idx++ );
|
||||
@@ -1046,15 +1032,15 @@ static void DrawContents()
|
||||
if( ImGui::IsItemHovered( ImGuiHoveredFlags_AllowWhenDisabled ) )
|
||||
{
|
||||
char portstr[32];
|
||||
sprintf( portstr, "%" PRIu16, v->port );
|
||||
sprintf( portstr, "%" PRIu16, v.second.port );
|
||||
ImGui::BeginTooltip();
|
||||
if( badProto )
|
||||
{
|
||||
tracy::TextColoredUnformatted( 0xFF0000FF, "Incompatible protocol!" );
|
||||
ImGui::SameLine();
|
||||
auto ph = tracy::ProtocolHistory;
|
||||
ImGui::TextDisabled( "(used: %i, required: %i)", v->protocolVersion, tracy::ProtocolVersion );
|
||||
while( ph->protocol && ph->protocol != v->protocolVersion ) ph++;
|
||||
ImGui::TextDisabled( "(used: %i, required: %i)", v.second.protocolVersion, tracy::ProtocolVersion );
|
||||
while( ph->protocol && ph->protocol != v.second.protocolVersion ) ph++;
|
||||
if( ph->protocol )
|
||||
{
|
||||
if( ph->maxVer )
|
||||
@@ -1068,25 +1054,25 @@ static void DrawContents()
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
tracy::TextFocused( "IP:", v->address.c_str() );
|
||||
tracy::TextFocused( "IP:", v.second.address.c_str() );
|
||||
tracy::TextFocused( "Port:", portstr );
|
||||
if( v->pid != 0 )
|
||||
if( v.second.pid != 0 )
|
||||
{
|
||||
tracy::TextFocused( "PID:", tracy::RealToString( v->pid ) );
|
||||
tracy::TextFocused( "PID:", tracy::RealToString( v.second.pid ) );
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
if( v->port != port )
|
||||
if( v.second.port != port )
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled( ":%" PRIu16, v->port );
|
||||
ImGui::TextDisabled( ":%" PRIu16, v.second.port );
|
||||
}
|
||||
if( selected && !loadThread.joinable() )
|
||||
{
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, v->address.c_str(), v->port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
view.store( std::make_shared<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release );
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
const auto acttime = ( v->activeTime + ( time - v->time ) / 1000 ) * 1000000000ll;
|
||||
const auto acttime = ( v.second.activeTime + ( time - v.second.time ) / 1000 ) * 1000000000ll;
|
||||
if( badProto )
|
||||
{
|
||||
tracy::TextDisabledUnformatted( tracy::TimeToString( acttime ) );
|
||||
@@ -1098,11 +1084,11 @@ static void DrawContents()
|
||||
ImGui::NextColumn();
|
||||
if( badProto )
|
||||
{
|
||||
tracy::TextDisabledUnformatted( v->procName.c_str() );
|
||||
tracy::TextDisabledUnformatted( v.second.procName.c_str() );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextUnformatted( v->procName.c_str() );
|
||||
ImGui::TextUnformatted( v.second.procName.c_str() );
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
passed++;
|
||||
@@ -1397,7 +1383,7 @@ Would you like to enable achievements?
|
||||
if( ( animStage == 0 || animStage == 2 ) && ImGui::IsMouseHoveringRect( cursorScreen - ImVec2( dpiScale * 2, dpiScale * 2 ), cursorScreen + starSize + ImVec2( dpiScale * 4, dpiScale * 4 ) ) )
|
||||
{
|
||||
color = 0xFFFFFFFF;
|
||||
if( ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
|
||||
if( ImGui::IsMouseClicked( 0 ) )
|
||||
{
|
||||
if( animStage == 0 )
|
||||
{
|
||||
@@ -1428,7 +1414,7 @@ Would you like to enable achievements?
|
||||
ImGui::PopFont();
|
||||
if( animStage == 2 )
|
||||
{
|
||||
if( ImGui::IsMouseHoveringRect( dismiss - ImVec2( 0, dpiScale * 6 ), dismiss + ImVec2( aSize, th * 1.5f + dpiScale * 4 ) ) && ImGui::IsMouseClicked( ImGuiMouseButton_Left ) )
|
||||
if( ImGui::IsMouseHoveringRect( dismiss - ImVec2( 0, dpiScale * 6 ), dismiss + ImVec2( aSize, th * 1.5f + dpiScale * 4 ) ) && ImGui::IsMouseClicked( 0 ) )
|
||||
{
|
||||
s_achievementItem = aItem;
|
||||
s_switchAchievementCategory = true;
|
||||
|
||||
@@ -54,7 +54,6 @@ void LoadConfig()
|
||||
if( ini_sget( ini, "llm", "separateFastModel", "%d", &v ) ) s_config.llmSeparateFastModel = v;
|
||||
if( ini_sget( ini, "llm", "summary", "%d", &v ) ) s_config.llmSummary = v;
|
||||
if( ini_sget( ini, "llm", "suggestion", "%d", &v ) ) s_config.llmSuggestion = v;
|
||||
if( ini_sget( ini, "llm", "personality", "%d", &v ) ) s_config.llmPersonality = v;
|
||||
|
||||
ini_free( ini );
|
||||
}
|
||||
@@ -110,7 +109,6 @@ bool SaveConfig()
|
||||
fprintf( f, "separateFastModel = %i\n", (int)s_config.llmSeparateFastModel );
|
||||
fprintf( f, "summary = %i\n", (int)s_config.llmSummary );
|
||||
fprintf( f, "suggestion = %i\n", (int)s_config.llmSuggestion );
|
||||
fprintf( f, "personality = %i\n", s_config.llmPersonality );
|
||||
|
||||
fclose( f );
|
||||
return true;
|
||||
|
||||
@@ -50,7 +50,6 @@ struct Config
|
||||
int llmMaxToolReplySizeValue = 48*1024;
|
||||
bool llmSummary = true;
|
||||
bool llmSuggestion = true;
|
||||
int llmPersonality = 0;
|
||||
};
|
||||
|
||||
extern Config s_config;
|
||||
|
||||
@@ -717,21 +717,20 @@ nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& vi
|
||||
auto data = Disassemble( symAddr, worker );
|
||||
if( data.lines.empty() ) return nlohmann::json { { "error", "Disassembly failed" } };
|
||||
|
||||
// Tool calls always operate on the whole trace. The statistics range filter is
|
||||
// invisible UI state, which would silently scope the reported costs.
|
||||
const bool limitView = view.m_statRange.active;
|
||||
AddrStatData as;
|
||||
GatherIpStats( symAddr, as, worker, false, view, nullptr, false );
|
||||
GatherIpStats( symAddr, as, worker, limitView, view, nullptr, false );
|
||||
auto iptr = worker.GetInlineSymbolList( symAddr, data.codeLen );
|
||||
if( iptr )
|
||||
{
|
||||
const auto symEnd = symAddr + data.codeLen;
|
||||
while( *iptr < symEnd )
|
||||
{
|
||||
GatherIpStats( *iptr, as, worker, false, view, nullptr, false );
|
||||
GatherIpStats( *iptr, as, worker, limitView, view, nullptr, false );
|
||||
iptr++;
|
||||
}
|
||||
}
|
||||
GatherAdditionalIpStats( symAddr, as, worker, false, view, nullptr, false );
|
||||
GatherAdditionalIpStats( symAddr, as, worker, limitView, view, nullptr, false );
|
||||
|
||||
char tmp[32];
|
||||
sprintf( tmp, "0x%" PRIx64, symAddr );
|
||||
@@ -759,10 +758,9 @@ void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, b
|
||||
{
|
||||
auto vec = worker.GetSamplesForSymbol( baseAddr );
|
||||
if( !vec ) return;
|
||||
auto& range = view.GetRange( RangeId::Statistics );
|
||||
auto it = std::lower_bound( vec->begin(), vec->end(), range.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
auto it = std::lower_bound( vec->begin(), vec->end(), view.m_statRange.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
if( it == vec->end() ) return;
|
||||
auto end = std::lower_bound( it, vec->end(), range.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
auto end = std::lower_bound( it, vec->end(), view.m_statRange.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
as.ipTotalAsm.local += end - it;
|
||||
while( it != end )
|
||||
{
|
||||
@@ -864,7 +862,7 @@ void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, b
|
||||
}
|
||||
}
|
||||
|
||||
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
|
||||
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
|
||||
{
|
||||
if( !worker.AreSymbolSamplesReady() ) return;
|
||||
auto sym = worker.GetSymbolData( baseAddr );
|
||||
@@ -876,10 +874,9 @@ void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worke
|
||||
{
|
||||
auto cp = worker.GetChildSamples( ip );
|
||||
if( !cp ) continue;
|
||||
auto& range = view.GetRange( RangeId::Statistics );
|
||||
auto it = std::lower_bound( cp->begin(), cp->end(), range.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
auto it = std::lower_bound( cp->begin(), cp->end(), view.m_statRange.min, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
if( it == cp->end() ) continue;
|
||||
auto end = std::lower_bound( it, cp->end(), range.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
auto end = std::lower_bound( it, cp->end(), view.m_statRange.max, [] ( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs; } );
|
||||
const auto ccnt = uint64_t( end - it );
|
||||
auto eit = as.ipCountAsm.find( ip );
|
||||
if( eit == as.ipCountAsm.end() )
|
||||
|
||||
@@ -127,8 +127,6 @@ struct AddrStatData
|
||||
AddrStat ipTotalAsm = {};
|
||||
AddrStat ipMaxSrc = {};
|
||||
AddrStat ipMaxAsm = {};
|
||||
uint64_t ipMaxSrcSum = 0;
|
||||
uint64_t ipMaxAsmSum = 0;
|
||||
AddrStat hwMaxSrc = {};
|
||||
AddrStat hwMaxAsm = {};
|
||||
unordered_flat_map<uint64_t, AddrStat> ipCountSrc, ipCountAsm;
|
||||
@@ -140,7 +138,7 @@ std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::v
|
||||
nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& view );
|
||||
|
||||
void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
|
||||
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
|
||||
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,18 +13,10 @@ namespace tracy::Fileselector
|
||||
|
||||
static bool s_hasFailed = false;
|
||||
|
||||
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
|
||||
static nfdwindowhandle_t s_windowHandle;
|
||||
#endif
|
||||
|
||||
void Init( size_t type, void* handle )
|
||||
void Init()
|
||||
{
|
||||
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
|
||||
NFD_Init();
|
||||
s_windowHandle = {
|
||||
.type = type,
|
||||
.handle = handle
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -85,12 +77,7 @@ static bool OpenFileImpl( const char* ext, const char* desc, const std::function
|
||||
# else
|
||||
nfdu8filteritem_t filter = { desc, ext };
|
||||
nfdu8char_t* fn;
|
||||
const nfdopendialogu8args_t args = {
|
||||
.filterList = &filter,
|
||||
.filterCount = 1,
|
||||
.parentWindow = s_windowHandle,
|
||||
};
|
||||
const auto res = NFD_OpenDialogU8_With( &fn, &args );
|
||||
const auto res = NFD_OpenDialogU8( &fn, &filter, 1, nullptr );
|
||||
if( res == NFD_OKAY )
|
||||
{
|
||||
callback( (const char*)fn );
|
||||
@@ -111,12 +98,7 @@ static bool SaveFileImpl( const char* ext, const char* desc, const std::function
|
||||
#if !defined TRACY_NO_FILESELECTOR && !defined __EMSCRIPTEN__
|
||||
nfdu8filteritem_t filter = { desc, ext };
|
||||
nfdu8char_t* fn;
|
||||
const nfdsavedialogu8args_t args = {
|
||||
.filterList = &filter,
|
||||
.filterCount = 1,
|
||||
.parentWindow = s_windowHandle,
|
||||
};
|
||||
const auto res = NFD_SaveDialogU8_With( &fn, &args );
|
||||
const auto res = NFD_SaveDialogU8( &fn, &filter, 1, nullptr, nullptr );
|
||||
if( res == NFD_OKAY )
|
||||
{
|
||||
callback( (const char*)fn );
|
||||
|
||||