Browse Source

Merge pull request #57 from godlikepanos/tracer

Improve the tracer
Panagiotis Christopoulos Charitos 6 years ago
parent
commit
525930a4d1
48 changed files with 861 additions and 795 deletions
  1. 9 9
      CMakeLists.txt
  2. 2 1
      README.md
  3. 1 1
      samples/common/Framework.cpp
  4. 2 2
      sandbox/Main.cpp
  5. 1 1
      src/anki/Core.h
  6. 88 80
      src/anki/core/App.cpp
  7. 4 0
      src/anki/core/App.h
  8. 1 1
      src/anki/core/CMakeLists.txt
  9. 397 0
      src/anki/core/CoreTracer.cpp
  10. 60 0
      src/anki/core/CoreTracer.h
  11. 1 1
      src/anki/core/StagingGpuMemoryManager.cpp
  12. 1 0
      src/anki/core/StagingGpuMemoryManager.h
  13. 0 119
      src/anki/core/Trace.h
  14. 6 2
      src/anki/gr/RenderGraph.cpp
  15. 2 0
      src/anki/gr/RenderGraph.h
  16. 2 2
      src/anki/gr/ShaderCompiler.cpp
  17. 1 1
      src/anki/gr/vulkan/CommandBufferFactory.cpp
  18. 1 1
      src/anki/gr/vulkan/CommandBufferImpl.inl.h
  19. 1 1
      src/anki/gr/vulkan/DescriptorSet.cpp
  20. 1 1
      src/anki/gr/vulkan/FenceFactory.inl.h
  21. 1 0
      src/anki/gr/vulkan/GrManagerImpl.h
  22. 1 1
      src/anki/gr/vulkan/Pipeline.cpp
  23. 1 1
      src/anki/gr/vulkan/SemaphoreFactory.inl.h
  24. 1 1
      src/anki/renderer/ClusterBin.cpp
  25. 1 1
      src/anki/renderer/Drawer.cpp
  26. 1 1
      src/anki/renderer/GBuffer.cpp
  27. 1 1
      src/anki/renderer/GlobalIllumination.cpp
  28. 3 3
      src/anki/renderer/MainRenderer.cpp
  29. 1 0
      src/anki/renderer/MainRenderer.h
  30. 1 1
      src/anki/renderer/ProbeReflections.cpp
  31. 1 1
      src/anki/renderer/Renderer.cpp
  32. 1 2
      src/anki/renderer/ShadowMapping.cpp
  33. 1 1
      src/anki/renderer/UiStage.cpp
  34. 1 1
      src/anki/resource/AsyncLoader.cpp
  35. 1 1
      src/anki/resource/ResourceFilesystem.cpp
  36. 1 1
      src/anki/resource/TransferGpuAllocator.cpp
  37. 1 1
      src/anki/scene/Octree.h
  38. 1 1
      src/anki/scene/SceneGraph.cpp
  39. 1 1
      src/anki/scene/SoftwareRasterizer.cpp
  40. 1 1
      src/anki/scene/VisibilityInternal.h
  41. 1 1
      src/anki/script/LuaBinder.cpp
  42. 2 1
      src/anki/util/CMakeLists.txt
  43. 21 8
      src/anki/util/HighRezTimerWindows.cpp
  44. 1 1
      src/anki/util/Singleton.h
  45. 94 467
      src/anki/util/Tracer.cpp
  46. 104 43
      src/anki/util/Tracer.h
  47. 6 5
      tests/util/HighRezTimer.cpp
  48. 29 25
      tests/util/Tracer.cpp

+ 9 - 9
CMakeLists.txt

@@ -1,5 +1,7 @@
 CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
 CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
 
 
+include(ProcessorCount)
+
 PROJECT(anki)
 PROJECT(anki)
 
 
 ################################################################################
 ################################################################################
@@ -135,19 +137,10 @@ endif()
 # Compiler & linker flags                                                      #
 # Compiler & linker flags                                                      #
 ################################################################################
 ################################################################################
 
 
-if(NOT MSVC)
-	if(ARCH_64)
-		add_definitions(-D_POSIX_VER_64)
-	else()
-		add_definitions(-D_POSIX_VER_32)
-	endif()
-endif()
-
 if(MINGW)
 if(MINGW)
 	set(COMPILER_FLAGS "${COMPILER_FLAGS} -mconsole ")
 	set(COMPILER_FLAGS "${COMPILER_FLAGS} -mconsole ")
 endif()
 endif()
 
 
-add_definitions(-DGLEW_NO_GLU)
 add_definitions(-DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS)
 add_definitions(-DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS)
 add_definitions(-DANKI_BUILD)
 add_definitions(-DANKI_BUILD)
 
 
@@ -196,6 +189,13 @@ if(NOT MSVC)
 
 
 	# Set the flags to cmake now
 	# Set the flags to cmake now
 	set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
 	set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
+else()
+	ProcessorCount(PC)
+	add_definitions("/MP${PC}")
+
+	if(${CMAKE_BUILD_TYPE} STREQUAL "Release" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo")
+		add_definitions("/Ox")
+	endif()
 endif()
 endif()
 
 
 # Use gold linker
 # Use gold linker

+ 2 - 1
README.md

@@ -63,6 +63,7 @@ Prerequisites:
 - Python 3.0 and up
 - Python 3.0 and up
 	- Make sure that the python executable's location is in `PATH` environment variable
 	- Make sure that the python executable's location is in `PATH` environment variable
 - Microsoft Visual Studio 2017 and up
 - Microsoft Visual Studio 2017 and up
+	- Make sure that `Windows 10 SDK (xxx) for Desktop C++ [x86 and x64]` component is installed
 
 
 To build the release version open `PowerShell` and type:
 To build the release version open `PowerShell` and type:
 
 
@@ -70,7 +71,7 @@ To build the release version open `PowerShell` and type:
 	$mkdir build
 	$mkdir build
 	$cd build
 	$cd build
 	$cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_BUILD_TYPE=Release
 	$cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_BUILD_TYPE=Release
-	$cmake --build .
+	$cmake --build . --config Release
 
 
 Alternatively, recent Visual Studio versions support building CMake projects from inside the IDE:
 Alternatively, recent Visual Studio versions support building CMake projects from inside the IDE:
 
 

+ 1 - 1
samples/common/Framework.cpp

@@ -162,7 +162,7 @@ Error SampleApp::userMainLoop(Bool& quit)
 
 
 		if(in.getKey(KeyCode::F12) == 1)
 		if(in.getKey(KeyCode::F12) == 1)
 		{
 		{
-			CoreTracerSingleton::get().m_enabled = !CoreTracerSingleton::get().m_enabled;
+			TracerSingleton::get().setEnabled(!TracerSingleton::get().getEnabled());
 		}
 		}
 
 
 		if(in.getMousePosition() != Vec2(0.0))
 		if(in.getMousePosition() != Vec2(0.0))

+ 2 - 2
sandbox/Main.cpp

@@ -52,7 +52,7 @@ Error MyApp::init(int argc, char* argv[])
 	{
 	{
 		m_profile = true;
 		m_profile = true;
 		setTimerTick(0.0);
 		setTimerTick(0.0);
-		CoreTracerSingleton::get().m_enabled = true;
+		TracerSingleton::get().setEnabled(true);
 	}
 	}
 
 
 // Input
 // Input
@@ -165,7 +165,7 @@ Error MyApp::userMainLoop(Bool& quit)
 
 
 	if(in.getKey(KeyCode::F12) == 1)
 	if(in.getKey(KeyCode::F12) == 1)
 	{
 	{
-		CoreTracerSingleton::get().m_enabled = !CoreTracerSingleton::get().m_enabled;
+		TracerSingleton::get().setEnabled(!TracerSingleton::get().getEnabled());
 	}
 	}
 
 
 #if !PLAYER
 #if !PLAYER

+ 1 - 1
src/anki/Core.h

@@ -8,4 +8,4 @@
 #include <anki/core/App.h>
 #include <anki/core/App.h>
 #include <anki/core/Config.h>
 #include <anki/core/Config.h>
 #include <anki/core/NativeWindow.h>
 #include <anki/core/NativeWindow.h>
-#include <anki/core/Trace.h>
+#include <anki/core/CoreTracer.h>

+ 88 - 80
src/anki/core/App.cpp

@@ -10,7 +10,8 @@
 #include <anki/util/Filesystem.h>
 #include <anki/util/Filesystem.h>
 #include <anki/util/System.h>
 #include <anki/util/System.h>
 #include <anki/util/ThreadHive.h>
 #include <anki/util/ThreadHive.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
+#include <anki/core/CoreTracer.h>
 #include <anki/core/DeveloperConsole.h>
 #include <anki/core/DeveloperConsole.h>
 #include <anki/core/NativeWindow.h>
 #include <anki/core/NativeWindow.h>
 #include <anki/input/Input.h>
 #include <anki/input/Input.h>
@@ -284,17 +285,7 @@ void App::cleanup()
 	m_heapAlloc.deleteInstance(m_window);
 	m_heapAlloc.deleteInstance(m_window);
 
 
 #if ANKI_ENABLE_TRACE
 #if ANKI_ENABLE_TRACE
-	if(CoreTracerSingleton::get().isInitialized())
-	{
-		StringAuto fname(m_heapAlloc);
-		fname.sprintf("%s/trace", m_settingsDir.cstr());
-		ANKI_CORE_LOGI("Will dump trace files: %s", fname.cstr());
-		if(CoreTracerSingleton::get().flush(fname.toCString()))
-		{
-			ANKI_CORE_LOGE("Ignoring error from the tracer");
-		}
-		CoreTracerSingleton::destroy();
-	}
+	m_heapAlloc.deleteInstance(m_coreTracer);
 #endif
 #endif
 
 
 	m_settingsDir.destroy(m_heapAlloc);
 	m_settingsDir.destroy(m_heapAlloc);
@@ -321,11 +312,6 @@ Error App::initInternal(const ConfigSet& config_, AllocAlignedCallback allocCb,
 	initMemoryCallbacks(allocCb, allocCbUserData);
 	initMemoryCallbacks(allocCb, allocCbUserData);
 	m_heapAlloc = HeapAllocator<U8>(m_allocCb, m_allocCbData);
 	m_heapAlloc = HeapAllocator<U8>(m_allocCb, m_allocCbData);
 
 
-#if ANKI_ENABLE_TRACE
-	CoreTracerSingleton::get().init(m_heapAlloc);
-	CoreTracerSingleton::get().newFrame(0);
-#endif
-
 	ANKI_CHECK(initDirs(config));
 	ANKI_CHECK(initDirs(config));
 
 
 	// Print a message
 	// Print a message
@@ -372,6 +358,14 @@ Error App::initInternal(const ConfigSet& config_, AllocAlignedCallback allocCb,
 
 
 	ANKI_CORE_LOGI("Number of main threads: %u", U(config.getNumberU32("core.mainThreadCount")));
 	ANKI_CORE_LOGI("Number of main threads: %u", U(config.getNumberU32("core.mainThreadCount")));
 
 
+	//
+	// Core tracer
+	//
+#if ANKI_ENABLE_TRACE
+	m_coreTracer = m_heapAlloc.newInstance<CoreTracer>();
+	ANKI_CHECK(m_coreTracer->init(m_heapAlloc, m_settingsDir));
+#endif
+
 	//
 	//
 	// Window
 	// Window
 	//
 	//
@@ -561,85 +555,99 @@ Error App::mainLoop()
 
 
 	while(!quit)
 	while(!quit)
 	{
 	{
-#if ANKI_ENABLE_TRACE
-		static U64 frame = 1;
-		CoreTracerSingleton::get().newFrame(frame++);
-#endif
-		ANKI_TRACE_START_EVENT(FRAME);
-		const Second startTime = HighRezTimer::getCurrentTime();
+		{
+			ANKI_TRACE_SCOPED_EVENT(FRAME);
+			const Second startTime = HighRezTimer::getCurrentTime();
 
 
-		prevUpdateTime = crntTime;
-		crntTime = HighRezTimer::getCurrentTime();
+			prevUpdateTime = crntTime;
+			crntTime = HighRezTimer::getCurrentTime();
 
 
-		// Update
-		ANKI_CHECK(m_input->handleEvents());
+			// Update
+			ANKI_CHECK(m_input->handleEvents());
 
 
-		// User update
-		ANKI_CHECK(userMainLoop(quit));
+			// User update
+			ANKI_CHECK(userMainLoop(quit));
 
 
-		ANKI_CHECK(m_scene->update(prevUpdateTime, crntTime));
+			ANKI_CHECK(m_scene->update(prevUpdateTime, crntTime));
 
 
-		RenderQueue rqueue;
-		m_scene->doVisibilityTests(rqueue);
+			RenderQueue rqueue;
+			m_scene->doVisibilityTests(rqueue);
 
 
-		// Inject stats UI
-		DynamicArrayAuto<UiQueueElement> newUiElementArr(m_heapAlloc);
-		injectUiElements(newUiElementArr, rqueue);
+			// Inject stats UI
+			DynamicArrayAuto<UiQueueElement> newUiElementArr(m_heapAlloc);
+			injectUiElements(newUiElementArr, rqueue);
 
 
-		// Render
-		TexturePtr presentableTex = m_gr->acquireNextPresentableTexture();
-		m_renderer->setStatsEnabled(m_displayStats);
-		ANKI_CHECK(m_renderer->render(rqueue, presentableTex));
+			// Render
+			TexturePtr presentableTex = m_gr->acquireNextPresentableTexture();
+			m_renderer->setStatsEnabled(m_displayStats
+#if ANKI_ENABLE_TRACE
+										|| TracerSingleton::get().getEnabled()
+#endif
+			);
+			ANKI_CHECK(m_renderer->render(rqueue, presentableTex));
 
 
-		// Pause and sync async loader. That will force all tasks before the pause to finish in this frame.
-		m_resources->getAsyncLoader().pause();
+			// Pause and sync async loader. That will force all tasks before the pause to finish in this frame.
+			m_resources->getAsyncLoader().pause();
 
 
-		m_gr->swapBuffers();
-		m_stagingMem->endFrame();
+			m_gr->swapBuffers();
+			m_stagingMem->endFrame();
 
 
-		// Update the trace info with some async loader stats
-		U64 asyncTaskCount = m_resources->getAsyncLoader().getCompletedTaskCount();
-		ANKI_TRACE_INC_COUNTER(RESOURCE_ASYNC_TASKS, asyncTaskCount - m_resourceCompletedAsyncTaskCount);
-		m_resourceCompletedAsyncTaskCount = asyncTaskCount;
+			// Update the trace info with some async loader stats
+			U64 asyncTaskCount = m_resources->getAsyncLoader().getCompletedTaskCount();
+			ANKI_TRACE_INC_COUNTER(RESOURCE_ASYNC_TASKS, asyncTaskCount - m_resourceCompletedAsyncTaskCount);
+			m_resourceCompletedAsyncTaskCount = asyncTaskCount;
 
 
-		// Now resume the loader
-		m_resources->getAsyncLoader().resume();
+			// Now resume the loader
+			m_resources->getAsyncLoader().resume();
 
 
-		ANKI_TRACE_STOP_EVENT(FRAME);
+			// Sleep
+			const Second endTime = HighRezTimer::getCurrentTime();
+			const Second frameTime = endTime - startTime;
+			if(frameTime < m_timerTick)
+			{
+				ANKI_TRACE_SCOPED_EVENT(TIMER_TICK_SLEEP);
+				HighRezTimer::sleep(m_timerTick - frameTime);
+			}
 
 
-		// Sleep
-		const Second endTime = HighRezTimer::getCurrentTime();
-		const Second frameTime = endTime - startTime;
-		if(frameTime < m_timerTick)
-		{
-			ANKI_TRACE_SCOPED_EVENT(TIMER_TICK_SLEEP);
-			HighRezTimer::sleep(m_timerTick - frameTime);
-		}
+			// Stats
+			if(m_displayStats)
+			{
+				StatsUi& statsUi = static_cast<StatsUi&>(*m_statsUi);
+				statsUi.m_frameTime.set(frameTime);
+				statsUi.m_renderTime.set(m_renderer->getStats().m_renderingCpuTime);
+				statsUi.m_lightBinTime.set(m_renderer->getStats().m_lightBinTime);
+				statsUi.m_sceneUpdateTime.set(m_scene->getStats().m_updateTime);
+				statsUi.m_visTestsTime.set(m_scene->getStats().m_visibilityTestsTime);
+				statsUi.m_physicsTime.set(m_scene->getStats().m_physicsUpdate);
+				statsUi.m_gpuTime.set(m_renderer->getStats().m_renderingGpuTime);
+				statsUi.m_allocatedCpuMem = m_memStats.m_allocatedMem.load();
+				statsUi.m_allocCount = m_memStats.m_allocCount.load();
+				statsUi.m_freeCount = m_memStats.m_freeCount.load();
+
+				GrManagerStats grStats = m_gr->getStats();
+				statsUi.m_vkCpuMem = grStats.m_cpuMemory;
+				statsUi.m_vkGpuMem = grStats.m_gpuMemory;
+				statsUi.m_vkCmdbCount = grStats.m_commandBufferCount;
+
+				statsUi.m_drawableCount = rqueue.countAllRenderables();
+			}
 
 
-		// Stats
-		if(m_displayStats)
-		{
-			StatsUi& statsUi = static_cast<StatsUi&>(*m_statsUi);
-			statsUi.m_frameTime.set(frameTime);
-			statsUi.m_renderTime.set(m_renderer->getStats().m_renderingCpuTime);
-			statsUi.m_lightBinTime.set(m_renderer->getStats().m_lightBinTime);
-			statsUi.m_sceneUpdateTime.set(m_scene->getStats().m_updateTime);
-			statsUi.m_visTestsTime.set(m_scene->getStats().m_visibilityTestsTime);
-			statsUi.m_physicsTime.set(m_scene->getStats().m_physicsUpdate);
-			statsUi.m_gpuTime.set(m_renderer->getStats().m_renderingGpuTime);
-			statsUi.m_allocatedCpuMem = m_memStats.m_allocatedMem.load();
-			statsUi.m_allocCount = m_memStats.m_allocCount.load();
-			statsUi.m_freeCount = m_memStats.m_freeCount.load();
-
-			GrManagerStats grStats = m_gr->getStats();
-			statsUi.m_vkCpuMem = grStats.m_cpuMemory;
-			statsUi.m_vkGpuMem = grStats.m_gpuMemory;
-			statsUi.m_vkCmdbCount = grStats.m_commandBufferCount;
-
-			statsUi.m_drawableCount = rqueue.countAllRenderables();
+#if ANKI_ENABLE_TRACE
+			if(m_renderer->getStats().m_renderingGpuTime >= 0.0)
+			{
+				ANKI_TRACE_CUSTOM_EVENT(GPU_TIME,
+					m_renderer->getStats().m_renderingGpuSubmitTimestamp,
+					m_renderer->getStats().m_renderingGpuTime);
+			}
+#endif
+
+			++m_globalTimestamp;
 		}
 		}
 
 
-		++m_globalTimestamp;
+#if ANKI_ENABLE_TRACE
+		static U64 frame = 1;
+		m_coreTracer->flushFrame(frame++);
+#endif
 	}
 	}
 
 
 	return Error::NONE;
 	return Error::NONE;

+ 4 - 0
src/anki/core/App.h

@@ -22,6 +22,7 @@ extern android_app* gAndroidApp;
 #endif
 #endif
 
 
 // Forward
 // Forward
+class CoreTracer;
 class ConfigSet;
 class ConfigSet;
 class ThreadHive;
 class ThreadHive;
 class NativeWindow;
 class NativeWindow;
@@ -166,6 +167,9 @@ private:
 	HeapAllocator<U8> m_heapAlloc;
 	HeapAllocator<U8> m_heapAlloc;
 
 
 	// Sybsystems
 	// Sybsystems
+#if ANKI_ENABLE_TRACE
+	CoreTracer* m_coreTracer = nullptr;
+#endif
 	NativeWindow* m_window = nullptr;
 	NativeWindow* m_window = nullptr;
 	Input* m_input = nullptr;
 	Input* m_input = nullptr;
 	GrManager* m_gr = nullptr;
 	GrManager* m_gr = nullptr;

+ 1 - 1
src/anki/core/CMakeLists.txt

@@ -1,4 +1,4 @@
-set(SOURCES App.cpp Config.cpp StagingGpuMemoryManager.cpp DeveloperConsole.cpp)
+set(SOURCES App.cpp Config.cpp StagingGpuMemoryManager.cpp DeveloperConsole.cpp CoreTracer.cpp)
 
 
 if(SDL)
 if(SDL)
 	set(SOURCES ${SOURCES} NativeWindowSdl.cpp)
 	set(SOURCES ${SOURCES} NativeWindowSdl.cpp)

+ 397 - 0
src/anki/core/CoreTracer.cpp

@@ -0,0 +1,397 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/core/CoreTracer.h>
+#include <anki/util/DynamicArray.h>
+#include <anki/util/Tracer.h>
+#include <anki/math/Functions.h>
+#include <ctime>
+
+namespace anki
+{
+
+static void getSpreadsheetColumnName(U32 column, Array<char, 3>& arr)
+{
+	U32 major = column / 26;
+	U32 minor = column % 26;
+
+	if(major)
+	{
+		arr[0] = char('A' + (major - 1));
+		arr[1] = char('A' + minor);
+	}
+	else
+	{
+		arr[0] = char('A' + minor);
+		arr[1] = '\0';
+	}
+
+	arr[2] = '\0';
+}
+
+class CoreTracer::ThreadWorkItem : public IntrusiveListEnabled<ThreadWorkItem>
+{
+public:
+	DynamicArrayAuto<TracerEvent> m_events;
+	DynamicArrayAuto<TracerCounter> m_counters;
+	ThreadId m_tid;
+	U64 m_frame;
+
+	ThreadWorkItem(GenericMemoryPoolAllocator<U8>& alloc)
+		: m_events(alloc)
+		, m_counters(alloc)
+	{
+	}
+};
+
+class CoreTracer::PerFrameCounters : public IntrusiveListEnabled<PerFrameCounters>
+{
+public:
+	DynamicArrayAuto<TracerCounter> m_counters;
+	U64 m_frame;
+
+	PerFrameCounters(GenericMemoryPoolAllocator<U8>& alloc)
+		: m_counters(alloc)
+	{
+	}
+};
+
+CoreTracer::CoreTracer()
+	: m_thread("Tracer")
+{
+}
+
+CoreTracer::~CoreTracer()
+{
+	// Stop thread
+	{
+		LockGuard<Mutex> lock(m_mtx);
+		m_quit = true;
+		m_cvar.notifyOne();
+	}
+	Error err = m_thread.join();
+	(void)err;
+
+	// Finalize trace file
+	if(m_traceJsonFile.isOpen())
+	{
+		err = m_traceJsonFile.writeText("{}\n]\n");
+	}
+
+	// Write counter file
+	err = writeCountersForReal();
+
+	// Cleanup
+	while(!m_frameCounters.isEmpty())
+	{
+		PerFrameCounters* frame = m_frameCounters.popBack();
+		m_alloc.deleteInstance(frame);
+	}
+
+	while(!m_workItems.isEmpty())
+	{
+		ThreadWorkItem* item = m_workItems.popBack();
+		m_alloc.deleteInstance(item);
+	}
+
+	for(String& s : m_counterNames)
+	{
+		s.destroy(m_alloc);
+	}
+	m_counterNames.destroy(m_alloc);
+
+	// Destroy the tracer
+	TracerSingleton::destroy();
+}
+
+Error CoreTracer::init(GenericMemoryPoolAllocator<U8> alloc, CString directory)
+{
+	TracerSingleton::init(alloc);
+
+	m_alloc = alloc;
+	m_thread.start(this,
+		[](ThreadCallbackInfo& info) -> Error { return static_cast<CoreTracer*>(info.m_userData)->threadWorker(); });
+
+	std::time_t t = std::time(nullptr);
+	std::tm* tm = std::localtime(&t);
+	StringAuto fname(m_alloc);
+	fname.sprintf("%s/%d%02d%02d-%02d%02d_",
+		directory.cstr(),
+		tm->tm_year + 1900,
+		tm->tm_mon + 1,
+		tm->tm_mday,
+		tm->tm_hour,
+		tm->tm_min);
+
+	ANKI_CHECK(m_traceJsonFile.open(StringAuto(alloc).sprintf("%strace.json", fname.cstr()), FileOpenFlag::WRITE));
+	ANKI_CHECK(m_traceJsonFile.writeText("[\n"));
+
+	ANKI_CHECK(m_countersCsvFile.open(StringAuto(alloc).sprintf("%scounters.csv", fname.cstr()), FileOpenFlag::WRITE));
+
+	return Error::NONE;
+}
+
+Error CoreTracer::threadWorker()
+{
+	Error err = Error::NONE;
+	Bool quit = false;
+
+	while(!err && !quit)
+	{
+		ThreadWorkItem* item = nullptr;
+
+		// Get some work
+		{
+			// Wait for something
+			LockGuard<Mutex> lock(m_mtx);
+			while(m_workItems.isEmpty() && !m_quit)
+			{
+				m_cvar.wait(m_mtx);
+			}
+
+			// Get some work
+			if(!m_workItems.isEmpty())
+			{
+				item = m_workItems.popFront();
+			}
+			else if(m_quit)
+			{
+				quit = true;
+			}
+		}
+
+		// Do some work using the frame and delete it
+		if(item)
+		{
+			err = writeEvents(*item);
+
+			if(!err)
+			{
+				gatherCounters(*item);
+			}
+
+			m_alloc.deleteInstance(item);
+		}
+	}
+
+	return err;
+}
+
+Error CoreTracer::writeEvents(ThreadWorkItem& item)
+{
+	// First sort them to fix overlaping in chrome
+	std::sort(item.m_events.getBegin(), item.m_events.getEnd(), [](const TracerEvent& a, TracerEvent& b) {
+		return (a.m_start != b.m_start) ? a.m_start < b.m_start : a.m_duration > b.m_duration;
+	});
+
+	// Write events
+	for(const TracerEvent& event : item.m_events)
+	{
+		const I64 startMicroSec = I64(event.m_start * 1000000.0);
+		const I64 durMicroSec = I64(event.m_duration * 1000000.0);
+
+		// Do a hack
+		const ThreadId tid = (event.m_name == "GPU_TIME") ? 1 : item.m_tid;
+
+		ANKI_CHECK(m_traceJsonFile.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"X\", "
+											 "\"pid\": 1, \"tid\": %llu, \"ts\": %lld, \"dur\": %lld},\n",
+			event.m_name.cstr(),
+			tid,
+			startMicroSec,
+			durMicroSec));
+	}
+
+	// Store counters
+	// TODO
+
+	return Error::NONE;
+}
+
+void CoreTracer::gatherCounters(ThreadWorkItem& item)
+{
+	// Sort
+	std::sort(item.m_counters.getBegin(), item.m_counters.getEnd(), [](const TracerCounter& a, const TracerCounter& b) {
+		return a.m_name < b.m_name;
+	});
+
+	// Merge same
+	DynamicArrayAuto<TracerCounter> mergedCounters(m_alloc);
+	for(U32 i = 0; i < item.m_counters.getSize(); ++i)
+	{
+		if(mergedCounters.getSize() == 0 || mergedCounters.getBack().m_name != item.m_counters[i].m_name)
+		{
+			// New
+			mergedCounters.emplaceBack(item.m_counters[i]);
+		}
+		else
+		{
+			// Merge
+			mergedCounters.getBack().m_value += item.m_counters[i].m_name;
+		}
+	}
+	ANKI_ASSERT(mergedCounters.getSize() > 0 && mergedCounters.getSize() <= item.m_counters.getSize());
+
+	// Add missing counter names
+	Bool addedCounterName = false;
+	for(U32 i = 0; i < mergedCounters.getSize(); ++i)
+	{
+		const TracerCounter& counter = mergedCounters[i];
+
+		Bool found = false;
+		for(const String& name : m_counterNames)
+		{
+			if(name == counter.m_name)
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if(!found)
+		{
+			m_counterNames.emplaceBack(m_alloc, m_alloc, counter.m_name);
+			addedCounterName = true;
+		}
+	}
+
+	if(addedCounterName)
+	{
+		std::sort(m_counterNames.getBegin(), m_counterNames.getEnd());
+	}
+
+	// Get a per-frame structure
+	if(m_frameCounters.isEmpty() || m_frameCounters.getBack().m_frame != item.m_frame)
+	{
+		// Create new frame
+		PerFrameCounters* newPerFrame = m_alloc.newInstance<PerFrameCounters>(m_alloc);
+		newPerFrame->m_counters = std::move(mergedCounters);
+		newPerFrame->m_frame = item.m_frame;
+		m_frameCounters.pushBack(newPerFrame);
+	}
+	else
+	{
+		// Merge counters to existing frame
+		PerFrameCounters& frame = m_frameCounters.getBack();
+		ANKI_ASSERT(frame.m_frame == item.m_frame);
+		for(const TracerCounter& newCounter : mergedCounters)
+		{
+			Bool found = false;
+			for(TracerCounter& existingCounter : frame.m_counters)
+			{
+				if(newCounter.m_name == existingCounter.m_name)
+				{
+					existingCounter.m_value += newCounter.m_value;
+					found = true;
+					break;
+				}
+			}
+
+			if(!found)
+			{
+				frame.m_counters.emplaceBack(newCounter);
+			}
+		}
+	}
+}
+
+void CoreTracer::flushFrame(U64 frame)
+{
+	struct Ctx
+	{
+		U64 m_frame;
+		CoreTracer* m_self;
+	};
+
+	Ctx ctx;
+	ctx.m_frame = frame;
+	ctx.m_self = this;
+
+	TracerSingleton::get().flush(
+		[](void* ud, ThreadId tid, ConstWeakArray<TracerEvent> events, ConstWeakArray<TracerCounter> counters) {
+			Ctx& ctx = *static_cast<Ctx*>(ud);
+			CoreTracer& self = *ctx.m_self;
+
+			ThreadWorkItem* item = self.m_alloc.newInstance<ThreadWorkItem>(self.m_alloc);
+			item->m_tid = tid;
+			item->m_frame = ctx.m_frame;
+
+			if(events.getSize() > 0)
+			{
+				item->m_events.create(events.getSize());
+				memcpy(&item->m_events[0], &events[0], events.getSizeInBytes());
+			}
+
+			if(counters.getSize() > 0)
+			{
+				item->m_counters.create(counters.getSize());
+				memcpy(&item->m_counters[0], &counters[0], counters.getSizeInBytes());
+			}
+
+			LockGuard<Mutex> lock(self.m_mtx);
+			self.m_workItems.pushBack(item);
+			self.m_cvar.notifyOne();
+		},
+		&ctx);
+}
+
+Error CoreTracer::writeCountersForReal()
+{
+	if(!m_countersCsvFile.isOpen() || m_frameCounters.getSize() == 0)
+	{
+		return Error::NONE;
+	}
+
+	// Write the header
+	ANKI_CHECK(m_countersCsvFile.writeText("Frame"));
+	for(U32 i = 0; i < m_counterNames.getSize(); ++i)
+	{
+		ANKI_CHECK(m_countersCsvFile.writeText(",%s", m_counterNames[i].cstr()));
+	}
+	ANKI_CHECK(m_countersCsvFile.writeText("\n"));
+
+	// Write each frame
+	for(const PerFrameCounters& frame : m_frameCounters)
+	{
+		ANKI_CHECK(m_countersCsvFile.writeText("%llu", frame.m_frame));
+
+		for(U32 j = 0; j < m_counterNames.getSize(); ++j)
+		{
+			// Find value
+			U64 value = 0;
+			for(const TracerCounter& counter : frame.m_counters)
+			{
+				if(counter.m_name == m_counterNames[j])
+				{
+					value = counter.m_value;
+					break;
+				}
+			}
+
+			ANKI_CHECK(m_countersCsvFile.writeText(",%llu", value));
+		}
+
+		ANKI_CHECK(m_countersCsvFile.writeText("\n"));
+	}
+
+	// Write some statistics
+	Array<const char*, 2> funcs = {{"SUM", "AVERAGE"}};
+	for(const char* func : funcs)
+	{
+		ANKI_CHECK(m_countersCsvFile.writeText(func));
+		for(U32 i = 0; i < m_frameCounters.getSize(); ++i)
+		{
+			Array<char, 3> columnName;
+			getSpreadsheetColumnName(i + 1, columnName);
+			ANKI_CHECK(m_countersCsvFile.writeText(
+				",=%s(%s2:%s%u)", func, &columnName[0], &columnName[0], m_frameCounters.getSize() + 1));
+		}
+
+		ANKI_CHECK(m_countersCsvFile.writeText("\n"));
+	}
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 60 - 0
src/anki/core/CoreTracer.h

@@ -0,0 +1,60 @@
+// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/core/Common.h>
+#include <anki/util/Thread.h>
+#include <anki/util/Allocator.h>
+#include <anki/util/List.h>
+#include <anki/util/File.h>
+
+namespace anki
+{
+
+/// @addtogroup core
+/// @{
+
+/// A system that sits on top of the tracer and processes the counters and events.
+class CoreTracer
+{
+public:
+	CoreTracer();
+
+	~CoreTracer();
+
+	/// @param directory The directory to store the trace and counters.
+	ANKI_USE_RESULT Error init(GenericMemoryPoolAllocator<U8> alloc, CString directory);
+
+	/// It will flush everything.
+	void flushFrame(U64 frame);
+
+private:
+	class ThreadWorkItem;
+	class PerFrameCounters;
+
+	GenericMemoryPoolAllocator<U8> m_alloc;
+
+	Thread m_thread;
+	ConditionVariable m_cvar;
+	Mutex m_mtx;
+
+	DynamicArray<String> m_counterNames;
+	IntrusiveList<PerFrameCounters> m_frameCounters;
+
+	IntrusiveList<ThreadWorkItem> m_workItems; ///< Items for the thread to process.
+	File m_traceJsonFile;
+	File m_countersCsvFile;
+	Bool m_quit = false;
+
+	Error threadWorker();
+
+	Error writeEvents(ThreadWorkItem& item);
+	void gatherCounters(ThreadWorkItem& item);
+	Error writeCountersForReal();
+};
+/// @}
+
+} // end namespace anki

+ 1 - 1
src/anki/core/StagingGpuMemoryManager.cpp

@@ -6,7 +6,7 @@
 #include <anki/core/StagingGpuMemoryManager.h>
 #include <anki/core/StagingGpuMemoryManager.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/gr/GrManager.h>
 #include <anki/gr/GrManager.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 0
src/anki/core/StagingGpuMemoryManager.h

@@ -5,6 +5,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <anki/core/Common.h>
 #include <anki/gr/Buffer.h>
 #include <anki/gr/Buffer.h>
 #include <anki/gr/common/FrameGpuAllocator.h>
 #include <anki/gr/common/FrameGpuAllocator.h>
 
 

+ 0 - 119
src/anki/core/Trace.h

@@ -1,119 +0,0 @@
-// Copyright (C) 2009-2019, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma once
-
-#include <anki/core/Common.h>
-#include <anki/util/Tracer.h>
-
-namespace anki
-{
-
-/// @addtogroup core
-/// @{
-
-/// Core tracer.
-class CoreTracer
-{
-public:
-	Tracer m_tracer;
-	Bool m_enabled = false;
-
-	/// @copydoc Tracer::init
-	void init(GenericMemoryPoolAllocator<U8> alloc)
-	{
-		m_tracer.init(alloc);
-	}
-
-	/// @copydoc Tracer::isInitialized
-	Bool isInitialized() const
-	{
-		return m_tracer.isInitialized();
-	}
-
-	/// @copydoc Tracer::beginEvent
-	ANKI_USE_RESULT TracerEventHandle beginEvent()
-	{
-		if(m_enabled)
-		{
-			return m_tracer.beginEvent();
-		}
-
-		return nullptr;
-	}
-
-	/// @copydoc Tracer::endEvent
-	void endEvent(const char* eventName, TracerEventHandle event)
-	{
-		if(event != nullptr)
-		{
-			m_tracer.endEvent(eventName, event);
-		}
-	}
-
-	/// @copydoc Tracer::increaseCounter
-	void increaseCounter(const char* counterName, U64 value)
-	{
-		if(m_enabled)
-		{
-			m_tracer.increaseCounter(counterName, value);
-		}
-	}
-
-	/// @copydoc Tracer::newFrame
-	void newFrame(U64 frame)
-	{
-		if(m_enabled)
-		{
-			m_tracer.newFrame(frame);
-		}
-	}
-
-	/// @copydoc Tracer::flush
-	ANKI_USE_RESULT Error flush(CString filename)
-	{
-		return m_tracer.flush(filename);
-	}
-};
-
-using CoreTracerSingleton = Singleton<CoreTracer>;
-
-class CoreTraceScopedEvent
-{
-public:
-	CoreTraceScopedEvent(const char* name)
-		: m_name(name)
-		, m_tracer(&CoreTracerSingleton::get())
-	{
-		m_handle = m_tracer->beginEvent();
-	}
-
-	~CoreTraceScopedEvent()
-	{
-		m_tracer->endEvent(m_name, m_handle);
-	}
-
-private:
-	const char* m_name;
-	TracerEventHandle m_handle;
-	CoreTracer* m_tracer;
-};
-
-/// @name Trace macros.
-/// @{
-#if ANKI_ENABLE_TRACE
-#	define ANKI_TRACE_START_EVENT(name_) TracerEventHandle _teh##name_ = CoreTracerSingleton::get().beginEvent()
-#	define ANKI_TRACE_STOP_EVENT(name_) CoreTracerSingleton::get().endEvent(#	name_, _teh##name_)
-#	define ANKI_TRACE_SCOPED_EVENT(name_) CoreTraceScopedEvent _tse##name_(#	name_)
-#	define ANKI_TRACE_INC_COUNTER(name_, val_) CoreTracerSingleton::get().increaseCounter(#	name_, val_)
-#else
-#	define ANKI_TRACE_START_EVENT(name_) ((void)0)
-#	define ANKI_TRACE_STOP_EVENT(name_) ((void)0)
-#	define ANKI_TRACE_SCOPED_EVENT(name_) ((void)0)
-#	define ANKI_TRACE_INC_COUNTER(name_, val_) ((void)0)
-#endif
-/// @}
-
-} // end namespace anki

+ 6 - 2
src/anki/gr/RenderGraph.cpp

@@ -9,10 +9,11 @@
 #include <anki/gr/Sampler.h>
 #include <anki/gr/Sampler.h>
 #include <anki/gr/Framebuffer.h>
 #include <anki/gr/Framebuffer.h>
 #include <anki/gr/CommandBuffer.h>
 #include <anki/gr/CommandBuffer.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/util/BitSet.h>
 #include <anki/util/BitSet.h>
 #include <anki/util/File.h>
 #include <anki/util/File.h>
 #include <anki/util/StringList.h>
 #include <anki/util/StringList.h>
+#include <anki/util/HighRezTimer.h>
 
 
 namespace anki
 namespace anki
 {
 {
@@ -1215,6 +1216,7 @@ void RenderGraph::flush()
 			m_ctx->m_graphicsCmdbs[i]->writeTimestamp(query);
 			m_ctx->m_graphicsCmdbs[i]->writeTimestamp(query);
 
 
 			m_statistics.m_timestamps[m_statistics.m_nextTimestamp * 2 + 1] = query;
 			m_statistics.m_timestamps[m_statistics.m_nextTimestamp * 2 + 1] = query;
+			m_statistics.m_cpuStartTimes[m_statistics.m_nextTimestamp] = HighRezTimer::getCurrentTime();
 		}
 		}
 
 
 		// Flush
 		// Flush
@@ -1281,7 +1283,7 @@ void RenderGraph::periodicCleanup()
 
 
 void RenderGraph::getStatistics(RenderGraphStatistics& statistics) const
 void RenderGraph::getStatistics(RenderGraphStatistics& statistics) const
 {
 {
-	const U oldFrame = (m_statistics.m_nextTimestamp + 1) % MAX_TIMESTAMPS_BUFFERED;
+	const U32 oldFrame = (m_statistics.m_nextTimestamp + 1) % MAX_TIMESTAMPS_BUFFERED;
 
 
 	if(m_statistics.m_timestamps[oldFrame * 2] && m_statistics.m_timestamps[oldFrame * 2 + 1])
 	if(m_statistics.m_timestamps[oldFrame * 2] && m_statistics.m_timestamps[oldFrame * 2 + 1])
 	{
 	{
@@ -1294,10 +1296,12 @@ void RenderGraph::getStatistics(RenderGraphStatistics& statistics) const
 
 
 		const Second diff = end - start;
 		const Second diff = end - start;
 		statistics.m_gpuTime = diff;
 		statistics.m_gpuTime = diff;
+		statistics.m_cpuStartTime = m_statistics.m_cpuStartTimes[oldFrame];
 	}
 	}
 	else
 	else
 	{
 	{
 		statistics.m_gpuTime = -1.0;
 		statistics.m_gpuTime = -1.0;
+		statistics.m_cpuStartTime = -1.0;
 	}
 	}
 }
 }
 
 

+ 2 - 0
src/anki/gr/RenderGraph.h

@@ -524,6 +524,7 @@ class RenderGraphStatistics
 {
 {
 public:
 public:
 	Second m_gpuTime; ///< Time spent in the GPU.
 	Second m_gpuTime; ///< Time spent in the GPU.
+	Second m_cpuStartTime; ///< Time the work was submited from the CPU (almost)
 };
 };
 
 
 /// Accepts a descriptor of the frame's render passes and sets the dependencies between them.
 /// Accepts a descriptor of the frame's render passes and sets the dependencies between them.
@@ -621,6 +622,7 @@ private:
 	{
 	{
 	public:
 	public:
 		Array<TimestampQueryPtr, MAX_TIMESTAMPS_BUFFERED * 2> m_timestamps;
 		Array<TimestampQueryPtr, MAX_TIMESTAMPS_BUFFERED * 2> m_timestamps;
+		Array<Second, MAX_TIMESTAMPS_BUFFERED> m_cpuStartTimes;
 		U8 m_nextTimestamp = 0;
 		U8 m_nextTimestamp = 0;
 	} m_statistics;
 	} m_statistics;
 
 

+ 2 - 2
src/anki/gr/ShaderCompiler.cpp

@@ -5,10 +5,10 @@
 
 
 #include <anki/gr/ShaderCompiler.h>
 #include <anki/gr/ShaderCompiler.h>
 #include <anki/gr/GrManager.h>
 #include <anki/gr/GrManager.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/util/StringList.h>
 #include <anki/util/StringList.h>
 #include <anki/util/Filesystem.h>
 #include <anki/util/Filesystem.h>
-#include <anki/core/Trace.h>
+#include <anki/util/File.h>
 
 
 #if ANKI_COMPILER_GCC_COMPATIBLE
 #if ANKI_COMPILER_GCC_COMPATIBLE
 #	pragma GCC diagnostic push
 #	pragma GCC diagnostic push

+ 1 - 1
src/anki/gr/vulkan/CommandBufferFactory.cpp

@@ -4,7 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <anki/gr/vulkan/CommandBufferFactory.h>
 #include <anki/gr/vulkan/CommandBufferFactory.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/gr/vulkan/CommandBufferImpl.inl.h

@@ -9,7 +9,7 @@
 #include <anki/gr/vulkan/OcclusionQueryImpl.h>
 #include <anki/gr/vulkan/OcclusionQueryImpl.h>
 #include <anki/gr/TimestampQuery.h>
 #include <anki/gr/TimestampQuery.h>
 #include <anki/gr/vulkan/TimestampQueryImpl.h>
 #include <anki/gr/vulkan/TimestampQueryImpl.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/gr/vulkan/DescriptorSet.cpp

@@ -8,7 +8,7 @@
 #include <anki/gr/vulkan/BufferImpl.h>
 #include <anki/gr/vulkan/BufferImpl.h>
 #include <anki/util/List.h>
 #include <anki/util/List.h>
 #include <anki/util/HashMap.h>
 #include <anki/util/HashMap.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <algorithm>
 #include <algorithm>
 
 
 namespace anki
 namespace anki

+ 1 - 1
src/anki/gr/vulkan/FenceFactory.inl.h

@@ -4,7 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <anki/gr/vulkan/FenceFactory.h>
 #include <anki/gr/vulkan/FenceFactory.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 0
src/anki/gr/vulkan/GrManagerImpl.h

@@ -20,6 +20,7 @@
 #include <anki/gr/vulkan/PipelineCache.h>
 #include <anki/gr/vulkan/PipelineCache.h>
 #include <anki/gr/vulkan/DescriptorSet.h>
 #include <anki/gr/vulkan/DescriptorSet.h>
 #include <anki/util/HashMap.h>
 #include <anki/util/HashMap.h>
+#include <anki/util/File.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/gr/vulkan/Pipeline.cpp

@@ -6,7 +6,7 @@
 #include <anki/gr/vulkan/Pipeline.h>
 #include <anki/gr/vulkan/Pipeline.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
 #include <anki/gr/common/Misc.h>
 #include <anki/gr/common/Misc.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/gr/vulkan/SemaphoreFactory.inl.h

@@ -4,7 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <anki/gr/vulkan/SemaphoreFactory.h>
 #include <anki/gr/vulkan/SemaphoreFactory.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/renderer/ClusterBin.cpp

@@ -7,8 +7,8 @@
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/Collision.h>
 #include <anki/Collision.h>
 #include <anki/util/ThreadHive.h>
 #include <anki/util/ThreadHive.h>
+#include <anki/util/Tracer.h>
 #include <anki/core/Config.h>
 #include <anki/core/Config.h>
-#include <anki/core/Trace.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/renderer/Drawer.cpp

@@ -8,7 +8,7 @@
 #include <anki/resource/MaterialResource.h>
 #include <anki/resource/MaterialResource.h>
 #include <anki/resource/TextureResource.h>
 #include <anki/resource/TextureResource.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/Renderer.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/util/Logger.h>
 #include <anki/util/Logger.h>
 
 
 namespace anki
 namespace anki

+ 1 - 1
src/anki/renderer/GBuffer.cpp

@@ -8,8 +8,8 @@
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/LensFlare.h>
 #include <anki/renderer/LensFlare.h>
 #include <anki/util/Logger.h>
 #include <anki/util/Logger.h>
+#include <anki/util/Tracer.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
-#include <anki/core/Trace.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/renderer/GlobalIllumination.cpp

@@ -7,7 +7,7 @@
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/collision/Aabb.h>
 #include <anki/collision/Aabb.h>
 #include <anki/collision/Functions.h>
 #include <anki/collision/Functions.h>
 
 

+ 3 - 3
src/anki/renderer/MainRenderer.cpp

@@ -12,8 +12,7 @@
 #include <anki/util/Logger.h>
 #include <anki/util/Logger.h>
 #include <anki/util/File.h>
 #include <anki/util/File.h>
 #include <anki/util/Filesystem.h>
 #include <anki/util/Filesystem.h>
-#include <anki/core/Trace.h>
-#include <anki/core/App.h>
+#include <anki/util/Tracer.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/ThreadHive.h>
 #include <anki/util/ThreadHive.h>
@@ -188,11 +187,12 @@ Error MainRenderer::render(RenderQueue& rqueue, TexturePtr presentTex)
 	if(m_statsEnabled)
 	if(m_statsEnabled)
 	{
 	{
 		static_cast<RendererStats&>(m_stats) = m_r->getStats();
 		static_cast<RendererStats&>(m_stats) = m_r->getStats();
-		m_stats.m_renderingCpuTime = HighRezTimer::getCurrentTime() - m_stats.m_renderingGpuTime;
+		m_stats.m_renderingCpuTime = HighRezTimer::getCurrentTime() - m_stats.m_renderingCpuTime;
 
 
 		RenderGraphStatistics rgraphStats;
 		RenderGraphStatistics rgraphStats;
 		m_rgraph->getStatistics(rgraphStats);
 		m_rgraph->getStatistics(rgraphStats);
 		m_stats.m_renderingGpuTime = rgraphStats.m_gpuTime;
 		m_stats.m_renderingGpuTime = rgraphStats.m_gpuTime;
+		m_stats.m_renderingGpuSubmitTimestamp = rgraphStats.m_cpuStartTime;
 	}
 	}
 
 
 	return Error::NONE;
 	return Error::NONE;

+ 1 - 0
src/anki/renderer/MainRenderer.h

@@ -27,6 +27,7 @@ class MainRendererStats : public RendererStats
 public:
 public:
 	Second m_renderingCpuTime ANKI_DEBUG_CODE(= -1.0);
 	Second m_renderingCpuTime ANKI_DEBUG_CODE(= -1.0);
 	Second m_renderingGpuTime ANKI_DEBUG_CODE(= -1.0);
 	Second m_renderingGpuTime ANKI_DEBUG_CODE(= -1.0);
+	Second m_renderingGpuSubmitTimestamp ANKI_DEBUG_CODE(= -1.0);
 };
 };
 
 
 /// Main onscreen renderer
 /// Main onscreen renderer

+ 1 - 1
src/anki/renderer/ProbeReflections.cpp

@@ -9,7 +9,7 @@
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/core/Config.h>
 #include <anki/core/Config.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/resource/MeshResource.h>
 #include <anki/resource/MeshResource.h>
 #include <shaders/glsl_cpp_common/TraditionalDeferredShading.h>
 #include <shaders/glsl_cpp_common/TraditionalDeferredShading.h>
 
 

+ 1 - 1
src/anki/renderer/Renderer.cpp

@@ -5,7 +5,7 @@
 
 
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/collision/Aabb.h>
 #include <anki/collision/Aabb.h>

+ 1 - 2
src/anki/renderer/ShadowMapping.cpp

@@ -6,10 +6,9 @@
 #include <anki/renderer/ShadowMapping.h>
 #include <anki/renderer/ShadowMapping.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/Renderer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
-#include <anki/core/App.h>
-#include <anki/core/Trace.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/util/ThreadHive.h>
 #include <anki/util/ThreadHive.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/renderer/UiStage.cpp

@@ -8,7 +8,7 @@
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/ui/Font.h>
 #include <anki/ui/Font.h>
 #include <anki/ui/UiManager.h>
 #include <anki/ui/UiManager.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/resource/AsyncLoader.cpp

@@ -5,7 +5,7 @@
 
 
 #include <anki/resource/AsyncLoader.h>
 #include <anki/resource/AsyncLoader.h>
 #include <anki/util/Logger.h>
 #include <anki/util/Logger.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/resource/ResourceFilesystem.cpp

@@ -6,7 +6,7 @@
 #include <anki/resource/ResourceFilesystem.h>
 #include <anki/resource/ResourceFilesystem.h>
 #include <anki/util/Filesystem.h>
 #include <anki/util/Filesystem.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <contrib/minizip/unzip.h>
 #include <contrib/minizip/unzip.h>
 
 
 namespace anki
 namespace anki

+ 1 - 1
src/anki/resource/TransferGpuAllocator.cpp

@@ -7,7 +7,7 @@
 #include <anki/gr/Fence.h>
 #include <anki/gr/Fence.h>
 #include <anki/gr/Buffer.h>
 #include <anki/gr/Buffer.h>
 #include <anki/gr/GrManager.h>
 #include <anki/gr/GrManager.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/scene/Octree.h

@@ -12,7 +12,7 @@
 #include <anki/util/Enum.h>
 #include <anki/util/Enum.h>
 #include <anki/util/ObjectAllocator.h>
 #include <anki/util/ObjectAllocator.h>
 #include <anki/util/List.h>
 #include <anki/util/List.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/scene/SceneGraph.cpp

@@ -9,12 +9,12 @@
 #include <anki/scene/ModelNode.h>
 #include <anki/scene/ModelNode.h>
 #include <anki/scene/Octree.h>
 #include <anki/scene/Octree.h>
 #include <anki/scene/components/FrustumComponent.h>
 #include <anki/scene/components/FrustumComponent.h>
-#include <anki/core/Trace.h>
 #include <anki/physics/PhysicsWorld.h>
 #include <anki/physics/PhysicsWorld.h>
 #include <anki/resource/ResourceManager.h>
 #include <anki/resource/ResourceManager.h>
 #include <anki/renderer/MainRenderer.h>
 #include <anki/renderer/MainRenderer.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/misc/ConfigSet.h>
 #include <anki/util/ThreadHive.h>
 #include <anki/util/ThreadHive.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/scene/SoftwareRasterizer.cpp

@@ -6,7 +6,7 @@
 #include <anki/scene/SoftwareRasterizer.h>
 #include <anki/scene/SoftwareRasterizer.h>
 #include <anki/collision/Aabb.h>
 #include <anki/collision/Aabb.h>
 #include <anki/collision/Functions.h>
 #include <anki/collision/Functions.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 1 - 1
src/anki/scene/VisibilityInternal.h

@@ -10,7 +10,7 @@
 #include <anki/scene/components/FrustumComponent.h>
 #include <anki/scene/components/FrustumComponent.h>
 #include <anki/scene/Octree.h>
 #include <anki/scene/Octree.h>
 #include <anki/util/Thread.h>
 #include <anki/util/Thread.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/RenderQueue.h>
 
 
 namespace anki
 namespace anki

+ 1 - 1
src/anki/script/LuaBinder.cpp

@@ -5,7 +5,7 @@
 
 
 #include <anki/script/LuaBinder.h>
 #include <anki/script/LuaBinder.h>
 #include <anki/util/Logger.h>
 #include <anki/util/Logger.h>
-#include <anki/core/Trace.h>
+#include <anki/util/Tracer.h>
 
 
 namespace anki
 namespace anki
 {
 {

+ 2 - 1
src/anki/util/CMakeLists.txt

@@ -1,4 +1,5 @@
-set(SOURCES Assert.cpp Functions.cpp File.cpp Filesystem.cpp Memory.cpp System.cpp HighRezTimer.cpp ThreadPool.cpp ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp)
+set(SOURCES Assert.cpp Functions.cpp File.cpp Filesystem.cpp Memory.cpp System.cpp HighRezTimer.cpp ThreadPool.cpp
+	ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp)
 
 
 if(LINUX OR ANDROID OR MACOS)
 if(LINUX OR ANDROID OR MACOS)
 	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp)
 	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp)

+ 21 - 8
src/anki/util/HighRezTimerWindows.cpp

@@ -5,6 +5,7 @@
 
 
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/Assert.h>
 #include <anki/util/Assert.h>
+#include <cstdio>
 #include <Windows.h>
 #include <Windows.h>
 
 
 namespace anki
 namespace anki
@@ -17,11 +18,18 @@ namespace
 class DummyInitTimer
 class DummyInitTimer
 {
 {
 public:
 public:
-	DWORD m_start;
+	LARGE_INTEGER m_start;
+	LARGE_INTEGER m_ticksPerSec;
 
 
 	DummyInitTimer()
 	DummyInitTimer()
 	{
 	{
-		m_start = GetTickCount();
+		if (!QueryPerformanceFrequency(&m_ticksPerSec))
+		{
+			fprintf(stderr, "QueryPerformanceFrequency() failed\n");
+			abort();
+		}
+
+		QueryPerformanceCounter(&m_start);
 	}
 	}
 };
 };
 
 
@@ -29,22 +37,27 @@ DummyInitTimer init;
 
 
 } // end namespace anonymous
 } // end namespace anonymous
 
 
-static U32 getMs()
+static U64 getMs()
 {
 {
-	DWORD now = GetTickCount();
-	return now - init.m_start;
+	LARGE_INTEGER now;
+	QueryPerformanceCounter(&now);
+
+	now.QuadPart -= init.m_start.QuadPart;
+	now.QuadPart *= 1000;
+	now.QuadPart /= init.m_ticksPerSec.QuadPart;
+
+	return now.QuadPart;
 }
 }
 
 
 void HighRezTimer::sleep(Second sec)
 void HighRezTimer::sleep(Second sec)
 {
 {
-	U32 ms = static_cast<U32>(sec * 1000.0);
-	Sleep(ms);
+	Sleep(U32(sec * 1000.0));
 }
 }
 
 
 Second HighRezTimer::getCurrentTime()
 Second HighRezTimer::getCurrentTime()
 {
 {
 	// Second(ticks) / 1000.0
 	// Second(ticks) / 1000.0
-	return static_cast<Second>(getMs()) * 0.001;
+	return Second(getMs()) * 0.001;
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 1 - 1
src/anki/util/Singleton.h

@@ -71,7 +71,7 @@ public:
 	static void init(TArgs... args)
 	static void init(TArgs... args)
 	{
 	{
 		ANKI_ASSERT(m_instance == nullptr);
 		ANKI_ASSERT(m_instance == nullptr);
-		m_instance = new Value(args...);
+		m_instance = new Value(std::forward<TArgs>(args)...);
 	}
 	}
 
 
 	/// Get instance
 	/// Get instance

+ 94 - 467
src/anki/util/Tracer.cpp

@@ -6,145 +6,43 @@
 #include <anki/util/Tracer.h>
 #include <anki/util/Tracer.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HashMap.h>
 #include <anki/util/HashMap.h>
+#include <anki/util/List.h>
 
 
 namespace anki
 namespace anki
 {
 {
 
 
-/// Lightweight event storage.
-class Tracer::Event
+class Tracer::Chunk : public IntrusiveListEnabled<Chunk>
 {
 {
 public:
 public:
-	const char* m_name;
-	Second m_timestamp;
-	Second m_duration;
-};
-
-/// Event batch allocation.
-class Tracer::EventsChunk : public IntrusiveListEnabled<EventsChunk>
-{
-public:
-	Array<Event, EVENTS_PER_CHUNK> m_events;
+	Array<TracerEvent, EVENTS_PER_CHUNK> m_events;
 	U32 m_eventCount = 0;
 	U32 m_eventCount = 0;
-};
-
-/// A heavyweight event with more info.
-class Tracer::GatherEvent
-{
-public:
-	CString m_name;
-	Second m_timestamp;
-	Second m_duration;
-	ThreadId m_tid;
-};
-
-/// Lightweight counter storage.
-class Tracer::Counter
-{
-public:
-	const char* m_name;
-	U64 m_value;
-};
-
-/// Counter batch allocation.
-class Tracer::CountersChunk : public IntrusiveListEnabled<CountersChunk>
-{
-public:
-	U64 m_frame;
-	Second m_startFrameTime;
-	Array<Counter, COUNTERS_PER_CHUNK> m_counters;
+	Array<TracerCounter, COUNTERS_PER_CHUNK> m_counters;
 	U32 m_counterCount = 0;
 	U32 m_counterCount = 0;
 };
 };
 
 
-/// Heavyweight counter storage.
-class Tracer::GatherCounter
-{
-public:
-	CString m_name;
-	U64 m_value;
-};
-
 /// Thread local storage.
 /// Thread local storage.
-class Tracer::ThreadLocal
+class alignas(ANKI_CACHE_LINE_SIZE) Tracer::ThreadLocal
 {
 {
 public:
 public:
-	ThreadId m_tid ANKI_DEBUG_CODE(= 0);
+	ThreadId m_tid = 0;
 
 
-	IntrusiveList<CountersChunk> m_counterChunks;
-	IntrusiveList<EventsChunk> m_eventChunks;
+	Chunk* m_currentChunk = nullptr;
+	IntrusiveList<Chunk> m_allChunks;
+	SpinLock m_currentChunkLock;
 };
 };
 
 
 thread_local Tracer::ThreadLocal* Tracer::m_threadLocal = nullptr;
 thread_local Tracer::ThreadLocal* Tracer::m_threadLocal = nullptr;
 
 
-/// Storage of counters per frame.
-class Tracer::PerFrameCounters
-{
-public:
-	DynamicArrayAuto<GatherCounter> m_counters;
-	DynamicArrayAuto<GatherCounter> m_tempCounters; ///< A temp storage.
-	U64 m_frame;
-	Second m_startFrameTime;
-
-	PerFrameCounters(GenericMemoryPoolAllocator<U8> alloc)
-		: m_counters(alloc)
-		, m_tempCounters(alloc)
-	{
-	}
-};
-
-/// Context for Tracer::flush().
-class Tracer::FlushCtx
-{
-public:
-	GenericMemoryPoolAllocator<U8> m_alloc;
-	CString m_filename;
-	DynamicArrayAuto<CString> m_counterNames;
-	DynamicArrayAuto<PerFrameCounters> m_counters;
-	DynamicArrayAuto<GatherEvent> m_events;
-
-	FlushCtx(GenericMemoryPoolAllocator<U8> alloc, const CString& filename)
-		: m_alloc(alloc)
-		, m_filename(filename)
-		, m_counterNames(alloc)
-		, m_counters(alloc)
-		, m_events(alloc)
-	{
-	}
-};
-
 Tracer::~Tracer()
 Tracer::~Tracer()
 {
 {
-	for(ThreadLocal* threadLocal : m_allThreadLocal)
+	LockGuard<Mutex> lock(m_allThreadLocalMtx);
+	for(ThreadLocal* tlocal : m_allThreadLocal)
 	{
 	{
-		while(!threadLocal->m_counterChunks.isEmpty())
-		{
-			CountersChunk& chunk = threadLocal->m_counterChunks.getFront();
-			threadLocal->m_counterChunks.popFront();
-			m_alloc.deleteInstance(&chunk);
-		}
-
-		while(!threadLocal->m_eventChunks.isEmpty())
-		{
-			EventsChunk& chunk = threadLocal->m_eventChunks.getFront();
-			threadLocal->m_eventChunks.popFront();
-			m_alloc.deleteInstance(&chunk);
-		}
-
-		m_alloc.deleteInstance(threadLocal);
+		m_alloc.deleteInstance(tlocal);
 	}
 	}
-
 	m_allThreadLocal.destroy(m_alloc);
 	m_allThreadLocal.destroy(m_alloc);
 }
 }
 
 
-void Tracer::newFrame(U64 frame)
-{
-	ANKI_ASSERT(frame == 0 || frame > m_frame);
-
-	LockGuard<SpinLock> lock(m_frameMtx);
-
-	m_startFrameTime = HighRezTimer::getCurrentTime();
-	m_frame = frame;
-}
-
 Tracer::ThreadLocal& Tracer::getThreadLocal()
 Tracer::ThreadLocal& Tracer::getThreadLocal()
 {
 {
 	ThreadLocal* out = m_threadLocal;
 	ThreadLocal* out = m_threadLocal;
@@ -155,417 +53,146 @@ Tracer::ThreadLocal& Tracer::getThreadLocal()
 		m_threadLocal = out;
 		m_threadLocal = out;
 
 
 		// Store it
 		// Store it
-		LockGuard<Mutex> lock(m_threadLocalMtx);
+		LockGuard<Mutex> lock(m_allThreadLocalMtx);
 		m_allThreadLocal.emplaceBack(m_alloc, out);
 		m_allThreadLocal.emplaceBack(m_alloc, out);
 	}
 	}
 
 
 	return *out;
 	return *out;
 }
 }
 
 
-TracerEventHandle Tracer::beginEvent()
+Tracer::Chunk& Tracer::getOrCreateChunk(ThreadLocal& tlocal)
 {
 {
-	ThreadLocal& threadLocal = getThreadLocal();
+	Chunk* out;
 
 
-	// Allocate new chunk
-	if(threadLocal.m_eventChunks.isEmpty() || threadLocal.m_eventChunks.getBack().m_eventCount >= EVENTS_PER_CHUNK)
+	if(tlocal.m_currentChunk && tlocal.m_currentChunk->m_eventCount < EVENTS_PER_CHUNK
+		&& tlocal.m_currentChunk->m_counterCount < COUNTERS_PER_CHUNK)
 	{
 	{
-		EventsChunk* chunk = m_alloc.newInstance<EventsChunk>();
-		threadLocal.m_eventChunks.pushBack(chunk);
+		// There is a chunk and it has enough space
+		out = tlocal.m_currentChunk;
+	}
+	else
+	{
+		// Create a new
+		out = m_alloc.newInstance<Chunk>();
+		tlocal.m_currentChunk = out;
+		tlocal.m_allChunks.pushBack(out);
 	}
 	}
 
 
-	EventsChunk& chunk = threadLocal.m_eventChunks.getBack();
-	Event* event = &chunk.m_events[chunk.m_eventCount++];
-	event->m_timestamp = HighRezTimer::getCurrentTime();
-
-	return event;
-}
-
-void Tracer::endEvent(const char* eventName, TracerEventHandle eventHandle)
-{
-	ANKI_ASSERT(eventName);
-	ANKI_ASSERT(eventHandle);
-
-	Event* event = static_cast<Event*>(eventHandle);
-	event->m_name = eventName;
-	event->m_duration = HighRezTimer::getCurrentTime() - event->m_timestamp;
-
-	// Store a counter as well. In ns
-	increaseCounter(eventName, U64(event->m_duration * 1000000000.0));
+	return *out;
 }
 }
 
 
-void Tracer::increaseCounter(const char* counterName, U64 value)
+TracerEventHandle Tracer::beginEvent()
 {
 {
-	ANKI_ASSERT(counterName);
+	TracerEventHandle out;
 
 
-	ThreadLocal& threadLocal = getThreadLocal();
-
-	// Create chunk
-	if(threadLocal.m_counterChunks.isEmpty() || threadLocal.m_counterChunks.getBack().m_frame != m_frame
-		|| threadLocal.m_counterChunks.getBack().m_counterCount >= COUNTERS_PER_CHUNK)
+	if(m_enabled)
 	{
 	{
-		CountersChunk* newChunk = m_alloc.newInstance<CountersChunk>();
-		threadLocal.m_counterChunks.pushBack(newChunk);
-
-		{
-			LockGuard<SpinLock> lock(m_frameMtx);
-			newChunk->m_frame = m_frame;
-			newChunk->m_startFrameTime = m_startFrameTime;
-		}
+		out.m_start = HighRezTimer::getCurrentTime();
+	}
+	else
+	{
+		out.m_start = 0.0;
 	}
 	}
 
 
-	CountersChunk& chunk = threadLocal.m_counterChunks.getBack();
-
-	Counter& counter = chunk.m_counters[chunk.m_counterCount++];
-	counter.m_name = counterName;
-	counter.m_value = value;
+	return out;
 }
 }
 
 
-void Tracer::gatherCounters(FlushCtx& ctx)
+void Tracer::endEvent(const char* eventName, TracerEventHandle event)
 {
 {
-	// Iterate all the chunks and create the PerFrameCounters
-	for(ThreadLocal* threadLocal : m_allThreadLocal)
+	if(!m_enabled || event.m_start == 0.0)
 	{
 	{
-		while(!threadLocal->m_counterChunks.isEmpty())
-		{
-			// Pop chunk
-			CountersChunk& chunk = threadLocal->m_counterChunks.getFront();
-			threadLocal->m_counterChunks.popFront();
-
-			// Iterate the PerFrameCounters to find if the frame is present
-			PerFrameCounters* perFrame = nullptr;
-			for(PerFrameCounters& pf : ctx.m_counters)
-			{
-				if(pf.m_frame == chunk.m_frame)
-				{
-					perFrame = &pf;
-					break;
-				}
-			}
-
-			if(!perFrame)
-			{
-				ctx.m_counters.emplaceBack(m_alloc);
-
-				perFrame = &ctx.m_counters.getBack();
-				perFrame->m_frame = chunk.m_frame;
-				perFrame->m_startFrameTime = chunk.m_startFrameTime;
-			}
-
-			ANKI_ASSERT(chunk.m_frame == perFrame->m_frame);
-
-			// Copy the counters
-			for(U i = 0; i < chunk.m_counterCount; ++i)
-			{
-				const Counter& inCounter = chunk.m_counters[i];
-
-				GatherCounter outCounter;
-				outCounter.m_name = inCounter.m_name;
-				outCounter.m_value = inCounter.m_value;
-
-				perFrame->m_tempCounters.emplaceBack(outCounter);
-			}
-
-			// Delete chunk
-			m_alloc.deleteInstance(&chunk);
-		}
-	}
-
-	if(ctx.m_counters.getSize() == 0)
-	{
-		// Early exit
 		return;
 		return;
 	}
 	}
 
 
-	// Compact the counters and get all counter names
-	for(PerFrameCounters& perFrame : ctx.m_counters)
+	// Get the time before the lock and everything
+	const Second duration = HighRezTimer::getCurrentTime() - event.m_start;
+	if(duration == 0.0)
 	{
 	{
-		if(perFrame.m_tempCounters.getSize() == 0)
-		{
-			continue;
-		}
-
-		// Sort counters
-		std::sort(perFrame.m_tempCounters.getBegin(),
-			perFrame.m_tempCounters.getEnd(),
-			[](const GatherCounter& a, const GatherCounter& b) { return a.m_name < b.m_name; });
-
-		// Compact counters
-		for(const GatherCounter& tmpCounter : perFrame.m_tempCounters)
-		{
-			if(perFrame.m_counters.getSize() == 0 || perFrame.m_counters.getBack().m_name != tmpCounter.m_name)
-			{
-				// Create new counter
-				perFrame.m_counters.emplaceBack(tmpCounter);
-
-				// Update the counter names
-				Bool found = false;
-				for(const CString& counterName : ctx.m_counterNames)
-				{
-					if(counterName == tmpCounter.m_name)
-					{
-						found = true;
-						break;
-					}
-				}
-
-				if(!found)
-				{
-					ctx.m_counterNames.emplaceBack(tmpCounter.m_name);
-				}
-			}
-			else
-			{
-				// Merge counters
-				GatherCounter& mergeTo = perFrame.m_counters.getBack();
-				ANKI_ASSERT(mergeTo.m_name == tmpCounter.m_name);
-				mergeTo.m_value += tmpCounter.m_value;
-			}
-		}
-
-		// Free some memory
-		perFrame.m_tempCounters.destroy();
+		return;
 	}
 	}
 
 
-	// Sort the counter names
-	ANKI_ASSERT(ctx.m_counterNames.getSize() > 0);
-	std::sort(ctx.m_counterNames.getBegin(), ctx.m_counterNames.getEnd(), [](CString a, CString b) { return a < b; });
+	ThreadLocal& tlocal = getThreadLocal();
 
 
-	// Fill the gaps. Some counters might have not appeared in some frames. Those counters need to have a zero value
-	// because the CSV wants all counters present on all rows
-	for(PerFrameCounters& perFrame : ctx.m_counters)
-	{
-		ANKI_ASSERT(perFrame.m_counters.getSize() <= ctx.m_counterNames.getSize());
-
-		for(U i = 0; i < ctx.m_counterNames.getSize(); ++i)
-		{
-			const CString& counterName = ctx.m_counterNames[i];
-
-			// Try to find the counter
-			Bool found = false;
-			for(const GatherCounter& c : perFrame.m_counters)
-			{
-				if(counterName == c.m_name)
-				{
-					found = true;
-					break;
-				}
-			}
-
-			if(!found)
-			{
-				// Counter is missing
-				GatherCounter missingCounter;
-				missingCounter.m_name = counterName;
-				missingCounter.m_value = 0;
-				perFrame.m_counters.emplaceBack(missingCounter);
-			}
-		}
+	// Write the event
+	LockGuard<SpinLock> lock(tlocal.m_currentChunkLock);
+	Chunk& chunk = getOrCreateChunk(tlocal);
 
 
-		// Sort again
-		std::sort(perFrame.m_counters.getBegin(),
-			perFrame.m_counters.getEnd(),
-			[](const GatherCounter& a, const GatherCounter& b) { return a.m_name < b.m_name; });
+	TracerEvent& writeEvent = chunk.m_events[chunk.m_eventCount++];
+	writeEvent.m_name = eventName;
+	writeEvent.m_start = event.m_start;
+	writeEvent.m_duration = duration;
 
 
-		ANKI_ASSERT(perFrame.m_counters.getSize() == ctx.m_counterNames.getSize());
-	}
+	// Write counter as well. In ns
+	TracerCounter& writeCounter = chunk.m_counters[chunk.m_counterCount++];
+	writeCounter.m_name = eventName;
+	writeCounter.m_value = U64(writeEvent.m_duration * 1000000000.0);
 }
 }
 
 
-void Tracer::gatherEvents(FlushCtx& ctx)
+void Tracer::addCustomEvent(const char* eventName, Second start, Second duration)
 {
 {
-	for(ThreadLocal* threadLocal : m_allThreadLocal)
+	ANKI_ASSERT(eventName && start >= 0.0 && duration >= 0.0);
+	if(!m_enabled || duration == 0.0)
 	{
 	{
-		while(!threadLocal->m_eventChunks.isEmpty())
-		{
-			// Pop chunk
-			EventsChunk& chunk = threadLocal->m_eventChunks.getFront();
-			threadLocal->m_eventChunks.popFront();
-
-			// Copy
-			for(U i = 0; i < chunk.m_eventCount; ++i)
-			{
-				const Event& inEvent = chunk.m_events[i];
-
-				GatherEvent outEvent;
-				outEvent.m_duration = inEvent.m_duration;
-				outEvent.m_name = inEvent.m_name;
-				outEvent.m_timestamp = inEvent.m_timestamp;
-				outEvent.m_tid = threadLocal->m_tid;
-
-				ctx.m_events.emplaceBack(outEvent);
-			}
-
-			// Delete poped chunk
-			m_alloc.deleteInstance(&chunk);
-		}
+		return;
 	}
 	}
 
 
-	// Sort them
-	std::sort(ctx.m_events.getBegin(), ctx.m_events.getEnd(), [](const GatherEvent& a, const GatherEvent& b) {
-		if(a.m_timestamp != b.m_timestamp)
-		{
-			return a.m_timestamp < b.m_timestamp;
-		}
+	ThreadLocal& tlocal = getThreadLocal();
 
 
-		if(a.m_duration != b.m_duration)
-		{
-			return a.m_duration < b.m_duration;
-		}
+	// Write the event
+	LockGuard<SpinLock> lock(tlocal.m_currentChunkLock);
+	Chunk& chunk = getOrCreateChunk(tlocal);
+
+	TracerEvent& writeEvent = chunk.m_events[chunk.m_eventCount++];
+	writeEvent.m_name = eventName;
+	writeEvent.m_start = start;
+	writeEvent.m_duration = duration;
 
 
-		return a.m_name < b.m_name;
-	});
+	// Write counter as well. In ns
+	TracerCounter& writeCounter = chunk.m_counters[chunk.m_counterCount++];
+	writeCounter.m_name = eventName;
+	writeCounter.m_value = U64(duration * 1000000000.0);
 }
 }
 
 
-Error Tracer::writeTraceJson(const FlushCtx& ctx)
+void Tracer::incrementCounter(const char* counterName, U64 value)
 {
 {
-	// Open the file
-	StringAuto newFname(m_alloc);
-	newFname.sprintf("%s.trace.json", ctx.m_filename.cstr());
-	File file;
-	ANKI_CHECK(file.open(newFname.toCString(), FileOpenFlag::WRITE));
-
-	if(ctx.m_events.getSize() == 0)
+	if(!m_enabled)
 	{
 	{
-		// Early exit
-		return Error::NONE;
-	}
-
-	ANKI_CHECK(file.writeText("[\n"));
-
-	// Write the events to the file
-	for(const GatherEvent& event : ctx.m_events)
-	{
-		const U64 startMicroSec = U64(event.m_timestamp * 1000000.0);
-		const U64 durMicroSec = U64(event.m_duration * 1000000.0);
-
-		if(durMicroSec == 0)
-		{
-			continue;
-		}
-
-		ANKI_CHECK(file.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"X\", "
-								  "\"pid\": 1, \"tid\": %llu, \"ts\": %llu, \"dur\": %llu},\n",
-			event.m_name.cstr(),
-			event.m_tid,
-			startMicroSec,
-			durMicroSec));
+		return;
 	}
 	}
 
 
-	// Write the counters
-	for(U i = 0; i < ctx.m_counters.getSize(); ++i)
-	{
-		const PerFrameCounters& frame = ctx.m_counters[i];
-		const Second startFrameTime = frame.m_startFrameTime;
+	ThreadLocal& tlocal = getThreadLocal();
 
 
-		// The counters need a range in order to appear. Add a dummy counter for the last frame
-		const Array<Second, 2> timestamps = {{startFrameTime, startFrameTime + 1.0}};
-		const U timestampCount = (i < ctx.m_counters.getSize() - 1) ? 1 : 2;
+	LockGuard<SpinLock> lock(tlocal.m_currentChunkLock);
+	Chunk& chunk = getOrCreateChunk(tlocal);
 
 
-		for(const GatherCounter& counter : frame.m_counters)
-		{
-			for(U j = 0; j < timestampCount; ++j)
-			{
-				ANKI_CHECK(file.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"C\", "
-										  "\"pid\": 1, \"ts\": %llu, \"args\": {\"val\": %llu}},\n",
-					counter.m_name.cstr(),
-					U64(timestamps[j] * 1000000.0),
-					counter.m_value));
-			}
-		}
-	}
-
-	ANKI_CHECK(file.writeText("{}\n]\n"));
-
-	return Error::NONE;
+	TracerCounter& writeTo = chunk.m_counters[chunk.m_counterCount++];
+	writeTo.m_name = counterName;
+	writeTo.m_value = value;
 }
 }
 
 
-Error Tracer::writeCounterCsv(const FlushCtx& ctx)
+void Tracer::flush(TracerFlushCallback callback, void* callbackUserData)
 {
 {
-	// Open the file
-	StringAuto fname(m_alloc);
-	fname.sprintf("%s.counters.csv", ctx.m_filename.cstr());
-	File file;
-	ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE));
+	ANKI_ASSERT(callback);
 
 
-	if(ctx.m_counters.getSize() == 0)
+	LockGuard<Mutex> lock(m_allThreadLocalMtx);
+	for(ThreadLocal* tlocal : m_allThreadLocal)
 	{
 	{
-		// If there are no counters leave the file empty and exit
-		return Error::NONE;
-	}
+		LockGuard<SpinLock> lock2(tlocal->m_currentChunkLock);
 
 
-	// Write the counter names
-	ANKI_CHECK(file.writeText("Frame"));
-	for(CString counterName : ctx.m_counterNames)
-	{
-		ANKI_CHECK(file.writeText(",%s", counterName.cstr()));
-	}
-	ANKI_CHECK(file.writeText("\n"));
-
-	// Dump the frames
-	U rowCount = 0;
-	for(const PerFrameCounters& frame : ctx.m_counters)
-	{
-		ANKI_CHECK(file.writeText("%llu", frame.m_frame));
-
-		for(const GatherCounter& c : frame.m_counters)
+		while(!tlocal->m_allChunks.isEmpty())
 		{
 		{
-			ANKI_CHECK(file.writeText(",%llu", c.m_value));
-		}
-
-		ANKI_CHECK(file.writeText("\n"));
-		++rowCount;
-	}
+			Chunk* chunk = tlocal->m_allChunks.popFront();
 
 
-	// Dump some spreadsheet functions
-	ANKI_CHECK(file.writeText("SUM"));
-	for(U i = 0; i < ctx.m_counterNames.getSize(); ++i)
-	{
-		Array<char, 3> columnName;
-		getSpreadsheetColumnName(i + 1, columnName);
-		ANKI_CHECK(file.writeText(",=SUM(%s2:%s%u)", &columnName[0], &columnName[0], rowCount + 1u));
-	}
-	ANKI_CHECK(file.writeText("\n"));
+			callback(callbackUserData,
+				tlocal->m_tid,
+				WeakArray<TracerEvent>(&chunk->m_events[0], chunk->m_eventCount),
+				WeakArray<TracerCounter>(&chunk->m_counters[0], chunk->m_counterCount));
 
 
-	ANKI_CHECK(file.writeText("AVG"));
-	for(U i = 0; i < ctx.m_counterNames.getSize(); ++i)
-	{
-		Array<char, 3> columnName;
-		getSpreadsheetColumnName(i + 1, columnName);
-		ANKI_CHECK(file.writeText(",=AVERAGE(%s2:%s%u)", &columnName[0], &columnName[0], rowCount + 1u));
-	}
-
-	return Error::NONE;
-}
-
-Error Tracer::flush(CString filename)
-{
-	FlushCtx ctx(m_alloc, filename);
-
-	gatherCounters(ctx);
-	gatherEvents(ctx);
-
-	ANKI_CHECK(writeTraceJson(ctx));
-	ANKI_CHECK(writeCounterCsv(ctx));
-
-	return Error::NONE;
-}
-
-void Tracer::getSpreadsheetColumnName(U column, Array<char, 3>& arr)
-{
-	U major = column / 26;
-	U minor = column % 26;
+			m_alloc.deleteInstance(chunk);
+		}
 
 
-	if(major)
-	{
-		arr[0] = char('A' + (major - 1));
-		arr[1] = char('A' + minor);
+		tlocal->m_currentChunk = nullptr;
 	}
 	}
-	else
-	{
-		arr[0] = char('A' + minor);
-		arr[1] = '\0';
-	}
-
-	arr[2] = '\0';
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 104 - 43
src/anki/util/Tracer.h

@@ -5,9 +5,11 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <anki/util/File.h>
-#include <anki/util/List.h>
-#include <anki/util/ObjectAllocator.h>
+#include <anki/util/Thread.h>
+#include <anki/util/WeakArray.h>
+#include <anki/util/DynamicArray.h>
+#include <anki/util/Singleton.h>
+#include <anki/util/String.h>
 
 
 namespace anki
 namespace anki
 {
 {
@@ -16,86 +18,145 @@ namespace anki
 /// @{
 /// @{
 
 
 /// @memberof Tracer
 /// @memberof Tracer
-using TracerEventHandle = void*;
+class TracerEventHandle
+{
+	friend class Tracer;
 
 
-/// Tracer.
-class Tracer : public NonCopyable
+private:
+	Second m_start;
+};
+
+/// @memberof Tracer
+class TracerEvent
 {
 {
 public:
 public:
-	Tracer()
+	CString m_name;
+	Second m_start;
+	Second m_duration;
+
+	TracerEvent()
 	{
 	{
+		// No init
 	}
 	}
+};
 
 
-	~Tracer();
+/// @memberof Tracer
+class TracerCounter
+{
+public:
+	CString m_name;
+	U64 m_value;
 
 
-	void init(GenericMemoryPoolAllocator<U8> alloc)
+	TracerCounter()
 	{
 	{
-		m_alloc = alloc;
+		// No init
 	}
 	}
+};
+
+/// Tracer flush callback.
+/// @memberof Tracer
+using TracerFlushCallback = void (*)(
+	void* userData, ThreadId tid, ConstWeakArray<TracerEvent> events, ConstWeakArray<TracerCounter> counters);
 
 
-	Bool isInitialized() const
+/// Tracer.
+class Tracer : public NonCopyable
+{
+public:
+	Tracer(GenericMemoryPoolAllocator<U8> alloc)
+		: m_alloc(alloc)
 	{
 	{
-		return !!m_alloc;
 	}
 	}
 
 
+	~Tracer();
+
 	/// Begin a new event.
 	/// Begin a new event.
+	/// @note It's thread-safe.
 	ANKI_USE_RESULT TracerEventHandle beginEvent();
 	ANKI_USE_RESULT TracerEventHandle beginEvent();
 
 
 	/// End the event that got started with beginEvent().
 	/// End the event that got started with beginEvent().
+	/// @note It's thread-safe.
 	void endEvent(const char* eventName, TracerEventHandle event);
 	void endEvent(const char* eventName, TracerEventHandle event);
 
 
-	/// Increase a counter.
-	void increaseCounter(const char* counterName, U64 value);
+	/// Add a custom event.
+	/// @note It's thread-safe.
+	void addCustomEvent(const char* eventName, Second start, Second duration);
 
 
-	/// Begin a new frame.
-	void newFrame(U64 frame);
+	/// Increment a counter.
+	/// @note It's thread-safe.
+	void incrementCounter(const char* counterName, U64 value);
 
 
-	/// Flush all results to a file. Don't call that more than once.
-	ANKI_USE_RESULT Error flush(CString filename);
+	/// Flush all counters and events and start clean. The callback will be called multiple times.
+	/// @note It's thread-safe.
+	void flush(TracerFlushCallback callback, void* callbackUserData);
 
 
-private:
-	static const U32 EVENTS_PER_CHUNK = 256;
-	static const U32 COUNTERS_PER_CHUNK = 512;
+	Bool getEnabled() const
+	{
+		return m_enabled;
+	}
 
 
-	class Event;
-	class EventsChunk;
-	class GatherEvent;
+	void setEnabled(Bool enabled)
+	{
+		m_enabled = enabled;
+	}
 
 
-	class Counter;
-	class CountersChunk;
-	class GatherCounter;
+private:
+	static constexpr U32 EVENTS_PER_CHUNK = 256;
+	static constexpr U32 COUNTERS_PER_CHUNK = 512;
 
 
 	class ThreadLocal;
 	class ThreadLocal;
-	class PerFrameCounters;
-	class FlushCtx;
+	class Chunk;
 
 
 	GenericMemoryPoolAllocator<U8> m_alloc;
 	GenericMemoryPoolAllocator<U8> m_alloc;
 
 
-	Second m_startFrameTime = 0.0;
-	U64 m_frame = 0;
-	SpinLock m_frameMtx; ///< Protect m_startFrameTime and m_frame.
-
 	static thread_local ThreadLocal* m_threadLocal;
 	static thread_local ThreadLocal* m_threadLocal;
 	DynamicArray<ThreadLocal*> m_allThreadLocal; ///< The Tracer should know about all the ThreadLocal.
 	DynamicArray<ThreadLocal*> m_allThreadLocal; ///< The Tracer should know about all the ThreadLocal.
-	Mutex m_threadLocalMtx;
+	Mutex m_allThreadLocalMtx;
+
+	Bool m_enabled = false;
 
 
 	/// Get the thread local ThreadLocal structure.
 	/// Get the thread local ThreadLocal structure.
+	/// @note Thread-safe.
 	ThreadLocal& getThreadLocal();
 	ThreadLocal& getThreadLocal();
 
 
-	/// Gather all counters from all the threads.
-	void gatherCounters(FlushCtx& ctx);
+	/// Get or create a new chunk.
+	Chunk& getOrCreateChunk(ThreadLocal& tlocal);
+};
 
 
-	/// Gather the events from all the threads.
-	void gatherEvents(FlushCtx& ctx);
+/// The global tracer.
+using TracerSingleton = SingletonInit<Tracer>;
 
 
-	/// Dump the counters to a CSV file
-	Error writeCounterCsv(const FlushCtx& ctx);
+/// Scoped tracer event.
+class TracerScopedEvent
+{
+public:
+	TracerScopedEvent(const char* name)
+		: m_name(name)
+		, m_tracer(&TracerSingleton::get())
+	{
+		m_handle = m_tracer->beginEvent();
+	}
 
 
-	/// Dump the events and the counters to a chrome trace file.
-	Error writeTraceJson(const FlushCtx& ctx);
+	~TracerScopedEvent()
+	{
+		m_tracer->endEvent(m_name, m_handle);
+	}
 
 
-	static void getSpreadsheetColumnName(U column, Array<char, 3>& arr);
+private:
+	const char* m_name;
+	TracerEventHandle m_handle;
+	Tracer* m_tracer;
 };
 };
+
+#if ANKI_ENABLE_TRACE
+#	define ANKI_TRACE_SCOPED_EVENT(name_) TracerScopedEvent _tse##name_(#	name_)
+#	define ANKI_TRACE_CUSTOM_EVENT(name_, start_, duration_) \
+		TracerSingleton::get().addCustomEvent(#name_, start_, duration_)
+#	define ANKI_TRACE_INC_COUNTER(name_, val_) TracerSingleton::get().incrementCounter(#	name_, val_)
+#else
+#	define ANKI_TRACE_SCOPED_EVENT(name_) ((void)0)
+#	define ANKI_TRACE_CUSTOM_EVENT(name_, start_, duration_) ((void)0)
+#	define ANKI_TRACE_INC_COUNTER(name_, val_) ((void)0)
+#endif
 /// @}
 /// @}
 
 
 } // end namespace anki
 } // end namespace anki

+ 6 - 5
tests/util/HighRezTimer.cpp

@@ -5,24 +5,25 @@
 
 
 #include "tests/framework/Framework.h"
 #include "tests/framework/Framework.h"
 #include "anki/util/HighRezTimer.h"
 #include "anki/util/HighRezTimer.h"
-#include <unistd.h>
+#include <chrono>
+#include <thread>
 
 
 ANKI_TEST(Util, Test)
 ANKI_TEST(Util, Test)
 {
 {
 	HighRezTimer t;
 	HighRezTimer t;
 	t.start();
 	t.start();
 
 
-	sleep(2);
+	std::this_thread::sleep_for(std::chrono::seconds(2));
 
 
 	ANKI_TEST_EXPECT_NEAR(t.getElapsedTime(), 2.0, 0.2);
 	ANKI_TEST_EXPECT_NEAR(t.getElapsedTime(), 2.0, 0.2);
 
 
-	sleep(1);
+	std::this_thread::sleep_for(std::chrono::seconds(1));
 
 
 	ANKI_TEST_EXPECT_NEAR(t.getElapsedTime(), 3.0, 0.2);
 	ANKI_TEST_EXPECT_NEAR(t.getElapsedTime(), 3.0, 0.2);
 
 
-	sleep(1);
+	std::this_thread::sleep_for(std::chrono::seconds(1));
 	t.stop();
 	t.stop();
-	sleep(1);
+	std::this_thread::sleep_for(std::chrono::seconds(1));
 
 
 	ANKI_TEST_EXPECT_NEAR(t.getElapsedTime(), 4.0, 0.2);
 	ANKI_TEST_EXPECT_NEAR(t.getElapsedTime(), 4.0, 0.2);
 }
 }

+ 29 - 25
tests/util/Tracer.cpp

@@ -5,48 +5,52 @@
 
 
 #include <tests/framework/Framework.h>
 #include <tests/framework/Framework.h>
 #include <anki/util/Tracer.h>
 #include <anki/util/Tracer.h>
+#include <anki/core/CoreTracer.h>
 #include <anki/util/HighRezTimer.h>
 #include <anki/util/HighRezTimer.h>
 
 
+#if ANKI_ENABLE_TRACE
 ANKI_TEST(Util, Tracer)
 ANKI_TEST(Util, Tracer)
 {
 {
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
-	Tracer tracer;
-	tracer.init(alloc);
+	CoreTracer tracer;
+	ANKI_TEST_EXPECT_NO_ERR(tracer.init(alloc, "./"));
+	TracerSingleton::get().setEnabled(true);
 
 
 	// 1st frame
 	// 1st frame
-	tracer.newFrame(0);
-	ANKI_TEST_EXPECT_NO_ERR(tracer.flush("./0"));
+	tracer.flushFrame(0);
 
 
 	// 2nd frame
 	// 2nd frame
-	// 2 same events
-	tracer.newFrame(1);
+	// 2 events
+	{
+		ANKI_TRACE_SCOPED_EVENT(EVENT);
+		HighRezTimer::sleep(0.5);
+	}
 
 
-	auto handle0 = tracer.beginEvent();
-	HighRezTimer::sleep(0.5);
-	tracer.endEvent("event", handle0);
+	{
+		ANKI_TRACE_SCOPED_EVENT(EVENT);
+		HighRezTimer::sleep(0.25);
+	}
 
 
-	auto handle1 = tracer.beginEvent();
-	HighRezTimer::sleep(0.5);
-	tracer.endEvent("event", handle1);
+	tracer.flushFrame(1);
 
 
 	// 4rd frame
 	// 4rd frame
 	// 2 different events & non zero counter
 	// 2 different events & non zero counter
-	tracer.newFrame(3);
+	{
+		ANKI_TRACE_SCOPED_EVENT(EVENT);
+		HighRezTimer::sleep(0.5);
+	}
 
 
-	auto handle2 = tracer.beginEvent();
-	HighRezTimer::sleep(0.5);
-	tracer.endEvent("event", handle2);
+	{
+		ANKI_TRACE_SCOPED_EVENT(EVENT2);
+		HighRezTimer::sleep(0.25);
+	}
 
 
-	auto handle3 = tracer.beginEvent();
-	HighRezTimer::sleep(0.5);
-	tracer.endEvent("event2", handle3);
+	ANKI_TRACE_INC_COUNTER(COUNTER, 100);
 
 
-	tracer.increaseCounter("counter", 100);
+	tracer.flushFrame(3);
 
 
 	// 5th frame
 	// 5th frame
-	tracer.newFrame(4);
-	tracer.increaseCounter("counter", 150);
-	HighRezTimer::sleep(0.1);
-
-	ANKI_TEST_EXPECT_NO_ERR(tracer.flush("./1"));
+	ANKI_TRACE_INC_COUNTER(COUNTER, 150);
+	tracer.flushFrame(4);
 }
 }
+#endif