diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index 24d72247..e37eb0dd 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -531,9 +532,10 @@ private: std::unordered_map instanceIndexById; // Collision scratch buffers are thread_local (see m2_renderer.cpp) for thread-safety. - // Collision query profiling (per frame). - mutable double queryTimeMs = 0.0; - mutable uint32_t queryCallCount = 0; + // Collision query profiling — atomic because getFloorHeight is dispatched + // on async threads from camera_controller while the main thread reads these. + mutable std::atomic queryTimeMs{0.0}; + mutable std::atomic queryCallCount{0}; // Persistent render buffers (avoid per-frame allocation/deallocation) struct VisibleEntry { diff --git a/include/rendering/vk_frame_data.hpp b/include/rendering/vk_frame_data.hpp index 482e76e7..bc06af10 100644 --- a/include/rendering/vk_frame_data.hpp +++ b/include/rendering/vk_frame_data.hpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace wowee { @@ -42,19 +43,25 @@ struct ShadowParamsUBO { float foliageMotionDamp; }; -// Timer utility for performance profiling queries +// Timer utility for performance profiling queries. +// Uses atomics because floor-height queries are dispatched on async threads +// from CameraController while the main thread may read the counters. struct QueryTimer { - double* totalMs = nullptr; - uint32_t* callCount = nullptr; + std::atomic* totalMs = nullptr; + std::atomic* callCount = nullptr; std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); - QueryTimer(double* total, uint32_t* calls) : totalMs(total), callCount(calls) {} + QueryTimer(std::atomic* total, std::atomic* calls) + : totalMs(total), callCount(calls) {} ~QueryTimer() { if (callCount) { - (*callCount)++; + callCount->fetch_add(1, std::memory_order_relaxed); } if (totalMs) { auto end = std::chrono::steady_clock::now(); - *totalMs += std::chrono::duration(end - start).count(); + double ms = std::chrono::duration(end - start).count(); + // Relaxed is fine for diagnostics — exact ordering doesn't matter. + double old = totalMs->load(std::memory_order_relaxed); + while (!totalMs->compare_exchange_weak(old, old + ms, std::memory_order_relaxed)) {} } } }; diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index 2189909c..e69d8e59 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -740,9 +741,10 @@ private: std::vector portalVisibleGroups_; // reused per frame (portal culling scratch) std::unordered_set portalVisibleGroupSet_; // reused per frame (portal culling scratch) - // Collision query profiling (per frame). - mutable double queryTimeMs = 0.0; - mutable uint32_t queryCallCount = 0; + // Collision query profiling — atomic because getFloorHeight is dispatched + // on async threads from camera_controller while the main thread reads these. + mutable std::atomic queryTimeMs{0.0}; + mutable std::atomic queryCallCount{0}; // Floor height cache - persistent precomputed grid static constexpr float FLOOR_GRID_CELL_SIZE = 2.0f; // 2 unit grid cells