Skip to content

GL skybox/environment renders blown-out white (Newport Loft HDRI) — Vulkan renders it correctly #797

Closed

Description

@drsnuggles8

Symptom

Every scene using an equirectangular HDRI environment (newport_loft.hdr: PBRReference, MaterialSpheres, Sponza*, VirtualGeometry*, SSGITest, SSRTest, ContactShadowTest, SphereAreaLightShadowTest — 13 scenes) renders the background/skybox as featureless blown-out white under OpenGL, while the Vulkan backend resolves the actual loft interior (warm wood, drapes, windows — matching the well-known Newport Loft source image).

Found by the #691 Phase 8 80-scene GL-vs-Vulkan A/B sweep; it is the dominant remaining visual divergence between the backends (mean-abs pixel diff ~170/255 on the affected scenes, ~99% of pixels).

Evidence that GL is the broken side

  • The Newport Loft HDRI's interior is warm brown wood — the Vulkan render matches it; GL shows uniform white in all directions, not just toward the bright windows.
  • Reproduced identically on a master-era build (7c514e49, the visual-scripting-634 worktree's binary) — so it predates the Phase 8 branch entirely and is not a Vulkan-parity regression.
  • Divergence is already present in SceneColor (pre-post-processing), so it is in the skybox/environment render, not the tonemap chain.

What was ruled out

  • Camera pose (olo_camera_get identical on both backends).
  • The env cubemap spec formats (both backends use RGBA32F specs in EnvironmentMap.cpp).
  • The Phase 8 shader edits (GL branches of Skybox.glsl / EquirectangularToCubemap.glsl untouched; master shows the same white).

Suspects for whoever picks this up

  • The GL equirect→cubemap bake output (mip/exposure/format on the GL arm of IBLPrecompute / EnvironmentMap::GenerateIBLTextures).
  • OloEngineImageFormatToGLInternalFormat mapping for the float formats on the cubemap path.
  • GL skybox sampling LOD/exposure vs the Vulkan heap-sampled path.

The reference path tracer (docs/agent-rules/reference-path-tracer.md) can arbitrate the final look once a fix is proposed.

Part of the #691 Phase 8 verification fallout; sweep artifacts and probe methodology are described in PR #794.

Activity

  1. coderabbitai commented on Aug 14, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #404 - fix(renderer): repair reflection-probe bake (was all-black) + add vis… [merged]
    #465 - Fix/flaky fog visual evidence test [merged]
    #674 - fix(renderer): GTAO black-sky artifact — exact texelFetch HZB depth copy + ULP-tolerant far-plane test [merged]
    #750 - feat(renderer): offline reference path tracer for ground-truth GI/BRDF validation (#709) [merged]
    #794 - Vulkan Phase 8: editor-frame parity, MCP diagnostic loop, command-ordered buffers (#691) [open]


    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. added
    bugSomething isn't working
    robustnessProduction hardening / shipping robustness
    on Aug 14, 2026
  3. drsnuggles8 commented on Aug 15, 2026

    @drsnuggles8
    OwnerAuthor

    Root-caused and fixed (60cf190b on the #691 Phase 8 branch)

    The one-line cause: OpenGLFramebuffer::Invalidate called Utils::BindTexture(id) — which is glBindTextureUnit(0, id) — on every freshly created color and depth attachment. The attachment setup is fully DSA, so the bind did nothing useful; it only replaced whatever was published on texture unit 0.

    Why that whitened every HDRI sky: ConvertEquirectangularToCubemap publishes the loaded HDR pano on unit 0 (TEX_DIFFUSE), then RenderToCubemap creates its capture framebuffer — whose depth attachment landed on unit 0 — so all six face bakes sampled the depth clear value (constant 1.0) instead of the pano. White cubemap → white skybox + white-tinted IBL in all 13 HDRI scenes, on GL only. Vulkan resolves bindings at draw from published state, so it was correct all along — exactly as the sweep suggested.

    The evidence ladder (all via a new env-gated OLO_ENV_BAKE_DUMP diagnostic, kept in-tree):

    1. CPU source mean of newport_loft.hdr: (0.335, 0.236, 0.183) ✓
    2. Uploaded-GL-texture readback: identical to source ✓ (upload exonerated)
    3. Baked faces: uniform wrong values ✗
    4. A uv-visualizing shader variant: per-face uv means correct ✓ (geometry/varyings exonerated — only the sample wrong)
    5. A unit-0 occupancy probe at draw time: unit 0 held GL id 46 (the capture FB's depth attachment) where the pano (id 43) had been published — caught red-handed.

    Fix: delete both Utils::BindTexture calls and the orphaned helper; hoist the cube-mesh build ahead of the publish so no future resource-creation path can slot into the same gap.

    Verified: GL bake per-face means are now byte-identical to Vulkan's; PBRReference renders at a 1.18/255 mean pixel difference between backends (was ~174/255). Full suite green; the RHI boundary ratchet stays at its shrunken baseline; no goldens involved HDRI environments, so none needed rebaking.

    Recommend closing once #794 merges (the fix rides that branch).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrobustnessProduction hardening / shipping robustness

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions