Skip to content

PBS: honour non-sRGB colour targets in the pass hash - #584

Open
steffen-roemer wants to merge 2 commits into
OGRECave:masterfrom
orkitec:pbs-honour-non-srgb-target
Open

PBS: honour non-sRGB colour targets in the pass hash#584
steffen-roemer wants to merge 2 commits into
OGRECave:masterfrom
orkitec:pbs-honour-non-srgb-target

Conversation

@steffen-roemer

Copy link
Copy Markdown
Contributor

What

HlmsPbs::preparePassHash hardcodes hw_gamma_write=1, assuming the colour target is always sRGB. When an app renders to a UNORM (non-sRGB) swapchain, PBS writes its linear lighting result raw into the target, and the image displays gamma-crushed — a mid-albedo surface under the zenith sun reads ~0.03 instead of ~0.5, and night/indoor scenes collapse to black.

This PR derives the property from the live pass descriptor instead: if the first colour target's format is sRGB, hardware conversion is used exactly as today; otherwise hw_gamma_write is 0 and the existing !hw_gamma_write template path gamma-encodes in the shader — that path was already written for this case but was unreachable with the hardcoded property.

Why not just use an sRGB swapchain?

Engines mixing PBS 3D with byte-exact 2D/UI pipelines (or matching a legacy renderer pixel-for-pixel) legitimately run UNORM swapchains. The template code says this configuration was meant to work; only the pass-hash property disagreed.

How tested

macOS 15 / Apple Silicon, Metal RS, static build via vcpkg at master ef2e8f3: measured readback of a mid-grey PBS reference under a known directional light on a UNORM swapchain — ~0.19 before, ~0.43 after (the sqrt-encode expectation); sRGB-target rendering byte-identical before/after (property still 1 there). Soaked through an engine's full desktop suite (789 green) including an Hlms-Unlit 2D parity pixel test that stays byte-identical.

preparePassHash hardcodes hw_gamma_write=1, assuming every colour target
is sRGB. On a UNORM swapchain the linear lighting result then lands raw in
the target and displays gamma-crushed (a mid-albedo surface under a zenith
sun reads ~0.03 instead of ~0.5). Derive the property from the current
pass descriptor's first colour target instead: sRGB targets keep hardware
conversion exactly as before, and non-sRGB targets engage the existing
!hw_gamma_write template path, whose shader-side encode was already
written for this case but unreachable.
@darksylinc

darksylinc commented Aug 29, 2026

Copy link
Copy Markdown
Member

Thanks for the PR but there is a blocking issue which is why it wasn't fixed before and I just pretended it doesn't exist:

Your solution assumes that if the target is not sRGB, then it should manually output as sRGB to preserve precision.
However that is exactly what we do NOT want to do when doing HDR, as it should work in linear space.

It is simple to detect the case for RenderTargets like PFG_RGBA16_FLOAT because it's common to output in linear space. While it's likely that PFG_RGBA8_UNORM wants sRGB.

But there are targets like PFG_R10G10B10A2_UNORM and PFG_R11G11B10_FLOAT that sit right in the middle between "wants sRGB" and "wants linear".

When we analyze the problem, we can inspect that there are two distinct problems (lol I sound like ChatGPT, but I swear LLMs copied me and not the reverse):

  1. We want to output to PFG_RGBA8_UNORM.
  2. We want it to be in linear/sRGB space.

Ideally, that's what PFG_RGBA8_UNORM_SRGB is for: It signals that you want sRGB. In fact, assuming that PFG_RGBA8_UNORM should always be in sRGB is problematic if the app is rendering non-colour data (such as when gazebo renders sensor data).

But sometimes reality can be disappointing because the swapchain may not have been created by you (e.g. Qt) and you don't know or it is impossible to request PFG_RGBA8_UNORM_SRGB, thus all you can hope for is that OgreNext outputs the expected format.

Technically you can workaround this by rendering everything to an internal RenderTexture in PFG_RGBA8_UNORM_SRGB and then apply a render_quad pass with a custom material/shader that reads from PFG_RGBA8_UNORM_SRGB and outputs to PFG_RGBA8_UNORM swapchain, manually converting to sRGB. It's slightly more expensive because you need an intermediary RenderTexture (more VRAM and more bandwidth) but requires no modification to OgreNext and adapts to your specific needs.

But if you want to avoid such workaround, then something more advanced is needed to indicate "I want it in sRGB regardless of what the pixel format says", because simply assuming everything should be output in sRGB is the wrong approach.

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.

2 participants