diff --git a/build.gradle.kts b/build.gradle.kts index faa105ee9fa..9b10f1fdb58 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,6 +100,8 @@ dependencies { // Natives for JNBullet natives(group = "org.terasology.jnbullet", name = "JNBullet", version = "1.0.4", ext = "zip") + // Natives for Tracy bindings (embedded in JAR) + natives("io.github.benjaminamos.TracyJavaBindings:TracyJavaBindings:1.0.0-SNAPSHOT") } tasks.register("extractWindowsNatives") { @@ -135,6 +137,13 @@ tasks.register("extractNativeBulletNatives") { into("$dirNatives") } +tasks.register("extractTracyJNINatives") { + description = "Extracts the Tracy JNI natives from the module jar" + from(configurations["natives"].filter { it.name.contains("TracyJavaBindings") }.map { zipTree(it) }) + into(dirNatives) + exclude("io/**", "META-INF/**") +} + tasks.register("extractNatives") { description = "Extracts all the native lwjgl libraries from the downloaded zip" dependsOn( @@ -142,7 +151,8 @@ tasks.register("extractNatives") { "extractLinuxNatives", "extractMacOSXNatives", "extractJNLuaNatives", - "extractNativeBulletNatives" + "extractNativeBulletNatives", + "extractTracyJNINatives" ) // specifying the outputs directory lets gradle have an up-to-date check, and automatic clean task outputs.dir("$dirNatives") diff --git a/engine/build.gradle.kts b/engine/build.gradle.kts index 6891217379c..e46ac5bd734 100644 --- a/engine/build.gradle.kts +++ b/engine/build.gradle.kts @@ -142,6 +142,8 @@ dependencies { implementation("org.terasology.crashreporter:cr-terasology:5.0.0") api(project(":subsystems:TypeHandlerLibrary")) + + implementation("io.github.benjaminamos.TracyJavaBindings:TracyJavaBindings:1.0.0-SNAPSHOT") } protobuf { diff --git a/engine/src/main/java/org/terasology/engine/config/SystemConfig.java b/engine/src/main/java/org/terasology/engine/config/SystemConfig.java index 006d160a593..3eb8efc1240 100644 --- a/engine/src/main/java/org/terasology/engine/config/SystemConfig.java +++ b/engine/src/main/java/org/terasology/engine/config/SystemConfig.java @@ -68,6 +68,12 @@ public class SystemConfig extends AutoConfig { name("${engine:menu#settings-monitoring-enabled}") ); + public final Setting tracyProfilerEnabled = setting( + type(Boolean.class), + defaultValue(false), + name("${engine:menu#settings-tracy-profiler-enabled}") + ); + public final Setting writeSaveGamesEnabled = setting( type(Boolean.class), defaultValue(true), diff --git a/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java b/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java index d3852f083ce..3ce433dab8e 100644 --- a/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java +++ b/engine/src/main/java/org/terasology/engine/core/TerasologyEngine.java @@ -468,12 +468,10 @@ public synchronized void runMain() { */ @SuppressWarnings("checkstyle:EmptyBlock") private void mainLoop() { - PerformanceMonitor.startActivity("Other"); // MAIN GAME LOOP while (tick()) { /* do nothing */ } - PerformanceMonitor.endActivity(); } /** @@ -482,6 +480,7 @@ private void mainLoop() { * @return true if the loop requesting a tick should continue running */ public boolean tick() { + PerformanceMonitor.startActivity("Tick"); if (shutdownRequested) { return false; } @@ -524,8 +523,8 @@ public boolean tick() { } assetTypeManager.disposedUnusedAssets(); + PerformanceMonitor.endActivity(); PerformanceMonitor.rollCycle(); - PerformanceMonitor.startActivity("Other"); return true; } diff --git a/engine/src/main/java/org/terasology/engine/monitoring/PerformanceMonitor.java b/engine/src/main/java/org/terasology/engine/monitoring/PerformanceMonitor.java index c0ff506acfe..ec1c7f3773b 100644 --- a/engine/src/main/java/org/terasology/engine/monitoring/PerformanceMonitor.java +++ b/engine/src/main/java/org/terasology/engine/monitoring/PerformanceMonitor.java @@ -19,6 +19,7 @@ */ public final class PerformanceMonitor { private static PerformanceMonitorInternal instance; + private static boolean tracyEnabled; static { instance = new NullPerformanceMonitor(); @@ -127,6 +128,10 @@ public static TObjectDoubleMap getAllocationMean() { return instance.getAllocationMean(); } + public static void setTracyEnabled(boolean enabled) { + tracyEnabled = enabled; + } + /** * Enables or disables the Performance Monitoring system. *

@@ -136,8 +141,14 @@ public static TObjectDoubleMap getAllocationMean() { */ public static void setEnabled(boolean enabled) { if (enabled && !(instance instanceof PerformanceMonitorImpl)) { - instance = new PerformanceMonitorImpl(); + if (instance != null) { + instance.shutdown(); + } + instance = new PerformanceMonitorImpl(tracyEnabled); } else if (!enabled && !(instance instanceof NullPerformanceMonitor)) { + if (instance != null) { + instance.shutdown(); + } instance = new NullPerformanceMonitor(); } } diff --git a/engine/src/main/java/org/terasology/engine/monitoring/impl/NullPerformanceMonitor.java b/engine/src/main/java/org/terasology/engine/monitoring/impl/NullPerformanceMonitor.java index a59a739156a..a8c2d1b2f64 100644 --- a/engine/src/main/java/org/terasology/engine/monitoring/impl/NullPerformanceMonitor.java +++ b/engine/src/main/java/org/terasology/engine/monitoring/impl/NullPerformanceMonitor.java @@ -38,4 +38,7 @@ public TObjectDoubleMap getAllocationMean() { return metrics; } + @Override + public void shutdown() { + } } diff --git a/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorImpl.java b/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorImpl.java index 842ead3ac5f..044a5c52c50 100644 --- a/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorImpl.java +++ b/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorImpl.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.monitoring.impl; +import io.github.benjaminamos.tracy.Tracy; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import gnu.trove.map.TObjectDoubleMap; @@ -31,6 +32,7 @@ public class PerformanceMonitorImpl implements PerformanceMonitorInternal { // on the main thread. Not strictly necessary (these processes are ignored by the PerformanceMonitor // anyway) an instance of this class offers a slight performance improvement over standard Activity // implementations as it doesn't call the PerformanceMonitor.endActivity() method. + private static boolean tracyEnabled = false; private final Activity activityInstance = new ActivityInstance(); @@ -57,7 +59,7 @@ public class PerformanceMonitorImpl implements PerformanceMonitorInternal { private final Thread mainThread; private final EngineTime timer; - public PerformanceMonitorImpl() { + public PerformanceMonitorImpl(boolean enableTracy) { activityStack = Queues.newArrayDeque(); executionData = Lists.newLinkedList(); allocationData = Lists.newLinkedList(); @@ -78,6 +80,11 @@ public PerformanceMonitorImpl() { timer = (EngineTime) CoreRegistry.get(Time.class); mainThread = Thread.currentThread(); + + if (enableTracy && !tracyEnabled) { + Tracy.startupProfiler(); + tracyEnabled = true; + } } @Override @@ -101,6 +108,12 @@ public void rollCycle() { currentExecutionData = new TObjectLongHashMap<>(); currentAllocationData = new TObjectLongHashMap<>(); + + activityStack.clear(); + + if (tracyEnabled) { + Tracy.markFrame(); + } } @Override @@ -111,6 +124,13 @@ public Activity startActivity(String activityName) { ActivityInfo newActivity = new ActivityInfo(activityName).initialize(); + if (tracyEnabled) { + StackWalker.StackFrame caller = java.security.AccessController.doPrivileged((java.security.PrivilegedAction) () -> + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(s -> s.skip(4).findFirst()).get()); + long sourceLocation = Tracy.allocSourceLocation(caller.getLineNumber(), caller.getFileName(), caller.getClassName() + "#" + caller.getMethodName(), activityName, 0); + newActivity.zoneContext = Tracy.zoneBegin(sourceLocation, 1); + } + if (!activityStack.isEmpty()) { ActivityInfo currentActivity = activityStack.peek(); currentActivity.ownTime += newActivity.startTime - ((currentActivity.resumeTime > 0) @@ -133,6 +153,10 @@ public void endActivity() { ActivityInfo oldActivity = activityStack.pop(); + if (tracyEnabled) { + Tracy.zoneEnd(oldActivity.zoneContext); + } + long endTime = timer.getRealTimeInMs(); long totalTime = (oldActivity.resumeTime > 0) ? oldActivity.ownTime + endTime - oldActivity.resumeTime @@ -152,6 +176,7 @@ public void endActivity() { } } + @Override public TObjectDoubleMap getRunningMean() { TObjectDoubleMap activityToMeanMap = new TObjectDoubleHashMap<>(); @@ -179,6 +204,14 @@ public TObjectDoubleMap getAllocationMean() { return activityToMeanMap; } + @Override + public void shutdown() { + if (tracyEnabled) { + tracyEnabled = false; + Tracy.shutdownProfiler(); + } + } + private class ActivityInfo { public String name; public long startTime; @@ -186,6 +219,8 @@ private class ActivityInfo { public long ownTime; public long startMem; public long ownMem; + public StackWalker.StackFrame caller; + public Tracy.ZoneContext zoneContext; ActivityInfo(String activityName) { this.name = activityName; diff --git a/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorInternal.java b/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorInternal.java index 5d214db8f2d..67373c6a061 100644 --- a/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorInternal.java +++ b/engine/src/main/java/org/terasology/engine/monitoring/impl/PerformanceMonitorInternal.java @@ -21,4 +21,6 @@ public interface PerformanceMonitorInternal { TObjectDoubleMap getDecayingSpikes(); TObjectDoubleMap getAllocationMean(); + + void shutdown(); } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java index 1e07784deec..7f90286b42a 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java @@ -271,6 +271,7 @@ protected boolean isEscapeToCloseAllowed() { */ public void toggleMetricsMode() { MetricsMode mode = debugMetricsSystem.toggle(); + PerformanceMonitor.setTracyEnabled(systemConfig.tracyProfilerEnabled.get()); PerformanceMonitor.setEnabled(mode.isPerformanceManagerMode()); } } diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang index 09e483cff6a..5e51ecaa3cd 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang @@ -340,6 +340,7 @@ "settings-debug-mode": "settings-debug-mode", "settings-language": "settings-language", "settings-monitoring-enabled": "settings-monitoring-enabled", + "settings-tracy-profiler-enabled": "settings-tracy-profiler-enabled", "settings-saves-enabled": "settings-saves-enabled", "settings-seconds-between-saves": "settings-seconds-between-saves", "settings-title": "settings-title", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang index 1538e87f9eb..03f4e1fcb00 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang @@ -350,6 +350,7 @@ "settings-debug-mode": "Debug mode", "settings-language": "Language", "settings-monitoring-enabled": "Monitoring", + "settings-tracy-profiler-enabled": "Tracy Profiler", "settings-saves-enabled": "Game saves", "settings-seconds-between-saves": "Seconds between saves", "settings-title": "Settings",