re(ui): close the gamma chain from canary's defaults -- the game writes a ramp
Continues the previous iteration, where the game was measured calling VdGetCurrentDisplayGamma at video init. The remaining link -- does it then WRITE the ramp -- is a GPU register operation (XE_GPU_REG_DC_LUT_RW_INDEX in CommandProcessor::WriteRegister), unlogged and invisible to kernel logging. Two facts from the source close it without instrumenting. 1. The swap-path gamma stage is a PURE LUT. apply_gamma_table.xesli is the whole transform: index by input*255, fetch from a 256-entry ramp buffer, output. No sRGB encode, no second transfer function. 2. The table DEFAULTS TO IDENTITY. CommandProcessor::Initialize fills it with value = i * 0x3FF / 0xFF, and its own comment says the linear default is "what games set when starting with the sRGB (return value 1) VdGetCurrentDisplayGamma". An unwritten ramp is a no-op. So the only transform is a LUT, the LUT is identity unless written, the game queries the display gamma at init, and the capture differs from our composite by gamma 1.34-1.49 -- which identity cannot produce. The guest wrote a non-identity ramp. Labelled an inference, with its weak joint named: it assumes our composite reproduces the PRE-RAMP framebuffer, which it does not exactly. What carries it is the shape -- a systematic ~1.4 fitted on flat patches across three screens is not a compositor bug. The obvious alternative, a fixed sRGB stage in the presenter, fits neither direction: an encode (^0.45) brightens and we measured darkening; a decode (^2.2) darkens far more than 1.4. Direct observation remains available and cheap, and needs the emulator only to boot: a GPU trace records gamma ramps as their own command type, or one log line at the DC_LUT register write would settle it outright. Not done. METHOD: a default value is evidence; and name the weak joint of an inference in the same breath as the conclusion.
This commit is contained in:
@@ -113,11 +113,48 @@ Per the export's own comment the returned type is "used in D3D
|
||||
SetGammaRamp/SetPWLGamma", so the game asks the question a ramp-builder asks, at
|
||||
the moment one would ask it.
|
||||
|
||||
🟡 **Still not established: that it then WRITES the ramp**, or that γ ≈ 1.34–1.49
|
||||
is that ramp. The write is a GPU register operation (`DC_LUT`), invisible to
|
||||
kernel logging — this run had Gpu logging masked off, and individual register
|
||||
writes are not logged in any case. A GPU trace records gamma ramps as a command
|
||||
type (`TraceWriter::WriteGammaRamp`), which is the next place to look.
|
||||
### 🟡 The ramp write: inferred from a closed chain, not directly observed
|
||||
|
||||
The write is a GPU register operation (`XE_GPU_REG_DC_LUT_RW_INDEX` in
|
||||
`CommandProcessor::WriteRegister`), invisible to kernel logging and unlogged in
|
||||
any case. But two facts from the source close the reasoning:
|
||||
|
||||
**1. The swap-path gamma stage is a pure LUT — nothing else.**
|
||||
`apply_gamma_table.xesli` is the whole transform:
|
||||
|
||||
```
|
||||
uint3 apply_gamma_input = uint3(texel_fetch(source, pixel).rgb * 255.0 + 0.5);
|
||||
apply_gamma_output.r = texel_fetch_buffer(xe_apply_gamma_ramp, input.r).b;
|
||||
… .g = …(input.g).g; … .b = …(input.b).r;
|
||||
```
|
||||
|
||||
An index into a 256-entry table. No sRGB encode, no second transfer function.
|
||||
|
||||
**2. The table defaults to IDENTITY.** `CommandProcessor::Initialize` fills it
|
||||
with `value = i * 0x3FF / 0xFF`, and its own comment says so: *"Initialize the
|
||||
gamma ramps to their default (linear) values — taken from what games set when
|
||||
starting with the sRGB (return value 1) `VdGetCurrentDisplayGamma`."* An unwritten
|
||||
ramp is a no-op.
|
||||
|
||||
So: the only transform is a LUT; the LUT is identity unless the guest writes it;
|
||||
the game queries the display gamma at init (measured above); and the captured
|
||||
frame differs from our composite by γ ≈ 1.34–1.49, which identity cannot produce.
|
||||
**⇒ the guest wrote a non-identity ramp.**
|
||||
|
||||
⚠️ **This is an inference, and here is its weak joint.** It assumes our composite
|
||||
faithfully reproduces the *pre-ramp* framebuffer, which it does not exactly — our
|
||||
renderer has its own inaccuracies. What makes it hold up is the *shape*: a
|
||||
systematic exponent near 1.4, fitted on flat patches, consistent across three
|
||||
screens, is not the signature of a compositor bug.
|
||||
|
||||
A fixed sRGB stage elsewhere in the presenter is the obvious alternative and does
|
||||
not fit: an sRGB **encode** (≈ `^0.45`) brightens, and we measured darkening; an
|
||||
sRGB **decode** (`^2.2`) darkens far more than 1.4.
|
||||
|
||||
✅ **Direct observation is still available and cheap**, and needs the emulator only
|
||||
to boot: a GPU trace records gamma ramps as their own command type
|
||||
(`TraceWriter::WriteGammaRamp`), or one log line at
|
||||
`XE_GPU_REG_DC_LUT_RW_INDEX` would settle it outright. Not done.
|
||||
|
||||
⚠️ And the ramp a game builds depends on the display type it is *told*. Canary
|
||||
hard-codes `2` (TV/BT.709); on hardware that is the console's display setting. So
|
||||
|
||||
Reference in New Issue
Block a user