Tracy 0.14 (#2019)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-08-15 19:09:36 -07:00
1 parent bf9492dffb
commit bc8116006c
21 files changed
+540 -80

No files matched your search

+5 -3
View File
@@ -2,7 +2,8 @@
# Builds the libTracyClient shared library for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
TRACY_VERSION="${TRACY_VERSION:-v0.14.0}"
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
TRACY_REPO="https://github.com/wolfpld/tracy"
@@ -16,7 +17,7 @@ case "$(uname -m)" in
arm64|aarch64) PLAT_ARCH="arm64" ;;
*) PLAT_ARCH="x64" ;;
esac
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$PLAT_OS-$PLAT_ARCH}"
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$TRACY_VERSION_BARE/$PLAT_OS-$PLAT_ARCH}"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
@@ -33,7 +34,8 @@ fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
+12 -23
View File
@@ -6,10 +6,6 @@ on:
- "tracy-v*"
workflow_dispatch:
inputs:
tracy-version:
description: Tracy git tag to build
required: false
default: v0.13.1
publish:
description: Publish angelica-tracy.jar to the GTNH maven
type: boolean
@@ -20,25 +16,25 @@ jobs:
build:
strategy:
matrix:
platform: [linux-x64, windows-x64, macos-x64, macos-arm64]
include:
- os: ubuntu-22.04
platform: linux-x64
- os: windows-2025
platform: windows-x64
- os: macos-15-intel
platform: macos-x64
- os: macos-15
platform: macos-arm64
- platform: linux-x64
os: ubuntu-22.04
- platform: windows-x64
os: windows-2025
- platform: macos-x64
os: macos-15-intel
- platform: macos-arm64
os: macos-15
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Build libTracyClient
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
OUT_DIR: dist/tracy-natives
run: |
export TRACY_VERSION="${RAW_VERSION#tracy-}"
export TRACY_VERSION="v$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
bash .github/scripts/build-tracy-natives.sh
- uses: actions/upload-artifact@v4
with:
@@ -56,14 +52,6 @@ jobs:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Check tracy version matches the version catalog
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
run: |
ver="${RAW_VERSION#tracy-}"
grep -q "^tracy = \"${ver#v}\"$" gradle/libs.versions.toml \
|| { echo "libs.versions.toml tracy does not match $ver" >&2; exit 1; }
- uses: actions/download-artifact@v5
with:
pattern: tracy-natives-*
@@ -71,8 +59,9 @@ jobs:
- name: Stage natives into tracy-client resources
shell: bash
run: |
version="$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
for d in staging/tracy-natives-*; do
dest="tracy-client/src/main/resources/natives/tracy/${d#staging/tracy-natives-}"
dest="tracy-client/src/main/resources/natives/tracy/$version/${d#staging/tracy-natives-}"
mkdir -p "$dest"
cp "$d"/* "$dest/"
done
+2
View File
@@ -43,3 +43,5 @@ tracy-client/src/main/resources/natives/
tracy-client/angelica/
dist/tracy-natives/
*.tracy
tracy-client/angelica/
+2 -2
View File
@@ -52,8 +52,8 @@ configurations.configureEach {
}
DependencyHandlerScope.of(dependencies).apply {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
@@ -0,0 +1,92 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import java.util.ArrayList;
import java.util.List;
public final class SectionRegistry {
public interface Emitter {
long enter(int category, String text);
void leave(long nativeId);
void setup(int category, String name);
boolean isConnected();
}
private static final class Category {
final int id;
final String name;
Category(int id, String name) {
this.id = id;
this.name = name;
}
}
private static final class Section {
final long token;
final int category;
final String text;
long nativeId;
Section(long token, int category, String text, long nativeId) {
this.token = token;
this.category = category;
this.text = text;
this.nativeId = nativeId;
}
}
private final Emitter emitter;
private final List<Category> categories = new ArrayList<>();
private final List<Section> open = new ArrayList<>();
private long nextToken;
private boolean connected;
public SectionRegistry(Emitter emitter) {
this.emitter = emitter;
}
public synchronized void registerCategory(int category, String name) {
categories.add(new Category(category, name));
}
public synchronized long enter(int category, String text) {
syncConnection();
final long token = ++nextToken;
open.add(new Section(token, category, text, emitter.enter(category, text)));
return token;
}
public synchronized void leave(long token) {
for (int i = 0; i < open.size(); i++) {
final Section section = open.get(i);
if (section.token == token) {
open.remove(i);
if (section.nativeId != 0L) emitter.leave(section.nativeId);
return;
}
}
}
public synchronized void poll() {
syncConnection();
}
private void syncConnection() {
final boolean now = emitter.isConnected();
if (now == connected) return;
connected = now;
if (now) replay();
}
private void replay() {
for (int i = 0; i < categories.size(); i++) {
final Category category = categories.get(i);
emitter.setup(category.id, category.name);
}
for (int i = 0; i < open.size(); i++) {
final Section section = open.get(i);
section.nativeId = emitter.enter(section.category, section.text);
}
}
}
@@ -44,6 +44,18 @@ public final class Tracy {
public static final int COLOR_IRIS = 0x9E862E; // gold
public static final int COLOR_FFP = 0x7A4E9E; // purple
public static final int SECTION_WORLD = 1;
public static final int SECTION_SHADERS = 2;
public static final int SECTION_BENCHMARK = 3;
public static final int SECTION_UI = 4;
private static final SectionRegistry SECTIONS = new SectionRegistry(new SectionRegistry.Emitter() {
@Override public long enter(int category, String text) { return BACKEND.sectionEnter(category, text); }
@Override public void leave(long nativeId) { BACKEND.sectionLeave(nativeId); }
@Override public void setup(int category, String name) { BACKEND.sectionSetup(category, name); }
@Override public boolean isConnected() { return BACKEND.isConnected(); }
});
private static final ThreadLocal<ZoneStack> STACK = new ThreadLocal<>() {
@Override
protected ZoneStack initialValue() {
@@ -62,6 +74,10 @@ public final class Tracy {
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
SECTIONS.registerCategory(SECTION_WORLD, "World");
SECTIONS.registerCategory(SECTION_SHADERS, "Shaders");
SECTIONS.registerCategory(SECTION_BENCHMARK, "Benchmark");
SECTIONS.registerCategory(SECTION_UI, "UI");
LOGGER.info("Tracy profiling enabled");
}
}
@@ -168,7 +184,7 @@ public final class Tracy {
final String name = stack.poppedName();
if (name != null && WARNED_LEAKS.add(name)) {
LOGGER.warn("Tracy: unbalanced profiler section '{}' left open (leaked by its caller); closed at next root", name);
BACKEND.message("unbalanced profiler section '" + name + "' closed at root");
BACKEND.message("unbalanced profiler section '" + name + "' closed at root", TracyBackend.SEVERITY_WARNING);
}
}
}
@@ -274,6 +290,7 @@ public final class Tracy {
threadNameRefreshed = true;
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
}
SECTIONS.poll();
BACKEND.frameMark();
}
@@ -330,8 +347,22 @@ public final class Tracy {
}
public static void message(String text) {
message(text, TracyBackend.SEVERITY_INFO);
}
public static void message(String text, int severity) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.message(text);
BACKEND.message(text, severity);
}
public static long sectionEnter(int category, String text) {
if (!ENABLED || SHUTTING_DOWN) return 0L;
return SECTIONS.enter(category, text);
}
public static void sectionLeave(long token) {
if (token == 0L || !ENABLED || SHUTTING_DOWN) return;
SECTIONS.leave(token);
}
public static boolean isConnected() {
@@ -5,6 +5,13 @@ public interface TracyBackend {
int PLOT_FORMAT_MEMORY = 1;
int PLOT_FORMAT_PERCENTAGE = 2;
int SEVERITY_TRACE = 0;
int SEVERITY_DEBUG = 1;
int SEVERITY_INFO = 2;
int SEVERITY_WARNING = 3;
int SEVERITY_ERROR = 4;
int SEVERITY_FATAL = 5;
boolean init();
long internSrcLoc(String name, int color);
@@ -24,7 +31,14 @@ public interface TracyBackend {
void plotInt(long namePtr, long value);
void plot(long namePtr, double value);
void message(String text);
void message(String text, int severity);
default void message(String text) { message(text, SEVERITY_INFO); }
long sectionEnter(int category, String text);
void sectionLeave(long id);
void sectionSetup(int category, String name);
void setCurrentThreadName(String name);
boolean isConnected();
void shutdown();
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.profiling;
public final class ZoneStack {
public static final long EMPTY = -1L;
static final int MAX_DEPTH = 512;
public static final int MAX_DEPTH = 512;
private long[] stack = new long[64];
private boolean[] gpu = new boolean[64];
@@ -0,0 +1,113 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SectionRegistryTest {
private static final class RecordingEmitter implements SectionRegistry.Emitter {
final List<String> setups = new ArrayList<>();
final List<String> enters = new ArrayList<>();
final List<Long> leaves = new ArrayList<>();
long nextId;
boolean connected;
@Override
public long enter(int category, String text) {
enters.add(category + ":" + text);
return connected ? ++nextId : 0L;
}
@Override
public void leave(long nativeId) {
leaves.add(nativeId);
}
@Override
public void setup(int category, String name) {
setups.add(category + ":" + name);
}
@Override
public boolean isConnected() {
return connected;
}
}
private final RecordingEmitter emitter = new RecordingEmitter();
private final SectionRegistry registry = new SectionRegistry(emitter);
private void reconnect() {
emitter.connected = false;
registry.poll();
emitter.connected = true;
registry.poll();
}
@Test
void tokensOutliveEveryReplayAndLeaveUsesTheNewestId() {
final long openedWhileDisconnected = registry.enter(1, "world");
assertNotEquals(0L, openedWhileDisconnected, "token must be valid while disconnected");
assertEquals(0L, emitter.nextId, "no native id while disconnected");
emitter.connected = true;
registry.poll();
final long openedWhileConnected = registry.enter(2, "shaders");
assertNotEquals(openedWhileDisconnected, openedWhileConnected);
reconnect();
emitter.leaves.clear();
registry.leave(openedWhileDisconnected);
registry.leave(openedWhileConnected);
assertEquals(List.of(3L, 4L), emitter.leaves, "leave must use the post-reconnect ids");
}
@Test
void aSectionEnteredOnTheConnectingFrameIsNotEnteredTwice() {
emitter.connected = true;
registry.enter(2, "shaders pack / Overworld");
registry.poll();
assertEquals(List.of("2:shaders pack / Overworld"), emitter.enters, "must not re-enter a section already live on this connection");
}
@Test
void reconnectReplaysEveryCategoryAndEveryOpenSection() {
registry.registerCategory(1, "World");
registry.registerCategory(4, "UI");
emitter.connected = true;
registry.poll();
registry.enter(1, "world");
registry.enter(4, "pause");
emitter.setups.clear();
emitter.enters.clear();
reconnect();
assertEquals(List.of("1:World", "4:UI"), emitter.setups, "all categories re-registered");
assertEquals(List.of("1:world", "4:pause"), emitter.enters, "all open sections re-entered");
}
@Test
void leftSectionsAreNotResurrectedByAReconnect() {
emitter.connected = true;
registry.poll();
final long token = registry.enter(1, "transient");
registry.leave(token);
emitter.enters.clear();
emitter.leaves.clear();
reconnect();
registry.leave(token);
assertTrue(emitter.enters.isEmpty(), "closed sections must not be re-entered");
assertTrue(emitter.leaves.isEmpty(), "closed tokens must not be left twice");
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ eventbus = "7.0-beta.15"
glslTransformLib = "0.2.0-33.g3ec381e"
antlr4Runtime = "4.13.2"
jcpp = "1.4.14"
tracy = "0.13.1"
tracy = "0.14.0"
retroFuturaBootstrap = "1.1.0"
lombok = "1.18.46"
@@ -57,6 +57,10 @@ public final class FlybyRunner {
private long plotTurning;
private int lastPhase = -1;
private long warmupSection;
private long runSection;
private long legSection;
private long[] frameTimesNs = NO_FRAMES;
private int frameCount;
private boolean framesTruncated;
@@ -180,9 +184,13 @@ public final class FlybyRunner {
this.buildPath();
this.freezeTimeAndWeather(mc);
this.state = this.warmupTicks > 0 ? State.WARMUP : State.RUNNING;
this.tick = 0;
if (this.state == State.RUNNING) this.beginMeasuring();
if (this.warmupTicks > 0) {
this.state = State.WARMUP;
this.warmupSection = Tracy.sectionEnter(Tracy.SECTION_BENCHMARK, "flyby warmup");
} else {
this.beginMeasuring();
}
LOGGER.info("Flyby {} starting at {} {} {} yaw {}", this.route.id(),
this.originX, this.originY, this.originZ, this.originYaw);
@@ -252,12 +260,18 @@ public final class FlybyRunner {
final int phase = (leg << 1) | turning;
if (phase != this.lastPhase) {
this.lastPhase = phase;
Tracy.message("flyby " + (turning != 0 ? "turn" : "leg") + " " + leg);
final String label = "flyby " + (turning != 0 ? "turn" : "leg") + " " + leg;
Tracy.message(label);
Tracy.sectionLeave(this.legSection);
this.legSection = Tracy.sectionEnter(Tracy.SECTION_BENCHMARK, label);
}
}
private void beginMeasuring() {
this.state = State.RUNNING;
Tracy.sectionLeave(this.warmupSection);
this.warmupSection = 0L;
if (Tracy.ENABLED) this.runSection = Tracy.sectionEnter(Tracy.SECTION_BENCHMARK, "flyby " + this.route.id());
this.plotLeg = Tracy.plotHandle("flyby.leg");
this.plotTurning = Tracy.plotHandle("flyby.turning");
this.lastPhase = -1;
@@ -265,11 +279,9 @@ public final class FlybyRunner {
this.frameCount = 0;
this.lastFrameNs = 0L;
this.runStartNs = System.nanoTime();
Tracy.message("flyby start route=" + this.route.id()
+ " length=" + this.runLength + this.route.lengthUnit()
+ " speed=" + this.route.speedOr(this.speed) + "b/t"
+ " ticks=" + this.runTicks
+ " sdlgpu=" + SystemProperties.USE_SDL_GPU);
if (Tracy.ENABLED) {
Tracy.message("flyby start route=" + this.route.id() + " length=" + this.runLength + this.route.lengthUnit() + " speed=" + this.route.speedOr(this.speed) + "b/t ticks=" + this.runTicks + " sdlgpu=" + SystemProperties.USE_SDL_GPU);
}
}
private void holdPosition(EntityClientPlayerMP player) {
@@ -364,7 +376,11 @@ public final class FlybyRunner {
this.state = State.SETTLE;
this.tick = 0;
final long elapsedNs = System.nanoTime() - this.runStartNs;
Tracy.message("flyby end route=" + this.route.id() + " frames=" + this.frameCount);
Tracy.sectionLeave(this.legSection);
this.legSection = 0L;
Tracy.sectionLeave(this.runSection);
this.runSection = 0L;
if (Tracy.ENABLED) Tracy.message("flyby end route=" + this.route.id() + " frames=" + this.frameCount);
final String summary = this.summarise(elapsedNs, player);
LOGGER.info(summary);
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.profiling;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiIngameMenu;
import java.util.HashMap;
import java.util.Map;
public final class TracyUiSections {
private static final Map<Class<?>, String> LABELS = new HashMap<>();
private static Class<?> current = TracyUiSections.class;
private static long section;
private TracyUiSections() {}
public static void poll(GuiScreen screen) {
if (!Tracy.ENABLED) return;
final Class<?> screenClass = screen == null ? null : screen.getClass();
if (screenClass == current) return;
current = screenClass;
Tracy.sectionLeave(section);
section = Tracy.sectionEnter(Tracy.SECTION_UI, label(screenClass));
}
private static String label(Class<?> screenClass) {
if (screenClass == null) return "in world";
String label = LABELS.get(screenClass);
if (label == null) {
if (screenClass == GuiMainMenu.class) label = "main menu";
else if (screenClass == GuiIngameMenu.class) label = "pause";
else label = screenClass.getSimpleName();
LABELS.put(screenClass, label);
}
return label;
}
}
@@ -55,6 +55,7 @@ import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.iris.IrisGLSMBridge;
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
@@ -216,9 +217,15 @@ public final class ClientProxy extends CommonProxy {
}
}
private long worldSection;
@SubscribeEvent
public void worldLoad(WorldEvent.Load event) {
if (!event.world.isRemote) return;
if (Tracy.ENABLED) {
Tracy.sectionLeave(this.worldSection);
this.worldSection = Tracy.sectionEnter(Tracy.SECTION_WORLD, "world " + event.world.provider.getDimensionName() + " (dim " + event.world.provider.dimensionId + ")");
}
if (GLStateManager.isRunningSplash()) {
GLStateManager.setRunningSplash(false);
LOGGER.info("World loaded - Enabling GLSM Cache");
@@ -238,6 +245,8 @@ public final class ClientProxy extends CommonProxy {
@SubscribeEvent
public void onWorldUnload(WorldEvent.Unload event) {
if (!event.world.isRemote) return;
Tracy.sectionLeave(this.worldSection);
this.worldSection = 0L;
DynamicLights.get().removeAllLightSources();
GpuCulling.onWorldUnload();
}
@@ -1,6 +1,7 @@
package net.coderbot.iris.pipeline;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import lombok.Getter;
import net.coderbot.iris.Iris;
import net.coderbot.iris.uniforms.SystemTimeUniforms;
@@ -19,6 +20,7 @@ public class PipelineManager {
private final Map<String, WorldRenderingPipeline> pipelinesPerDimension = new HashMap<>();
private WorldRenderingPipeline pipeline = new FixedFunctionWorldRenderingPipeline();
private String lastPreparedDimension = null;
private long shaderSection;
@Getter
private int versionCounterForSodiumShaderReload = 0;
@@ -41,6 +43,9 @@ public class PipelineManager {
Iris.logger.info("Creating pipeline for dimension '{}'", currentDimension);
pipeline = pipelineFactory.apply(currentDimension);
pipelinesPerDimension.put(currentDimension, pipeline);
if (Tracy.ENABLED) {
shaderSection = Tracy.sectionEnter(Tracy.SECTION_SHADERS, "shaders " + Iris.getCurrentPackName() + " / " + currentDimension);
}
} else {
pipeline = pipelinesPerDimension.get(currentDimension);
}
@@ -70,6 +75,8 @@ public class PipelineManager {
* @see <a href="https://github.com/IrisShaders/Iris/issues/1330">this GitHub issue</a>
*/
public void destroyPipeline() {
Tracy.sectionLeave(shaderSection);
shaderSection = 0L;
pipelinesPerDimension.forEach((dimensionName, pipeline) -> {
Iris.logger.info("Destroying pipeline for dimension '{}'", dimensionName);
resetTextureState();
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.tracy;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.profiling.GpuFrameLagMeter;
import com.gtnewhorizons.angelica.profiling.TracyFramePlots;
import com.gtnewhorizons.angelica.profiling.TracyUiSections;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -20,6 +21,7 @@ public class MixinMinecraft_Tracy {
TracyFramePlots.onFrame((Minecraft) (Object) this);
GpuFrameLagMeter.onFrame();
Tracy.frameMark();
TracyUiSections.poll(((Minecraft) (Object) this).currentScreen);
Tracy.gpuCollect();
}
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
import com.gtnewhorizons.angelica.glsm.profiling.ZoneStack;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -18,12 +19,12 @@ import org.lwjgl.system.libffi.FFIType;
import org.lwjgl.system.libffi.LibFFI;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.LongBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.NULL;
import static org.lwjgl.system.MemoryUtil.memAddress;
import static org.lwjgl.system.MemoryUtil.memGetInt;
import static org.lwjgl.system.MemoryUtil.memPutAddress;
import static org.lwjgl.system.MemoryUtil.memPutByte;
import static org.lwjgl.system.MemoryUtil.memPutInt;
@@ -33,12 +34,16 @@ import static org.lwjgl.system.MemoryUtil.nmemAllocChecked;
public final class TracyClientBackend implements TracyBackend {
private static final Logger LOGGER = LogManager.getLogger("Tracy");
private static final int MAX_TEXT = 8192;
private static final int ACTIVE_OFFSET = 4;
private SharedLibrary lib;
private TracyLibrary sym;
private SrcLocInterner interner;
private long emptyString;
private FFICIF plotCif;
private int ctxSize;
int zoneCtxSize() {
return ctxSize;
}
@Override
public boolean init() {
@@ -52,18 +57,15 @@ public final class TracyClientBackend implements TracyBackend {
sym = TracyLibrary.resolve(lib);
if (sym == null) return false;
emptyString = nmemAllocChecked(1);
memPutByte(emptyString, (byte) 0);
final PointerBuffer plotArgTypes = MemoryUtil.memAllocPointer(2);
plotArgTypes.put(0, LibFFI.ffi_type_pointer.address()).put(1, LibFFI.ffi_type_double.address());
plotCif = FFICIF.malloc();
final int status = LibFFI.ffi_prep_cif(plotCif, LibFFI.FFI_DEFAULT_ABI, LibFFI.ffi_type_void, plotArgTypes);
if (status != LibFFI.FFI_OK) {
LOGGER.warn("Tracy: ffi_prep_cif failed: {}", status);
ctxSize = (int) JNI.invokeJ(sym.zoneCtxSize);
if (ctxSize <= 0 || Integer.bitCount(ctxSize) != 1) {
LOGGER.warn("Tracy: zone context size {} is not a power of two; slot addressing would be wrong", ctxSize);
return false;
}
emptyString = nmemAllocChecked(1);
memPutByte(emptyString, (byte) 0);
interner = new SrcLocInterner(this::allocSrcLoc, SystemProperties.TRACY_MAX_SRC_LOCS);
JNI.invokeV(sym.startupProfiler);
@@ -101,26 +103,64 @@ public final class TracyClientBackend implements TracyBackend {
return interner.dynamicSrcLoc();
}
private static final ConcurrentLinkedQueue<SlotArena> ARENAS = new ConcurrentLinkedQueue<>();
private static final class SlotArena {
private final long base;
private final int strideShift;
private int depth;
SlotArena(int stride) {
this.strideShift = Integer.numberOfTrailingZeros(stride);
this.base = nmemAllocChecked((long) stride * ZoneStack.MAX_DEPTH);
}
long top() {
return depth == ZoneStack.MAX_DEPTH ? NULL : base + ((long) depth << strideShift);
}
void unwindTo(long slot) {
final long index = (slot - base) >> strideShift;
if (index >= 0 && index < ZoneStack.MAX_DEPTH) depth = (int) index;
}
}
private final ThreadLocal<SlotArena> slots = ThreadLocal.withInitial(() -> {
final SlotArena arena = new SlotArena(ctxSize);
ARENAS.add(arena);
return arena;
});
@Override
public long beginZone(long srcLoc) {
return JNI.invokePJ(srcLoc, 1, sym.zoneBegin);
final SlotArena arena = slots.get();
final long slot = arena.top();
if (slot == NULL) return 0L;
JNI.invokePPV(srcLoc, 1, slot, sym.zoneBegin);
if (memGetInt(slot + ACTIVE_OFFSET) == 0) return 0L;
arena.depth++;
return slot;
}
@Override
public void endZone(long ctx) {
JNI.invokeJV(ctx, sym.zoneEnd);
if (ctx == 0L) return;
slots.get().unwindTo(ctx);
JNI.invokePV(ctx, sym.zoneEnd);
}
@Override
public void zoneText(long ctx, String text) {
if (ctx == 0L) return;
try (MemoryStack stack = stackPush()) {
final ByteBuffer utf8 = stack.UTF8(truncate(text), false);
JNI.invokeJPPV(ctx, memAddress(utf8), utf8.remaining(), sym.zoneText);
JNI.invokePPPV(ctx, memAddress(utf8), utf8.remaining(), sym.zoneText);
}
}
@Override
public void zoneValue(long ctx, long value) {
if (ctx == 0L) return;
JNI.invokePJV(ctx, value, sym.zoneValue);
}
@@ -153,19 +193,33 @@ public final class TracyClientBackend implements TracyBackend {
@Override
public void plot(long namePtr, double value) {
JNI.invokePJV(namePtr, Double.doubleToRawLongBits(value), sym.plot);
}
@Override
public void message(String text, int severity) {
try (MemoryStack stack = stackPush()) {
final LongBuffer arg0 = stack.longs(namePtr);
final DoubleBuffer arg1 = stack.doubles(value);
final PointerBuffer args = stack.pointers(memAddress(arg0), memAddress(arg1));
LibFFI.ffi_call(plotCif, sym.plot, null, args);
final ByteBuffer utf8 = stack.UTF8(truncate(text), false);
JNI.invokePPV(severity, 0, 0, (long) utf8.remaining(), memAddress(utf8), sym.message);
}
}
@Override
public void message(String text) {
public long sectionEnter(int category, String text) {
try (MemoryStack stack = stackPush()) {
final ByteBuffer utf8 = stack.UTF8(truncate(text), false);
JNI.invokePPV(memAddress(utf8), (long) utf8.remaining(), 0, sym.message);
return Integer.toUnsignedLong(JNI.invokePI(category, memAddress(stack.UTF8(truncate(text), true)), sym.sectionEnter));
}
}
@Override
public void sectionLeave(long id) {
JNI.invokeV((int) id, sym.sectionLeave);
}
@Override
public void sectionSetup(int category, String name) {
try (MemoryStack stack = stackPush()) {
JNI.invokePV(category, memAddress(stack.UTF8(truncate(name), true)), sym.sectionSetup);
}
}
@@ -191,15 +245,15 @@ public final class TracyClientBackend implements TracyBackend {
@Override
public void shutdown() {
JNI.invokeV(sym.shutdownProfiler);
for (SlotArena arena = ARENAS.poll(); arena != null; arena = ARENAS.poll()) {
MemoryUtil.nmemFree(arena.base);
}
}
private static String truncate(String text) {
return text.length() > MAX_TEXT ? text.substring(0, MAX_TEXT) : text;
}
/*
* GPU zones: port of TracyOpenGL.hpp GpuCtx (v0.13.1).
*/
private static final int GPU_QUERY_COUNT = 16 * 1024;
private static final byte GPU_CONTEXT_ID = 0;
private static final byte GPU_CONTEXT_TYPE_OPENGL = 1;
@@ -11,6 +11,7 @@ import static org.lwjgl.system.MemoryUtil.NULL;
final class TracyLibrary {
private static final Logger LOGGER = LogManager.getLogger("Tracy");
final long zoneCtxSize;
final long startupProfiler;
final long shutdownProfiler;
final long profilerStarted;
@@ -26,6 +27,9 @@ final class TracyLibrary {
final long message;
final long messageAppinfo;
final long setThreadName;
final long sectionEnter;
final long sectionLeave;
final long sectionSetup;
final long gpuZoneBegin;
final long gpuZoneEnd;
final long gpuTime;
@@ -43,21 +47,25 @@ final class TracyLibrary {
}
private TracyLibrary(SharedLibrary lib) {
zoneCtxSize = req(lib, "ang_zone_ctx_size");
startupProfiler = req(lib, "___tracy_startup_profiler");
shutdownProfiler = req(lib, "___tracy_shutdown_profiler");
profilerStarted = req(lib, "___tracy_profiler_started");
connected = req(lib, "___tracy_connected");
zoneBegin = req(lib, "___tracy_emit_zone_begin");
zoneEnd = req(lib, "___tracy_emit_zone_end");
zoneText = req(lib, "___tracy_emit_zone_text");
zoneValue = req(lib, "___tracy_emit_zone_value");
zoneBegin = req(lib, "ang_zone_begin");
zoneEnd = req(lib, "ang_zone_end");
zoneText = req(lib, "ang_zone_text");
zoneValue = req(lib, "ang_zone_value");
frameMark = req(lib, "___tracy_emit_frame_mark");
plot = req(lib, "___tracy_emit_plot");
plot = req(lib, "ang_plot");
plotInt = req(lib, "___tracy_emit_plot_int");
plotConfig = req(lib, "___tracy_emit_plot_config");
message = req(lib, "___tracy_emit_message");
message = req(lib, "___tracy_emit_logString");
messageAppinfo = req(lib, "___tracy_emit_message_appinfo");
setThreadName = req(lib, "___tracy_set_thread_name");
sectionEnter = req(lib, "ang_section_enter");
sectionLeave = req(lib, "ang_section_leave");
sectionSetup = req(lib, "ang_section_setup");
gpuZoneBegin = req(lib, "___tracy_emit_gpu_zone_begin");
gpuZoneEnd = req(lib, "___tracy_emit_gpu_zone_end");
gpuTime = req(lib, "___tracy_emit_gpu_time");
@@ -47,7 +47,7 @@ final class TracyNativeLoader {
case MACOS -> "libTracyClient.dylib";
case LINUX -> "libTracyClient.so";
};
final String resource = "/natives/tracy/" + platform + "/" + libName;
final String resource = "/natives/tracy/" + TRACY_VERSION + "/" + platform + "/" + libName;
final byte[] lib;
try (InputStream in = TracyNativeLoader.class.getResourceAsStream(resource)) {
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.13)
project(angelica-tracy-native CXX)
if(NOT DEFINED TRACY_SRC_DIR)
message(FATAL_ERROR "TRACY_SRC_DIR must be set to a Tracy source checkout")
endif()
add_subdirectory("${TRACY_SRC_DIR}" tracy)
target_sources(TracyClient PRIVATE "${CMAKE_CURRENT_LIST_DIR}/angelica_tracy_shim.cpp")
target_include_directories(TracyClient PRIVATE "${TRACY_SRC_DIR}/public")
@@ -0,0 +1,53 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "tracy/TracyC.h"
#include "client/TracyProfiler.hpp"
#include "common/TracyApi.h"
static_assert(offsetof(TracyCZoneCtx, active) == 4, "TracyClientBackend.ACTIVE_OFFSET reads the active flag at a fixed offset of 4");
// Shim to cover things not reachable, or not efficiently usable, from Java; including header-inlined functions.
extern "C" {
TRACY_API uint64_t ang_zone_ctx_size(void) {
return (uint64_t)sizeof(TracyCZoneCtx);
}
TRACY_API void ang_zone_begin(const struct ___tracy_source_location_data* srcloc, int32_t active, void* out) {
*(TracyCZoneCtx*)out = ___tracy_emit_zone_begin(srcloc, active);
}
TRACY_API void ang_zone_end(const void* ctx) {
___tracy_emit_zone_end(*(const TracyCZoneCtx*)ctx);
}
TRACY_API void ang_zone_text(const void* ctx, const char* txt, uint64_t size) {
___tracy_emit_zone_text(*(const TracyCZoneCtx*)ctx, txt, (size_t)size);
}
TRACY_API void ang_zone_value(const void* ctx, uint64_t value) {
___tracy_emit_zone_value(*(const TracyCZoneCtx*)ctx, value);
}
TRACY_API void ang_plot(const char* name, int64_t valueBits) {
double value;
memcpy(&value, &valueBits, sizeof(value));
___tracy_emit_plot(name, value);
}
TRACY_API uint32_t ang_section_enter(uint32_t category, const char* txt) {
return tracy::Profiler::SectionEnter((uint16_t)category, "%s", txt);
}
TRACY_API void ang_section_leave(uint32_t id) {
tracy::Profiler::SectionLeave(id);
}
TRACY_API void ang_section_setup(uint32_t category, const char* txt) {
tracy::Profiler::SectionSetup((uint16_t)category, "%s", txt);
}
}
@@ -34,7 +34,7 @@ class TracyBindingSmokeTest {
static boolean nativePresent() {
for (String platform : new String[] {"windows-x64", "linux-x64", "macos-x64", "macos-arm64"}) {
for (String lib : new String[] {"TracyClient.dll", "libTracyClient.so", "libTracyClient.dylib"}) {
if (TracyBindingSmokeTest.class.getResource("/natives/tracy/" + platform + "/" + lib) != null) {
if (TracyBindingSmokeTest.class.getResource("/natives/tracy/" + TracyTags.TRACY_VERSION + "/" + platform + "/" + lib) != null) {
return true;
}
}
@@ -57,8 +57,26 @@ class TracyBindingSmokeTest {
return backend;
}
@Test
@Order(0)
void nativeIsBuiltWithOnDemand() throws Exception {
assertEquals(16, backend().zoneCtxSize(), "ctx size is 16 only with TRACY_ON_DEMAND");
}
@Test
@Order(1)
void zeroHandlesAreInertWhileDisconnected() throws Exception {
final TracyClientBackend b = backend();
assertEquals(0L, b.beginZone(b.internSrcLoc("disconnected", 0)), "zone must be inactive with no profiler attached");
b.endZone(0L);
b.zoneText(0L, "text");
b.zoneValue(0L, 1L);
assertEquals(0L, b.sectionEnter(1, "disconnected section"));
b.sectionLeave(0L);
}
@Test
@Order(2)
void internAndSingleZone() throws Exception {
final TracyClientBackend b = backend();
final long srcLoc = b.internSrcLoc("smoke", 0);
@@ -72,7 +90,7 @@ class TracyBindingSmokeTest {
}
@Test
@Order(2)
@Order(3)
void hammerZonesAcrossThreads() throws Exception {
final TracyClientBackend b = backend();
final int threads = 4;
@@ -108,7 +126,7 @@ class TracyBindingSmokeTest {
}
@Test
@Order(3)
@Order(4)
void plotsMessagesAndFrameMarks() throws Exception {
final TracyClientBackend b = backend();
final long plotName = b.internPlotName("smokePlot", 0);
@@ -126,7 +144,7 @@ class TracyBindingSmokeTest {
}
@Test
@Order(4)
@Order(5)
void dynamicSrcLocOverflowPath() throws Exception {
final TracyClientBackend b = backend();
final long dynamic = b.dynamicSrcLoc();
@@ -137,7 +155,7 @@ class TracyBindingSmokeTest {
}
@Test
@Order(5)
@Order(6)
void gpuEmitStructMarshalling() throws Exception {
final TracyClientBackend b = backend();
final long srcLoc = b.internSrcLoc("gpu-smoke", 0);
@@ -152,7 +170,7 @@ class TracyBindingSmokeTest {
}
@Test
@Order(6)
@Order(7)
void shutdownFlushes() throws Exception {
backend().shutdown();
}