SMPTE RP219-1 pattern signal
This commit is contained in:
+10
-4
@@ -192,16 +192,22 @@ type pattern struct {
|
|||||||
var patterns = map[string]pattern{
|
var patterns = map[string]pattern{
|
||||||
"bars": {
|
"bars": {
|
||||||
name: "bars",
|
name: "bars",
|
||||||
description: "SMPTE 75% color bars",
|
description: "75% color bars",
|
||||||
kernelPath: "kernels/static/smpteBars.wgsl",
|
kernelPath: "kernels/static/colorBars.wgsl",
|
||||||
motion: false,
|
motion: false,
|
||||||
},
|
},
|
||||||
"bars-move": {
|
"bars-move": {
|
||||||
name: "bars-move",
|
name: "bars-move",
|
||||||
description: "SMPTE 75% color bars with moving square",
|
description: "75% color bars with moving square",
|
||||||
kernelPath: "kernels/dynamic/smpteBars.wgsl",
|
kernelPath: "kernels/dynamic/colorBars.wgsl",
|
||||||
motion: true,
|
motion: true,
|
||||||
},
|
},
|
||||||
|
"smpte": {
|
||||||
|
name: "smpte",
|
||||||
|
description: "SMPTE RP-219 Color Bar Signal",
|
||||||
|
kernelPath: "kernels/static/smpteBars.wgsl",
|
||||||
|
motion: false,
|
||||||
|
},
|
||||||
"gray-bars": {
|
"gray-bars": {
|
||||||
name: "gray-bars",
|
name: "gray-bars",
|
||||||
description: "13-step grayscale bars (Y 64..940)",
|
description: "13-step grayscale bars (Y 64..940)",
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ func LoadFace(path string, size float64) (font.Face, error) {
|
|||||||
return face, nil
|
return face, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTextOverlay rasterizes text (white, anti-aliased) onto a black backing
|
// NewTextOverlay rasterizes text (white, anti-aliased) onto a black backing box.
|
||||||
// box, centered horizontally. Box width and x are multiples of 6 so the box
|
// Box width and x are multiples of 6 so the box
|
||||||
// covers whole v210 blocks and the per-frame copy is word-aligned.
|
// covers whole v210 blocks and the per-frame copy is word-aligned.
|
||||||
func NewTextOverlay(text string, frameW, frameH, posX, posY int, face font.Face) (*TextOverlay, error) {
|
func NewTextOverlay(text string, frameW, frameH, posX, posY int, face font.Face) (*TextOverlay, error) {
|
||||||
if text == "" {
|
if text == "" {
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// 75% SMPTE color 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,
|
||||||
|
_pad0: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<storage, read_write> out: array<u32>;
|
||||||
|
@group(0) @binding(1) var<uniform> params: Params;
|
||||||
|
|
||||||
|
fn bar_index(px: u32) -> u32 {
|
||||||
|
return min((px * 7u) / params.width, 6u);
|
||||||
|
}
|
||||||
|
|
||||||
|
@compute @workgroup_size(64)
|
||||||
|
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||||
|
let block = gid.x;
|
||||||
|
let total = (params.width * params.height) / 6u;
|
||||||
|
if (block >= total) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = (block * 6u) % params.width;
|
||||||
|
|
||||||
|
// Bar order: 75% white, yellow, cyan, green, magenta, red, blue
|
||||||
|
let y_tab = array<u32, 7>(721u, 674u, 581u, 534u, 251u, 204u, 111u);
|
||||||
|
let cb_tab = array<u32, 7>(512u, 176u, 589u, 253u, 771u, 435u, 848u);
|
||||||
|
let cr_tab = array<u32, 7>(512u, 543u, 176u, 207u, 817u, 848u, 481u);
|
||||||
|
|
||||||
|
var y: array<u32, 6>;
|
||||||
|
var cb: array<u32, 6>;
|
||||||
|
var cr: array<u32, 6>;
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// 75% SMPTE color bars, Rec.709, 10-bit Y'CbCr, packed as v210 (4:2:2).
|
// SMPTE RP 219-1 Color Bar Pattern Generator
|
||||||
// One work-item per 16-byte block = 6 pixels (6 Y + 3 Cb + 3 Cr).
|
// 10-bit Y'CbCr packed as v210 (4:2:2). Compliant with SMPTE RP 219-1:2014
|
||||||
|
|
||||||
struct Params {
|
struct Params {
|
||||||
width: u32,
|
width: u32,
|
||||||
@@ -11,8 +11,8 @@ struct Params {
|
|||||||
@group(0) @binding(0) var<storage, read_write> out: array<u32>;
|
@group(0) @binding(0) var<storage, read_write> out: array<u32>;
|
||||||
@group(0) @binding(1) var<uniform> params: Params;
|
@group(0) @binding(1) var<uniform> params: Params;
|
||||||
|
|
||||||
fn bar_index(px: u32) -> u32 {
|
fn bar_index(px: u32, bars_75_width: u32) -> u32 {
|
||||||
return min((px * 7u) / params.width, 6u);
|
return min((px * 7u) / bars_75_width, 6u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@compute @workgroup_size(64)
|
@compute @workgroup_size(64)
|
||||||
@@ -24,25 +24,109 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let x = (block * 6u) % params.width;
|
let x = (block * 6u) % params.width;
|
||||||
|
let y_px = (block * 6u) / params.width;
|
||||||
|
|
||||||
// Bar order: 75% white, yellow, cyan, green, magenta, red, blue
|
// 75% Color Table: white, yellow, cyan, green, magenta, red, blue
|
||||||
let y_tab = array<u32, 7>(721u, 674u, 581u, 534u, 251u, 204u, 111u);
|
let y_tab = array<u32, 7>(721u, 674u, 581u, 534u, 251u, 204u, 111u);
|
||||||
let cb_tab = array<u32, 7>(512u, 176u, 589u, 253u, 771u, 435u, 848u);
|
let cb_tab = array<u32, 7>(512u, 176u, 589u, 253u, 771u, 435u, 848u);
|
||||||
let cr_tab = array<u32, 7>(512u, 543u, 176u, 207u, 817u, 848u, 481u);
|
let cr_tab = array<u32, 7>(512u, 543u, 176u, 207u, 817u, 848u, 481u);
|
||||||
|
|
||||||
|
// Layout calculations mapping the 4:3 area centered in a 16:9 canvas
|
||||||
|
let bars_width: u32 = (params.height / 3u) * 4u;
|
||||||
|
let bars_start: u32 = (params.width - bars_width) / 2u;
|
||||||
|
let bars_end: u32 = bars_width + bars_start;
|
||||||
|
let one_bar_width: u32 = bars_width / 7u;
|
||||||
|
|
||||||
|
// Strict vertical boundary calculations based on 12th parts
|
||||||
|
let unit_h: u32 = params.height / 12u;
|
||||||
|
let bound_s1: u32 = unit_h * 7u; // Row 630 on 1080p
|
||||||
|
let bound_s2: u32 = bound_s1 + unit_h; // Row 720 on 1080p
|
||||||
|
let bound_s3: u32 = bound_s2 + unit_h; // Row 810 on 1080p
|
||||||
|
|
||||||
|
// Section 3 Linear Y-Ramp boundaries
|
||||||
|
let ramp_start = bars_start + one_bar_width;
|
||||||
|
let ramp_end = bars_end;
|
||||||
|
let ramp_width = ramp_end - ramp_start;
|
||||||
|
|
||||||
var y: array<u32, 6>;
|
var y: array<u32, 6>;
|
||||||
var cb: array<u32, 6>;
|
var cb: array<u32, 6>;
|
||||||
var cr: array<u32, 6>;
|
var cr: array<u32, 6>;
|
||||||
|
|
||||||
for (var i = 0u; i < 6u; i++) {
|
for (var i = 0u; i < 6u; i++) {
|
||||||
let b = bar_index(x + i);
|
let px_x = x + i;
|
||||||
|
|
||||||
|
// SECTION 1 (Top 7/12): Main 75% Color Bars
|
||||||
|
if (y_px < bound_s1) {
|
||||||
|
if (px_x >= bars_start && px_x < bars_end) {
|
||||||
|
let b = bar_index(px_x - bars_start, bars_width);
|
||||||
y[i] = y_tab[b];
|
y[i] = y_tab[b];
|
||||||
cb[i] = cb_tab[b];
|
cb[i] = cb_tab[b];
|
||||||
cr[i] = cr_tab[b];
|
cr[i] = cr_tab[b];
|
||||||
|
} else {
|
||||||
|
// 40% Gray Flanks
|
||||||
|
y[i] = 414u; cb[i] = 512u; cr[i] = 512u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SECTION 2 (Next 1/12): 100% Cyan, -I Signal, 75% White, 100% Blue
|
||||||
|
else if (y_px >= bound_s1 && y_px < bound_s2) {
|
||||||
|
if (px_x < bars_start) {
|
||||||
|
y[i] = 754u; cb[i] = 615u; cr[i] = 64u; // 100% Cyan
|
||||||
|
} else if (px_x >= bars_start && px_x < ramp_start) {
|
||||||
|
y[i] = 244u; cb[i] = 612u; cr[i] = 395u; // -I Signal
|
||||||
|
} else if (px_x >= bars_end) {
|
||||||
|
y[i] = 127u; cb[i] = 960u; cr[i] = 471u; // 100% Blue
|
||||||
|
} else {
|
||||||
|
y[i] = 721u; cb[i] = 512u; cr[i] = 512u; // 75% White
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SECTION 3 (Next 1/12): 100% Yellow, +Q Signal, Linear Y-Ramp, 75% Red
|
||||||
|
else if (y_px >= bound_s2 && y_px < bound_s3) {
|
||||||
|
if (px_x < bars_start) {
|
||||||
|
y[i] = 877u; cb[i] = 64u; cr[i] = 553u; // 100% Yellow
|
||||||
|
} else if (px_x >= bars_start && px_x < ramp_start) {
|
||||||
|
y[i] = 141u; cb[i] = 697u; cr[i] = 606u; // +Q Signal
|
||||||
|
} else if (px_x >= bars_end) {
|
||||||
|
y[i] = 250u; cb[i] = 409u; cr[i] = 960u; // 75% Red
|
||||||
|
} else {
|
||||||
|
// Perfect linear scaling from Digital Black (64u) to Digital White (940u)
|
||||||
|
let clamped_x = clamp(px_x, ramp_start, ramp_end);
|
||||||
|
y[i] = 64u + ((clamped_x - ramp_start) * (940u - 64u)) / ramp_width;
|
||||||
|
cb[i] = 512u;
|
||||||
|
cr[i] = 512u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SECTION 4 (Bottom 3/12): Pluge Panel Calibration Blocks
|
||||||
|
else {
|
||||||
|
if (px_x >= bars_start && px_x < bars_end) {
|
||||||
|
let b = (px_x - bars_start) / one_bar_width;
|
||||||
|
|
||||||
|
if (b == 0u || b == 2u || b >= 4u) {
|
||||||
|
y[i] = 64u; cb[i] = 512u; cr[i] = 512u; // 0% Black
|
||||||
|
} else if (b == 1u) {
|
||||||
|
y[i] = 940u; cb[i] = 512u; cr[i] = 512u; // 100% White Calibration
|
||||||
|
} else if (b == 3u) {
|
||||||
|
// PLUGE Sub-bar generation (Split into 3 absolute equal fragments)
|
||||||
|
let sub_bar_offset = (px_x - bars_start) % one_bar_width;
|
||||||
|
let sub_b = (sub_bar_offset * 3u) / one_bar_width;
|
||||||
|
|
||||||
|
cb[i] = 512u;
|
||||||
|
cr[i] = 512u;
|
||||||
|
if (sub_b == 0u) {
|
||||||
|
y[i] = 46u; // -2% Sub-Black
|
||||||
|
} else if (sub_b == 1u) {
|
||||||
|
y[i] = 64u; // 0% Black
|
||||||
|
} else {
|
||||||
|
y[i] = 82u; // +2% Above-Black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 15% Gray Flanks
|
||||||
|
y[i] = 195u; cb[i] = 512u; cr[i] = 512u;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// v210 word layout, chroma co-sited with luma samples 0/2/4:
|
// Packing into standard v210 10-bit macroblock layout words
|
||||||
// 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 + 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 + 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 + 2u] = (cr[2] & 0x3FFu) | ((y[3] & 0x3FFu) << 10u) | ((cb[4] & 0x3FFu) << 20u);
|
||||||
|
|||||||
Reference in New Issue
Block a user