Skip to content

Commit fc467d6

Browse files
Spruill-1Copilot
andcommitted
Migrate Luminance Heatmap + Luminance Highlight + ICtCp Highlight Desat to D3D11 compute
Three more effects from the compute-migration cohort, all sharing the same template as the ICtCp Inverse Tone Map pilot: * Luminance Heatmap -- MinNits + MaxNits gpuBindable. Natural binding: Luminance Statistics .Min / .Max so the heatmap auto-ranges to the source luminance distribution. effectVersion 2 -> 3. * Luminance Highlight -- MinNits + MaxNits gpuBindable (only meaningful when TargetRange == Custom). Same natural binding as the Heatmap. effectVersion 5 -> 6. * ICtCp Highlight Desaturation -- KneeNits + PeakNits gpuBindable. Natural binding: LumStats.P95 -> KneeNits, LumStats.Max -> PeakNits for content-driven highlight rolloff. effectVersion 3 -> 4. In all three cases the migration unblocks the Phase 8c skip-readback path: the bound parameter routes upstream's structured-buffer SRV directly into a t-slot, avoiding the per-frame Map() round-trip that was previously required for cbuffer-pack updates. Verified each renders correctly on HDR Test Pattern. 154/154 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9d61b0e commit fc467d6

1 file changed

Lines changed: 111 additions & 56 deletions

File tree

Effects/ShaderLabEffects.cpp

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,41 @@ namespace ShaderLab::Effects
3737
// -----------------------------------------------------------------------
3838

3939
static const std::string s_luminanceHeatmapHLSL = R"HLSL(
40-
// Luminance Heatmap - false-color visualization of luminance
41-
Texture2D Source : register(t0);
40+
// Luminance Heatmap -- D3D11 compute, false-color luminance visualization.
41+
// MinNits / MaxNits are gpuBindable so a Luminance Statistics .Min / .Max
42+
// can drive the heatmap range automatically per source-distribution
43+
// without a CPU readback round-trip.
44+
#include "shaderlab_params.hlsli"
45+
46+
Texture2D<float4> Source : register(t0);
47+
RWTexture2D<float4> ImageOutput : register(u1);
48+
49+
SHADERLAB_GPU_BUFFER(MinNits, t1)
50+
SHADERLAB_GPU_BUFFER(MaxNits, t2)
4251
4352
cbuffer constants : register(b0) {
44-
float MinNits; // default 0.0
45-
float MaxNits; // default 10000.0
46-
uint ColormapMode; // 0=Turbo, 1=Inferno
53+
uint Width;
54+
uint Height;
55+
SHADERLAB_PARAM(float, MinNits) // default 0.0
56+
SHADERLAB_PARAM(float, MaxNits) // default 10000.0
57+
uint ColormapMode; // 0=Turbo, 1=Inferno
4758
};
4859
49-
float4 main(
50-
float4 pos : SV_POSITION,
51-
float4 uv0 : TEXCOORD0) : SV_TARGET
60+
[numthreads(8, 8, 1)]
61+
void main(uint3 dtid : SV_DispatchThreadID)
5262
{
53-
float4 color = Source.Load(int3(uv0.xy, 0));
63+
if (dtid.x >= Width || dtid.y >= Height) return;
64+
65+
SHADERLAB_LOAD_PARAM(float, MinNits)
66+
SHADERLAB_LOAD_PARAM(float, MaxNits)
67+
68+
float4 color = Source.Load(int3(dtid.xy, 0));
5469
float nits = ScRGBLuminanceNits(color.rgb);
5570
float t = saturate((nits - MinNits) / max(MaxNits - MinNits, 0.001));
5671
float3 mapped = TurboColormap(t);
57-
// Turbo colormap outputs perceptual 0-1 values.
58-
// Keep as-is in scRGB (1.0 = 80 nits SDR white) for visible display.
59-
return float4(mapped, color.a);
72+
// Turbo colormap outputs perceptual 0-1 values; keep in scRGB
73+
// (1.0 = 80 nits SDR white) for visible display.
74+
ImageOutput[dtid.xy] = float4(mapped, color.a);
6075
}
6176
)HLSL";
6277

@@ -137,39 +152,52 @@ float4 main(
137152
)HLSL";
138153

139154
static const std::string s_luminanceHighlightHLSL = R"HLSL(
140-
// Luminance Highlight
141-
// Mirrors Gamut Highlight: pixels whose luminance falls outside the
142-
// active nit range (or inside, depending on Mode) are tinted with an
143-
// overlay color.
155+
// Luminance Highlight -- D3D11 compute, mirrors Gamut Highlight but on
156+
// luminance. Pixels whose luminance falls outside (or inside, per Mode)
157+
// the active nit range are tinted with an overlay color.
158+
//
159+
// MinNits / MaxNits are gpuBindable so a Luminance Statistics .Min / .Max
160+
// (or Working Space.MinNits / .PeakNits) can drive the range
161+
// automatically without a CPU readback round-trip.
144162
//
145163
// TargetRange selects the [min,max] nit window:
146164
// 0 = SDR (0 .. 80 nits)
147165
// 1 = HDR 400 (0 .. 400 nits)
148166
// 2 = HDR 1000 (0 .. 1000 nits)
149167
// 3 = HDR 4000 (0 .. 4000 nits)
150168
// 4 = HDR 10000 (0 .. 10000 nits)
151-
// 5 = Custom (use MinNits / MaxNits sliders directly; bind
152-
// to `Working Space.MinNits/PeakNits` for
153-
// monitor-matched analysis.)
154-
Texture2D Source : register(t0);
169+
// 5 = Custom (use MinNits / MaxNits sliders directly)
170+
#include "shaderlab_params.hlsli"
171+
172+
Texture2D<float4> Source : register(t0);
173+
RWTexture2D<float4> ImageOutput : register(u1);
174+
175+
SHADERLAB_GPU_BUFFER(MinNits, t1)
176+
SHADERLAB_GPU_BUFFER(MaxNits, t2)
155177
156178
cbuffer constants : register(b0) {
157-
uint TargetRange;
158-
float MinNits;
159-
float MaxNits;
179+
uint Width;
180+
uint Height;
181+
uint TargetRange;
182+
SHADERLAB_PARAM(float, MinNits)
183+
SHADERLAB_PARAM(float, MaxNits)
160184
float OverlayR;
161185
float OverlayG;
162186
float OverlayB;
163187
float OverlayStrength;
164-
uint Mode; // 0 = Out-of-Range, 1 = In-Range
188+
uint Mode; // 0 = Out-of-Range, 1 = In-Range
165189
};
166190
167-
float4 main(
168-
float4 pos : SV_POSITION,
169-
float4 uv0 : TEXCOORD0) : SV_TARGET
191+
[numthreads(8, 8, 1)]
192+
void main(uint3 dtid : SV_DispatchThreadID)
170193
{
171-
float4 color = Source.Load(int3(uv0.xy, 0));
172-
if (color.a < 0.001) return color;
194+
if (dtid.x >= Width || dtid.y >= Height) return;
195+
196+
SHADERLAB_LOAD_PARAM(float, MinNits)
197+
SHADERLAB_LOAD_PARAM(float, MaxNits)
198+
199+
float4 color = Source.Load(int3(dtid.xy, 0));
200+
if (color.a < 0.001) { ImageOutput[dtid.xy] = color; return; }
173201
174202
// ScRGBLuminanceNits: scRGB luminance * 80 (1.0 = 80 nits SDR white).
175203
float nits = ScRGBLuminanceNits(color.rgb);
@@ -192,14 +220,13 @@ float4 main(
192220
effMax = MaxNits;
193221
}
194222
195-
// Defensive: degenerate range collapses to "everything out-of-range".
196223
bool outOfRange = (effMin >= effMax) || (nits < effMin) || (nits > effMax);
197224
bool highlight = (Mode > 0.5) ? !outOfRange : outOfRange;
198225
if (highlight) {
199226
float3 overlay = float3(OverlayR, OverlayG, OverlayB);
200227
color.rgb = lerp(color.rgb, overlay, OverlayStrength);
201228
}
202-
return color;
229+
ImageOutput[dtid.xy] = color;
203230
}
204231
)HLSL";
205232

@@ -307,19 +334,24 @@ float4 main(
307334
const auto& colorMath = GetColorMathHLSL();
308335

309336
// ---- Luminance Heatmap ----
337+
// D3D11 compute -- MinNits / MaxNits gpuBindable.
310338
{
311339
ShaderLabEffectDescriptor desc;
312340
desc.name = L"Luminance Heatmap";
313-
desc.effectId = L"Luminance Heatmap"; desc.effectVersion = 2;
341+
desc.effectId = L"Luminance Heatmap"; desc.effectVersion = 3;
314342
desc.category = L"Analysis";
315343
desc.subcategory = L"Highlights";
316-
desc.shaderType = Graph::CustomShaderType::PixelShader;
344+
desc.shaderType = Graph::CustomShaderType::D3D11ComputeShader;
345+
desc.hasImageOutput = true;
346+
desc.threadGroupX = 8;
347+
desc.threadGroupY = 8;
348+
desc.threadGroupZ = 1;
317349
desc.hlslSource = colorMath + s_luminanceHeatmapHLSL;
318350
desc.inputNames = { L"Source" };
319351
desc.parameters = {
320-
{ L"MinNits", L"float", 0.0f, 0.0f, 10000.0f, 1.0f },
321-
{ L"MaxNits", L"float", 10000.0f, 0.0f, 10000.0f, 100.0f },
322-
{ L"ColormapMode", L"float", 0.0f, 0.0f, 1.0f, 1.0f, { L"Turbo", L"Inferno" } },
352+
Graph::ParameterDefinition{ L"MinNits", L"float", 0.0f, 0.0f, 10000.0f, 1.0f, {}, L"", true },
353+
Graph::ParameterDefinition{ L"MaxNits", L"float", 10000.0f, 0.0f, 10000.0f, 100.0f, {}, L"", true },
354+
Graph::ParameterDefinition{ L"ColormapMode", L"float", 0.0f, 0.0f, 1.0f, 1.0f, { L"Turbo", L"Inferno" } },
323355
};
324356
m_effects.push_back(std::move(desc));
325357
}
@@ -363,18 +395,22 @@ float4 main(
363395
{
364396
ShaderLabEffectDescriptor desc;
365397
desc.name = L"Luminance Highlight";
366-
desc.effectId = L"Luminance Highlight"; desc.effectVersion = 5;
398+
desc.effectId = L"Luminance Highlight"; desc.effectVersion = 6;
367399
desc.category = L"Analysis";
368400
desc.subcategory = L"Highlights";
369-
desc.shaderType = Graph::CustomShaderType::PixelShader;
401+
desc.shaderType = Graph::CustomShaderType::D3D11ComputeShader;
402+
desc.hasImageOutput = true;
403+
desc.threadGroupX = 8;
404+
desc.threadGroupY = 8;
405+
desc.threadGroupZ = 1;
370406
desc.hlslSource = colorMath + s_luminanceHighlightHLSL;
371407
desc.inputNames = { L"Source" };
372408
desc.parameters = {
373409
{ L"TargetRange", L"float", 0.0f, 0.0f, 5.0f, 1.0f,
374410
{ L"SDR (0-80)", L"HDR 400", L"HDR 1000", L"HDR 4000",
375411
L"HDR 10000", L"Custom" } },
376-
{ L"MinNits", L"float", 0.0f, 0.0f, 10000.0f, 1.0f, {}, L"TargetRange == 5" },
377-
{ L"MaxNits", L"float", 1000.0f, 0.0f, 10000.0f, 10.0f, {}, L"TargetRange == 5" },
412+
Graph::ParameterDefinition{ L"MinNits", L"float", 0.0f, 0.0f, 10000.0f, 1.0f, {}, L"TargetRange == 5", true },
413+
Graph::ParameterDefinition{ L"MaxNits", L"float", 1000.0f, 0.0f, 10000.0f, 10.0f, {}, L"TargetRange == 5", true },
378414
{ L"OverlayR", L"float", 1.0f, 0.0f, 1.0f, 0.01f },
379415
{ L"OverlayG", L"float", 0.0f, 0.0f, 1.0f, 0.01f },
380416
{ L"OverlayB", L"float", 1.0f, 0.0f, 1.0f, 0.01f },
@@ -2334,47 +2370,66 @@ float4 main(float4 pos : SV_POSITION, float2 uv : TEXCOORD) : SV_Target
23342370
// multiplier ramps from 1 toward (1 - Amount); above PeakNits
23352371
// the saturation is fully attenuated. Pairs naturally with
23362372
// ICtCp Tone Map: tone-map first, then desat the highlights.
2373+
// D3D11 compute -- KneeNits + PeakNits gpuBindable (typical
2374+
// bindings: LumStats.P95 -> KneeNits, LumStats.Max -> PeakNits).
23372375
{
23382376
static const std::string ictcpHighlightDesatHLSL = R"HLSL(
2339-
// ICtCp Highlight Desaturation — smooth Ct/Cp rolloff vs. I
2340-
Texture2D Source : register(t0);
2341-
SamplerState Sampler : register(s0);
2377+
// ICtCp Highlight Desaturation -- D3D11 compute, smooth Ct/Cp rolloff vs. I.
2378+
#include "shaderlab_params.hlsli"
2379+
2380+
Texture2D<float4> Source : register(t0);
2381+
RWTexture2D<float4> ImageOutput : register(u1);
2382+
2383+
SHADERLAB_GPU_BUFFER(KneeNits, t1)
2384+
SHADERLAB_GPU_BUFFER(PeakNits, t2)
23422385
23432386
cbuffer constants : register(b0) {
2344-
float KneeNits; // start of the rolloff (e.g. 200)
2345-
float PeakNits; // end of the rolloff (e.g. 1000)
2346-
float Amount; // [0..1], how far to desaturate at peak (1 = grayscale at peak)
2387+
uint Width;
2388+
uint Height;
2389+
SHADERLAB_PARAM(float, KneeNits) // start of the rolloff (e.g. 200)
2390+
SHADERLAB_PARAM(float, PeakNits) // end of the rolloff (e.g. 1000)
2391+
float Amount; // [0..1] how far to desaturate at peak
23472392
};
23482393
2349-
float4 main(float4 pos : SV_POSITION, float2 uv : TEXCOORD) : SV_Target
2394+
[numthreads(8, 8, 1)]
2395+
void main(uint3 dtid : SV_DispatchThreadID)
23502396
{
2351-
float4 color = Source.Load(int3(uv, 0));
2397+
if (dtid.x >= Width || dtid.y >= Height) return;
2398+
2399+
SHADERLAB_LOAD_PARAM(float, KneeNits)
2400+
SHADERLAB_LOAD_PARAM(float, PeakNits)
2401+
2402+
float4 color = Source.Load(int3(dtid.xy, 0));
23522403
float3 ictcp = ScRGBToICtCp(color.rgb);
23532404
2354-
// Map I (PQ) back to nits so the user's parameters mean what they say
2355-
// even though the I axis is non-linear.
2405+
// Map I (PQ) back to nits so the user's parameters mean what they
2406+
// say even though the I axis is non-linear.
23562407
float nits = IToNits(ictcp.x);
23572408
float t = saturate((nits - KneeNits) / max(PeakNits - KneeNits, 1e-3));
23582409
float scale = 1.0 - saturate(Amount) * smoothstep(0.0, 1.0, t);
23592410
23602411
ictcp.y *= scale;
23612412
ictcp.z *= scale;
23622413
float3 outRgb = ICtCpToScRGB(ictcp);
2363-
return float4(outRgb, color.a);
2414+
ImageOutput[dtid.xy] = float4(outRgb, color.a);
23642415
}
23652416
)HLSL";
23662417
ShaderLabEffectDescriptor desc;
23672418
desc.name = L"ICtCp Highlight Desaturation";
2368-
desc.effectId = L"ICtCp Highlight Desaturation"; desc.effectVersion = 3;
2419+
desc.effectId = L"ICtCp Highlight Desaturation"; desc.effectVersion = 4;
23692420
desc.category = L"Analysis";
23702421
desc.subcategory = L"Tone Mapping";
2371-
desc.shaderType = Graph::CustomShaderType::PixelShader;
2422+
desc.shaderType = Graph::CustomShaderType::D3D11ComputeShader;
2423+
desc.hasImageOutput = true;
2424+
desc.threadGroupX = 8;
2425+
desc.threadGroupY = 8;
2426+
desc.threadGroupZ = 1;
23722427
desc.hlslSource = colorMath + ictcpHighlightDesatHLSL;
23732428
desc.inputNames = { L"Source" };
23742429
desc.parameters = {
2375-
{ L"KneeNits", L"float", 200.0f, 10.0f, 5000.0f, 10.0f },
2376-
{ L"PeakNits", L"float", 1000.0f, 50.0f, 10000.0f, 50.0f },
2377-
{ L"Amount", L"float", 1.0f, 0.0f, 1.0f, 0.05f },
2430+
Graph::ParameterDefinition{ L"KneeNits", L"float", 200.0f, 10.0f, 5000.0f, 10.0f, {}, L"", true },
2431+
Graph::ParameterDefinition{ L"PeakNits", L"float", 1000.0f, 50.0f, 10000.0f, 50.0f, {}, L"", true },
2432+
{ L"Amount", L"float", 1.0f, 0.0f, 1.0f, 0.05f },
23782433
};
23792434
m_effects.push_back(std::move(desc));
23802435
}

0 commit comments

Comments
 (0)