// MXL pattern generator feed rendered on the GPU via wgpu (WebGPU/Vulkan). // Run from the repo root: make wgpu-gen // (mixing libmxl cgo with wgpu/goffi needs the internal linker) package main import ( "fmt" "log" "os" "os/signal" "syscall" "github.com/qvest-digital/go-mxl/mxl" "github.com/spf13/pflag" flowdef "mxl-pattern-generator/internal/flow-def" "mxl-pattern-generator/internal/generator" ) type appArgs struct { showHelp bool domain string videoFlowDefFile string audioFlowDefFile string pattern uint8 listPatterns bool textOverlay string videoWidth uint16 videoHeight uint16 videoFPS float32 audioChannels uint8 audioSamplingFreq uint32 } func printUsage() { fmt.Println("CLI USAGE FUNC") fmt.Fprintln(os.Stderr, "Usage: ") } func main() { var args appArgs flagSet := pflag.NewFlagSet("args", pflag.ContinueOnError) flagSet.SortFlags = false flagSet.Usage = func() { printUsage() } // common flags flagSet.BoolVarP(&args.showHelp, "help", "h", false, "Show help message and exit") // MXL flags flagSet.StringVarP(&args.domain, "domain", "d", "", "MXL domain") flagSet.StringVarP(&args.videoFlowDefFile, "video", "v", "", "Video flow definition JSON file path") flagSet.StringVarP(&args.audioFlowDefFile, "audio", "a", "", "Audio flow definition JSON file path [TODO]") // Video pattern flags flagSet.Uint8VarP(&args.pattern, "pattern", "p", 0, "Video pattern type [TODO]") flagSet.BoolVar(&args.listPatterns, "list-patterns", false, "List video available video patterns and exit [TODO]") flagSet.StringVarP(&args.textOverlay, "overlay", "o", "", "Text overlay above video pattern [TODO]") flagSet.Uint16VarP(&args.videoWidth, "width", "w", 1920, "Video pattern width") // flagSet.Uint16VarP(&args.videoHeight, "height", "h", 1920, "Video pattern height") args.videoFPS = *flagSet.Float32P("fps", "f", 1920, "Video pattern FPS") // Audio pattern flags flagSet.Uint8VarP(&args.audioChannels, "channel", "c", 2, "Amount of audio channels. Each channel: num * 1kHz [TODO]") flagSet.Uint32VarP(&args.audioSamplingFreq, "freq", "f", 0, "Sampling frequency of test audio feed [TODO]") var width, height uint = 1920, 1080 var fpsNum, fpsDen uint = 25, 1 flowUUID := "8f1d2a4b-6c3e-4f5a-9b2c-1d7e8a3f0b5d" domain := "/dev/shm/mxl" // create domain if not exist if _, err := os.Stat(domain); err != nil { err := os.MkdirAll(domain, 0775) if err != nil { log.Fatalf("Could not create domain dir: %v", err) } } // TODO: move to parse args if _, err := mxl.IsTmpFs(domain); err != nil { log.Fatalf("Domain is not tmpfs dir: %v", err) } log.Printf("Go Pattern Gen (wgpu)") log.Printf("Video: %dx%dp%d/%d", width, height, fpsNum, fpsDen) log.Printf("UUID: %s", flowUUID) // gen, err := generator.NewWGPUGenerator(width, height, "kernels/v210_bars.wgsl") gen, err := generator.NewWGPUGenerator(width, height, "kernels/v210_bars_move.wgsl") if err != nil { log.Fatalf("wgpu init failed: %v", err) } defer gen.Close() inst, err := mxl.NewInstance(domain, "") if err != nil { log.Fatalf("MXL Init Failed: %v", err) } defer inst.Close() flowDef, err := flowdef.NewFlowDefJSON( flowdef.TYPE_VIDEO, flowUUID, width, height, fpsNum, fpsDen, ) if err != nil { log.Fatalf("Could not create Flow Definition: %v", err) } writer, isCreated, err := inst.NewWriter(flowDef) if err != nil { log.Fatalf("Failed to create MXL writer: %v", err) } if !isCreated { log.Printf("reusing existing flow: %s, domain: %s", flowUUID, domain) } defer writer.Close() flowCfg := writer.Config() rate := flowCfg.Common.GrainRate idx := mxl.CurrentIndex(rate) log.Printf("writing flow grainRate=%d/%d starting at idx=%d", rate.Num, rate.Den, idx) stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) var written int64 var tick uint32 // animation clock: small counter, not the huge grain index for { select { case <-stop: log.Printf("stopping after %d grains", written) return default: } gwa, err := writer.OpenGrain(idx) if err != nil { log.Fatalf("OpenGrain(%d): %v", idx, err) } if err := gen.GenerateFrame(gwa.Payload, int(tick)); err != nil { log.Fatalf("GenerateFrame(%d): %v", idx, err) } if err := gwa.Commit(gwa.TotalSlices, 0); err != nil { log.Fatalf("Commit(%d): %v", idx, err) } written++ idx++ tick++ if written%100 == 0 { log.Printf("grains written=%d, index=%d", written, idx) } // Pace ourselves to roughly the grain rate mxl.SleepNs(mxl.NsUntilIndex(idx, rate)) } }