initial
This commit is contained in:
+236
@@ -0,0 +1,236 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
vk "github.com/christerso/vulkan-go/vk"
|
||||
"github.com/ebitengine/purego"
|
||||
sdl "github.com/jupiterrider/purego-sdl3/sdl"
|
||||
)
|
||||
|
||||
const (
|
||||
APP_NAME = "MXL Player"
|
||||
APP_VER = "0.0.1"
|
||||
WIN_WIDTH int32 = 1280
|
||||
WIN_HEIGHT int32 = 720
|
||||
)
|
||||
|
||||
const (
|
||||
initVideo uint32 = 0x00000020
|
||||
windowVulkan uint64 = 0x0000000010000000
|
||||
windowResizable uint64 = 0x0000000000000020
|
||||
|
||||
eventQuit uint32 = 0x100
|
||||
eventKeyDown uint32 = 0x300
|
||||
keyEscape uint32 = 0x1B
|
||||
)
|
||||
|
||||
var (
|
||||
sdlInit func(flags uint32) bool
|
||||
sdlQuit func()
|
||||
sdlGetError func() uintptr
|
||||
sdlCreateWindow func(title *byte, w, h int32, flags uint64) uintptr
|
||||
sdlDestroyWindow func(window uintptr)
|
||||
sdlVulkanGetInstanceExtensions func(count *uint32) uintptr
|
||||
sdlVulkanCreateSurface func(window, instance, allocator uintptr, surface *uint64) bool
|
||||
sdlPollEvent func(event unsafe.Pointer) bool
|
||||
sdlGetWindowSizeInPixels func(window uintptr, w, h *int32) bool
|
||||
)
|
||||
|
||||
func sdlError() string { return cstr(sdlGetError()) }
|
||||
|
||||
var loaded = false
|
||||
|
||||
func loadSDLMissing() error {
|
||||
if loaded {
|
||||
return nil
|
||||
}
|
||||
h, err := purego.Dlopen("libSDL3.so.0", purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("win: load SDL3: %w", err)
|
||||
}
|
||||
purego.RegisterLibFunc(&sdlInit, h, "SDL_Init")
|
||||
purego.RegisterLibFunc(&sdlQuit, h, "SDL_Quit")
|
||||
purego.RegisterLibFunc(&sdlGetError, h, "SDL_GetError")
|
||||
purego.RegisterLibFunc(&sdlCreateWindow, h, "SDL_CreateWindow")
|
||||
purego.RegisterLibFunc(&sdlDestroyWindow, h, "SDL_DestroyWindow")
|
||||
purego.RegisterLibFunc(&sdlVulkanGetInstanceExtensions, h, "SDL_Vulkan_GetInstanceExtensions")
|
||||
purego.RegisterLibFunc(&sdlVulkanCreateSurface, h, "SDL_Vulkan_CreateSurface")
|
||||
purego.RegisterLibFunc(&sdlPollEvent, h, "SDL_PollEvent")
|
||||
purego.RegisterLibFunc(&sdlGetWindowSizeInPixels, h, "SDL_GetWindowSizeInPixels")
|
||||
loaded = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// cstr reads a NUL-terminated C string at p.
|
||||
func cstr(p uintptr) string {
|
||||
if p == 0 {
|
||||
return ""
|
||||
}
|
||||
var n int
|
||||
for *(*byte)(unsafe.Pointer(p + uintptr(n))) != 0 {
|
||||
n++
|
||||
}
|
||||
return string(unsafe.Slice((*byte)(unsafe.Pointer(p)), n))
|
||||
}
|
||||
|
||||
// cbytes returns a NUL-terminated copy of s as *byte, kept alive by the caller.
|
||||
func cbytes(s string) *byte {
|
||||
b := make([]byte, len(s)+1)
|
||||
copy(b, s)
|
||||
runtime.KeepAlive(b)
|
||||
return &b[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
// SDL init
|
||||
// if !sdl.Init(sdl.InitVideo) {
|
||||
// log.Fatal(sdl.GetError())
|
||||
// }
|
||||
// defer sdl.Quit()
|
||||
|
||||
// window := sdl.CreateWindow(
|
||||
// fmt.Sprintf("%s ver. %s", APP_NAME, APP_VER),
|
||||
// 1280,
|
||||
// 720,
|
||||
// sdl.WindowVulkan|sdl.WindowResizable,
|
||||
// )
|
||||
// if window == nil {
|
||||
// log.Fatal(sdl.GetError())
|
||||
// }
|
||||
// defer sdl.DestroyWindow(window)
|
||||
if err := loadSDLMissing(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !sdlInit(initVideo) {
|
||||
log.Fatalf("SDL_Init: %s", sdlError())
|
||||
return
|
||||
}
|
||||
|
||||
windowHandler := sdlCreateWindow(cbytes(APP_NAME), WIN_WIDTH, WIN_HEIGHT, windowVulkan|windowResizable)
|
||||
if windowHandler == 0 {
|
||||
sdlQuit()
|
||||
log.Fatalf("SDL_CreateWindow: %s", sdlError())
|
||||
return
|
||||
}
|
||||
|
||||
// Vulkan init
|
||||
if err := vk.Load(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Vulkan instance extensions SDL needs.
|
||||
var sdlExtCount uint32
|
||||
arr := sdlVulkanGetInstanceExtensions(&sdlExtCount)
|
||||
if arr == 0 {
|
||||
log.Fatal("sdlVulkanGetInstanceExtensions is 0")
|
||||
return
|
||||
}
|
||||
sdlExtensions := make([]string, sdlExtCount)
|
||||
for i := uint32(0); i < sdlExtCount; i++ {
|
||||
p := *(*uintptr)(unsafe.Pointer(arr + uintptr(i)*unsafe.Sizeof(uintptr(0))))
|
||||
sdlExtensions[i] = cstr(p)
|
||||
}
|
||||
// Vulkan Instance creation
|
||||
var vkLayers []string
|
||||
vkExtensions := append(sdlExtensions, vk.ExtDebugUtils)
|
||||
vkInstance, err := vk.CreateInstance(vk.InstanceConfig{
|
||||
ApplicationName: APP_NAME,
|
||||
EngineName: "no engine",
|
||||
Extensions: vkExtensions,
|
||||
Layers: vkLayers,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
defer vkInstance.Destroy()
|
||||
// Vulkan surface
|
||||
var vkSurface uint64
|
||||
if !sdlVulkanCreateSurface(windowHandler, uintptr(vkInstance), 0, &vkSurface) {
|
||||
log.Fatalf("SDL_Vulkan_CreateSurface: %s", sdlError())
|
||||
return
|
||||
}
|
||||
vkSurf := vk.SurfaceKHR(vkSurface)
|
||||
defer vkInstance.DestroySurface(vkSurf)
|
||||
|
||||
// Vulkan Physical Device
|
||||
devices, err := vkInstance.EnumeratePhysicalDevices()
|
||||
if err != nil || len(devices) == 0 {
|
||||
panic("No Vulkan devices")
|
||||
}
|
||||
for _, pd := range devices {
|
||||
info := pd.Info()
|
||||
fmt.Printf("%s (%s)\n", info.Name, info.Type)
|
||||
}
|
||||
vkPhysDevice := devices[0]
|
||||
// Vulkan Surface & Graphics Queue
|
||||
gfx, err := vkPhysDevice.GraphicsFamily()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
if !vkPhysDevice.SurfaceSupport(gfx, vkSurf) {
|
||||
log.Fatalf("graphics queue cannot present")
|
||||
return
|
||||
}
|
||||
vkDevice, vkQueue, err := vkPhysDevice.CreateDevice(vk.DeviceConfig{
|
||||
GraphicsFamily: gfx,
|
||||
Extensions: []string{"VK_KHR_swapchain"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer vkDevice.Destroy()
|
||||
defer vkDevice.WaitIdle()
|
||||
// TODO: colorspace & render pass
|
||||
var vkFormat vk.Format
|
||||
var vkColorSpace uint32
|
||||
formats, _ := vkPhysDevice.SurfaceFormats(vkSurf)
|
||||
for _, f := range formats {
|
||||
if f.Format == vk.FormatB8G8R8A8Srgb && f.ColorSpace == vk.ColorSpaceSRGBNonlinear {
|
||||
vkFormat = f.Format
|
||||
vkColorSpace = f.ColorSpace
|
||||
}
|
||||
}
|
||||
var vkPresentMode vk.PresentMode
|
||||
modes, _ := vkPhysDevice.SurfacePresentModes(vkSurf)
|
||||
for _, m := range modes {
|
||||
if m == vk.PresentModeMailbox {
|
||||
vkPresentMode = m
|
||||
}
|
||||
}
|
||||
|
||||
vkRenderPass, err := vkDevice.CreateColorDepthRenderPass(vkFormat, vk.FormatD32Sfloat)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer vkDevice.DestroyRenderPass(vkRenderPass)
|
||||
|
||||
// Vulkan Command Pool
|
||||
vkCommandPool, err := vkDevice.CreateCommandPool(gfx)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer vkDevice.DestroyCommandPool(vkCommandPool)
|
||||
vkCommands, err := vkDevice.AllocateCommandBuffers(vkCommandPool, 1)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
// Core Loop
|
||||
running := true
|
||||
for running {
|
||||
var event sdl.Event
|
||||
|
||||
for sdl.PollEvent(&event) {
|
||||
switch event.Type() {
|
||||
case sdl.EventQuit:
|
||||
running = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module mxl-player
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/christerso/vulkan-go v0.0.0-20260618152204-bff25e5b7646
|
||||
github.com/jupiterrider/purego-sdl3 v0.0.0-20260514083405-8523da70a041
|
||||
)
|
||||
|
||||
require github.com/ebitengine/purego v0.10.2 // indirect
|
||||
@@ -0,0 +1,6 @@
|
||||
github.com/christerso/vulkan-go v0.0.0-20260618152204-bff25e5b7646 h1:xfc6rFxs+U4XktxQa7jyPfg9g0xlb5STDsQn8sURsyo=
|
||||
github.com/christerso/vulkan-go v0.0.0-20260618152204-bff25e5b7646/go.mod h1:DIo6W2yFav1DflNKc0W0Oc7mecbdBg2btsaHhIYmwUA=
|
||||
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
|
||||
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/jupiterrider/purego-sdl3 v0.0.0-20260514083405-8523da70a041 h1:sDTZNtan3t8NsIM15J4vvuFuqxN6qLtWYhmHnLy5TjU=
|
||||
github.com/jupiterrider/purego-sdl3 v0.0.0-20260514083405-8523da70a041/go.mod h1:pRSNvzaSfMxcVPHP5VKo5VF5SAFp78pPIGlBIoI8KBw=
|
||||
@@ -0,0 +1,14 @@
|
||||
Milestones
|
||||
M0 — skeleton builds: Go project, go-mxl, SDL3, Vulkan initialization, logging, clean startup/shutdown. Program opens a window and exits correctly.
|
||||
M1 — MXL source reader: Open one MXL video flow, read grains, print width/height/rate/index, recover cleanly from timeout, and never leak or retain an MXL payload beyond its valid lifetime.
|
||||
M2 — raw Vulkan upload: Copy V210 payload directly into a mapped Vulkan staging buffer. No color conversion yet. Validate sizes, strides, synchronization, and buffer lifetime.
|
||||
M3 — V210 GPU decoding: Implement v210.comp; unpack 10-bit samples on GPU and write to an RGBA16F image. Do not introduce an RGB8 intermediate.
|
||||
M4 — correct color: Implement limited/full-range normalization, BT.709 first, then metadata-driven matrix selection. Add test patterns so you can verify black, white, gray, primary colors, and chroma alignment.
|
||||
M5 — presentation: Scale RGBA16F to the window while preserving aspect ratio, recreate the swapchain correctly on resize, add fullscreen, VSync, and 10-bit swapchain output where supported with 8-bit fallback.
|
||||
M6 — timing: Track MXL grain indexes, detect skipped/late frames, avoid accumulating latency, resync after source interruption, and expose render time / frame time / dropped frames.
|
||||
M7 — resilience: Test writer disappearance, MXL restart, resolution/rate changes, long runtime, fullscreen switching, minimized windows, GPU device/surface errors, and clean shutdown during blocked reads.
|
||||
M8 — audio: Only after video is reliable. Read Float32 MXL audio, feed SDL audio, add buffering, then synchronize audio/video.
|
||||
M9 — usable player: Flow discovery/selection, CLI arguments, basic overlay, source info, color info, FPS, dropped frames, fullscreen and freeze controls.
|
||||
M10 — packaging: Linux and Windows builds first; package libmxl/runtime dependencies appropriately, validate Vulkan loader requirements, and add CI builds.
|
||||
|
||||
I’d define v1.0 as M0–M7 plus basic source selection. Audio can even be 1.1 if getting video stability right is more important.
|
||||
@@ -0,0 +1,8 @@
|
||||
M0.1 Go module + SDL3 window
|
||||
M0.2 Vulkan instance
|
||||
M0.3 enumerate/select GPU
|
||||
M0.4 SDL Vulkan surface
|
||||
M0.5 logical device + queues
|
||||
M0.6 swapchain
|
||||
M0.7 clear window to a solid color
|
||||
M0.8 resize + clean teardown
|
||||
Reference in New Issue
Block a user