WGPU is coming
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package generator
|
||||
|
||||
// 8x16 1-bpp bitmap font baked from DejaVu Sans Mono 13px (public-domain
|
||||
// style console glyphs). ASCII 32..127, one byte per row, LSB = leftmost pixel.
|
||||
var fontAtlas8x16 = [96 * 16]byte{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x48, 0x68, 0x28, 0xFE, 0x24, 0x24, 0x7F, 0x16, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3C, 0x16, 0x02, 0x1C, 0x38, 0x40, 0x62, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0E, 0x09, 0x09, 0x6E, 0x18, 0x76, 0xD0, 0xD0, 0x70, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x06, 0x04, 0x0C, 0x9A, 0xD3, 0x63, 0x62, 0x5C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x10, 0x10, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x10, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0C, 0x08, 0x18, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x08, 0x0C, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x4A, 0x1C, 0x1C, 0x4A, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7F, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x08, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x60, 0x20, 0x30, 0x10, 0x10, 0x08, 0x08, 0x0C, 0x04, 0x06, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x26, 0x62, 0x42, 0x5A, 0x42, 0x62, 0x26, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x22, 0x60, 0x20, 0x30, 0x18, 0x0C, 0x06, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x22, 0x60, 0x20, 0x3C, 0x60, 0x60, 0x62, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x30, 0x38, 0x28, 0x24, 0x26, 0x22, 0x7E, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3E, 0x06, 0x06, 0x3E, 0x20, 0x60, 0x60, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x06, 0x02, 0x3E, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7E, 0x60, 0x20, 0x30, 0x10, 0x18, 0x18, 0x08, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x66, 0x62, 0x26, 0x3C, 0x66, 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x26, 0x62, 0x62, 0x66, 0x7C, 0x60, 0x20, 0x1C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x08, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x38, 0x06, 0x06, 0x38, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1C, 0x70, 0x70, 0x1C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x60, 0x60, 0x30, 0x18, 0x08, 0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x46, 0x42, 0xF3, 0xC9, 0xC9, 0xC9, 0xF3, 0x02, 0x06, 0x38, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x18, 0x18, 0x3C, 0x34, 0x24, 0x66, 0x7E, 0x42, 0xC3, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3E, 0x62, 0x62, 0x62, 0x3E, 0x62, 0x42, 0x62, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x38, 0x44, 0x06, 0x02, 0x02, 0x02, 0x06, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1E, 0x22, 0x62, 0x42, 0x42, 0x42, 0x62, 0x22, 0x1E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7E, 0x06, 0x06, 0x06, 0x7E, 0x06, 0x06, 0x06, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7E, 0x06, 0x06, 0x06, 0x7E, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x46, 0x02, 0x02, 0x72, 0x42, 0x42, 0x46, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x32, 0x1E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x42, 0x22, 0x12, 0x0E, 0x1E, 0x12, 0x32, 0x62, 0x42, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x67, 0x67, 0x67, 0x5F, 0x5B, 0x5B, 0x43, 0x43, 0x43, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x46, 0x46, 0x4E, 0x4A, 0x5A, 0x52, 0x72, 0x62, 0x62, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x66, 0x62, 0x42, 0x42, 0x42, 0x62, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3E, 0x66, 0x46, 0x46, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x66, 0x62, 0x42, 0x42, 0x42, 0x62, 0x66, 0x3C, 0x30, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3E, 0x62, 0x62, 0x62, 0x3E, 0x32, 0x62, 0x42, 0xC2, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3C, 0x06, 0x02, 0x06, 0x3C, 0x60, 0x40, 0x62, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x43, 0x42, 0x62, 0x26, 0x24, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xC1, 0xC3, 0xC3, 0x5B, 0x5A, 0x5A, 0x76, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x42, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x24, 0x66, 0x43, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x43, 0x66, 0x24, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7E, 0x60, 0x20, 0x30, 0x18, 0x08, 0x04, 0x06, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x08, 0x10, 0x10, 0x30, 0x20, 0x60, 0x00, 0x00,
|
||||
0x00, 0x1C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x18, 0x3C, 0x26, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x0C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x22, 0x60, 0x7C, 0x62, 0x62, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x02, 0x02, 0x02, 0x3E, 0x66, 0x46, 0x42, 0x46, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x06, 0x06, 0x06, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x60, 0x60, 0x60, 0x60, 0x7C, 0x66, 0x62, 0x62, 0x62, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, 0x7E, 0x02, 0x46, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x70, 0x18, 0x08, 0x08, 0x7E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x66, 0x62, 0x62, 0x62, 0x66, 0x7C, 0x60, 0x20, 0x1C, 0x00,
|
||||
0x00, 0x02, 0x02, 0x02, 0x02, 0x3E, 0x66, 0x66, 0x62, 0x62, 0x62, 0x62, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x18, 0x00, 0x00, 0x00, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x10, 0x00, 0x00, 0x00, 0x1C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0E, 0x00,
|
||||
0x00, 0x06, 0x06, 0x06, 0x06, 0x66, 0x36, 0x1E, 0x1E, 0x36, 0x66, 0x46, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x70, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x62, 0x62, 0x62, 0x62, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x66, 0x46, 0x42, 0x46, 0x66, 0x3E, 0x02, 0x02, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x66, 0x62, 0x62, 0x62, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x26, 0x06, 0x3C, 0x20, 0x22, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x08, 0x7E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x78, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x62, 0x62, 0x62, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x62, 0x26, 0x24, 0x34, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0xC3, 0x5A, 0x5A, 0x7E, 0x66, 0x26, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x24, 0x18, 0x18, 0x3C, 0x24, 0x42, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x66, 0x24, 0x24, 0x3C, 0x18, 0x18, 0x18, 0x08, 0x06, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x20, 0x10, 0x18, 0x0C, 0x04, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x30, 0x18, 0x18, 0x18, 0x08, 0x0E, 0x08, 0x18, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00,
|
||||
0x00, 0x0E, 0x08, 0x18, 0x18, 0x18, 0x30, 0x18, 0x18, 0x18, 0x08, 0x08, 0x0E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7E, 0x00, 0x00,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package generator
|
||||
|
||||
type FrameGenerator interface {
|
||||
GenerateFrame(dest []byte, frameIndex int) error
|
||||
Close() error
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
// OpenCL pattern gen
|
||||
package generator
|
||||
|
||||
/*
|
||||
#cgo linux LDFLAGS: -lOpenCL
|
||||
#cgo windows LDFLAGS: -lOpenCL
|
||||
|
||||
#include <CL/cl.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static cl_program build_cl_program(cl_context ctx, cl_device_id dev, const char* src, cl_int* err) {
|
||||
cl_program prog = clCreateProgramWithSource(ctx, 1, &src, NULL, err);
|
||||
if (*err != CL_SUCCESS) return NULL;
|
||||
*err = clBuildProgram(prog, 1, &dev, NULL, NULL, NULL);
|
||||
return prog;
|
||||
}
|
||||
|
||||
static const char* get_build_log(cl_program prog, cl_device_id dev) {
|
||||
static char buf[4096];
|
||||
size_t n = 0;
|
||||
if (clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, sizeof(buf) - 1, buf, &n) != CL_SUCCESS) return "";
|
||||
buf[n < sizeof(buf) - 1 ? n : sizeof(buf) - 1] = '\0';
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type OpenCLGenerator struct {
|
||||
ctx C.cl_context
|
||||
queue C.cl_command_queue
|
||||
kernel C.cl_kernel
|
||||
prog C.cl_program
|
||||
dev C.cl_device_id
|
||||
atlasBuf C.cl_mem
|
||||
textBuf C.cl_mem
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
// TextConfig describes a burn-in text overlay rendered by the kernel
|
||||
// with the baked 8x16 bitmap font. FG/BG are 10-bit {Y, Cb, Cr}.
|
||||
type TextConfig struct {
|
||||
Str string
|
||||
X, Y int
|
||||
Scale int
|
||||
FG [3]uint32
|
||||
BG [3]uint32
|
||||
Boxed bool
|
||||
}
|
||||
|
||||
func (g *OpenCLGenerator) setArg(idx int, size C.size_t, p unsafe.Pointer) error {
|
||||
if err := C.clSetKernelArg(g.kernel, C.cl_uint(idx), size, p); err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: set arg %d failed: %d", idx, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setTextArgs pushes the overlay arguments (kernel args 5..16) to the kernel.
|
||||
// buf is the text byte buffer; a placeholder is allowed when len(cfg.Str) == 0,
|
||||
// because the kernel checks text_len before dereferencing the text pointer.
|
||||
func (g *OpenCLGenerator) setTextArgs(buf C.cl_mem, cfg TextConfig) error {
|
||||
textLen := C.int(len(cfg.Str))
|
||||
tx, ty := C.int(cfg.X), C.int(cfg.Y)
|
||||
scale := C.int(cfg.Scale)
|
||||
if scale < 1 {
|
||||
scale = 1
|
||||
}
|
||||
hasBG := C.int(0)
|
||||
if cfg.Boxed {
|
||||
hasBG = 1
|
||||
}
|
||||
fgY, fgCb, fgCr := C.cl_uint(cfg.FG[0]), C.cl_uint(cfg.FG[1]), C.cl_uint(cfg.FG[2])
|
||||
bgY, bgCb, bgCr := C.cl_uint(cfg.BG[0]), C.cl_uint(cfg.BG[1]), C.cl_uint(cfg.BG[2])
|
||||
args := []struct {
|
||||
idx int
|
||||
p unsafe.Pointer
|
||||
sz C.size_t
|
||||
}{
|
||||
{5, unsafe.Pointer(&buf), C.sizeof_cl_mem},
|
||||
{6, unsafe.Pointer(&textLen), C.sizeof_int},
|
||||
{7, unsafe.Pointer(&tx), C.sizeof_int},
|
||||
{8, unsafe.Pointer(&ty), C.sizeof_int},
|
||||
{9, unsafe.Pointer(&scale), C.sizeof_int},
|
||||
{10, unsafe.Pointer(&hasBG), C.sizeof_int},
|
||||
{11, unsafe.Pointer(&fgY), C.sizeof_cl_uint},
|
||||
{12, unsafe.Pointer(&fgCb), C.sizeof_cl_uint},
|
||||
{13, unsafe.Pointer(&fgCr), C.sizeof_cl_uint},
|
||||
{14, unsafe.Pointer(&bgY), C.sizeof_cl_uint},
|
||||
{15, unsafe.Pointer(&bgCb), C.sizeof_cl_uint},
|
||||
{16, unsafe.Pointer(&bgCr), C.sizeof_cl_uint},
|
||||
}
|
||||
for _, a := range args {
|
||||
if err := g.setArg(a.idx, a.sz, a.p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetText installs (or, with an empty string, removes) the burn-in overlay.
|
||||
func (g *OpenCLGenerator) SetText(cfg TextConfig) error {
|
||||
if len(cfg.Str) > 0 && (cfg.Scale < 1 || cfg.Scale > 8) {
|
||||
return fmt.Errorf("opencl: text scale must be 1..8, got %d", cfg.Scale)
|
||||
}
|
||||
if len(cfg.Str) > 1024 {
|
||||
return fmt.Errorf("opencl: text too long: %d bytes", len(cfg.Str))
|
||||
}
|
||||
|
||||
var err C.cl_int
|
||||
buf := g.atlasBuf // placeholder: text_len = 0 keeps the kernel off it
|
||||
if len(cfg.Str) > 0 {
|
||||
cData := C.CBytes([]byte(cfg.Str))
|
||||
defer C.free(cData)
|
||||
buf = C.clCreateBuffer(g.ctx, C.CL_MEM_READ_ONLY|C.CL_MEM_COPY_HOST_PTR,
|
||||
C.size_t(len(cfg.Str)), cData, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: text buffer upload failed: %d", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.setTextArgs(buf, cfg); err != nil {
|
||||
if len(cfg.Str) > 0 {
|
||||
C.clReleaseMemObject(buf)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if g.textBuf != nil && g.textBuf != buf {
|
||||
C.clReleaseMemObject(g.textBuf)
|
||||
}
|
||||
if len(cfg.Str) > 0 {
|
||||
g.textBuf = buf
|
||||
} else {
|
||||
g.textBuf = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewOpenCLGenerator(width, height uint, kernelPath string) (*OpenCLGenerator, error) {
|
||||
var err C.cl_int
|
||||
var platform C.cl_platform_id
|
||||
|
||||
if err = C.clGetPlatformIDs(1, &platform, nil); err != C.CL_SUCCESS {
|
||||
return nil, fmt.Errorf("opencl: platform not found: %d", err)
|
||||
}
|
||||
|
||||
g := &OpenCLGenerator{width: int(width), height: int(height)}
|
||||
if err = C.clGetDeviceIDs(platform, C.CL_DEVICE_TYPE_GPU, 1, &g.dev, nil); err != C.CL_SUCCESS {
|
||||
return nil, fmt.Errorf("opencl: no suitable gpu device found")
|
||||
}
|
||||
|
||||
g.ctx = C.clCreateContext(nil, 1, &g.dev, nil, nil, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
return nil, fmt.Errorf("opencl: ctx creation error")
|
||||
}
|
||||
|
||||
g.queue = C.clCreateCommandQueueWithProperties(g.ctx, g.dev, nil, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
g.queue = C.clCreateCommandQueue(g.ctx, g.dev, 0, &err)
|
||||
}
|
||||
|
||||
src, errGo := os.ReadFile(kernelPath)
|
||||
if errGo != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: read kernel error: %w", errGo)
|
||||
}
|
||||
|
||||
cSrc := C.CString(string(src))
|
||||
defer C.free(unsafe.Pointer(cSrc))
|
||||
|
||||
g.prog = C.build_cl_program(g.ctx, g.dev, cSrc, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
msg := "clCreateProgramWithSource failed"
|
||||
if g.prog != nil {
|
||||
msg = C.GoString(C.get_build_log(g.prog, g.dev))
|
||||
}
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: compilation failed: %s", msg)
|
||||
}
|
||||
|
||||
cKName := C.CString("generate_v210_pattern")
|
||||
defer C.free(unsafe.Pointer(cKName))
|
||||
g.kernel = C.clCreateKernel(g.prog, cKName, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: kernel init failed")
|
||||
}
|
||||
|
||||
g.atlasBuf = C.clCreateBuffer(g.ctx, C.CL_MEM_READ_ONLY|C.CL_MEM_COPY_HOST_PTR,
|
||||
C.size_t(len(fontAtlas8x16)), unsafe.Pointer(&fontAtlas8x16[0]), &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: font atlas upload failed: %d", err)
|
||||
}
|
||||
if err := g.setArg(4, C.sizeof_cl_mem, unsafe.Pointer(&g.atlasBuf)); err != nil {
|
||||
g.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Overlay defaults: no text, white on black if SetText is ever called.
|
||||
if err := g.setTextArgs(g.atlasBuf, TextConfig{
|
||||
Scale: 1,
|
||||
FG: [3]uint32{940, 512, 512},
|
||||
BG: [3]uint32{64, 512, 512},
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *OpenCLGenerator) GenerateFrame(dest []byte, frameIndex int) error {
|
||||
var err C.cl_int
|
||||
byteSize := C.size_t(len(dest))
|
||||
|
||||
// ВАЖНО: Мы маппим память MXL SDK ([]byte) напрямую в OpenCL буфер.
|
||||
// GPU запишет данные прямо в общую память MXL без лишнего копирования (Zero-Copy).
|
||||
if want := (g.width * g.height / 6) * 16; len(dest) < want {
|
||||
return fmt.Errorf("opencl: dest too small: %d bytes, need %d", len(dest), want)
|
||||
}
|
||||
clMem := C.clCreateBuffer(
|
||||
g.ctx,
|
||||
C.CL_MEM_WRITE_ONLY|C.CL_MEM_USE_HOST_PTR,
|
||||
byteSize,
|
||||
unsafe.Pointer(&dest[0]),
|
||||
&err,
|
||||
)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: mxl host pointer mapping failed: %d", err)
|
||||
}
|
||||
defer C.clReleaseMemObject(clMem)
|
||||
|
||||
if err := g.setArg(0, C.sizeof_cl_mem, unsafe.Pointer(&clMem)); err != nil {
|
||||
return err
|
||||
}
|
||||
w, h, f := C.int(g.width), C.int(g.height), C.int(frameIndex)
|
||||
if err := g.setArg(1, C.sizeof_int, unsafe.Pointer(&w)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.setArg(2, C.sizeof_int, unsafe.Pointer(&h)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.setArg(3, C.sizeof_int, unsafe.Pointer(&f)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Каждый поток GPU пакует блок из 6 пикселей (16 байт)
|
||||
globalWorkSize := C.size_t((g.width * g.height) / 6)
|
||||
err = C.clEnqueueNDRangeKernel(g.queue, g.kernel, 1, nil, &globalWorkSize, nil, 0, nil, nil)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: dispatch failed: %d", err)
|
||||
}
|
||||
|
||||
// Гарантированная синхронизация записи GPU в host-память: map/unmap.
|
||||
// На discrete GPU драйвер держит копию в device memory; одного clFinish
|
||||
// недостаточно — данные обязаны появиться в host_ptr только после unmap.
|
||||
mapped := C.clEnqueueMapBuffer(g.queue, clMem, C.CL_TRUE, C.CL_MAP_READ, 0, byteSize, 0, nil, nil, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: map failed: %d", err)
|
||||
}
|
||||
if err = C.clEnqueueUnmapMemObject(g.queue, clMem, mapped, 0, nil, nil); err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: unmap failed: %d", err)
|
||||
}
|
||||
if err = C.clFinish(g.queue); err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: finish failed: %d", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *OpenCLGenerator) Close() error {
|
||||
if g.textBuf != nil {
|
||||
C.clReleaseMemObject(g.textBuf)
|
||||
g.textBuf = nil
|
||||
}
|
||||
if g.atlasBuf != nil {
|
||||
C.clReleaseMemObject(g.atlasBuf)
|
||||
g.atlasBuf = nil
|
||||
}
|
||||
if g.kernel != nil {
|
||||
C.clReleaseKernel(g.kernel)
|
||||
g.kernel = nil
|
||||
}
|
||||
if g.prog != nil {
|
||||
C.clReleaseProgram(g.prog)
|
||||
g.prog = nil
|
||||
}
|
||||
if g.queue != nil {
|
||||
C.clReleaseCommandQueue(g.queue)
|
||||
g.queue = nil
|
||||
}
|
||||
if g.ctx != nil {
|
||||
C.clReleaseContext(g.ctx)
|
||||
g.ctx = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// WGPU pattern gen: renders v210 frames with a wgpu (WebGPU/Vulkan)
|
||||
// compute shader. Pure Go — no cgo in the GPU layer.
|
||||
package generator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gogpu/gputypes"
|
||||
"github.com/gogpu/wgpu"
|
||||
|
||||
// Register all available GPU backends (Vulkan, DX12, GLES, Metal, etc.)
|
||||
_ "github.com/gogpu/wgpu/hal/allbackends"
|
||||
)
|
||||
|
||||
const wgpuWorkgroupSize = 64
|
||||
|
||||
// WGPUGenerator renders v210 frames on the GPU via wgpu and implements
|
||||
// FrameGenerator. Per frame the kernel packs the pattern into a storage
|
||||
// buffer, the GPU DMAs it into a persistent host-visible buffer, and the
|
||||
// mapped contents are copied straight into the destination grain.
|
||||
type WGPUGenerator struct {
|
||||
instance *wgpu.Instance
|
||||
adapter *wgpu.Adapter
|
||||
device *wgpu.Device
|
||||
queue *wgpu.Queue
|
||||
shader *wgpu.ShaderModule
|
||||
bgl *wgpu.BindGroupLayout
|
||||
bg *wgpu.BindGroup
|
||||
pl *wgpu.PipelineLayout
|
||||
pipeline *wgpu.ComputePipeline
|
||||
out *wgpu.Buffer
|
||||
host *wgpu.Buffer
|
||||
uniform *wgpu.Buffer
|
||||
params []byte
|
||||
width int
|
||||
height int
|
||||
blocks int
|
||||
frameSize uint64
|
||||
}
|
||||
|
||||
var _ FrameGenerator = (*WGPUGenerator)(nil)
|
||||
|
||||
func NewWGPUGenerator(width, height uint, kernelPath string) (*WGPUGenerator, error) {
|
||||
g := &WGPUGenerator{
|
||||
width: int(width),
|
||||
height: int(height),
|
||||
blocks: int(width*height) / 6,
|
||||
params: make([]byte, 16),
|
||||
}
|
||||
g.frameSize = uint64(g.blocks) * 16
|
||||
binary.LittleEndian.PutUint32(g.params[0:], uint32(width))
|
||||
binary.LittleEndian.PutUint32(g.params[4:], uint32(height))
|
||||
|
||||
var err error
|
||||
if g.instance, err = wgpu.CreateInstance(nil); err != nil {
|
||||
return nil, fmt.Errorf("wgpu: create instance: %w", err)
|
||||
}
|
||||
if g.adapter, err = g.instance.RequestAdapter(nil); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: request adapter: %w", err)
|
||||
}
|
||||
if g.device, err = g.adapter.RequestDevice(nil); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: request device: %w", err)
|
||||
}
|
||||
g.queue = g.device.Queue()
|
||||
|
||||
wgsl, err := os.ReadFile(kernelPath)
|
||||
if err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: read kernel: %w", err)
|
||||
}
|
||||
if g.shader, err = g.device.CreateShaderModule(&wgpu.ShaderModuleDescriptor{
|
||||
Label: "v210-shader", WGSL: string(wgsl),
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: shader: %w", err)
|
||||
}
|
||||
if g.out, err = g.device.CreateBuffer(&wgpu.BufferDescriptor{
|
||||
Label: "v210-out", Size: g.frameSize,
|
||||
Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopySrc,
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: out buffer: %w", err)
|
||||
}
|
||||
if g.host, err = g.device.CreateBuffer(&wgpu.BufferDescriptor{
|
||||
Label: "v210-host", Size: g.frameSize,
|
||||
Usage: wgpu.BufferUsageCopyDst | wgpu.BufferUsageMapRead,
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: host buffer: %w", err)
|
||||
}
|
||||
if g.uniform, err = g.device.CreateBuffer(&wgpu.BufferDescriptor{
|
||||
Label: "v210-params", Size: uint64(len(g.params)),
|
||||
Usage: wgpu.BufferUsageUniform | wgpu.BufferUsageCopyDst,
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: params buffer: %w", err)
|
||||
}
|
||||
if err := g.queue.WriteBuffer(g.uniform, 0, g.params); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: write params: %w", err)
|
||||
}
|
||||
if g.bgl, err = g.device.CreateBindGroupLayout(&wgpu.BindGroupLayoutDescriptor{
|
||||
Label: "v210-bgl",
|
||||
Entries: []gputypes.BindGroupLayoutEntry{
|
||||
{Binding: 0, Visibility: wgpu.ShaderStageCompute, Buffer: &gputypes.BufferBindingLayout{Type: gputypes.BufferBindingTypeStorage}},
|
||||
{Binding: 1, Visibility: wgpu.ShaderStageCompute, Buffer: &gputypes.BufferBindingLayout{Type: gputypes.BufferBindingTypeUniform}},
|
||||
},
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: bind group layout: %w", err)
|
||||
}
|
||||
if g.bg, err = g.device.CreateBindGroup(&wgpu.BindGroupDescriptor{
|
||||
Label: "v210-bg", Layout: g.bgl,
|
||||
Entries: []wgpu.BindGroupEntry{
|
||||
{Binding: 0, Buffer: g.out, Size: g.frameSize},
|
||||
{Binding: 1, Buffer: g.uniform, Size: uint64(len(g.params))},
|
||||
},
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: bind group: %w", err)
|
||||
}
|
||||
if g.pl, err = g.device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
|
||||
Label: "v210-pl", BindGroupLayouts: []*wgpu.BindGroupLayout{g.bgl},
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: pipeline layout: %w", err)
|
||||
}
|
||||
if g.pipeline, err = g.device.CreateComputePipeline(&wgpu.ComputePipelineDescriptor{
|
||||
Label: "v210-pipeline", Layout: g.pl, Module: g.shader, EntryPoint: "main",
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("wgpu: pipeline: %w", err)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *WGPUGenerator) GenerateFrame(dest []byte, frameIndex int) error {
|
||||
if uint64(len(dest)) < g.frameSize {
|
||||
return fmt.Errorf("wgpu: dest too small: %d bytes, need %d", len(dest), g.frameSize)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(g.params[8:], uint32(frameIndex))
|
||||
if err := g.queue.WriteBuffer(g.uniform, 0, g.params); err != nil {
|
||||
return fmt.Errorf("wgpu: write params: %w", err)
|
||||
}
|
||||
|
||||
encoder, err := g.device.CreateCommandEncoder(nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wgpu: encoder: %w", err)
|
||||
}
|
||||
pass, err := encoder.BeginComputePass(nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wgpu: compute pass: %w", err)
|
||||
}
|
||||
pass.SetPipeline(g.pipeline)
|
||||
pass.SetBindGroup(0, g.bg, nil)
|
||||
pass.Dispatch(uint32((g.blocks+wgpuWorkgroupSize-1)/wgpuWorkgroupSize), 1, 1)
|
||||
if err := pass.End(); err != nil {
|
||||
return fmt.Errorf("wgpu: end pass: %w", err)
|
||||
}
|
||||
encoder.CopyBufferToBuffer(g.out, 0, g.host, 0, g.frameSize)
|
||||
cmd, err := encoder.Finish()
|
||||
if err != nil {
|
||||
return fmt.Errorf("wgpu: finish: %w", err)
|
||||
}
|
||||
if _, err := g.queue.Submit(cmd); err != nil {
|
||||
return fmt.Errorf("wgpu: submit: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := g.host.Map(ctx, wgpu.MapModeRead, 0, g.frameSize); err != nil {
|
||||
return fmt.Errorf("wgpu: map: %w", err)
|
||||
}
|
||||
rng, err := g.host.MappedRange(0, g.frameSize)
|
||||
if err != nil {
|
||||
_ = g.host.Unmap()
|
||||
return fmt.Errorf("wgpu: mapped range: %w", err)
|
||||
}
|
||||
copy(dest, rng.Bytes())
|
||||
return g.host.Unmap()
|
||||
}
|
||||
|
||||
func (g *WGPUGenerator) Close() error {
|
||||
if g.pipeline != nil {
|
||||
g.pipeline.Release()
|
||||
g.pipeline = nil
|
||||
}
|
||||
if g.pl != nil {
|
||||
g.pl.Release()
|
||||
g.pl = nil
|
||||
}
|
||||
if g.bg != nil {
|
||||
g.bg.Release()
|
||||
g.bg = nil
|
||||
}
|
||||
if g.bgl != nil {
|
||||
g.bgl.Release()
|
||||
g.bgl = nil
|
||||
}
|
||||
if g.shader != nil {
|
||||
g.shader.Release()
|
||||
g.shader = nil
|
||||
}
|
||||
if g.uniform != nil {
|
||||
g.uniform.Release()
|
||||
g.uniform = nil
|
||||
}
|
||||
if g.host != nil {
|
||||
g.host.Release()
|
||||
g.host = nil
|
||||
}
|
||||
if g.out != nil {
|
||||
g.out.Release()
|
||||
g.out = nil
|
||||
}
|
||||
if g.device != nil {
|
||||
g.device.Release()
|
||||
g.device = nil
|
||||
}
|
||||
if g.adapter != nil {
|
||||
g.adapter.Release()
|
||||
g.adapter = nil
|
||||
}
|
||||
if g.instance != nil {
|
||||
g.instance.Release()
|
||||
g.instance = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWGPUMoveSquare(t *testing.T) {
|
||||
const width, height = 1920, 1080
|
||||
g, err := NewWGPUGenerator(width, height, filepath.Join("..", "..", "kernels", "v210_bars_move.wgsl"))
|
||||
if err != nil {
|
||||
t.Fatalf("init: %v", err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
buf := make([]byte, width*height*8/3)
|
||||
sample := func(x, y int) (yc, cb, cr uint32) {
|
||||
p := y*width + x
|
||||
off := (p / 6) * 16
|
||||
w0 := binary.LittleEndian.Uint32(buf[off:])
|
||||
w1 := binary.LittleEndian.Uint32(buf[off+4:])
|
||||
w2 := binary.LittleEndian.Uint32(buf[off+8:])
|
||||
w3 := binary.LittleEndian.Uint32(buf[off+12:])
|
||||
switch p % 6 {
|
||||
case 0:
|
||||
return (w0 >> 10) & 0x3FF, w0 & 0x3FF, (w0 >> 20) & 0x3FF
|
||||
case 1:
|
||||
return w1 & 0x3FF, w0 & 0x3FF, (w0 >> 20) & 0x3FF
|
||||
case 2:
|
||||
return (w1 >> 20) & 0x3FF, (w1 >> 10) & 0x3FF, w2 & 0x3FF
|
||||
case 3:
|
||||
return (w2 >> 10) & 0x3FF, (w1 >> 10) & 0x3FF, w2 & 0x3FF
|
||||
case 4:
|
||||
return w3 & 0x3FF, (w2 >> 20) & 0x3FF, (w3 >> 10) & 0x3FF
|
||||
default:
|
||||
return (w3 >> 20) & 0x3FF, (w2 >> 20) & 0x3FF, (w3 >> 10) & 0x3FF
|
||||
}
|
||||
}
|
||||
|
||||
// square center x=960 sits on the green bar (534/253/207):
|
||||
// inverted -> Y=1004-534=470, Cb=1024-253=771, Cr=1024-207=817
|
||||
const invY, invCb, invCr = 470, 771, 817
|
||||
const greenY = 534 // bar 3 covers x in [823,1098)
|
||||
|
||||
// tick 0: offset = 0, square centered at (960,540), half = 75
|
||||
if err := g.GenerateFrame(buf, 0); err != nil {
|
||||
t.Fatalf("tick 0: %v", err)
|
||||
}
|
||||
if y, cb, cr := sample(960, 540); y != invY || cb != invCb || cr != invCr {
|
||||
t.Fatalf("tick 0 center: got %d/%d/%d, want inverted green 470/771/817", y, cb, cr)
|
||||
}
|
||||
if y, _, _ := sample(860, 540); y != greenY {
|
||||
t.Fatalf("tick 0 left of square: Y=%d, want green %d", y, greenY)
|
||||
}
|
||||
|
||||
// amplitude = centerX - half = 885: tick 79 (sin~1) puts the square at
|
||||
// the far right, x in [1770,1920), over the blue bar (111/848/481):
|
||||
// inverted -> Y=1004-111=893, Cb=1024-848=176, Cr=1024-481=543
|
||||
const invBlueY, invBlueCb, invBlueCr = 893, 176, 543
|
||||
if err := g.GenerateFrame(buf, 79); err != nil {
|
||||
t.Fatalf("tick 79: %v", err)
|
||||
}
|
||||
if y, cb, cr := sample(1840, 540); y != invBlueY || cb != invBlueCb || cr != invBlueCr {
|
||||
t.Fatalf("tick 79 shifted center: got %d/%d/%d, want inverted blue 893/176/543", y, cb, cr)
|
||||
}
|
||||
if y, _, _ := sample(960, 540); y != greenY {
|
||||
t.Fatalf("tick 79 old center: Y=%d, want green %d (square moved away)", y, greenY)
|
||||
}
|
||||
// bars untouched far from the square
|
||||
if y, _, _ := sample(100, 100); y != 721 {
|
||||
t.Fatalf("tick 79 bars: Y=%d, want white 721", y)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWGPUGenerator(t *testing.T) {
|
||||
const width, height = 1920, 1080
|
||||
g, err := NewWGPUGenerator(width, height, filepath.Join("..", "..", "kernels", "v210_bars.wgsl"))
|
||||
if err != nil {
|
||||
t.Fatalf("init: %v", err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
buf := make([]byte, width*height*8/3)
|
||||
if err := g.GenerateFrame(buf, 0); err != nil {
|
||||
t.Fatalf("GenerateFrame: %v", err)
|
||||
}
|
||||
if err := g.GenerateFrame(buf, 1); err != nil {
|
||||
t.Fatalf("GenerateFrame 2: %v", err)
|
||||
}
|
||||
|
||||
want := [7][3]uint32{
|
||||
{721, 512, 512}, {674, 176, 543}, {581, 589, 176},
|
||||
{534, 253, 207}, {251, 771, 817}, {204, 435, 848}, {111, 848, 481},
|
||||
}
|
||||
barOf := func(x int) int {
|
||||
if b := x * 7 / width; b < 7 {
|
||||
return b
|
||||
}
|
||||
return 6
|
||||
}
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
p := y*width + x
|
||||
off := (p / 6) * 16
|
||||
w0 := binary.LittleEndian.Uint32(buf[off:])
|
||||
w1 := binary.LittleEndian.Uint32(buf[off+4:])
|
||||
w2 := binary.LittleEndian.Uint32(buf[off+8:])
|
||||
w3 := binary.LittleEndian.Uint32(buf[off+12:])
|
||||
var yv, cb, cr uint32
|
||||
switch p % 6 {
|
||||
case 0:
|
||||
yv, cb, cr = (w0>>10)&0x3FF, w0&0x3FF, (w0>>20)&0x3FF
|
||||
case 1:
|
||||
yv = w1 & 0x3FF
|
||||
case 2:
|
||||
yv, cb, cr = (w1>>20)&0x3FF, (w1>>10)&0x3FF, w2&0x3FF
|
||||
case 3:
|
||||
yv = (w2 >> 10) & 0x3FF
|
||||
case 4:
|
||||
yv, cb, cr = w3&0x3FF, (w2>>20)&0x3FF, (w3>>10)&0x3FF
|
||||
case 5:
|
||||
yv = (w3 >> 20) & 0x3FF
|
||||
}
|
||||
b := want[barOf(x)]
|
||||
if yv != b[0] {
|
||||
t.Fatalf("pixel (%d,%d): Y=%d want %d", x, y, yv, b[0])
|
||||
}
|
||||
if x%2 == 0 && (cb != b[1] || cr != b[2]) {
|
||||
t.Fatalf("pixel (%d,%d): Cb=%d Cr=%d want %d/%d", x, y, cb, cr, b[1], b[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user