CPU generator
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
package generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YCbCr10 struct {
|
||||||
|
Y uint32
|
||||||
|
Cb uint32
|
||||||
|
Cr uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type PixelSampler func(x, y, width, height, frameIndex int) YCbCr10
|
||||||
|
|
||||||
|
type CPUGenerator struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
sampler PixelSampler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCPUGenerator(
|
||||||
|
width, height uint,
|
||||||
|
sampler PixelSampler,
|
||||||
|
) (*CPUGenerator, error) {
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"width and height must be greater than 0, got: %dx%d",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if width%6 != 0 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"width must be divisible by 6, got: %d",
|
||||||
|
width,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if sampler == nil {
|
||||||
|
return nil, fmt.Errorf("pixel sampler is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CPUGenerator{
|
||||||
|
width: int(width),
|
||||||
|
height: int(height),
|
||||||
|
sampler: sampler,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CPUGenerator) GenerateFrame(dest []byte, frameIndex int) error {
|
||||||
|
frameSize := (g.width / 6) * g.height * 16
|
||||||
|
if len(dest) < frameSize {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"cpu: destination is too small: got %d bytes, need %d",
|
||||||
|
len(dest),
|
||||||
|
frameSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentMask uint32 = 0x3ff
|
||||||
|
offset := 0
|
||||||
|
for y := 0; y < g.height; y++ {
|
||||||
|
for x := 0; x < g.width; x += 6 {
|
||||||
|
var pixels [6]YCbCr10
|
||||||
|
for i := range pixels {
|
||||||
|
pixels[i] = g.sampler(x+i, y, g.width, g.height, frameIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
word0 := pixels[0].Cb&componentMask |
|
||||||
|
(pixels[0].Y&componentMask)<<10 |
|
||||||
|
(pixels[0].Cr&componentMask)<<20
|
||||||
|
word1 := pixels[1].Y&componentMask |
|
||||||
|
(pixels[2].Cb&componentMask)<<10 |
|
||||||
|
(pixels[2].Y&componentMask)<<20
|
||||||
|
word2 := pixels[2].Cr&componentMask |
|
||||||
|
(pixels[3].Y&componentMask)<<10 |
|
||||||
|
(pixels[4].Cb&componentMask)<<20
|
||||||
|
word3 := pixels[4].Y&componentMask |
|
||||||
|
(pixels[4].Cr&componentMask)<<10 |
|
||||||
|
(pixels[5].Y&componentMask)<<20
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(dest[offset:], word0)
|
||||||
|
binary.LittleEndian.PutUint32(dest[offset+4:], word1)
|
||||||
|
binary.LittleEndian.PutUint32(dest[offset+8:], word2)
|
||||||
|
binary.LittleEndian.PutUint32(dest[offset+12:], word3)
|
||||||
|
offset += 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CPUGenerator) Close() error { return nil }
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCPUGeneratorValidation(t *testing.T) {
|
||||||
|
sampler := func(_, _, _, _, _ int) YCbCr10 { return YCbCr10{} }
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
width uint
|
||||||
|
height uint
|
||||||
|
sampler PixelSampler
|
||||||
|
wantErrSub string
|
||||||
|
}{
|
||||||
|
{name: "zero width", height: 1, sampler: sampler, wantErrSub: "greater than 0"},
|
||||||
|
{name: "zero height", width: 6, sampler: sampler, wantErrSub: "greater than 0"},
|
||||||
|
{name: "unaligned width", width: 7, height: 1, sampler: sampler, wantErrSub: "divisible by 6"},
|
||||||
|
{name: "nil sampler", width: 6, height: 1, wantErrSub: "sampler is nil"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := NewCPUGenerator(tc.width, tc.height, tc.sampler)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tc.wantErrSub) {
|
||||||
|
t.Fatalf("error = %v, want substring %q", err, tc.wantErrSub)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCPUGeneratorGenerateFrame(t *testing.T) {
|
||||||
|
const width, height = 12, 2
|
||||||
|
const frameIndex = 7
|
||||||
|
g, err := NewCPUGenerator(width, height, func(x, y, _, _ int, tick int) YCbCr10 {
|
||||||
|
return YCbCr10{
|
||||||
|
Y: uint32(100 + x + 10*y + tick),
|
||||||
|
Cb: uint32(200 + x + 10*y + tick),
|
||||||
|
Cr: uint32(300 + x + 10*y + tick),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCPUGenerator: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := make([]byte, width*height*8/3)
|
||||||
|
if err := g.GenerateFrame(frame, frameIndex); err != nil {
|
||||||
|
t.Fatalf("GenerateFrame: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
gotY, gotCb, gotCr := sampleV210(frame, width, x, y)
|
||||||
|
chromaX := x - x%2
|
||||||
|
wantY := uint32(100 + x + 10*y + frameIndex)
|
||||||
|
wantCb := uint32(200 + chromaX + 10*y + frameIndex)
|
||||||
|
wantCr := uint32(300 + chromaX + 10*y + frameIndex)
|
||||||
|
if gotY != wantY || gotCb != wantCb || gotCr != wantCr {
|
||||||
|
t.Fatalf("pixel (%d,%d): got %d/%d/%d, want %d/%d/%d",
|
||||||
|
x, y, gotY, gotCb, gotCr, wantY, wantCb, wantCr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCPUGeneratorMasksComponentsToTenBits(t *testing.T) {
|
||||||
|
g, err := NewCPUGenerator(6, 1, func(_, _, _, _, _ int) YCbCr10 {
|
||||||
|
return YCbCr10{Y: 0xC01, Cb: 0xC02, Cr: 0xC03}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCPUGenerator: %v", err)
|
||||||
|
}
|
||||||
|
frame := make([]byte, 16)
|
||||||
|
if err := g.GenerateFrame(frame, 0); err != nil {
|
||||||
|
t.Fatalf("GenerateFrame: %v", err)
|
||||||
|
}
|
||||||
|
y, cb, cr := sampleV210(frame, 6, 0, 0)
|
||||||
|
if y != 1 || cb != 2 || cr != 3 {
|
||||||
|
t.Fatalf("masked components = %d/%d/%d, want 1/2/3", y, cb, cr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCPUGeneratorRejectsSmallDestination(t *testing.T) {
|
||||||
|
g, err := NewCPUGenerator(6, 1, func(_, _, _, _, _ int) YCbCr10 { return YCbCr10{} })
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCPUGenerator: %v", err)
|
||||||
|
}
|
||||||
|
if err := g.GenerateFrame(make([]byte, 15), 0); err == nil || !strings.Contains(err.Error(), "too small") {
|
||||||
|
t.Fatalf("error = %v, want destination size error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user