Files
go-mxl-pattern-generator/cmd/wgpu-gen/main.go
T
Dmitry Sergeev 787b89474c pflag
2026-09-08 08:06:36 +03:00

158 lines
4.5 KiB
Go

// 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))
}
}