diff --git a/cmd/mxl-pattern/main.go b/cmd/mxl-pattern/main.go index 857db0e..d8f2039 100644 --- a/cmd/mxl-pattern/main.go +++ b/cmd/mxl-pattern/main.go @@ -190,17 +190,29 @@ type pattern struct { } var patterns = map[string]pattern{ - "bars": { - name: "bars", - description: "75% color bars", - kernelPath: "kernels/static/colorBars.wgsl", + "ebu75": { + name: "ebu75", + description: "EBU 75% Color Bar Signal", + kernelPath: "kernels/static/ebu75.wgsl", motion: false, }, - "bars-move": { - name: "bars-move", - description: "75% color bars with moving square", - kernelPath: "kernels/dynamic/colorBars.wgsl", - motion: true, + "ebu75-move": { + name: "ebu75-move", + description: "EBU 75% Color Bar Signal with moving square", + kernelPath: "kernels/dynamic/ebu75.wgsl", + motion: false, + }, + "ebu100": { + name: "ebu100", + description: "EBU 100% Color Bar Signal", + kernelPath: "kernels/static/ebu100.wgsl", + motion: false, + }, + "ebu100-move": { + name: "ebu100-move", + description: "EBU 100% Color Bar Signal with moving square", + kernelPath: "kernels/dynamic/ebu100.wgsl", + motion: false, }, "smpte": { name: "smpte", @@ -210,7 +222,7 @@ var patterns = map[string]pattern{ }, "smpte-move": { name: "smpte-move", - description: "SMPTE RP-219 Color Bar Signal", + description: "SMPTE RP-219 Color Bar Signal with moving square", kernelPath: "kernels/dynamic/smpteBars.wgsl", motion: false, }, diff --git a/kernels/dynamic/ebu100.wgsl b/kernels/dynamic/ebu100.wgsl new file mode 100644 index 0000000..06d8160 --- /dev/null +++ b/kernels/dynamic/ebu100.wgsl @@ -0,0 +1,77 @@ +// EBU 100% bars, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2). +// One work-item per 16-byte block = 6 pixels (6 Y + 3 Cb + 3 Cr). + +struct Params { + width: u32, + height: u32, + frame: u32, // animation tick (0,1,2,... per generated frame), NOT the raw grain index + _pad0: u32, +}; + +@group(0) @binding(0) var out: array; +@group(0) @binding(1) var params: Params; + +fn bar_index(px: u32) -> u32 { + return min((px * 8u) / params.width, 7u); +} + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) gid: vec3) { + let block = gid.x; + let total = (params.width * params.height) / 6u; + if (block >= total) { + return; + } + + let x = (block * 6u) % params.width; + let py = (block * 6u) / params.width; + + // Bar order: 100% white, yellow, cyan, green, magenta, red, blue, black + let y_tab = array(940u, 877u, 754u, 691u, 313u, 250u, 127u, 64u); + let cb_tab = array(512u, 64u, 615u, 167u, 857u, 409u, 960u, 512u); + let cr_tab = array(512u, 553u, 64u, 105u, 919u, 960u, 471u, 512u); + + // Moving square: horizontal oscillation around screen center. + // frame is a small tick; converting the huge raw grain index here + // would destroy f32 precision and freeze the motion. + let centerX = f32(params.width) / 2.0; + let centerY = f32(params.height) / 2.0; + let fps = 25.0; + let t = f32(params.frame) / fps; + const squareSize = 150u; + let half = f32(squareSize) / 2.0; + let offsetPixels = sin(t * 0.5) * (centerX - half); + + let sq_x_min = centerX - half + offsetPixels; + let sq_x_max = centerX + half + offsetPixels; + let sq_y_min = centerY - half; + let sq_y_max = centerY + half; + let py_f = f32(py); + + var y: array; + var cb: array; + var cr: array; + for (var i = 0u; i < 6u; i++) { + let px = f32(x + i); + let b = bar_index(x + i); + if (px >= sq_x_min && px < sq_x_max && py_f >= sq_y_min && py_f < sq_y_max) { + // inverted bar: luma mirrored across studio range (64..940), + // chroma mirrored across neutral 512 + y[i] = 1004u - y_tab[b]; + cb[i] = 1024u - cb_tab[b]; + cr[i] = 1024u - cr_tab[b]; + } else { + y[i] = y_tab[b]; + cb[i] = cb_tab[b]; + cr[i] = cr_tab[b]; + } + } + + // v210 word layout, chroma co-sited with luma samples 0/2/4: + // w0 = Cb0|Y0<<10|Cr0<<20; w1 = Y1|Cb2<<10|Y2<<20; + // w2 = Cr2|Y3<<10|Cb4<<20; w3 = Y4|Cr4<<10|Y5<<20 + out[block * 4u + 0u] = (cb[0] & 0x3FFu) | ((y[0] & 0x3FFu) << 10u) | ((cr[0] & 0x3FFu) << 20u); + out[block * 4u + 1u] = (y[1] & 0x3FFu) | ((cb[2] & 0x3FFu) << 10u) | ((y[2] & 0x3FFu) << 20u); + out[block * 4u + 2u] = (cr[2] & 0x3FFu) | ((y[3] & 0x3FFu) << 10u) | ((cb[4] & 0x3FFu) << 20u); + out[block * 4u + 3u] = (y[4] & 0x3FFu) | ((cr[4] & 0x3FFu) << 10u) | ((y[5] & 0x3FFu) << 20u); +} diff --git a/kernels/dynamic/colorBars.wgsl b/kernels/dynamic/ebu75.wgsl similarity index 87% rename from kernels/dynamic/colorBars.wgsl rename to kernels/dynamic/ebu75.wgsl index 1a97270..d4526bd 100644 --- a/kernels/dynamic/colorBars.wgsl +++ b/kernels/dynamic/ebu75.wgsl @@ -1,4 +1,4 @@ -// 75% SMPTE color bars + moving square, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2). +// EBU 75% bars, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2). // One work-item per 16-byte block = 6 pixels (6 Y + 3 Cb + 3 Cr). struct Params { @@ -12,7 +12,7 @@ struct Params { @group(0) @binding(1) var params: Params; fn bar_index(px: u32) -> u32 { - return min((px * 7u) / params.width, 6u); + return min((px * 8u) / params.width, 7u); } @compute @workgroup_size(64) @@ -26,10 +26,10 @@ fn main(@builtin(global_invocation_id) gid: vec3) { let x = (block * 6u) % params.width; let py = (block * 6u) / params.width; - // Bar order: 75% white, yellow, cyan, green, magenta, red, blue - let y_tab = array(721u, 674u, 581u, 534u, 251u, 204u, 111u); - let cb_tab = array(512u, 176u, 589u, 253u, 771u, 435u, 848u); - let cr_tab = array(512u, 543u, 176u, 207u, 817u, 848u, 481u); + // Bar order: 75% white, yellow, cyan, green, magenta, red, blue, black + let y_tab = array(721u, 674u, 581u, 534u, 251u, 204u, 111u, 64u); + let cb_tab = array(512u, 176u, 589u, 253u, 771u, 435u, 848u, 512u); + let cr_tab = array(512u, 543u, 176u, 207u, 817u, 848u, 481u, 512u); // Moving square: horizontal oscillation around screen center. // frame is a small tick; converting the huge raw grain index here diff --git a/kernels/static/ebu100.wgsl b/kernels/static/ebu100.wgsl new file mode 100644 index 0000000..e7dd9d7 --- /dev/null +++ b/kernels/static/ebu100.wgsl @@ -0,0 +1,50 @@ +// EBU 100% bars, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2). +// One work-item per 16-byte block = 6 pixels (6 Y + 3 Cb + 3 Cr). + +struct Params { + width: u32, + height: u32, + frame: u32, // animation tick (0,1,2,... per generated frame), NOT the raw grain index + _pad0: u32, +}; + +@group(0) @binding(0) var out: array; +@group(0) @binding(1) var params: Params; + +fn bar_index(px: u32) -> u32 { + return min((px * 8u) / params.width, 7u); +} + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) gid: vec3) { + let block = gid.x; + let total = (params.width * params.height) / 6u; + if (block >= total) { + return; + } + + let x = (block * 6u) % params.width; + + // Bar order: 100% white, yellow, cyan, green, magenta, red, blue, black + let y_tab = array(940u, 877u, 754u, 691u, 313u, 250u, 127u, 64u); + let cb_tab = array(512u, 64u, 615u, 167u, 857u, 409u, 960u, 512u); + let cr_tab = array(512u, 553u, 64u, 105u, 919u, 960u, 471u, 512u); + + var y: array; + var cb: array; + var cr: array; + for (var i = 0u; i < 6u; i++) { + let b = bar_index(x + i); + y[i] = y_tab[b]; + cb[i] = cb_tab[b]; + cr[i] = cr_tab[b]; + } + + // v210 word layout, chroma co-sited with luma samples 0/2/4: + // w0 = Cb0|Y0<<10|Cr0<<20; w1 = Y1|Cb2<<10|Y2<<20; + // w2 = Cr2|Y3<<10|Cb4<<20; w3 = Y4|Cr4<<10|Y5<<20 + out[block * 4u + 0u] = (cb[0] & 0x3FFu) | ((y[0] & 0x3FFu) << 10u) | ((cr[0] & 0x3FFu) << 20u); + out[block * 4u + 1u] = (y[1] & 0x3FFu) | ((cb[2] & 0x3FFu) << 10u) | ((y[2] & 0x3FFu) << 20u); + out[block * 4u + 2u] = (cr[2] & 0x3FFu) | ((y[3] & 0x3FFu) << 10u) | ((cb[4] & 0x3FFu) << 20u); + out[block * 4u + 3u] = (y[4] & 0x3FFu) | ((cr[4] & 0x3FFu) << 10u) | ((y[5] & 0x3FFu) << 20u); +} diff --git a/kernels/static/colorBars.wgsl b/kernels/static/ebu75.wgsl similarity index 75% rename from kernels/static/colorBars.wgsl rename to kernels/static/ebu75.wgsl index 08cdfac..7102826 100644 --- a/kernels/static/colorBars.wgsl +++ b/kernels/static/ebu75.wgsl @@ -1,10 +1,10 @@ -// 75% SMPTE color bars, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2). +// EBU 75% bars, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2). // One work-item per 16-byte block = 6 pixels (6 Y + 3 Cb + 3 Cr). struct Params { width: u32, height: u32, - frame: u32, + frame: u32, // animation tick (0,1,2,... per generated frame), NOT the raw grain index _pad0: u32, }; @@ -12,7 +12,7 @@ struct Params { @group(0) @binding(1) var params: Params; fn bar_index(px: u32) -> u32 { - return min((px * 7u) / params.width, 6u); + return min((px * 8u) / params.width, 7u); } @compute @workgroup_size(64) @@ -25,10 +25,11 @@ fn main(@builtin(global_invocation_id) gid: vec3) { let x = (block * 6u) % params.width; - // Bar order: 75% white, yellow, cyan, green, magenta, red, blue - let y_tab = array(721u, 674u, 581u, 534u, 251u, 204u, 111u); - let cb_tab = array(512u, 176u, 589u, 253u, 771u, 435u, 848u); - let cr_tab = array(512u, 543u, 176u, 207u, 817u, 848u, 481u); + // Bar order: 75% white, yellow, cyan, green, magenta, red, blue, black + let y_tab = array(721u, 674u, 581u, 534u, 251u, 204u, 111u, 64u); + let cb_tab = array(512u, 176u, 589u, 253u, 771u, 435u, 848u, 512u); + let cr_tab = array(512u, 543u, 176u, 207u, 817u, 848u, 481u, 512u); + var y: array; var cb: array;