Skip to content

refactor(rhi): sweep raw GL call sites out of OloEngine/src — Phase 2 step 2 (#691) - #700

Merged
drsnuggles8 merged 3 commits into
masterfrom
feature/vulkan-rhi-phase2-callsite-sweep-691
Jul 31, 2026
Merged

refactor(rhi): sweep raw GL call sites out of OloEngine/src — Phase 2 step 2 (#691)#700
drsnuggles8 merged 3 commits into
masterfrom
feature/vulkan-rhi-phase2-callsite-sweep-691

Conversation

@drsnuggles8

@drsnuggles8 drsnuggles8 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Closes the headline target of Phase 2 of #691: the raw OpenGL call sites are gone from OloEngine/src/**, and the translation units there can no longer name a GL symbol at all.

Step 1 (#698) converted the facade's vocabulary. This is step 2 of 3 — the call-site sweep. Step 3 is deliberately not here (reasons at the bottom), so Phase 2's checkbox stays unticked.

Counters

Counter Phase 1 After #698 This PR Target
sweep_gl_calls 313 313 0 0 (Phase 2) ✅
sweep_glad_includes 70 39 0 0 (Phase 2) ✅
tools_gl_calls 236 236 236 0 — Phase 8, relocation not exemption
debug_escape_hatch 0 0 0 must stay 0

sweep_glad_includes is the one that proves something: a TU that cannot see <glad/gl.h> provably cannot name a GL symbol. That is compiler-enforced, not a grep. rhi_boundary_baseline.json is updated in the same commit, as the ratchet requires.

The finding: the facade wasn't just GL-typed, it was incomplete

ADR 0011 §1.7 framed Phase 2 as "strip the GLenums, then sweep". That undersold it by about half.

84 distinct GL entry points appear across the 313 call sites, and ~60% had no RendererAPI equivalent at all — so closing the sweep needed ~60 new virtuals against an existing 74. Whole categories had never been abstracted, so every pass reached past the facade to do them:

Category New virtuals Why it had no entry point
Buffer binding points (glBindBufferBase) 2 Biggest single gap — 26 call sites
Raw buffer lifecycle 9 VirtualMeshRegistry hand-rolls an arena + persistent-mapped upload ring
Named-framebuffer state 10 SetDrawBuffers existed but only for the bound FBO; every site names one via DSA
Queries 7 BeginConditionalRender existed; the pools feeding it did not
Fences 4 FrameResourceManager used GLsync directly
Draws from bound geometry 4 CommandDispatch keeps a redundant-bind cache the *Raw family would defeat
Program / VAO / FBO binding 5 The POD dispatcher holds a u32, not a Ref<Shader>
Texture clear / offset upload / readback / dims / barrier 8
VAO lifecycle, debug groups, device idle, MSAA caps, separate blend, front face, clear depth, patch count 11

Generalisable, and worth carrying into Phase 5: an abstraction's completeness is measured by how many distinct operations the layer above performs, not by how many call sites it already serves. 74 virtuals looked thorough while 60 operations went around it, because each was rare enough (1–3 sites) to read as a special case. Frequency wasn't the signal either — glBindBufferBase had 26 sites and was still missing.

ADR 0011 amendments (5)–(10)

  • (6) RHI::NoAttachment — a "this draw slot writes nowhere" sentinel. DecalRenderPass steers a decal into exactly one G-Buffer attachment using GL_NONE entries, which an attachment index cannot express. Both backends need it (GL_NONE / VK_ATTACHMENT_UNUSED); only the neutral layer lacked it.
  • (7) glGetError disappears rather than being abstracted. GL's error model is a sticky global flag, Vulkan's is a per-call result — exposing either forces the other backend to fake it. The readback virtuals return bool instead.
  • (8) DrawBoundIndexed / …Instanced / DrawBoundArrays draw from previously bound geometry. That is the native Vulkan shape (vkCmdBindIndexBuffer + vkCmdDrawIndexed), not a GL-ism — the combined bind-and-draw *Raw form is the less portable one. They also carry RHI::PrimitiveTopology / RHI::IndexType explicitly instead of hard-coding triangles + u32.
  • (9) One recorded, deliberate debt. SetProgramUniformFloat is the single virtual a Vulkan backend cannot implement faithfully — SPIR-V has no name-queryable default uniform block. One call site (DrawInfiniteGrid's u_GridScale). The alternatives were a shader change (forfeits golden-image parity, this branch's only safety net) or reaching for Shader (the POD dispatcher holds a u32 by design). Phase 6 folds it into a UBO and deletes it. Flagged now rather than surprising Phase 7 bring-up.
  • (10) MemoryResidency moved from RHIResources.h to RHITypes.h — see below.

Two boundary leaks the ratchet cannot see, also closed

  1. A Platform/<Backend>/ include leaks exactly as much as glad/gl.h. Three passes (FluidIntermediatesPass, WaterRenderPass, VirtualMeshRegistry) included Platform/OpenGL/OpenGLUtilities.h for GLClearProgramGuard. Deleting their direct glad includes would have driven sweep_glad_includes to zero while all three TUs could still name every symbol in OpenGL — the exact counter-gaming the baseline's own _comment warns about, arrived at honestly. The fix is a layering one: the guard is an NVIDIA driver hazard, so it moved into the backend clear implementations, where ClearDepthOnly() had been carrying it correctly all along. The passes now get the behaviour for free.

  2. A loader-symbol probe is invisible by construction. RGCommandContext guarded its debug markers with if (GLAD_GL_KHR_debug) — which does not match gl[A-Z] and so never appeared in any count. The capability check moved into OpenGLRendererAPI::PushDebugGroup. Same class as step 1's glad_glCreateTextures != nullptr context probe.

A third near-miss is recorded because the catch was luck. The sweep started inventing RHI::BufferUsage { DynamicDraw, DynamicCopy, DynamicRead } — a straight transcription of GL's hints. Phase 1 had already designed that concept, better, as RHI::MemoryResidency { DeviceLocal, HostToDevice, DeviceToHost }, sitting beside BufferDesc where nothing could reach it. What surfaced the duplication was a name collision with RHIResources.h's bind-flag BufferUsage — and it only fired in the test build, since the engine library never includes that header. Rename either enum and a duplicate concept ships silently. Lesson written up: read an earlier phase's declaration-only header for the vocabulary you are about to invent, not just the types you consume.

Testing

  • Full suite: 5195 run / 5189 passed / 0 failed / 6 skipped. Goldens and visual-evidence tests executed, not skipped; OLOENGINE_GOLDEN_REBASE unset throughout. The 6 skips are the same environment-dependent ones as refactor(rhi): strip GLenum/GLuint out of RendererAPI's interface (#691 Phase 2, step 1) #698 (app-launch smoke ×3, video fixture, real-asset cook, MCP headless attach).

  • New tests. RHIEnumLoweringTest gains 6 cases for the new enums, each with the last-ordinal static_assert tripwire amendment (4) established — including one asserting the NoAttachment sentinel cannot be folded into attachment-0 arithmetic (GL_NONE is not GL_COLOR_ATTACHMENT0 + anything, and collapsing it would leave the previous draw-buffer list live).

  • This PR changes no pixels. Nine evidence PNGs regenerated; each was pixel-diffed against HEAD after measuring the run-to-run noise floor on the identical binary. Every delta sits at or below its own noise:

    PNG vs HEAD (mean) noise floor verdict
    EASU_GTAONative 0.4469 (max 86) 0.4469 (max 86) identical — pure noise, untouched code
    Fluid_Waterline 0.0021 0.0020 same magnitude
    Fluid_Side 0.0011 0.0014 below noise
    Fluid_ThreeQuarter 0.0008 0.0009 below noise
    VirtualGeometry_Debug_ClusterId 0.0006 (max 158) 0.0009 (max 158) the max-158 spike reproduces in the control
    OcclusionCull_*, WorldOriginRebase, CameraRelative, SphereAreaLight ≤0.0003 ~0 below every threshold

    The max=158 on the cluster-ID debug image was the alarming one; it appears identically in the no-change control, so it is GPU-order-dependent cluster-ID colouring, not a regression. All nine restored.

  • Live editor. Renders correctly; zero shader compile/link errors and zero GL_INVALID_* (exactly what a wrong enum lowering or a bad draw-buffer list would produce). olo_render_frame_breakdown shows the rewritten dispatch path (DrawMeshInstanced / DrawSkybox) executing with real GPU timings.

One verification gap, stated plainly

I could not exercise virtual geometry or deferred lighting in the live editor. Both require the Deferred path; that path is not scene-serialized (the VirtualGeometrySponza.olo header documents this and says to switch it by hand after load), and the MCP olo_renderer_settings_set write gate is off by default and cannot be unlocked non-interactively. Both paths are covered by the suite's evidence/golden tests, which executed and matched HEAD within noise. Worth a human eyeball on VirtualGeometrySponza.olo + SponzaDeferred.olo with the path switched to Deferred.

One behaviour delta worth knowing about

VirtualGeometryPass set depth state with raw glEnable(GL_DEPTH_TEST), which left OpenGLRendererAPI::m_DepthTestEnabled stale — and Clear() derives its GLbitfield from that member. Routing the pass through the facade makes that cache truthful, so a later Clear() can now clear depth where it previously did not. That is a fix, but it is rendering-visible; the golden/evidence tests show no resulting pixel change.

Profiler counters also move slightly: the facade's state setters bump RendererProfiler::StateChanges where the raw calls did not. Draw counters deliberately do not move — the new DrawBound* family does not touch RendererProfiler, because the call sites it replaces never did and several keep their own CommandDispatch::Statistics.

Why step 3 is not in this PR

Step 3 is TransientPool's bare RendererID / GetRendererID() split three ways per ADR 0011 §1.1/§1.3/§1.4. Three reasons it is separate rather than an unjustified slice:

  • RHI::ResourceHandle has no producer. RHIResources.h says so in its own header comment, and GetNativeHandleForDebug is declared but defined nowhere. Step 3 needs a generation-checked handle-minting layer built first.
  • GetRendererID() has ~320 call sites, not a handful (Scene.cpp 56, Renderer3DMeshSubmission.cpp 33, …).
  • Part of it is Phase-3-entangled. ADR 0011 §1.3 says BindTexture "does not get abstracted, it disappears" — under heap-bindless, which is Phase 3's ARB_bindless_texture rehearsal.

This matches how #691 is deliberately structured (phase-at-a-time PRs; step 1 shipped alone as #698).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Expanded renderer support for framebuffer operations, resource management, GPU queries, fences, synchronization, and debug markers.
    • Added broader multisample and rendering capability detection.
  • Bug Fixes
    • Improved GPU readback failure handling to prevent invalid data from being used.
    • Improved synchronization, attachment handling, and state restoration for more consistent rendering.
  • Documentation
    • Updated renderer abstraction guidance, coverage details, and migration progress documentation.

…se 2 step 2)

Drives both Phase 2 ratchet counters to zero:

  sweep_gl_calls      313 -> 0
  sweep_glad_includes  39 -> 0

No translation unit in the sweep bucket can name a GL symbol any more, which
is a compiler-enforced property rather than a grep. tools_gl_calls stays at
236 (Renderer/Debug/, Phase 8 relocation) and debug_escape_hatch stays at 0.

The headline finding is that the facade was not merely GL-typed, it was
INCOMPLETE. 84 distinct GL entry points appear across the 313 call sites and
roughly 60% of them had no RendererAPI equivalent at all, so closing the sweep
needed ~60 new virtuals against an existing 74. Whole categories had never been
abstracted and every pass reached past the facade to perform them: buffer
binding points (glBindBufferBase, 26 sites), raw buffer lifecycle, named-
framebuffer draw/read attachment selection, blits, clears, completeness,
occlusion + timer queries, fences, VAO lifecycle, texture clear/upload/readback,
debug markers, device idle and MSAA caps.

Shape decisions are recorded as ADR 0011 amendments (5)-(10):

* RHI::NoAttachment - a "this draw slot writes nowhere" sentinel. DecalRenderPass
  steers a decal into exactly one G-Buffer attachment with GL_NONE entries, which
  an attachment index cannot express; both backends need it (GL_NONE /
  VK_ATTACHMENT_UNUSED).
* glGetError DISAPPEARS rather than being abstracted. GL's error model is a
  sticky global flag and Vulkan's is a per-call result, so exposing either forces
  the other backend to fake it. The readback virtuals return bool.
* DrawBoundIndexed / DrawBoundIndexedInstanced / DrawBoundArrays draw from
  previously bound geometry, which is the native Vulkan shape
  (vkCmdBindIndexBuffer + vkCmdDrawIndexed), not a GL-ism. They carry topology
  and index width explicitly instead of hard-coding triangles + u32.
* SetProgramUniformFloat is a recorded, deliberate debt: the one virtual a Vulkan
  backend cannot implement faithfully, since SPIR-V has no name-queryable default
  uniform block. One call site (DrawInfiniteGrid's u_GridScale); Phase 6 folds it
  into a UBO and deletes it.
* MemoryResidency moved from RHIResources.h to RHITypes.h. The sweep started
  reinventing it as a GL-transcribed "BufferUsage" and only a name collision with
  RHIResources.h's bind-flag BufferUsage surfaced that Phase 1 had already
  designed the concept, better. RendererAPI.h includes only RHITypes.h, so it
  belongs there.

Two boundary leaks the ratchet cannot see were also closed:

* Three passes included Platform/OpenGL/OpenGLUtilities.h for GLClearProgramGuard.
  Deleting their direct glad includes would have zeroed sweep_glad_includes while
  all three TUs could still see the whole GL API. The guard is an NVIDIA driver
  hazard, so it moved INTO the backend clear implementations, where
  ClearDepthOnly() had been carrying it correctly all along.
* RGCommandContext guarded its debug markers with `if (GLAD_GL_KHR_debug)`, a glad
  loader symbol that does not match gl[A-Z] and so never appeared in any count.
  The capability check moved into OpenGLRendererAPI::PushDebugGroup.

Every new enum is pinned by the same last-ordinal static_assert plus literal-GL_*
token table that step 1's amendment (4) established, including a test that the
NoAttachment sentinel cannot be folded into attachment-0 arithmetic.

Verification: full suite 5195 run / 5189 passed / 0 failed / 6 skipped, with the
goldens and visual-evidence tests executing (not skipping) and
OLOENGINE_GOLDEN_REBASE unset. Nine evidence PNGs were regenerated; every one
differs from HEAD by at or below its own run-to-run noise measured on the
identical binary, so all were restored and this commit changes no pixels. Live
editor: renders correctly, zero shader compile/link errors, zero GL_INVALID_*,
and the frame breakdown shows the rewritten dispatch path executing with real
GPU timings.

Step 3 (TransientPool's RendererID split) is deliberately not in this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4c61f4f2-f41b-4d67-80ba-4b09fce02368

📥 Commits

Reviewing files that changed from the base of the PR and between b2606d1 and 2d22926.

📒 Files selected for processing (1)
  • OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp

📝 Walkthrough

Walkthrough

The change expands the RHI and RenderCommand APIs, implements them in the OpenGL backend and test mock, and migrates renderer subsystems from direct OpenGL calls to the abstraction layer. Boundary documentation and sweep baselines now record Phase 2 step 2 completion.

Changes

RHI abstraction migration

Layer / File(s) Summary
RHI vocabulary and facade contracts
OloEngine/src/OloEngine/Renderer/RHI/*, OloEngine/src/OloEngine/Renderer/RendererAPI.h, OloEngine/src/OloEngine/Renderer/RenderCommand.h
Adds query, fence, blit, residency, and attachment vocabulary plus renderer virtuals and command forwarders.
OpenGL backend and test support
OloEngine/src/Platform/OpenGL/*, OloEngine/tests/Rendering/*
Implements the expanded OpenGL backend, adds enum lowering helpers, extends the mock renderer, and tests mappings and sentinel handling.
Renderer call-site migration
OloEngine/src/OloEngine/Renderer/Commands/*, OloEngine/src/OloEngine/Renderer/Passes/*, OloEngine/src/OloEngine/Renderer/VirtualGeometry/*
Routes bindings, draws, framebuffer operations, resource management, synchronization, readbacks, and barriers through RenderCommand and RendererAPI.
Supporting subsystem migration
OloEngine/src/OloEngine/Animation/*, OloEngine/src/OloEngine/Precipitation/*, OloEngine/src/OloEngine/Scene/*, OloEngine/src/OloEngine/UI/*, OloEngine/src/OloEngine/SaveGame/*
Migrates GPU evaluation, timer queries, texture dimension queries, UI integer types, and thumbnail readback away from direct OpenGL dependencies.
Boundary records and documentation
OloEngine/tests/Rendering/rhi_boundary_baseline.json, docs/adr/*, docs/agent-rules/*, CLAUDE.md
Records zero GL-call/include counters and documents the Phase 2 step 2 abstraction and boundary changes.

Possibly related issues

  • Issue 691 — The pull request implements the RendererAPI hardening and direct OpenGL call-site migration described by the issue.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: removing raw OpenGL call sites from OloEngine/src as part of Phase 2 step 2.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@drsnuggles8

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp (1)

110-141: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the repeated "count color attachments + rebuild 0..n-1 draw-buffer list" snippet into a shared helper.

The same ~15-line pattern — count non-depth color attachments from a FramebufferSpecification, then build a sequential std::array<u32,16> of indices and call RenderCommand::SetFramebufferDrawAttachments — is copy-pasted at 8 call sites across 4 files (3 of them within DeferredLightingPass.cpp alone). A future attachment-layout change (adding/removing a G-Buffer or scene-FB RT) requires updating every site consistently; missing one silently leaves the wrong draw buffers bound.

  • OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp#L110-L141: extract bindGBufferForDraw's count (113-121) + build (128-132) into a shared helper (e.g. Framebuffer::GetColorAttachmentCount() + RenderCommand::SetFramebufferAllColorDrawAttachments(fbID, count)).
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredLightingPass.cpp#L186-L199: reuse the shared count helper instead of the inline loop feeding the line-199 call.
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredLightingPass.cpp#L358-L370: replace the inline array rebuild with the shared full-restore helper.
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredLightingPass.cpp#L404-L423: replace this second inline rebuild (same file) with the same shared helper.
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredLightingPass.cpp#L504-L523: replace the third occurrence (BlitVirtualGeometryDebugOverlay's count + rebuild) with the shared helper.
  • OloEngine/src/OloEngine/Renderer/Passes/ForwardOverlayRenderPass.cpp#L96-L107: reuse the shared count helper.
  • OloEngine/src/OloEngine/Renderer/Passes/ForwardOverlayRenderPass.cpp#L147-L158: replace the inline rebuild with the shared full-restore helper.
  • OloEngine/src/OloEngine/Renderer/Passes/GPUDrivenOcclusionPass.cpp#L117-L140: replace bindSceneForDraw's inline count + rebuild with the shared helpers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp` around
lines 110 - 141, Extract the repeated color-attachment counting and sequential
draw-buffer setup into shared helpers, such as a Framebuffer color-count method
and a RenderCommand full-color draw-attachment method. Update
OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp:110-141,
DeferredLightingPass.cpp:186-199, 358-370, 404-423, and 504-523,
ForwardOverlayRenderPass.cpp:96-107 and 147-158, and
GPUDrivenOcclusionPass.cpp:117-140 to use those helpers, preserving the existing
attachment ordering and draw behavior.
OloEngine/src/OloEngine/Renderer/ReflectionProbeBaker.cpp (1)

182-194: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cubemap face readback failure doesn't prevent baking the stale buffer into the probe.

RenderCommand::ReadTextureImage failure only logs a warning; cubemap->SetFaceData(face, pixelBuffer.data(), ...) still runs on the next line regardless, uploading whatever pixelBuffer currently holds (garbage on the first face, or the previous face's data on later faces) into the baked reflection probe's cubemap that later feeds EnvironmentMap::CreateFromCubemap and is stored as probe.m_BakedEnvironment.

🛡️ Proposed fix: skip the face upload and fail the capture on error
                 if (!RenderCommand::ReadTextureImage(colorAttachmentID, 0, RHI::Format::RGBA32Float,
                                                      faceBytes, pixelBuffer.data()))
                 {
                     OLO_CORE_WARN("ReflectionProbeBaker: cubemap face readback failed");
+                    captureOk = false;
+                    break;
                 }

(captureOk would need to become non-const and checked after the loop, or an early return nullptr used instead.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OloEngine/src/OloEngine/Renderer/ReflectionProbeBaker.cpp` around lines 182 -
194, Update the face-capture flow in the reflection probe baking method around
RenderCommand::ReadTextureImage and cubemap->SetFaceData so a readback failure
does not upload pixelBuffer. Track the failure with a mutable capture-success
flag and skip or abort the bake, returning the failure result after the loop;
only call SetFaceData for successfully read-back faces.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/adr/0011-rhi-neutral-resource-and-binding-model.md`:
- Around line 955-956: The cross-reference in the enum requirement must point to
the “One new guard” paragraph, which establishes the last-ordinal static_assert
and literal-token table; replace the incorrect amendment (4) reference without
changing the surrounding requirement.
- Around line 816-818: Clarify in
docs/adr/0011-rhi-neutral-resource-and-binding-model.md lines 816-818 that the
84 figure counts GL entry points while the category table counts expanded
RendererAPI virtuals, or correct the percentage so the two measurements are not
compared directly. Update docs/agent-rules/rhi-abstraction-boundary.md lines
203-205 and OloEngine/src/OloEngine/Renderer/RendererAPI.h lines 195-197 to use
the same corrected wording or an ADR cross-reference, eliminating the
inconsistent “~60%” and “~60 new virtuals” restatements.

In `@docs/agent-rules/rhi-abstraction-boundary.md`:
- Around line 143-148: Revise the “last four files standing” sentence in the
Step 2 narrative so it does not claim a set while naming only UIRenderer.cpp.
Either identify the other three files explicitly or remove the count and
describe UIRenderer.cpp as the remaining example.

In `@OloEngine/src/OloEngine/Renderer/Commands/FrameResourceManager.cpp`:
- Around line 295-307: Update the switch on RenderCommand::ClientWaitFence in
FrameResourceManager::WaitForFence to add a default branch that logs the
unrecognized fence status as an error and returns false, ensuring future
RHI::FenceStatus values are not treated as successful waits.

In `@OloEngine/src/OloEngine/Renderer/DDGI/DDGIProbeUpdatePass.cpp`:
- Around line 720-726: Update RelocateAndClassifyProbe’s ReadTextureSubImage
failure path to return immediately after logging, preventing aggregation and
probe relocation/classification from using invalid texels; preserve the existing
success path unchanged.

In `@OloEngine/src/OloEngine/Renderer/LightProbeBaker.cpp`:
- Around line 71-77: Update RenderCubemapAtPosition to propagate a failed
ReadTextureImage result instead of converting rgbaBuffer for that face; ensure
BakeProbeAtPosition/BakeVolume can observe the failure and prevent stale
readback data from contributing to persisted coefficients, with outValid
reflecting any partial capture as appropriate.

In `@OloEngine/src/OloEngine/Renderer/Ocean/OceanFFTGpu.cpp`:
- Around line 271-292: The DebugInverseFFT2D readback path still processes the
buffer after ReadTextureSubImage fails. Update the failure branch in
DebugInverseFFT2D to return immediately with the utility’s appropriate
empty/failed result, preventing normalization and result construction from using
stale readback data.

In `@OloEngine/src/OloEngine/Renderer/Passes/SceneRenderPass.cpp`:
- Around line 589-592: Extract the duplicated identity draw-buffer construction
from the scene render flow into a shared file-local helper, such as
BuildIdentityDrawBufferList(u32 count), preserving the 16-entry cap and
sequential 0..n-1 values. Replace the inline builders in the current pass and
BlitForwardVelocityDebug with calls to this helper.

In `@OloEngine/src/OloEngine/UI/UIRenderer.cpp`:
- Around line 29-32: Correct the type comment in UIRenderer.cpp so it contrasts
the engine coordinate types with the removed OpenGL types, naming the latter as
GLint/GLsizei instead of repeating i32/u32.

In `@OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp`:
- Around line 64-66: Update DrawIndexedPatches and DrawIndexedPatchesRaw to use
the cached m_MaxPatchVertices value instead of calling
glGetIntegerv(GL_MAX_PATCH_VERTICES) per draw. Preserve the existing > 0
fail-open condition in the validation logic, and keep tessellation-cap
validation centralized without changing other patch-draw behavior.

In `@OloEngine/tests/Rendering/MockRendererAPI.h`:
- Around line 602-608: Update MockRendererAPI::SetFramebufferDrawAttachments to
record the actual attachmentIndices values, not only their count in ParamU32_1,
so tests can verify each draw slot’s attachment mapping including
RHI::NoAttachment. Use the existing RecordedCall representation or extend it as
needed while preserving the recorded framebuffer handle and call name.
- Around line 792-806: Update MockRendererAPI::CreateFence to return a nonzero,
monotonically increasing fake fence ID using a counter alongside the existing ID
generators, while continuing to record the call. Preserve the existing
always-signaled behavior in IsFenceSignaled and ClientWaitFence so callers
exercise the successful fence path and use the issued handle.

In `@OloEngine/tests/Rendering/rhi_boundary_baseline.json`:
- Line 107: Replace the non-reproducible measured_on_commit value in the
rendering baseline with the actual finalized commit identifier for the recorded
0/0 measurement, removing the working-tree and phase description so the baseline
can be re-measured.

In `@OloEngine/tests/Rendering/RHIEnumLoweringTest.cpp`:
- Around line 72-90: Update the enum tripwires near the existing static_asserts
so each checked RHI enum exposes and asserts an explicit Count/Max value
representing the number of members, rather than asserting only an existing
member value. Apply this to IndexType, FrontFace, QueryType, MemoryResidency,
BlitAspect, and FenceStatus, and preserve the existing lowering/update guidance
in the assertion messages.

---

Outside diff comments:
In `@OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp`:
- Around line 110-141: Extract the repeated color-attachment counting and
sequential draw-buffer setup into shared helpers, such as a Framebuffer
color-count method and a RenderCommand full-color draw-attachment method. Update
OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp:110-141,
DeferredLightingPass.cpp:186-199, 358-370, 404-423, and 504-523,
ForwardOverlayRenderPass.cpp:96-107 and 147-158, and
GPUDrivenOcclusionPass.cpp:117-140 to use those helpers, preserving the existing
attachment ordering and draw behavior.

In `@OloEngine/src/OloEngine/Renderer/ReflectionProbeBaker.cpp`:
- Around line 182-194: Update the face-capture flow in the reflection probe
baking method around RenderCommand::ReadTextureImage and cubemap->SetFaceData so
a readback failure does not upload pixelBuffer. Track the failure with a mutable
capture-success flag and skip or abort the bake, returning the failure result
after the loop; only call SetFaceData for successfully read-back faces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 52701d9c-10c5-4050-9fbf-f83f92bc6672

📥 Commits

Reviewing files that changed from the base of the PR and between 04b0ab3 and 11d7cea.

📒 Files selected for processing (52)
  • CLAUDE.md
  • OloEngine/src/OloEngine/Animation/MorphTargets/MorphTargetEvaluator.cpp
  • OloEngine/src/OloEngine/Precipitation/PrecipitationSystem.cpp
  • OloEngine/src/OloEngine/Renderer/Commands/CommandDispatch.cpp
  • OloEngine/src/OloEngine/Renderer/Commands/FrameResourceManager.cpp
  • OloEngine/src/OloEngine/Renderer/DDGI/DDGIProbeUpdatePass.cpp
  • OloEngine/src/OloEngine/Renderer/GBuffer.cpp
  • OloEngine/src/OloEngine/Renderer/IBLPrecompute.cpp
  • OloEngine/src/OloEngine/Renderer/Instancing/GPUFrustumCuller.cpp
  • OloEngine/src/OloEngine/Renderer/LightCulling/TiledForwardPlus.cpp
  • OloEngine/src/OloEngine/Renderer/LightProbeBaker.cpp
  • OloEngine/src/OloEngine/Renderer/Occlusion/OcclusionQueryPool.cpp
  • OloEngine/src/OloEngine/Renderer/Ocean/OceanFFTGpu.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/BloomRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/DecalRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredLightingPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredOpaqueDecalPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/FluidCompositePass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/FluidIntermediatesPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/ForwardOverlayRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/GPUDrivenOcclusionPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/GTAORenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/OITPrepareRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/OITResolveRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/PlanarReflectionRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/SSAORenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/SceneRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/WaterRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/RGCommandContext.cpp
  • OloEngine/src/OloEngine/Renderer/RHI/RHIResources.h
  • OloEngine/src/OloEngine/Renderer/RHI/RHITypes.h
  • OloEngine/src/OloEngine/Renderer/ReflectionProbeBaker.cpp
  • OloEngine/src/OloEngine/Renderer/RenderCommand.h
  • OloEngine/src/OloEngine/Renderer/RenderGraph.cpp
  • OloEngine/src/OloEngine/Renderer/Renderer3DLifecycle.cpp
  • OloEngine/src/OloEngine/Renderer/RendererAPI.h
  • OloEngine/src/OloEngine/Renderer/ShaderPack.cpp
  • OloEngine/src/OloEngine/Renderer/VirtualGeometry/VirtualGeometryPass.cpp
  • OloEngine/src/OloEngine/Renderer/VirtualGeometry/VirtualGeometryShadow.cpp
  • OloEngine/src/OloEngine/Renderer/VirtualGeometry/VirtualMeshRegistry.cpp
  • OloEngine/src/OloEngine/SaveGame/ThumbnailCapture.cpp
  • OloEngine/src/OloEngine/Scene/Scene.cpp
  • OloEngine/src/OloEngine/UI/UIRenderer.cpp
  • OloEngine/src/Platform/OpenGL/OpenGLRHIConversions.h
  • OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp
  • OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.h
  • OloEngine/tests/Rendering/MockRendererAPI.h
  • OloEngine/tests/Rendering/RHIEnumLoweringTest.cpp
  • OloEngine/tests/Rendering/rhi_boundary_baseline.json
  • docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  • docs/agent-rules/rhi-abstraction-boundary.md
💤 Files with no reviewable changes (3)
  • OloEngine/src/OloEngine/Renderer/ShaderPack.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/OITResolveRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/BloomRenderPass.cpp

Comment thread docs/adr/0011-rhi-neutral-resource-and-binding-model.md Outdated
Comment thread docs/adr/0011-rhi-neutral-resource-and-binding-model.md Outdated
Comment thread docs/agent-rules/rhi-abstraction-boundary.md Outdated
Comment thread OloEngine/src/OloEngine/Renderer/Commands/FrameResourceManager.cpp
Comment thread OloEngine/src/OloEngine/Renderer/DDGI/DDGIProbeUpdatePass.cpp
Comment thread OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp
Comment thread OloEngine/tests/Rendering/MockRendererAPI.h
Comment thread OloEngine/tests/Rendering/MockRendererAPI.h
Comment thread OloEngine/tests/Rendering/rhi_boundary_baseline.json Outdated
Comment on lines +72 to +90
// Phase 2 step 2 vocabulary (ADR 0011 amendment (10)). Same tripwire
// discipline: IndexType in particular has only two members, which makes a
// swapped mapping look harmless right up until a u16-indexed mesh reads its
// element buffer at 4-byte stride and renders as scattered triangles.
static_assert(static_cast<int>(RHI::IndexType::UInt32) == 1,
"RHI::IndexType changed — update ToGL() and IndexTypeLowering");
static_assert(static_cast<int>(RHI::FrontFace::Clockwise) == 1,
"RHI::FrontFace changed — update ToGL() and FrontFaceLowering");
static_assert(static_cast<int>(RHI::QueryType::TimeElapsed) == 1,
"RHI::QueryType changed — update ToGL() and QueryTypeLowering");
static_assert(static_cast<int>(RHI::MemoryResidency::DeviceToHost) == 2,
"RHI::MemoryResidency changed — update ToGL() and MemoryResidencyLowering");
static_assert(static_cast<int>(RHI::BlitAspect::DepthStencil) == 3,
"RHI::BlitAspect changed — update ToGLBlitMask() and BlitAspectLowering");
// FenceStatus has no ToGL() — it is produced BY the backend, not consumed by
// it — so its tripwire lives with ClientWaitFence's switch instead. Pinned
// here anyway so a new member is noticed at the same place as its siblings.
static_assert(static_cast<int>(RHI::FenceStatus::Failed) == 3,
"RHI::FenceStatus changed — update OpenGLRendererAPI::ClientWaitFence");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the enum tripwires detect appended members.

These assertions do not detect a new member appended after the current last member—for example, adding another QueryType leaves TimeElapsed == 1, so an unhandled value could still fall through the lowering default. Add an explicit Count/Max enumerator (or an equivalent maintained count) and assert that instead for every new enum.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OloEngine/tests/Rendering/RHIEnumLoweringTest.cpp` around lines 72 - 90,
Update the enum tripwires near the existing static_asserts so each checked RHI
enum exposes and asserts an explicit Count/Max value representing the number of
members, rather than asserting only an existing member value. Apply this to
IndexType, FrontFace, QueryType, MemoryResidency, BlitAspect, and FenceStatus,
and preserve the existing lowering/update guidance in the assertion messages.

Two of these were load-bearing and one was a bug introduced by the sweep.

* UIRenderer.cpp's clip-rect comment was corrupted by the sweep's own bulk
  GLint->i32 rewrite: it rendered as "i32/u32, not i32/u32" and "issue //691".
  Rewritten to say what it meant.

* Nine call sites open-coded the same identity draw-attachment list (a
  std::array, a fill loop and a span). The facade was simply missing the method:
  RestoreAllFramebufferDrawAttachments(framebufferID, count) is the
  named-framebuffer counterpart of the RestoreAllDrawBuffers(u32) that already
  existed for the BOUND framebuffer. All nine collapse to one line, across
  SceneRenderPass, DeferredLightingPass (x3), ForwardOverlayRenderPass,
  GPUDrivenOcclusionPass, DeferredGPUOcclusionPass, VirtualGeometryPass and
  GBuffer. Restoring a narrower list than the target actually has silently drops
  later shader outputs, which is exactly the off-by-one an open-coded loop
  invites.

* Three migrated readbacks logged failure and then used the buffer anyway — a
  half-fix from the sweep. DDGI now skips that probe for the frame (the update
  is amortized, so it retries); OceanFFTGpu::DebugInverseFFT2D returns empty;
  and LightProbeBaker, the one that matters, reports the probe through its
  existing outValid out-param instead of projecting undefined pixels into SH
  coefficients that get PERSISTED into LightProbeVolumeAsset.

* SetPatchVertexCount cached GL_MAX_PATCH_VERTICES but the two pre-existing
  patch-draw sites still queried it per call. Both use the cache now.

* MockRendererAPI records the draw-attachment INDICES, not just how many
  (DecalRenderPass's four modes all pass five entries, so a count cannot tell
  them apart), and CreateFence() returns a non-zero handle so tests exercise the
  success path rather than only the creation-failed branch.

* rhi_boundary_baseline.json pins measured_on_commit to 11d7cea instead of
  naming a working tree.

* The 84 / 54 / 60 figures are stated as three distinct quantities in all three
  places that carried them. An entry point can expand into more than one virtual
  (glClearTexImage becomes a float and a uint clear; the readbacks each gained a
  bool return), so dividing 60 by 84 compared an operation count with an API
  count. 54 is the size of the gap, 60 the size of the fix. Also fixes a wrong
  internal cross-reference and a sentence that promised four files and named one.

Two findings were fixed differently than suggested, both to avoid trading a
compile-time guarantee for a runtime one:

* A `default:` on the RHI::FenceStatus switch would suppress the compiler's
  exhaustiveness warning, which is what actually catches a new enumerator. The
  switch stays exhaustive with no `default:`; the post-switch fallthrough is the
  backstop and now FAILS CLOSED, because a fence gates reuse of double-buffered
  GPU resources and reporting success for an unrecognized status is the one
  answer that could hand a caller memory the GPU still owns.

* The last-ordinal static_assert genuinely cannot detect an APPENDED enumerator.
  Chasing that turned up something worse: 16 of the 19 lowering switches
  deliberately carry no `default:` label — which is what makes clang's -Wswitch
  catch an append — while OpenGLRHIConversions.h's header comment claimed
  "Every switch carries a `default:`". Reconciling the code to that comment would
  have removed the only append guard, engine-wide. The comment is corrected and
  the division of labour is now documented in the header, the test and the ADR:
  static_assert catches insert/remove/reorder, absent-`default:` + -Wswitch
  catches append, and MSVC's C4062 being off by default at /W4 makes the clang-cl
  CI job the one that enforces it. A per-enum Count sentinel was considered and
  rejected: it makes an invalid value representable in the neutral vocabulary and
  forces a dead case in every lowering switch.

Full suite 5195 run / 5191 passed / 0 failed / 4 skipped; the RHI ratchet still
reports 0 / 0 / 236 / 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@drsnuggles8

Copy link
Copy Markdown
Owner Author

Worked through all 14. Thanks — two of these were genuinely load-bearing, and one caught a bug I introduced.

Fixed as suggested

  • UIRenderer.cpp contradictory comment — this was a real bug of mine, not just prose. A bulk GLinti32 regex rewrote the comment's own counter-example into "i32/u32, not i32/u32", and a #// substitution turned #691 into //691. Rewritten.
  • Duplicated identity draw-buffer builder (the Major one) — you were right that this wanted extracting, and it turned out the facade was simply missing the method. Added RendererAPI::RestoreAllFramebufferDrawAttachments(framebufferID, count), the named-framebuffer counterpart of the RestoreAllDrawBuffers(u32) that already existed for the bound framebuffer. Nine open-coded std::array + fill-loop + span sites collapse to one line each, across SceneRenderPass, DeferredLightingPass (×3), ForwardOverlayRenderPass, GPUDrivenOcclusionPass, DeferredGPUOcclusionPass, VirtualGeometryPass and GBuffer.
  • Three readback sites logged failure but used the buffer anyway — correct, and that was a half-fix on my part. Each now acts: DDGI returns early and retries the probe next frame (the update is amortized); OceanFFTGpu::DebugInverseFFT2D returns empty. LightProbeBaker was the one that mattered most, since those SH coefficients are persisted into LightProbeVolumeAssetRenderCubemapAtPosition is now [[nodiscard]] bool, and BakeProbeAtPosition reports the probe through its existing outValid out-param rather than writing SH derived from undefined pixels to disk.
  • Cached patch-vertex cap left two per-call queries behind — right, I added the cache and didn't use it at the two pre-existing sites. Both now read m_MaxPatchVertices.
  • Mock: record indices, not just the count — agreed; DecalRenderPass's four modes all pass 5 entries, so a count cannot distinguish them. RecordedCall gained a ParamU32List.
  • Mock: CreateFence() returning 0 — good catch, that made the failure branch the only one tests could reach (and made FrameResourceManager log an error on a healthy test). Returns an incrementing non-zero handle now.
  • measured_on_commit recorded a working tree — now pinned to 11d7cea7.
  • ADR/agent-rules/header arithmetic — you're right that the entry-point count and the virtual count are different quantities and shouldn't be divided into each other. Corrected at all three sites to state both explicitly: 84 distinct entry points, 54 of them with no facade equivalent, 60 new virtuals — with a note that an entry point can expand into several (glClearTexImage → float + uint clears, mirroring VkClearColorValue's union; the readbacks each gained a bool return). 54 is the gap, 60 is the fix.
  • Wrong internal cross-reference — correct, the last-ordinal guard is the "One new guard" paragraph, not amendment (4) (which is UploadTextureSubImage2D's source-buffer format). Fixed.
  • "the last four files standing" named only one — fixed by naming all four and the distinction between them: BloomRenderPass.cpp, OITResolveRenderPass.cpp and ShaderPack.cpp named no GL* identifier so their includes just fell out; UIRenderer.cpp was the one that needed retyping first.

Two I fixed differently, with reasoning

1. default: on the RHI::FenceStatus switch. The concern is right — falling through to return true for an unrecognized status is the wrong direction for a primitive gating reuse of double-buffered GPU resources. But adding default: inside the switch would suppress the compiler's exhaustiveness warning, which is what actually catches a new enumerator at build time; it trades a build error for a silent runtime one.

So I kept the switch exhaustive with no default:, moved the success cases to return true directly, and made the post-switch fallthrough the backstop — failing closed with an error log. That satisfies the safety concern and keeps the compile-time check. Same idiom the lowering switches in OpenGLRHIConversions.h already use (fallback after the switch, not a default: label inside it).

2. Enum tripwires don't detect appended members. This is correct and it's the most useful finding in the review — but chasing it turned up something worse than the gap itself.

Appends are caught, just not by the static_assert: 16 of the 19 lowering switches deliberately carry no default: label, so clang's -Wswitch errors on an unhandled enumerator. Meanwhile the header comment on OpenGLRHIConversions.h claimed "Every switch carries a default: that logs and falls back" — which was false for those 16, and anyone "fixing" the code to match that comment would have silently removed the only guard against appends, engine-wide.

So rather than change the assertions I documented the actual division of labour, in the test, the ADR, and that header comment:

  • last-ordinal static_assert → catches insert / remove / reorder
  • absence of default: + -Wswitch → catches append
  • MSVC's C4062 is off by default even at /W4, so the clang-cl CI job is what enforces it — worth knowing before anyone drops that job
  • the three switches that do have default: (ToGLPixelFormat, ToGLPixelType, ToGLImageAccess) are intentional: their enums have many members that are legitimately invalid for those conversions, so exhaustiveness there would be noise rather than a guard

I considered a Count sentinel per enum, which would make the static_assert catch appends directly, and rejected it: it makes an invalid value representable in the neutral vocabulary (ToGL(Count) becomes well-formed) and forces a dead case in every lowering switch. Happy to revisit if you disagree.


Rebuilt clean; full suite 5195 run / 0 failed / 6 skipped (same environment-dependent skips), and the RHI ratchet still reports 0 / 0 / 236 / 0.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp (1)

1735-1748: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard debug groups by actual entry-point availability, not GLAD_GL_KHR_debug.

GLAD_GL_KHR_debug is a loader symbol and is not the same capability/API symbol the renderer is supposed to rely on when it now checks glad_glCreateTextures != nullptr elsewhere. Use glad_glPushDebugGroup != nullptr / core-version support instead so core 4.6 marker support is not skipped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp` around lines 1735 -
1748, Update OpenGLRendererAPI::PushDebugGroup and
OpenGLRendererAPI::PopDebugGroup to guard each debug-group call using actual
entry-point availability, specifically glad_glPushDebugGroup (or equivalent
core-version support), instead of GLAD_GL_KHR_debug. Preserve the early-return
behavior when the entry point is unavailable and allow core 4.6 debug markers to
execute.
OloEngine/src/OloEngine/Renderer/LightProbeBaker.h (1)

50-56: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document readback failure in the public validity contract.

BakeProbeAtPosition now also reports GPU readback failures through outValid, but its public comment still says false only means the probe is inside geometry. Update that contract so callers do not misclassify failed bakes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OloEngine/src/OloEngine/Renderer/LightProbeBaker.h` around lines 50 - 56,
Update the public documentation for BakeProbeAtPosition to state that outValid
is false both when the probe is inside geometry and when GPU readback fails.
Ensure callers are directed not to persist or project SH data when the bake is
invalid, while preserving the existing RenderCubemapAtPosition contract.
OloEngine/src/OloEngine/UI/UIRenderer.cpp (1)

80-96: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

**Validate clip dimensions before converting to u32.`

size.x/y are converted before clamping, and non-finite/negative inputs can flow into u32 or the i32 scissor intersections. Clamp or reject invalid dimensions before the conversion so the scissor rect cannot receive malformed ranges.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@OloEngine/src/OloEngine/UI/UIRenderer.cpp` around lines 80 - 96, In the UI
rectangle conversion and parent-clip intersection logic, validate that size.x
and size.y are finite and non-negative before converting them to u32 or using
them in i32 calculations. Reject or safely clamp invalid dimensions first, then
preserve the existing clipping behavior for valid sizes so the scissor rectangle
never receives malformed ranges.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp`:
- Around line 1328-1334: Remove the fixed 16-element cap in the attachment setup
around SetFramebufferDrawAttachments. Allocate or use storage sized for the
requested count, populate every attachment index from 0 through count - 1, and
preserve the existing framebuffer draw-attachment call so all requested outputs
are restored.

---

Outside diff comments:
In `@OloEngine/src/OloEngine/Renderer/LightProbeBaker.h`:
- Around line 50-56: Update the public documentation for BakeProbeAtPosition to
state that outValid is false both when the probe is inside geometry and when GPU
readback fails. Ensure callers are directed not to persist or project SH data
when the bake is invalid, while preserving the existing RenderCubemapAtPosition
contract.

In `@OloEngine/src/OloEngine/UI/UIRenderer.cpp`:
- Around line 80-96: In the UI rectangle conversion and parent-clip intersection
logic, validate that size.x and size.y are finite and non-negative before
converting them to u32 or using them in i32 calculations. Reject or safely clamp
invalid dimensions first, then preserve the existing clipping behavior for valid
sizes so the scissor rectangle never receives malformed ranges.

In `@OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp`:
- Around line 1735-1748: Update OpenGLRendererAPI::PushDebugGroup and
OpenGLRendererAPI::PopDebugGroup to guard each debug-group call using actual
entry-point availability, specifically glad_glPushDebugGroup (or equivalent
core-version support), instead of GLAD_GL_KHR_debug. Preserve the early-return
behavior when the entry point is unavailable and allow core 4.6 debug markers to
execute.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf2cbfe8-640b-43af-b5aa-9a9858d80e5b

📥 Commits

Reviewing files that changed from the base of the PR and between 11d7cea and b2606d1.

📒 Files selected for processing (23)
  • OloEngine/src/OloEngine/Renderer/Commands/FrameResourceManager.cpp
  • OloEngine/src/OloEngine/Renderer/DDGI/DDGIProbeUpdatePass.cpp
  • OloEngine/src/OloEngine/Renderer/GBuffer.cpp
  • OloEngine/src/OloEngine/Renderer/LightProbeBaker.cpp
  • OloEngine/src/OloEngine/Renderer/LightProbeBaker.h
  • OloEngine/src/OloEngine/Renderer/Ocean/OceanFFTGpu.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredGPUOcclusionPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/DeferredLightingPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/ForwardOverlayRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/GPUDrivenOcclusionPass.cpp
  • OloEngine/src/OloEngine/Renderer/Passes/SceneRenderPass.cpp
  • OloEngine/src/OloEngine/Renderer/RenderCommand.h
  • OloEngine/src/OloEngine/Renderer/RendererAPI.h
  • OloEngine/src/OloEngine/Renderer/VirtualGeometry/VirtualGeometryPass.cpp
  • OloEngine/src/OloEngine/UI/UIRenderer.cpp
  • OloEngine/src/Platform/OpenGL/OpenGLRHIConversions.h
  • OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp
  • OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.h
  • OloEngine/tests/Rendering/MockRendererAPI.h
  • OloEngine/tests/Rendering/RHIEnumLoweringTest.cpp
  • OloEngine/tests/Rendering/rhi_boundary_baseline.json
  • docs/adr/0011-rhi-neutral-resource-and-binding-model.md
  • docs/agent-rules/rhi-abstraction-boundary.md

Comment thread OloEngine/src/Platform/OpenGL/OpenGLRendererAPI.cpp Outdated
The helper added in the previous commit contradicted its own declaration
comment: that comment warns that restoring a narrower list than the target
actually has silently drops later fragment outputs, and then the implementation
clipped the list to a 16-entry stack array whenever GL_MAX_DRAW_BUFFERS was
larger.

Above 16 it now allocates instead of clipping, so GL_MAX_DRAW_BUFFERS is the
only ceiling — and that one already clamps with a warning. The stack path still
covers every framebuffer in the engine (the G-Buffer is the widest at 5), so
this costs nothing in practice; it just stops the cap being silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@drsnuggles8

Copy link
Copy Markdown
Owner Author

Good catch, and a fair one — that helper contradicted its own declaration comment. The comment warns that restoring a narrower list than the target actually has silently drops later fragment outputs, and then the implementation clipped to a 16-entry stack array whenever GL_MAX_DRAW_BUFFERS was larger.

Fixed in 2d229261: above 16 it allocates rather than clips, so GL_MAX_DRAW_BUFFERS is the only ceiling — and that one already clamps with a warning. The stack path still covers every framebuffer in the engine (the G-Buffer is the widest at 5), so this costs nothing in practice; it just stops the cap being silent.

Rebuilt clean; RHI ratchet + enum lowering (21 tests) and the render-graph / deferred / G-Buffer / scene-pass / occlusion / framebuffer / visual-evidence selection (494 tests) all pass.

@sonarqubecloud

Copy link
Copy Markdown

@drsnuggles8
drsnuggles8 merged commit 211b64e into master Jul 31, 2026
10 checks passed
@drsnuggles8
drsnuggles8 deleted the feature/vulkan-rhi-phase2-callsite-sweep-691 branch July 31, 2026 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant