Download scenario/3_post_process_shared.h from Arm/neural-super-sampling: direct link, hf CLI and curl.
- Browser
- Download file 36.2 kB
-
https://huggingface.co/Arm/neural-super-sampling/resolve/main/scenario/3_post_process_shared.h
- Command line
-
hf download hf://Arm/neural-super-sampling/scenario/3_post_process_shared.h
-
curl -L -o 3_post_process_shared.h https://huggingface.co/Arm/neural-super-sampling/resolve/main/scenario/3_post_process_shared.h
36.2 kB
| // | |
| // ----------------------------------------------------------------------------- | |
| // The proprietary software and information contained in this file is | |
| // confidential and may only be used by an authorized person under a valid | |
| // licensing agreement from Arm Limited or its affiliates. | |
| // | |
| // Copyright (C) 2026 Arm Limited or its affiliates. All rights reserved. | |
| // | |
| // This entire notice must be reproduced on all copies of this file and | |
| // copies of this file may only be made by an authorized person under a valid | |
| // licensing agreement from Arm Limited or its affiliates. | |
| // ----------------------------------------------------------------------------- | |
| // | |
| // Post-process build define matrix. | |
| // | |
| // This lists the defines consumed by this shared post-process header. Defaults | |
| // may be omitted by the scenario JSON, but the effective values should match | |
| // the entries below. | |
| // | |
| // static LUT / exact 2x | high quality: | |
| // -DNSS_FILTER_MODE=0 | |
| // -DNSS_INPUT_LAYOUT=0 | |
| // -DNSS_USE_HISTORY_CATMULL=1 | |
| // -DNSS_V1_SHARP_THETA=1 | |
| // | |
| // static LUT / exact 2x | mid quality: | |
| // -DNSS_FILTER_MODE=1 | |
| // -DNSS_INPUT_LAYOUT=1 | |
| // -DNSS_USE_HISTORY_CATMULL=1 | |
| // -DNSS_V1_SHARP_THETA=1 | |
| // | |
| // static LUT / exact 2x | low quality: | |
| // -DNSS_FILTER_MODE=1 | |
| // -DNSS_INPUT_LAYOUT=1 | |
| // -DNSS_USE_HISTORY_CATMULL=0 | |
| // -DNSS_V1_SHARP_THETA=1 | |
| // | |
| // non-integer dynamic LUT | high quality: | |
| // post-process: | |
| // -DNSS_FILTER_MODE=2 | |
| // -DNSS_INPUT_LAYOUT=0 | |
| // -DNSS_USE_HISTORY_CATMULL=1 | |
| // -DNSS_V1_SHARP_THETA=1 | |
| // paired LUT generator: | |
| // -DNSS_FILTER_MODE=2 | |
| // | |
| // non-integer dynamic LUT | mid quality: | |
| // post-process: | |
| // -DNSS_FILTER_MODE=3 | |
| // -DNSS_INPUT_LAYOUT=1 | |
| // -DNSS_USE_HISTORY_CATMULL=1 | |
| // -DNSS_V1_SHARP_THETA=1 | |
| // paired LUT generator: | |
| // -DNSS_FILTER_MODE=3 | |
| // | |
| // non-integer dynamic LUT | low quality: | |
| // post-process: | |
| // -DNSS_FILTER_MODE=3 | |
| // -DNSS_INPUT_LAYOUT=1 | |
| // -DNSS_USE_HISTORY_CATMULL=0 | |
| // -DNSS_V1_SHARP_THETA=1 | |
| // paired LUT generator: | |
| // -DNSS_FILTER_MODE=3 | |
| // 0: static 2x high, 1: static 2x sparse, 2: dynamic dense LUT, 3: dynamic sparse LUT. | |
| // 0: full-resolution preprocess outputs, 1: half-resolution outputs with packed nearest-depth offsets. | |
| // Matches Slang layout and includes both geometric scales and temporal controls. | |
| layout(push_constant, std430) uniform PushConstants { | |
| // ─────────────── 8-byte aligned ─────────────── | |
| // High-res output extent divided by low-res input extent. | |
| layout(offset = 0) float2 _Scale; // 8 B | |
| // Inverse of `_Scale`. | |
| layout(offset = 8) float2 _InvScale; // 8 B | |
| // High-resolution output dimensions. | |
| layout(offset = 16) int32_t2 _OutputDims; // 8 B | |
| // Low-resolution input dimensions. | |
| layout(offset = 24) int32_t2 _InputDims; // 8 B | |
| // KPN tensor dimensions. | |
| layout(offset = 32) int32_t2 _KpnDims; // 8 B | |
| // Inverse of `_OutputDims`. | |
| layout(offset = 40) float2 _InvOutputDims; // 8 B | |
| // Inverse of `_InputDims`. | |
| layout(offset = 48) float2 _InvInputDims; // 8 B | |
| // Inverse of `_KpnDims`. | |
| layout(offset = 56) float2 _InvKpnDims; // 8 B | |
| // UV scale from preprocess space into temporal-feedback space. | |
| layout(offset = 64) float2 _PaddedUvScale; // 8 B | |
| // UV scale from temporal-feedback space into KPN space. | |
| layout(offset = 72) float2 _KpnScale; // 8 B | |
| // Tile modulo used for offset-LUT pattern selection. | |
| layout(offset = 80) int32_t2 _IdxModulo; // 8 B | |
| // Exposure packed as (exposure, 1/exposure). | |
| layout(offset = 88) float2 _Exposure; // 8 B | |
| // ─────────────── 4-byte aligned ─────────────── | |
| // Temporal reset / history blending gate. | |
| layout(offset = 96) float _Reset; // 4 B | |
| // ─────────────── padding to next 8-byte member ─────────────── | |
| // Offsets [100, 103] are implicit pad to preserve Slang parity. | |
| // ─────────────── 8-byte aligned ─────────────── | |
| // Jitter-dependent tile remap offset inside the modulo lattice. | |
| layout(offset = 104) int32_t2 _LutOffset; // 8 B | |
| // Logical preprocess dimensions used to address temporal feedback. | |
| layout(offset = 112) int32_t2 _PreprocessDims; // 8 B | |
| // Total: **120 bytes** | |
| }; | |
| const float kEps = 1e-7; | |
| const half kMotionThreshold = 0.1HF; | |
| const float kMotionThresholdSq = float(kMotionThreshold) * float(kMotionThreshold); | |
| const int32_t kKpnChannels = 16; | |
| const int32_t kKpnChannels = 36; | |
| const int32_t kKpnPackedChannelsPerTexel = 4; | |
| const int32_t kKpnPackedTexelsPerPixel = kKpnChannels / kKpnPackedChannelsPerTexel; | |
| const int32_t kKpnPackedChannelShift = 2; | |
| const int32_t kKpnPackedChannelMask = kKpnPackedChannelsPerTexel - 1; | |
| const int32_t kKpnLaneX = 0; | |
| const int32_t kKpnLaneY = 1; | |
| const int32_t kKpnLaneZ = 2; | |
| const bool kUseMotionThreshold = false; | |
| // QAT metadata (`_KpnCoefficients` / `_TemporalTensor` SINT). | |
| const half2 kKpnQuant = half2(0.003937007859349251, -127.0); | |
| // Temporal params are sampled from SNORM image alias, so use SNORM dequant. | |
| const half2 kTemporalQuant = half2(0.49999999813735485, -1.0); | |
| const int32_t kLutPatternCount = 4; | |
| struct KernelPattern | |
| { | |
| int16_t2 base_offset; | |
| int16_t base_channel; | |
| int16_t _pad0; | |
| }; | |
| const int16_t4 kTapDx0 = int16_t4(0, 0, 0, 2); | |
| const int16_t4 kTapDy0 = int16_t4(0, 2, 4, 0); | |
| const int16_t4 kTapCh0 = int16_t4(0, 2, 4, 12); | |
| const int16_t4 kTapDx1 = int16_t4(2, 2, 4, 4); | |
| const int16_t4 kTapDy1 = int16_t4(2, 4, 0, 2); | |
| const int16_t4 kTapCh1 = int16_t4(14, 16, 24, 26); | |
| const int16_t2 kTapD2 = int16_t2(4, 4); | |
| const int16_t kTapCh2 = int16_t(28); | |
| // 2x scale patterns from generated NSS v1 LUT for jitter in [-0.5, 0.5), with jitter-driven tile remap via _LutOffset. | |
| const KernelPattern kKernelLut[kLutPatternCount] = KernelPattern[kLutPatternCount]( | |
| KernelPattern( | |
| int16_t2(-1, -1), int16_t(7), int16_t(0) | |
| ), | |
| KernelPattern( | |
| int16_t2(-2, -1), int16_t(1), int16_t(0) | |
| ), | |
| KernelPattern( | |
| int16_t2(-1, -2), int16_t(6), int16_t(0) | |
| ), | |
| KernelPattern( | |
| int16_t2(-2, -2), int16_t(0), int16_t(0) | |
| ) | |
| ); | |
| // 2x2 sparse low-quality mode keeps the centered 4x4 subset of the original | |
| // 6x6 KPN, preserving the generated offset-LUT tap order after pruning. | |
| const int16_t4 kTap2x2Dx[kLutPatternCount] = int16_t4[kLutPatternCount]( | |
| int16_t4(-1, -1, +1, +1), | |
| int16_t4(+0, +0, +2, +2), | |
| int16_t4(-1, +1, -1, +1), | |
| int16_t4(+0, +0, +2, +2) | |
| ); | |
| const int16_t4 kTap2x2Dy[kLutPatternCount] = int16_t4[kLutPatternCount]( | |
| int16_t4(-1, +1, -1, +1), | |
| int16_t4(-1, +1, -1, +1), | |
| int16_t4(+0, +0, +2, +2), | |
| int16_t4(+0, +2, +0, +2) | |
| ); | |
| const int16_t4 kTap2x2Ch[kLutPatternCount] = int16_t4[kLutPatternCount]( | |
| int16_t4(0, 2, 8, 10), | |
| int16_t4(4, 6, 12, 14), | |
| int16_t4(1, 9, 3, 11), | |
| int16_t4(5, 7, 13, 15) | |
| ); | |
| struct OffsetLutTap | |
| { | |
| int32_t2 lr_offset; | |
| int32_t tap_channel; | |
| bool valid; | |
| bool center; | |
| }; | |
| int32_t DecodePackedI8(uint32_t packed, uint32_t shift) | |
| { | |
| return bitfieldExtract(int32_t(packed), int32_t(shift), int32_t(8)); | |
| } | |
| OffsetLutTap DecodePackedOffsetLutTap(uint32_t packed) | |
| { | |
| OffsetLutTap tap; | |
| tap.lr_offset = int32_t2( | |
| DecodePackedI8(packed, uint32_t(0)), | |
| DecodePackedI8(packed, uint32_t(8)) | |
| ); | |
| tap.tap_channel = int32_t((packed >> uint32_t(16)) & uint32_t(0x3F)); | |
| tap.valid = ((packed >> uint32_t(22)) & uint32_t(1)) != uint32_t(0); | |
| tap.center = ((packed >> uint32_t(23)) & uint32_t(1)) != uint32_t(0); | |
| return tap; | |
| } | |
| uint32_t4 LoadOffsetLutPacked4(int32_t lut_idx, int32_t group_idx) | |
| { | |
| return texelFetch(_OffsetLutUint4Tex, int32_t2(group_idx, lut_idx), 0); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Numeric helpers | |
| //----------------------------------------------------------------------------- | |
| half MaxHalf(half x) | |
| { | |
| // Clamp to fp16 max to mimic storage/arith limits used in runtime. | |
| return min(x, 65504.HF); | |
| } | |
| half3 MaxHalf(half3 x) | |
| { | |
| return min(x, half3(65504.HF)); | |
| } | |
| half4 MaxHalf(half4 x) | |
| { | |
| return min(x, half4(65504.HF)); | |
| } | |
| int32_t2 DecodeNearestOffset(int32_t2 pixel) | |
| { | |
| // Decode packed nearest-depth offset from R8_UNORM or low-quality RG8 quad packing. | |
| float norm_code = 0.0; | |
| int32_t2 texel = clamp( | |
| int32_t2(floor((float2(pixel) + float2(0.5)) * float2(_PreprocessDims) * _InvInputDims)), | |
| int32_t2(0), | |
| _PreprocessDims - int32_t2(1) | |
| ); | |
| float2 norm_code_pair = texelFetch(_NearestDepthOffsetTex, texel, 0).rg; | |
| int32_t lane_idx = (pixel.y & int32_t(1)) * int32_t(2) + (pixel.x & int32_t(1)); | |
| int32_t packed_byte = lane_idx < int32_t(2) | |
| ? int32_t(norm_code_pair.x * 255.0 + 0.5) | |
| : int32_t(norm_code_pair.y * 255.0 + 0.5); | |
| int32_t packed_nibble = (lane_idx & int32_t(1)) == int32_t(0) | |
| ? (packed_byte & 0xF) | |
| : ((packed_byte >> 4) & 0xF); | |
| return DecodeNearestDepthCoordNibble(packed_nibble); | |
| // Use logical input domain (not physical image extent) so padded fragment | |
| // outputs preserve compute-path sampling behaviour. | |
| pixel = clamp(pixel, int32_t2(0), _InputDims - int32_t2(1)); | |
| norm_code = texelFetch(_NearestDepthOffsetTex, pixel, 0).r; | |
| int32_t code = int32_t(norm_code * 255.0 + 0.5); | |
| int32_t x = (code & 0x7) - 2; | |
| int32_t y = ((code >> 3) & 0x7) - 2; | |
| return int32_t2(x, y); | |
| } | |
| half2 LoadMotion(int32_t2 output_coord) | |
| { | |
| // Reproject motion lookup using nearest-depth dilated coordinate. | |
| int32_t2 input_coord = int32_t2(float2(output_coord) * _InvScale); | |
| int32_t2 dilation_offset = DecodeNearestOffset(input_coord); | |
| int32_t2 dilated_coord = clamp(input_coord + dilation_offset, int32_t2(0), _InputDims - int32_t2(1)); | |
| half2 v = half2(texelFetch(_MotionVectorTex, dilated_coord, 0).xy) * half2(_Scale); | |
| if (kUseMotionThreshold) { | |
| v *= half(dot(float2(v), float2(v)) > kMotionThresholdSq); | |
| } | |
| return v; | |
| } | |
| half3 LoadColourTap(int32_t2 lr_tap) | |
| { | |
| return half3(texelFetch(_ColourTex, lr_tap, 0).rgb); | |
| } | |
| half4 LoadColourTap4(int32_t2 lr_tap) | |
| { | |
| return half4(LoadColourTap(lr_tap), 1.HF); | |
| } | |
| int8_t ReadKpnParamsInt8(int32_t2 kpn_tap, int32_t channel); | |
| int8_t ReadKpnParamsInt8FromBase(int32_t kpn_texel_base_x, int32_t kpn_y, int32_t channel); | |
| int32_t KpnCoordFromInputCoord(int32_t input_coord, int32_t input_dim, int32_t kpn_dim) | |
| { | |
| int32_t safe_input_dim = max(input_dim, int32_t(1)); | |
| return min((input_coord * kpn_dim) / safe_input_dim, kpn_dim - int32_t(1)); | |
| } | |
| int16_t4 KpnCoordFromInputCoord4(int16_t4 input_coord, int32_t input_dim, int32_t kpn_dim) | |
| { | |
| int32_t safe_input_dim = max(input_dim, int32_t(1)); | |
| int32_t4 scaled = (int32_t4(input_coord) * int32_t4(kpn_dim)) / int32_t4(safe_input_dim); | |
| return int16_t4(min(scaled, int32_t4(kpn_dim - int32_t(1)))); | |
| } | |
| int32_t KpnCoordFromScaledTap(int32_t tap_coord, float scale, int32_t kpn_dim) | |
| { | |
| int32_t scaled = int32_t(floor((float(tap_coord) + 0.5 + 1e-3) * scale)); | |
| return min(scaled, kpn_dim - int32_t(1)); | |
| } | |
| int16_t4 KpnCoordFromScaledTap4(int16_t4 tap_coord, float scale, int32_t kpn_dim) | |
| { | |
| int32_t4 scaled = int32_t4(floor((float4(tap_coord) + float4(0.5 + 1e-3)) * float4(scale))); | |
| return int16_t4(min(scaled, int32_t4(kpn_dim - int32_t(1)))); | |
| } | |
| half SampleKpnWeight(int32_t tap_z, int32_t2 kpn_tap) | |
| { | |
| // KPN comes from `1_nss` as an int8 tensor with `kKpnChannels` channels. | |
| int8_t q = ReadKpnParamsInt8FromBase(kpn_tap.x, kpn_tap.y, tap_z); | |
| return max(Dequantize(half(q), kKpnQuant), EPS); | |
| } | |
| half4 LoadWarpedHistory(float2 uv) | |
| { | |
| return half4(texture(_HistoryTex, uv).rgb, 1.HF); | |
| } | |
| half4 LoadWarpedHistoryCatmull(float2 uv) | |
| { | |
| //------------------------------------------------------------------------------------ | |
| // 1) Compute Catmull–Rom weights | |
| //------------------------------------------------------------------------------------ | |
| float2 scaledUV = uv * _OutputDims; | |
| float2 baseFloor = floor(scaledUV - 0.5) + 0.5; | |
| half2 f = half2(scaledUV - baseFloor); | |
| half2 f2 = f * f; | |
| half2 f3 = f2 * f; | |
| // Catmull–Rom basis terms used by the 5-tap cross filter. | |
| half2 w0 = f2 - 0.5HF * (f3 + f); | |
| half2 w3 = 0.5HF * (f3 - f2); | |
| half2 w2 = 0.5HF * f + f2 * (2.0HF - 1.5HF * f); | |
| half2 w12 = 1.0HF + 0.5HF * f - 0.5HF * f2; // w1 + w2 | |
| // Keep axis-combination math in half2 to improve vector utilization. | |
| half2 wx02 = half2(w0.x, w3.x); // left/right X weights | |
| half2 wy02 = half2(w0.y, w3.y); // up/down Y weights | |
| half2 w_ud = half2(w12.x) * wy02; // up, down | |
| half2 w_lr = half2(w12.y) * wx02; // left, right | |
| half wCenter = w12.x * w12.y; | |
| // Fractional offsets for the center sample location. | |
| half2 dxy = w2 / max(w12, half2(EPS)); | |
| //------------------------------------------------------------------------------------ | |
| // 2) Gather the 5 taps | |
| //------------------------------------------------------------------------------------ | |
| float2 base_uv = baseFloor * _InvOutputDims; | |
| float2 center_uv = base_uv + float2(dxy) * _InvOutputDims; | |
| float left_x = base_uv.x - _InvOutputDims.x; | |
| float right_x = base_uv.x + 2.0 * _InvOutputDims.x; | |
| float up_y = base_uv.y - _InvOutputDims.y; | |
| float down_y = base_uv.y + 2.0 * _InvOutputDims.y; | |
| half4 left = half4(texture(_HistoryTex, float2(left_x, center_uv.y)).rgb, 1.HF); | |
| half4 up = half4(texture(_HistoryTex, float2(center_uv.x, up_y)).rgb, 1.HF); | |
| half4 center = half4(texture(_HistoryTex, center_uv).rgb, 1.HF); | |
| half4 right = half4(texture(_HistoryTex, float2(right_x, center_uv.y)).rgb, 1.HF); | |
| half4 down = half4(texture(_HistoryTex, float2(center_uv.x, down_y)).rgb, 1.HF); | |
| //------------------------------------------------------------------------------------ | |
| // 3) Accumulate and track min/max | |
| //------------------------------------------------------------------------------------ | |
| half4 accum = up * w_ud.x + | |
| left * w_lr.x + | |
| center* wCenter + | |
| right * w_lr.y + | |
| down * w_ud.y; | |
| half4 cmin4 = min(up, min(left, min(center, min(right, down)))); | |
| half4 cmax4 = max(up, max(left, max(center, max(right, down)))); | |
| //------------------------------------------------------------------------------------ | |
| // 4) Final color | |
| //------------------------------------------------------------------------------------ | |
| half3 color = accum.rgb * rcp(accum.w); | |
| // dering in the case where we have negative values, we don't do this all the time | |
| // as it can impose unnecessary blurring on the output | |
| color = any(lessThan(color, half3(0.HF))) ? clamp(color, cmin4.rgb, cmax4.rgb) : color; | |
| return half4(color, 1.HF); | |
| } | |
| half4 SampleKpnWeight4(int16_t4 tap_z, int16_t4 kpn_x, int16_t4 kpn_y) | |
| { | |
| int8_t4 q = int8_t4( | |
| ReadKpnParamsInt8FromBase(kpn_x.x, kpn_y.x, tap_z.x), | |
| ReadKpnParamsInt8FromBase(kpn_x.y, kpn_y.y, tap_z.y), | |
| ReadKpnParamsInt8FromBase(kpn_x.z, kpn_y.z, tap_z.z), | |
| ReadKpnParamsInt8FromBase(kpn_x.w, kpn_y.w, tap_z.w) | |
| ); | |
| return max(Dequantize(half4(q), kKpnQuant), half4(EPS)); | |
| } | |
| void NormalizeFilterMoments( | |
| half4 accum_m1, | |
| half4 accum_m2, | |
| out half4 m1, | |
| out half4 m2) | |
| { | |
| half denom = max(accum_m1.a, EPS); | |
| m1 = half4(accum_m1.rgb * rcp(denom), 0.HF); | |
| m2 = half4(accum_m2.rgb * rcp(denom), 0.HF); | |
| } | |
| int32_t2 KpnCoordForDenseFilter(int32_t2 lr_tap, int32_t2 kpn_max) | |
| { | |
| return clamp( | |
| int32_t2( | |
| KpnCoordFromInputCoord(lr_tap.x, _InputDims.x, _KpnDims.x), | |
| KpnCoordFromInputCoord(lr_tap.y, _InputDims.y, _KpnDims.y) | |
| ), | |
| int32_t2(0), | |
| kpn_max | |
| ); | |
| return clamp( | |
| int32_t2( | |
| KpnCoordFromScaledTap(lr_tap.x, _KpnScale.x, _KpnDims.x), | |
| KpnCoordFromScaledTap(lr_tap.y, _KpnScale.y, _KpnDims.y) | |
| ), | |
| int32_t2(0), | |
| kpn_max | |
| ); | |
| } | |
| void KpnCoordsForDenseFilter4( | |
| int16_t4 tap_x, | |
| int16_t4 tap_y, | |
| int16_t4 zero4, | |
| int16_t4 kpn_max_x4, | |
| int16_t4 kpn_max_y4, | |
| out int16_t4 kpn_x, | |
| out int16_t4 kpn_y) | |
| { | |
| kpn_x = clamp(KpnCoordFromInputCoord4(tap_x, _InputDims.x, _KpnDims.x), zero4, kpn_max_x4); | |
| kpn_y = clamp(KpnCoordFromInputCoord4(tap_y, _InputDims.y, _KpnDims.y), zero4, kpn_max_y4); | |
| kpn_x = clamp(KpnCoordFromScaledTap4(tap_x, _KpnScale.x, _KpnDims.x), zero4, kpn_max_x4); | |
| kpn_y = clamp(KpnCoordFromScaledTap4(tap_y, _KpnScale.y, _KpnDims.y), zero4, kpn_max_y4); | |
| } | |
| int32_t2 PackedOffsetLutModulo() | |
| { | |
| return max(int32_t2(_IdxModulo.y, _IdxModulo.x), int32_t2(1)); | |
| } | |
| void AccumulatePackedLrOffsetTap( | |
| OffsetLutTap lut_tap, | |
| int32_t2 lr_base, | |
| int32_t2 colour_max, | |
| int32_t2 kpn_max, | |
| inout half4 accum_m1, | |
| inout half4 accum_m2, | |
| inout half4 center_sample) | |
| { | |
| if (!lut_tap.valid && !lut_tap.center) { | |
| return; | |
| } | |
| int32_t2 lr_tap = clamp(lr_base + lut_tap.lr_offset, int32_t2(0), colour_max); | |
| half4 tap_col = LoadColourTap4(lr_tap); | |
| if (lut_tap.center) { | |
| center_sample = tap_col; | |
| } | |
| if (!lut_tap.valid) { | |
| return; | |
| } | |
| int32_t2 kpn_tap = KpnCoordForDenseFilter(lr_tap, kpn_max); | |
| half tap_weight = SampleKpnWeight(lut_tap.tap_channel, kpn_tap); | |
| accum_m1 += tap_col * tap_weight; | |
| accum_m2 += (tap_col * tap_col) * tap_weight; | |
| } | |
| void FilterColour( | |
| int32_t2 output_px, | |
| out half4 m1, | |
| out half4 m2, | |
| out half4 center_sample) | |
| { | |
| //------------------------------------------------------------------------- | |
| // Non-integer fastest path: the LUT stores final LR offsets, tap channel, | |
| // valid, and center bits. The LUT is generated on GPU and consumed as three | |
| // RGBA32_UINT texels per modulo tile. | |
| //------------------------------------------------------------------------- | |
| int32_t2 colour_max = _InputDims - int32_t2(1); | |
| int32_t2 kpn_max = _KpnDims - int32_t2(1); | |
| int32_t2 idx_mod_xy = PackedOffsetLutModulo(); | |
| int32_t2 tile_idx = output_px % idx_mod_xy; | |
| int32_t lut_idx = tile_idx.y * idx_mod_xy.x + tile_idx.x; | |
| int32_t2 lr_base = int32_t2(floor(float2(output_px) * _InvScale)); | |
| half4 accum_m1 = half4(0.HF); | |
| half4 accum_m2 = half4(0.HF); | |
| center_sample = half4(0.HF); | |
| uint32_t4 packed_taps0 = LoadOffsetLutPacked4(lut_idx, int32_t(0)); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps0.x), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps0.y), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps0.z), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps0.w), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| uint32_t4 packed_taps1 = LoadOffsetLutPacked4(lut_idx, int32_t(1)); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps1.x), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps1.y), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps1.z), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps1.w), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| uint32_t4 packed_taps2 = LoadOffsetLutPacked4(lut_idx, int32_t(2)); | |
| AccumulatePackedLrOffsetTap( | |
| DecodePackedOffsetLutTap(packed_taps2.x), lr_base, colour_max, kpn_max, accum_m1, accum_m2, center_sample | |
| ); | |
| NormalizeFilterMoments(accum_m1, accum_m2, m1, m2); | |
| } | |
| int16_t4 DecodePackedI8x4(uint32_t4 packed, uint32_t shift) | |
| { | |
| return int16_t4(bitfieldExtract(int32_t4(packed), int32_t(shift), int32_t(8))); | |
| } | |
| int16_t4 DecodePackedTapChannel4(uint32_t4 packed) | |
| { | |
| return int16_t4((packed >> uint32_t4(16)) & uint32_t4(0x3F)); | |
| } | |
| half4 DecodePackedValidMask4(uint32_t4 packed) | |
| { | |
| return half4((packed >> uint32_t4(22)) & uint32_t4(1)); | |
| } | |
| void FilterColour( | |
| int32_t2 output_px, | |
| out half4 m1, | |
| out half4 m2, | |
| out half4 center_sample) | |
| { | |
| //------------------------------------------------------------------------- | |
| // Non-integer sparse path: one RGBA32_UINT LUT texel stores the 4 selected | |
| // 2x2 taps for the modulo tile. The tap payload is identical to the dense | |
| // dynamic path, but the generator has already pruned the 4x4 KPN window. | |
| //------------------------------------------------------------------------- | |
| int32_t2 colour_max = _InputDims - int32_t2(1); | |
| int32_t2 kpn_max = _KpnDims - int32_t2(1); | |
| int32_t2 idx_mod_xy = PackedOffsetLutModulo(); | |
| int32_t2 tile_idx = output_px % idx_mod_xy; | |
| int32_t lut_idx = tile_idx.y * idx_mod_xy.x + tile_idx.x; | |
| int32_t2 lr_base = int32_t2(floor(float2(output_px) * _InvScale)); | |
| half4 accum_m1 = half4(0.HF); | |
| half4 accum_m2 = half4(0.HF); | |
| center_sample = half4(0.HF); | |
| uint32_t4 packed_taps = LoadOffsetLutPacked4(lut_idx, int32_t(0)); | |
| int32_t4 lr_tap_x = clamp( | |
| int32_t4(lr_base.x) + int32_t4(DecodePackedI8x4(packed_taps, uint32_t(0))), | |
| int32_t4(0), | |
| int32_t4(colour_max.x) | |
| ); | |
| int32_t4 lr_tap_y = clamp( | |
| int32_t4(lr_base.y) + int32_t4(DecodePackedI8x4(packed_taps, uint32_t(8))), | |
| int32_t4(0), | |
| int32_t4(colour_max.y) | |
| ); | |
| int16_t4 tap_x = int16_t4(lr_tap_x); | |
| int16_t4 tap_y = int16_t4(lr_tap_y); | |
| int16_t4 kpn_x; | |
| int16_t4 kpn_y; | |
| KpnCoordsForDenseFilter4( | |
| tap_x, | |
| tap_y, | |
| int16_t4(0), | |
| int16_t4(kpn_max.x), | |
| int16_t4(kpn_max.y), | |
| kpn_x, | |
| kpn_y | |
| ); | |
| f16mat4x4 taps; | |
| NSS_LOAD_TAPS_4(tap_x, tap_y, taps); | |
| half4 weights = SampleKpnWeight4(DecodePackedTapChannel4(packed_taps), kpn_x, kpn_y); | |
| weights *= DecodePackedValidMask4(packed_taps); | |
| uint32_t4 center_bits = (packed_taps >> uint32_t4(23)) & uint32_t4(1); | |
| if (center_bits.x != uint32_t(0)) { | |
| center_sample = taps[0]; | |
| } else if (center_bits.y != uint32_t(0)) { | |
| center_sample = taps[1]; | |
| } else if (center_bits.z != uint32_t(0)) { | |
| center_sample = taps[2]; | |
| } else if (center_bits.w != uint32_t(0)) { | |
| center_sample = taps[3]; | |
| } | |
| accum_m1 += taps * weights; | |
| accum_m2 += NSS_SQ_MAT(taps) * weights; | |
| NormalizeFilterMoments(accum_m1, accum_m2, m1, m2); | |
| } | |
| void FilterColour( | |
| int32_t2 output_px, | |
| out half4 m1, | |
| out half4 m2, | |
| out half4 center_sample) | |
| { | |
| //------------------------------------------------------------------------- | |
| // Mid-quality path: sparse 2x2 subset of the 6x6 KPN. | |
| //------------------------------------------------------------------------- | |
| int32_t2 colour_max = _InputDims - int32_t2(1); | |
| int32_t2 kpn_max = _KpnDims - int32_t2(1); | |
| int16_t4 colour_max_x4 = int16_t4(colour_max.x); | |
| int16_t4 colour_max_y4 = int16_t4(colour_max.y); | |
| int16_t4 kpn_max_x4 = int16_t4(kpn_max.x); | |
| int16_t4 kpn_max_y4 = int16_t4(kpn_max.y); | |
| int16_t4 zero4 = int16_t4(0); | |
| int16_t2 out_px16 = int16_t2(output_px); | |
| int32_t2 tile_idx = (output_px + _LutOffset) & int32_t2(1); | |
| int32_t lut_idx = (tile_idx.y << int32_t(1)) + tile_idx.x; | |
| half4 accum_m1 = half4(0.HF); | |
| half4 accum_m2 = half4(0.HF); | |
| int16_t4 tap_x = clamp((int16_t4(out_px16.x) + kTap2x2Dx[lut_idx]) >> int16_t(1), zero4, colour_max_x4); | |
| int16_t4 tap_y = clamp((int16_t4(out_px16.y) + kTap2x2Dy[lut_idx]) >> int16_t(1), zero4, colour_max_y4); | |
| int16_t4 kpn_x = clamp(KpnCoordFromInputCoord4(tap_x, _InputDims.x, _KpnDims.x), zero4, kpn_max_x4); | |
| int16_t4 kpn_y = clamp(KpnCoordFromInputCoord4(tap_y, _InputDims.y, _KpnDims.y), zero4, kpn_max_y4); | |
| f16mat4x4 taps; | |
| NSS_LOAD_TAPS_4(tap_x, tap_y, taps); | |
| half4 weights = SampleKpnWeight4(kTap2x2Ch[lut_idx], kpn_x, kpn_y); | |
| center_sample = (lut_idx == int32_t(3)) ? taps[0] : half4(0.HF); | |
| accum_m1 += taps * weights; | |
| accum_m2 += NSS_SQ_MAT(taps) * weights; | |
| NormalizeFilterMoments(accum_m1, accum_m2, m1, m2); | |
| } | |
| void FilterColour( | |
| int32_t2 output_px, | |
| out half4 m1, | |
| out half4 m2, | |
| out half4 center_sample) | |
| { | |
| //------------------------------------------------------------------------- | |
| // High-quality 2x path: static generated 6x6 KPN tap pattern. | |
| //------------------------------------------------------------------------- | |
| int32_t2 colour_max = _InputDims - int32_t2(1); | |
| int32_t2 kpn_max = _KpnDims - int32_t2(1); | |
| int16_t4 colour_max_x4 = int16_t4(colour_max.x); | |
| int16_t4 colour_max_y4 = int16_t4(colour_max.y); | |
| int16_t4 kpn_max_x4 = int16_t4(kpn_max.x); | |
| int16_t4 kpn_max_y4 = int16_t4(kpn_max.y); | |
| int16_t4 zero4 = int16_t4(0); | |
| int16_t2 out_px16 = int16_t2(output_px); | |
| int32_t2 tile_idx = (output_px + _LutOffset) & int32_t2(1); | |
| int32_t lut_idx = (tile_idx.y << int32_t(1)) + tile_idx.x; | |
| half4 accum_m1 = half4(0.HF); | |
| half4 accum_m2 = half4(0.HF); | |
| center_sample = half4(0.HF); | |
| KernelPattern lut = kKernelLut[lut_idx]; | |
| int16_t4 tap_x0 = clamp((int16_t4(out_px16.x + lut.base_offset.x) + kTapDx0) >> int16_t(1), zero4, colour_max_x4); | |
| int16_t4 tap_y0 = clamp((int16_t4(out_px16.y + lut.base_offset.y) + kTapDy0) >> int16_t(1), zero4, colour_max_y4); | |
| int16_t4 kpn_x0; | |
| int16_t4 kpn_y0; | |
| KpnCoordsForDenseFilter4(tap_x0, tap_y0, zero4, kpn_max_x4, kpn_max_y4, kpn_x0, kpn_y0); | |
| f16mat4x4 taps0; | |
| NSS_LOAD_TAPS_4(tap_x0, tap_y0, taps0); | |
| half4 w0 = SampleKpnWeight4(kTapCh0 + int16_t4(lut.base_channel), kpn_x0, kpn_y0); | |
| accum_m1 += taps0 * w0; | |
| accum_m2 += NSS_SQ_MAT(taps0) * w0; | |
| int16_t4 tap_x1 = clamp((int16_t4(out_px16.x + lut.base_offset.x) + kTapDx1) >> int16_t(1), zero4, colour_max_x4); | |
| int16_t4 tap_y1 = clamp((int16_t4(out_px16.y + lut.base_offset.y) + kTapDy1) >> int16_t(1), zero4, colour_max_y4); | |
| int16_t4 kpn_x1; | |
| int16_t4 kpn_y1; | |
| KpnCoordsForDenseFilter4(tap_x1, tap_y1, zero4, kpn_max_x4, kpn_max_y4, kpn_x1, kpn_y1); | |
| f16mat4x4 taps1; | |
| NSS_LOAD_TAPS_4(tap_x1, tap_y1, taps1); | |
| half4 w1 = SampleKpnWeight4(kTapCh1 + int16_t4(lut.base_channel), kpn_x1, kpn_y1); | |
| center_sample = (lut_idx == int32_t(3)) ? taps1[0] : half4(0.HF); | |
| accum_m1 += taps1 * w1; | |
| accum_m2 += NSS_SQ_MAT(taps1) * w1; | |
| int32_t2 tap2 = output_px + int32_t2(lut.base_offset) + int32_t2(kTapD2); | |
| int32_t2 lr_tap2 = clamp(tap2 >> int32_t(1), int32_t2(0), colour_max); | |
| int32_t2 kpn_tap2 = KpnCoordForDenseFilter(lr_tap2, kpn_max); | |
| half4 tap2_col = LoadColourTap4(lr_tap2); | |
| half tap2_w = SampleKpnWeight(int32_t(lut.base_channel + kTapCh2), kpn_tap2); | |
| accum_m1 += tap2_col * tap2_w; | |
| accum_m2 += (tap2_col * tap2_col) * tap2_w; | |
| NormalizeFilterMoments(accum_m1, accum_m2, m1, m2); | |
| } | |
| void SampleTemporalParams(float2 uv, out half theta, out half alpha, out half gamma) | |
| { | |
| // Temporal params live over the logical preprocess domain and may be backed | |
| // by a padded image in the scenario runtime. | |
| float2 uv_temporal = uv * _PaddedUvScale; | |
| half4 params = Dequantize(half4(textureLod(_TemporalTensor, uv_temporal, 0.0)), kTemporalQuant); | |
| half theta_in = clamp(params.x, 0.HF, 1.HF); | |
| half theta_inv = 1.HF - theta_in; | |
| half theta_a = theta_in * theta_in; | |
| half theta_b = theta_inv * theta_inv; | |
| theta = theta_a * rcp(max(theta_a + theta_b, 1e-6HF)); | |
| theta = params.x; | |
| alpha = params.y * 0.35HF + 0.05HF; | |
| gamma = params.z * 2.0HF; | |
| } | |
| void WarpHistory( | |
| int32_t2 output_px, | |
| float2 uv, | |
| out half4 warped_colour, | |
| out half onscreen) | |
| { | |
| // Reproject history with motion; report if sample stayed on-screen. | |
| half2 motion = LoadMotion(output_px); | |
| float2 reproj_uv = uv - (float2(motion) * _InvOutputDims); | |
| onscreen = half(all(greaterThanEqual(reproj_uv, float2(0.0))) && all(lessThanEqual(reproj_uv, float2(1.0)))); | |
| warped_colour = LoadWarpedHistoryCatmull(reproj_uv); | |
| warped_colour = LoadWarpedHistory(reproj_uv); | |
| } | |
| void ClampHistoryToStats( | |
| half4 m1, | |
| half4 m2, | |
| half4 warped_history, | |
| half theta, | |
| half gamma, | |
| half reset, | |
| half onscreen, | |
| out half4 rectified) | |
| { | |
| //------------------------------------------------------------------------- | |
| // Statistical clamp (AABB): | |
| // Clamp reprojected history using local filtered moments to reduce ghosts. | |
| //------------------------------------------------------------------------- | |
| half4 sigma_4 = sqrt(max(abs(m2 - m1 * m1), half4(EPS))) * half4(gamma); | |
| sigma_4.a = 0.HF; | |
| half4 aabb_min = m1 - sigma_4; | |
| half4 aabb_max = m1 + sigma_4; | |
| half4 history_clamped = mix(m1, clamp(warped_history, aabb_min, aabb_max), half4(reset)); | |
| rectified = mix(history_clamped, warped_history, half4(theta * onscreen * reset)); | |
| } | |
| half3 ClampToInvertibleRange(half3 accumulated, half inv_exposure) | |
| { | |
| // Keep value within invertible Karis range, then restore linear domain. | |
| half3 clamped_output = clamp(accumulated, half3(0.HF), half3(1.HF - EPS)); | |
| return SafeColour(MaxHalf(InverseTonemap(clamped_output) * half3(inv_exposure))); | |
| } | |
| half4 Tonemap4(half4 x) | |
| { | |
| x = clamp(x, half4(0.HF), half4(MAX_FP16)); | |
| half m = max(max(x.r, x.g), x.b); | |
| return x * rcp(half4(1.HF + m)); | |
| } | |
| half3 SimulateR11G11B10Precision(half3 rgb) | |
| { | |
| const float epsilon = 1e-12; | |
| const int32_t3 m_bits = int32_t3(6, 6, 5); | |
| const int32_t exp_bits = 5; | |
| const float3 bias = float3((1 << (exp_bits - 1)) - 1); // 15.0 | |
| const float3 exp_max = float3((1 << exp_bits) - 1); | |
| float3 val = max(float3(rgb), float3(epsilon)); | |
| float3 exp_unclamped = floor(log2(val)); | |
| float3 exp_clipped = clamp(exp_unclamped, -bias, bias + 1.0); | |
| float3 mant = val / exp2(exp_clipped) - 1.0; | |
| float3 scale = float3(int32_t3(1) << m_bits); | |
| float3 rgb_exp = clamp(exp_clipped + bias, float3(0.0), exp_max) - bias; | |
| float3 rgb_mant = clamp(round(mant * scale), float3(0.0), scale - 1.0) / scale; | |
| float3 rgb_out = (1.0 + rgb_mant) * exp2(rgb_exp); | |
| return half3(rgb_out); | |
| } | |
| void WriteColourOutTarget(int32_t2 coord, half3 out_linear); | |
| void WriteColourOut(int32_t2 coord, half3 out_linear) | |
| { | |
| half3 to_write = SafeColour(out_linear); | |
| to_write = SimulateR11G11B10Precision(to_write); | |
| WriteColourOutTarget(coord, to_write); | |
| } | |
| void PostProcessMain(int32_t2 output_px) | |
| { | |
| //------------------------------------------------------------------------- | |
| // 1) Per-pixel setup | |
| //------------------------------------------------------------------------- | |
| half exposure = half(_Exposure.x); | |
| half reset = half(_Reset); | |
| float2 uv = (float2(output_px) + float2(0.5)) * _InvOutputDims; | |
| //------------------------------------------------------------------------- | |
| // 2) Spatial filtering (KPN) + local moments | |
| //------------------------------------------------------------------------- | |
| half4 m1; | |
| half4 m2; | |
| half4 center_sample; | |
| FilterColour(output_px, m1, m2, center_sample); | |
| //------------------------------------------------------------------------- | |
| // 3) Read temporal controls and reproject history | |
| //------------------------------------------------------------------------- | |
| half theta; | |
| half alpha; | |
| half gamma; | |
| SampleTemporalParams(uv, theta, alpha, gamma); | |
| half4 warped_colour; | |
| half onscreen; | |
| WarpHistory(output_px, uv, warped_colour, onscreen); | |
| //------------------------------------------------------------------------- | |
| // 4) Clamp and rectify history | |
| //------------------------------------------------------------------------- | |
| half4 rectified; | |
| ClampHistoryToStats( | |
| m1, | |
| m2, | |
| warped_colour, | |
| theta, | |
| gamma, | |
| reset, | |
| onscreen, | |
| rectified | |
| ); | |
| //------------------------------------------------------------------------- | |
| // 5) Tonemapped accumulation | |
| //------------------------------------------------------------------------- | |
| half4 rectified_tm4 = Tonemap4(MaxHalf(rectified * half4(exposure))); | |
| half learnt_masked_alpha = alpha * center_sample.a * reset; | |
| half4 colour_to_accum_tm4 = Tonemap4(MaxHalf(center_sample * half4(exposure))); | |
| half4 accumulated4 = mix(rectified_tm4, colour_to_accum_tm4, half4(learnt_masked_alpha)); | |
| //------------------------------------------------------------------------- | |
| // 6) Convert back to linear and write outputs | |
| //------------------------------------------------------------------------- | |
| half3 out_linear = ClampToInvertibleRange(accumulated4.rgb, half(_Exposure.y)); | |
| WriteColourOut(output_px, out_linear); | |
| } | |