Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Read before doing anything non-trivial; do not duplicate their content here:
- [docs/agent-rules/gl-clear-program-revalidation.md](docs/agent-rules/gl-clear-program-revalidation.md) — NVIDIA JIT-recompiles the *bound* program's vertex shader at `glClear` against the newly bound FBO (debug id 131218); wrap any new clear site in `Utils::GLClearProgramGuard` (unbind + **restore** — a bare unbind blacked out every bind-once-draw-per-face bake loop), plus how to debug per-program driver warnings (program-id→shader-name labels, synchronous-callback stack capture).
- [docs/agent-rules/cluster-lod-simplification.md](docs/agent-rules/cluster-lod-simplification.md) — `VirtualMeshBuilder` / meshoptimizer: a TERMINAL group's boundary lock must outlive the level that created it (the per-level lock pass stops seeing it once its clusters leave `pending`, and only the COARSE cuts crack); `meshopt_SimplifyPermissive` and the old position-weld cure the same #651 many-attribute-wedges stall but trade cook time against UV-seam fidelity; `meshopt_simplifyWithUpdate` is unusable while one vertex array serves every LOD level; `simplifySloppy` needs a manifold + border-edge guard; and how to A/B a builder change (the cook is NOT reachable from a plain editor run).
- [docs/agent-rules/render-pipeline-caches.md](docs/agent-rules/render-pipeline-caches.md) — process-wide render caches (blackboard fingerprint, etc.) must invalidate on every topology reset, not just on a fingerprint change; hash `RenderGraph::GetTopologyGeneration()` into per-frame cache keys (issue #530).
- [docs/agent-rules/rhi-abstraction-boundary.md](docs/agent-rules/rhi-abstraction-boundary.md) — where the OpenGL boundary *actually* leaks (issue #691): a `glXxx(` grep is wrong three different ways (comments, log strings, and `glfw*` counted as GL — 549 real calls, not the 620/724 previously published); the **include** graph is the provable boundary and is far worse than the call count (40 files include all of GL while calling none of it, purely to name the `GLenum`s in `RendererAPI`'s own virtuals) — and because `RendererAPI.h` includes `glad/gl.h`, deleting a per-file include proves nothing until that header is clean; `Renderer/Debug/` is 43% of the calls and must *relocate*, not be exempted, or Phase 7 is unverifiable under the rendering-verification rule; `ResourceTransition` is neutral only by accident (its `RGWriteUsage → RGReadUsage` pair cannot express write→write, and silently defaults to `ShaderSample`); shader hot-reload invalidates 1 GL program but N `VkPipeline`s; backend selection is startup-time with an already-load-bearing ordering contract (`s_API` before `Window::Create`); and how to write a ratchet test that cannot silently pass.
- [docs/agent-rules/rhi-abstraction-boundary.md](docs/agent-rules/rhi-abstraction-boundary.md) — where the OpenGL boundary *actually* leaks (issue #691): a `glXxx(` grep is wrong three different ways (comments, log strings, and `glfw*` counted as GL — 549 real calls, not the 620/724 previously published); the **include** graph is the provable boundary and is far worse than the call count (40 files include all of GL while calling none of it, purely to name the `GLenum`s in `RendererAPI`'s own virtuals) — and because `RendererAPI.h` includes `glad/gl.h`, deleting a per-file include proves nothing until that header is clean; `Renderer/Debug/` is 43% of the calls and must *relocate*, not be exempted, or Phase 7 is unverifiable under the rendering-verification rule; `ResourceTransition` is neutral only by accident (its `RGWriteUsage → RGReadUsage` pair cannot express write→write, and silently defaults to `ShaderSample`); shader hot-reload invalidates 1 GL program but N `VkPipeline`s; backend selection is startup-time with an already-load-bearing ordering contract (`s_API` before `Window::Create`); and how to write a ratchet test that cannot silently pass. **Phase 2 step 2 (both counters now 0) added three more:** the facade was not just GL-typed but *incomplete* — 84 distinct entry points at the call sites, ~60% with no `RendererAPI` equivalent, so the sweep needed ~60 **new** virtuals (measure distinct *operations*, not call count, before scoping a sweep); a `Platform/<Backend>/` include leaks exactly as much as `glad/gl.h` and this scan cannot see it either (three passes pulled `OpenGLUtilities.h` in for `GLClearProgramGuard` — the fix was moving the guard **into** the backend clears, not deleting the include); and an earlier phase's declaration-only header must be read for the **vocabulary you are about to invent**, not just the types you consume (the sweep nearly shipped a second, worse spelling of `RHI::MemoryResidency`, caught only by a name collision that fires in the *test* build alone).
- [docs/agent-rules/two-phase-occlusion-culling.md](docs/agent-rules/two-phase-occlusion-culling.md) — phase 1 must test the PREVIOUS frame's FINAL pyramid (a mid-frame one holds only the occluders drawn before it, which is why VG could never occlude VG); `BuildCurrentOcclusionHZB` overwrites the retained pyramid **in place**, so pass order decides who still sees previous-frame depth; phase 2 needs its own command/args region or the second MDI re-issues every phase-1 draw; and a 1.24%-of-pixels A/B diff that was software-raster depth-tie noise, not an over-cull — with the one-switch check that proved it (issue #682).
- [docs/agent-rules/render-graph-transient-aliasing.md](docs/agent-rules/render-graph-transient-aliasing.md) — `WriteNewVersion` is a RENAME of the same physical resource (`m_VersionAliasTargets` resolution alias + planner lifetime folding, never an allocation); the stale-pool-read bug archetype (LIFO reuse hides it in steady state, plan rebuilds surface one-frame garbage, hit rate grows with pool age, per-pass toggle bisection can NOT find it) and the permanent hunt instruments `OLO_RG_POISON_TRANSIENTS` (per-resource hue poison — turns the stochastic artifact into a deterministic one-screenshot signal) / `OLO_RG_DISABLE_ALIASING`.
- [docs/agent-rules/per-frame-scratch-reuse.md](docs/agent-rules/per-frame-scratch-reuse.md) — a per-tick hot-path local (`std::vector` scratch built inside `AnimationGraph::Update`/`AnimationStateMachine::Update`) should become persistent per-instance state instead, but only after checking three things: every writer fully overwrites the buffer (or `.clear()`s it first), the owning instance isn't shared across entities, and the call isn't running on an unsynchronized `.Parallelizable()` path (issue #445).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#include "OloEnginePCH.h"
#include "MorphTargetEvaluator.h"
#include "OloEngine/Core/Log.h"
#include "OloEngine/Renderer/MemoryBarrierFlags.h"
#include "OloEngine/Renderer/RenderCommand.h"

#include <glad/gl.h>
#include <glm/glm.hpp>

#include <algorithm>
Expand Down Expand Up @@ -93,23 +94,23 @@ namespace OloEngine
// binding 2: Weights (readonly SSBO)
// binding 3: OutputVerts (writeonly SSBO)

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, baseVertexSSBO);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, morphDeltaSSBO);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, weightsSSBO);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, outputVertexSSBO);
RenderCommand::BindStorageBuffer(0, baseVertexSSBO);
RenderCommand::BindStorageBuffer(1, morphDeltaSSBO);
RenderCommand::BindStorageBuffer(2, weightsSSBO);
RenderCommand::BindStorageBuffer(3, outputVertexSSBO);

// Dispatch compute shader with enough work groups to cover all vertices
const u32 workGroupSize = 256;
const u32 numGroups = (vertexCount + workGroupSize - 1) / workGroupSize;
glDispatchCompute(numGroups, 1, 1);
RenderCommand::DispatchCompute(numGroups, 1, 1);

// Memory barrier to ensure compute shader writes are visible
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
RenderCommand::MemoryBarrier(MemoryBarrierFlags::ShaderStorage);

// Unbind SSBOs
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0);
RenderCommand::BindStorageBuffer(0, 0);
RenderCommand::BindStorageBuffer(1, 0);
RenderCommand::BindStorageBuffer(2, 0);
RenderCommand::BindStorageBuffer(3, 0);
}
} // namespace OloEngine
17 changes: 6 additions & 11 deletions OloEngine/src/OloEngine/Precipitation/PrecipitationSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#include "OloEngine/Renderer/ShaderBindingLayout.h"
#include "OloEngine/Snow/SnowAccumulationSystem.h"

#include <glad/gl.h>

#include <algorithm>
#include <cmath>
#include <numbers>
Expand Down Expand Up @@ -198,7 +196,7 @@ namespace OloEngine
}

// Create GPU timer queries for performance monitoring
glGenQueries(2, s_Data.m_TimerQueries);
RenderCommand::CreateQueries(RHI::QueryType::TimeElapsed, s_Data.m_TimerQueries);

s_Data.m_CurrentIntensity = 0.0f;
s_Data.m_TargetIntensity = 0.0f;
Expand All @@ -221,7 +219,7 @@ namespace OloEngine

if (s_Data.m_TimerQueries[0] != 0)
{
glDeleteQueries(2, s_Data.m_TimerQueries);
RenderCommand::DeleteQueries(s_Data.m_TimerQueries);
s_Data.m_TimerQueries[0] = 0;
s_Data.m_TimerQueries[1] = 0;
}
Expand Down Expand Up @@ -307,7 +305,7 @@ namespace OloEngine

// --- Begin GPU timer ---
u32 queryIdx = s_Data.m_CurrentTimerQuery;
glBeginQuery(GL_TIME_ELAPSED, s_Data.m_TimerQueries[queryIdx]);
RenderCommand::BeginQuery(RHI::QueryType::TimeElapsed, s_Data.m_TimerQueries[queryIdx]);

// 1. Intensity interpolation
s_Data.m_LastBaseEmissionRate = settings.BaseEmissionRate;
Expand Down Expand Up @@ -404,18 +402,15 @@ namespace OloEngine
}

// --- End GPU timer ---
glEndQuery(GL_TIME_ELAPSED);
RenderCommand::EndQuery(RHI::QueryType::TimeElapsed);

// Read back previous frame's timer result (double-buffered to avoid stalls)
u32 prevQueryIdx = 1 - queryIdx;
if (s_Data.m_TimerQueryActive)
{
GLint available = GL_FALSE;
glGetQueryObjectiv(s_Data.m_TimerQueries[prevQueryIdx], GL_QUERY_RESULT_AVAILABLE, &available);
if (available == GL_TRUE)
if (RenderCommand::IsQueryResultAvailable(s_Data.m_TimerQueries[prevQueryIdx]))
{
GLuint64 elapsedNs = 0;
glGetQueryObjectui64v(s_Data.m_TimerQueries[prevQueryIdx], GL_QUERY_RESULT, &elapsedNs);
const u64 elapsedNs = RenderCommand::GetQueryResultU64(s_Data.m_TimerQueries[prevQueryIdx]);
s_Data.m_LastFrameTimeMs = static_cast<f32>(elapsedNs) / 1000000.0f;
}
// If not available yet, keep the previous value — avoids CPU stall
Expand Down
Loading
Loading