engine: SR downsample's horizontal pass reads its texels into registers
down_m.comp (presets M and L) unpacked the block's 16x100 source texels into a shared tile, waited for it, filtered the rows from there and waited again. Each invocation of the horizontal pass now loads the ten packed texels of four output columns of one source row straight into registers and filters them (200 such items for the block's 512 invocations): no shared source tile (12.5 KiB) and one barrier fewer. 15 -> 5 KiB of shared memory, 36 -> 17 shared-memory instructions, 36 -> 48 vector registers (32 waves as before). An output column's parity stays a run-time value (gl_NumWorkGroups.z - 1 added, the dispatch's z count is 1): with constant weights the compiler shares products between neighbouring filters and splits their fused multiply-adds, and 30% of the output pixels change (62 dB) - that build is the positive control. ACO now unpacks with a fused multiply-convert (v_fma_mix*_f16: one rounding instead of f32, then binary16), which gives the same value for each of the 2048 / 1024 codes of a channel (checked exhaustively, HDR and LDR constants). Every output frame byte-identical (M and L, HDR and LDR, and the RGB10 output variant bound in the plain one's place; 1280x720 -> 1440p, 1114x626 -> 1080p, 1920x1080 -> 1440p, the stress inputs). Only down_m.spv and down_m_rgb10.spv change. RX 9070 XT, d4r-m, interleaved, downsample stage: 720p -> 1440p 102 -> 91 us (L; M 117 -> 113), 1114x626 -> 1080p 62 -> 47 (L 61 -> 47); noise (the stress inputs, whose output does not compress) unchanged, 144 -> 144 (L 139 -> 138). Frame (L) 2213 -> 2202 us and 1963 -> 1951. Two or eight columns per invocation instead of four: 92 / 93 us. Signed-off-by: itchyfeetleech <[email protected]>
This commit is contained in:
1 parent
8ffb0aee45
commit
c90dc62112
1 file changed
+31
-18
+31
-18
@@ -4,6 +4,7 @@
|
||||
// five-tap filter whose weights depend on the output pixel's parity, limits each pass to the range of its taps
|
||||
// (no ringing), and undoes the range compression and the exposure.
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types : require
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
#include "output_store.glsl"
|
||||
// LDR (NGX's *_ldr_* kernel): the packed image is the display-referred colour itself.
|
||||
@@ -13,8 +14,7 @@
|
||||
// D4RM0003: the runtime dispatches 8x64 output pixels per workgroup.
|
||||
#define BLOCK_X 8
|
||||
#define BLOCK_Y 64
|
||||
#define TILE_W (3 * BLOCK_X / 2 + 4)
|
||||
#define TILE_H (3 * BLOCK_Y / 2 + 4)
|
||||
#define TILE_H (3 * BLOCK_Y / 2 + 4) // source rows of the block
|
||||
layout(local_size_x = BLOCK_X, local_size_y = BLOCK_Y) in;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Params
|
||||
{
|
||||
@@ -27,8 +27,19 @@ layout(set = 0, binding = 0, std430) readonly buffer Params
|
||||
layout(set = 0, binding = 1) uniform sampler2D colorTex; // render resolution; its alpha is passed through
|
||||
layout(set = 0, binding = 2, FINAL_OUTPUT_FORMAT) uniform writeonly image2D outImg;
|
||||
layout(set = 0, binding = 3, std430) readonly buffer Src { uint w[]; } src;
|
||||
shared f16vec4 tile[TILE_W * TILE_H]; // source texels around the block
|
||||
shared f16vec4 rows[TILE_H * BLOCK_X]; // after the horizontal pass
|
||||
// The horizontal pass reads the source texels straight into registers, without a shared tile: an invocation filters
|
||||
// SEG output columns of one source row from the 3 SEG / 2 + 4 texels around them.
|
||||
#define SEG 4
|
||||
#define SPAN (3 * SEG / 2 + 4)
|
||||
f16vec3 unpack(uint w)
|
||||
{
|
||||
#if LDR
|
||||
return f16vec3(vec3(float(w & 2047u) * (1.0 / 2047.0), float((w >> 11) & 2047u) * (1.0 / 2047.0), float(w >> 22) * (1.0 / 1023.0)));
|
||||
#else
|
||||
return f16vec3(vec3(float(w & 2047u) * 0.0011968734906986356, float((w >> 11) & 2047u) * 0.0011968734906986356, float(w >> 22) * 0.002394916955381632));
|
||||
#endif
|
||||
}
|
||||
// weights of taps -2 .. +2 for an even output coordinate; an odd one uses them mirrored
|
||||
// as binary16 bit patterns: NGX rounds its f32 constants to the nearest binary16 (cvt.rn), while glslang folds
|
||||
// float16_t(<literal>) toward zero (W0 became -0.041321 instead of -0.041351: 6% of the captured frame's outputs
|
||||
@@ -49,25 +60,27 @@ void main()
|
||||
{
|
||||
const ivec2 lid = ivec2(gl_LocalInvocationID.xy), pix = ivec2(gl_GlobalInvocationID.xy);
|
||||
const ivec2 base = ivec2(3 * BLOCK_X / 2, 3 * BLOCK_Y / 2) * ivec2(gl_WorkGroupID.xy) - 2;
|
||||
for (int i = lid.y * BLOCK_X + lid.x; i < TILE_W * TILE_H; i += BLOCK_X * BLOCK_Y)
|
||||
// the horizontal pass, SEG output columns x0 .. x0 + SEG - 1 of source row y per item
|
||||
const int zero = int(gl_NumWorkGroups.z) - 1;
|
||||
for (int item = lid.y * BLOCK_X + lid.x; item < TILE_H * (BLOCK_X / SEG); item += BLOCK_X * BLOCK_Y)
|
||||
{
|
||||
const ivec2 at = clamp(base + ivec2(i % TILE_W, i / TILE_W), ivec2(0), p.srcSize - 1);
|
||||
const uint w = src.w[at.y * p.srcSize.x + at.x];
|
||||
#if LDR
|
||||
tile[i] = f16vec4(vec4(float(w & 2047u) * (1.0 / 2047.0), float((w >> 11) & 2047u) * (1.0 / 2047.0), float(w >> 22) * (1.0 / 1023.0), 0.0));
|
||||
#else
|
||||
tile[i] = f16vec4(vec4(float(w & 2047u) * 0.0011968734906986356, float((w >> 11) & 2047u) * 0.0011968734906986356, float(w >> 22) * 0.002394916955381632, 0.0));
|
||||
#endif
|
||||
const int y = item / (BLOCK_X / SEG), x0 = item % (BLOCK_X / SEG) * SEG;
|
||||
const int row = clamp(base.y + y, 0, p.srcSize.y - 1) * p.srcSize.x;
|
||||
f16vec3 t[SPAN];
|
||||
[[unroll]] for (int k = 0; k < SPAN; ++k)
|
||||
t[k] = unpack(src.w[row + clamp(base.x + 3 * x0 / 2 + k, 0, p.srcSize.x - 1)]);
|
||||
[[unroll]] for (int q = 0; q < SEG; ++q)
|
||||
{
|
||||
// column x0 + q (as even as q) is centred on texel 3 (q / 2) + 2 (q & 1) + 2 of the item's. Its parity stays a
|
||||
// run-time value: with constant weights the compiler shares products between neighbouring filters and
|
||||
// splits their fused multiply-adds (30% of the outputs change)
|
||||
const int o = 3 * (q >> 1) + 2 * (q & 1) + 2;
|
||||
rows[y * BLOCK_X + x0 + q].rgb = filter5(t[o - 2], t[o - 1], t[o], t[o + 1], t[o + 2], ((q + zero) & 1) != 0);
|
||||
}
|
||||
}
|
||||
memoryBarrierShared(); barrier();
|
||||
const ivec2 c = ivec2(floor((vec2(pix) + 0.5) * 1.5)) - base;
|
||||
for (int y = lid.y; y < TILE_H; y += BLOCK_Y)
|
||||
{
|
||||
const int o = y * TILE_W + c.x;
|
||||
rows[y * BLOCK_X + lid.x].rgb = filter5(tile[o - 2].rgb, tile[o - 1].rgb, tile[o].rgb, tile[o + 1].rgb, tile[o + 2].rgb, (pix.x & 1) != 0);
|
||||
}
|
||||
memoryBarrierShared(); barrier();
|
||||
// After both shared barriers: borrowed images may extend beyond the feature's true output rectangle.
|
||||
// After the shared barrier: borrowed images may extend beyond the feature's true output rectangle.
|
||||
// M accepts even output dimensions and srcSize = outputSize/2*3, so this bounds check is exact.
|
||||
if (3 * pix.x >= 2 * p.srcSize.x || 3 * pix.y >= 2 * p.srcSize.y) return;
|
||||
const int o = c.y * BLOCK_X + lid.x;
|
||||
|
||||
Reference in new issue
Block a user