From f5df9506ce1a0d13bfafd207bf0ef126339dbabd Mon Sep 17 00:00:00 2001 From: Dmitry Sergeev Date: Thu, 17 Sep 2026 22:59:42 +0300 Subject: [PATCH] static pattern frame cache --- internal/video/pattern.go | 6 +++++ internal/video/pattern_test.go | 9 +++++++- internal/video/runner.go | 40 ++++++++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/internal/video/pattern.go b/internal/video/pattern.go index d63bbe1..49d3951 100644 --- a/internal/video/pattern.go +++ b/internal/video/pattern.go @@ -15,6 +15,7 @@ type PatternInfo struct { type pattern struct { PatternInfo kernelSource string + dynamic bool } var patterns = map[string]pattern{ @@ -25,6 +26,7 @@ var patterns = map[string]pattern{ "ebu75-move": { PatternInfo: PatternInfo{Name: "ebu75-move", Description: "EBU 75% Color Bar Signal with moving square"}, kernelSource: mustReadKernel("dynamic/ebu75.wgsl"), + dynamic: true, }, "ebu100": { PatternInfo: PatternInfo{Name: "ebu100", Description: "EBU 100% Color Bar Signal"}, @@ -33,6 +35,7 @@ var patterns = map[string]pattern{ "ebu100-move": { PatternInfo: PatternInfo{Name: "ebu100-move", Description: "EBU 100% Color Bar Signal with moving square"}, kernelSource: mustReadKernel("dynamic/ebu100.wgsl"), + dynamic: true, }, "smpte": { PatternInfo: PatternInfo{Name: "smpte", Description: "SMPTE RP-219 Color Bar Signal"}, @@ -41,6 +44,7 @@ var patterns = map[string]pattern{ "smpte-move": { PatternInfo: PatternInfo{Name: "smpte-move", Description: "SMPTE RP-219 Color Bar Signal with moving square"}, kernelSource: mustReadKernel("dynamic/smpteBars.wgsl"), + dynamic: true, }, "gray-bars": { PatternInfo: PatternInfo{Name: "gray-bars", Description: "13-step grayscale bars (Y 64..940)"}, @@ -49,6 +53,7 @@ var patterns = map[string]pattern{ "gray-bars-move": { PatternInfo: PatternInfo{Name: "gray-bars-move", Description: "13-step grayscale bars (Y 64..940) with moving square"}, kernelSource: mustReadKernel("dynamic/yBars.wgsl"), + dynamic: true, }, "gray-ramp": { PatternInfo: PatternInfo{Name: "gray-ramp", Description: "Y gradient (black -> 100% white)"}, @@ -57,6 +62,7 @@ var patterns = map[string]pattern{ "gray-ramp-move": { PatternInfo: PatternInfo{Name: "gray-ramp-move", Description: "Y gradient with moving square"}, kernelSource: mustReadKernel("dynamic/yRamp.wgsl"), + dynamic: true, }, } diff --git a/internal/video/pattern_test.go b/internal/video/pattern_test.go index 9b738da..55f3b06 100644 --- a/internal/video/pattern_test.go +++ b/internal/video/pattern_test.go @@ -1,6 +1,9 @@ package video -import "testing" +import ( + "strings" + "testing" +) func TestPatterns(t *testing.T) { got := Patterns() @@ -43,5 +46,9 @@ func TestPatternRegistryKeysMatchNames(t *testing.T) { if pattern.kernelSource == "" { t.Errorf("pattern %q has empty kernel source", name) } + wantDynamic := strings.HasSuffix(name, "-move") + if pattern.dynamic != wantDynamic { + t.Errorf("pattern %q dynamic = %t, want %t", name, pattern.dynamic, wantDynamic) + } } } diff --git a/internal/video/runner.go b/internal/video/runner.go index 5df83f0..9355257 100644 --- a/internal/video/runner.go +++ b/internal/video/runner.go @@ -35,6 +35,21 @@ func Run(ctx context.Context, inst *mxl.Instance, cfg Config) (runErr error) { return err } + var staticFrame []byte + if !pattern.dynamic { + frameSize := int(cfg.Width()*cfg.Height()) * 8 / 3 + staticFrame = make([]byte, frameSize) + + if err := gen.GenerateFrame(staticFrame, 0); err != nil { + return fmt.Errorf("generate static frame: %w", err) + } + if overlay != nil { + if err := overlay.ApplyV210(staticFrame); err != nil { + return fmt.Errorf("apply text overlay to static frame: %w", err) + } + } + } + flowJSON, err := json.Marshal(cfg.Definition) if err != nil { return fmt.Errorf("marshal video flow definition: %w", err) @@ -70,13 +85,25 @@ func Run(ctx context.Context, inst *mxl.Instance, cfg Config) (runErr error) { if err != nil { return fmt.Errorf("open video grain %d: %w", idx, err) } - if err := gen.GenerateFrame(grain.Payload, int(tick)); err != nil { - return cancelGrain(grain, fmt.Errorf("generate frame for grain %d: %w", idx, err)) - } - if overlay != nil { - if err := overlay.ApplyV210(grain.Payload); err != nil { - return cancelGrain(grain, fmt.Errorf("apply text overlay to grain %d: %w", idx, err)) + if staticFrame != nil { + if len(grain.Payload) != len(staticFrame) { + return cancelGrain(grain, fmt.Errorf( + "video grain payload size %d, expected %d", + len(grain.Payload), + len(staticFrame), + )) } + copy(grain.Payload, staticFrame) + } else { + if err := gen.GenerateFrame(grain.Payload, int(tick)); err != nil { + return cancelGrain(grain, fmt.Errorf("generate frame for grain %d: %w", idx, err)) + } + if overlay != nil { + if err := overlay.ApplyV210(grain.Payload); err != nil { + return cancelGrain(grain, fmt.Errorf("apply text overlay to grain %d: %w", idx, err)) + } + } + tick++ } if err := grain.Commit(grain.TotalSlices, 0); err != nil { return fmt.Errorf("commit video grain %d: %w", idx, err) @@ -84,7 +111,6 @@ func Run(ctx context.Context, inst *mxl.Instance, cfg Config) (runErr error) { grainsWritten++ idx++ - tick++ if grainsWritten%100 == 0 { log.Printf("video grains written=%d, next index=%d", grainsWritten, idx) }