From d3c66a285e39b6db6ca4e0a1750b46dca75076b7 Mon Sep 17 00:00:00 2001 From: Dmitry Sergeev Date: Thu, 20 Aug 2026 18:40:29 +0300 Subject: [PATCH] FUCK YEAH! We have colored window! --- cmd/main.go | 216 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 188 insertions(+), 28 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index c48c7c4..db4f18c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,7 +8,6 @@ import ( vk "github.com/christerso/vulkan-go/vk" "github.com/ebitengine/purego" - sdl "github.com/jupiterrider/purego-sdl3/sdl" ) const ( @@ -23,9 +22,9 @@ const ( windowVulkan uint64 = 0x0000000010000000 windowResizable uint64 = 0x0000000000000020 - eventQuit uint32 = 0x100 - eventKeyDown uint32 = 0x300 - keyEscape uint32 = 0x1B + sdlEventQuit uint32 = 0x100 + sdlEventKeyDown uint32 = 0x300 + keyEscape uint32 = 0x1B ) var ( @@ -87,22 +86,6 @@ func cbytes(s string) *byte { 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) } @@ -181,13 +164,13 @@ func main() { GraphicsFamily: gfx, Extensions: []string{"VK_KHR_swapchain"}, }) + fmt.Println("vkQueue=", vkQueue) if err != nil { panic(err) } defer vkDevice.Destroy() - defer vkDevice.WaitIdle() // TODO: colorspace & render pass - var vkFormat vk.Format + var vkFormat vk.Format = vk.FormatUndefined var vkColorSpace uint32 formats, _ := vkPhysDevice.SurfaceFormats(vkSurf) for _, f := range formats { @@ -196,7 +179,12 @@ func main() { vkColorSpace = f.ColorSpace } } - var vkPresentMode vk.PresentMode + if vkFormat == vk.FormatUndefined { + vkFormat = formats[0].Format + vkColorSpace = formats[0].ColorSpace + } + + var vkPresentMode vk.PresentMode = vk.PresentModeFIFO modes, _ := vkPhysDevice.SurfacePresentModes(vkSurf) for _, m := range modes { if m == vk.PresentModeMailbox { @@ -220,17 +208,189 @@ func main() { if err != nil { panic(err.Error()) } + fmt.Println(vkCommands) + defer vkDevice.WaitIdle() + // Vulkan Surface Capabilities + caps, err := vkPhysDevice.SurfaceCapabilities(vkSurf) + if err != nil { + panic(err.Error()) + } + //compute swapchain extent + var w, h int32 = WIN_WIDTH, WIN_HEIGHT + if !sdlGetWindowSizeInPixels(windowHandler, &w, &h) { + log.Fatalf("SDL_GetWindowSizeInPixels: %s", sdlError()) + } + if w < int32(caps.MinImageExtent.Width) { + w = int32(caps.MinImageExtent.Width) + } + if h < int32(caps.MinImageExtent.Height) { + h = int32(caps.MinImageExtent.Height) + } + if w > int32(caps.MaxImageExtent.Width) { + w = int32(caps.MaxImageExtent.Width) + } + if h > int32(caps.MaxImageExtent.Height) { + h = int32(caps.MaxImageExtent.Height) + } + vkExtent := vk.Extent2D{Width: uint32(w), Height: uint32(h)} + // Vulkan swapchain (pool of presentable images, that OS and GPU share) + vkSwapchain, err := vkDevice.CreateSwapchain(vk.SwapchainConfig{ + Surface: vkSurf, + MinImageCount: caps.MinImageCount, + Format: vkFormat, + ColorSpace: vkColorSpace, + Extent: vkExtent, + PresentMode: vkPresentMode, + PreTransform: caps.CurrentTransform, + }) + if err != nil { + panic(err.Error()) + } + defer vkDevice.DestroySwapchain(vkSwapchain) + + swapImages, err := vkDevice.SwapchainImages(vkSwapchain) + if err != nil { + panic(err.Error()) + } + // one image view per swapchain. Image = raw GPU memory with a format + swapViews := make([]vk.ImageView, len(swapImages)) + for i, img := range swapImages { + v, err := vkDevice.CreateImageView(img, vkFormat, vk.AspectColor) + if err != nil { + panic(err.Error()) + } + swapViews[i] = v + } + defer func() { + for _, v := range swapViews { + vkDevice.DestroyImageView(v) + } + }() + // depth image + view + depthImg, err := vkDevice.CreateImage2D( + vkPhysDevice, vk.FormatD32Sfloat, vkExtent, vk.ImageUsageDepthStencilAttachment) + if err != nil { + panic(err.Error()) + } + defer vkDevice.DestroyImage(depthImg) + + depthView, err := vkDevice.CreateImageView(depthImg.Image, vk.FormatD32Sfloat, vk.AspectDepth) + if err != nil { + panic(err.Error()) + } + defer vkDevice.DestroyImageView(depthView) + + // frame buffer i.e. binding between RenderPass and actual ImageViews + fbs := make([]vk.Framebuffer, len(swapViews)) + for i, cv := range swapViews { + fb, err := vkDevice.CreateFramebuffer(vkRenderPass, []vk.ImageView{cv, depthView}, vkExtent) + if err != nil { + panic(err.Error()) + } + fbs[i] = fb + } + defer func() { + for _, fb := range fbs { + vkDevice.DestroyFramebuffer(fb) + } + }() + // syncs + // Semaphores sync GPU work to GPU work (you never wait on these from the CPU). You need two: + // - imageAvailable — AcquireNextImage signals it when a swapchain image is ready to render into. + // Your draw submission waits on it before touching color. + // - renderFinished — your draw submission signals it when rendering is done. + // Present waits on it before showing the frame. + // Fences sync GPU work to CPU. You need one: + // - inFlight — your submission signals it when the GPU is done with the command buffer. + // Your CPU waits on it before re-recording that same command buffer. + // It's created signaled so the very first frame's wait returns immediately + // (otherwise you'd deadlock waiting for work that hasn't been submitted yet). + imageAvailable, err := vkDevice.CreateSemaphore() + if err != nil { + panic(err.Error()) + } + defer vkDevice.DestroySemaphore(imageAvailable) + + renderFinished, err := vkDevice.CreateSemaphore() + if err != nil { + panic(err.Error()) + } + defer vkDevice.DestroySemaphore(renderFinished) + + inFlight, err := vkDevice.CreateFence(true) + if err != nil { + panic(err.Error()) + } + defer vkDevice.DestroyFence(inFlight) // Core Loop running := true for running { - var event sdl.Event - - for sdl.PollEvent(&event) { - switch event.Type() { - case sdl.EventQuit: + var event [128]byte + for sdlPollEvent(unsafe.Pointer(&event[0])) { + eventType := *(*uint32)(unsafe.Pointer(&event[0])) + if eventType == sdlEventQuit { running = false } } + if !running { + break + } + // Acquire the next swapchain image + imageIndex, r := vkDevice.AcquireNextImage(vkSwapchain, imageAvailable, ^uint64(0)) + if !r.Ok() { + panic(fmt.Errorf("acquire: %v", r)) + } + // Wait until GPU is done with our command buffer, then reset fence + if err := vkDevice.WaitFence(inFlight, ^uint64(0)); err != nil { + panic(err) + } + if err := vkDevice.ResetFence(inFlight); err != nil { + panic(err) + } + + // Record: clear color & depth attachments + cmd := vkCommands[0] + if err := cmd.Reset(); err != nil { + panic(err) + } + if err := cmd.Begin(vk.CommandBufferOneTimeSubmit); err != nil { + panic(err) + } + cmd.BeginRenderPass( + vkRenderPass, + fbs[imageIndex], + vk.Rect2D{Offset: vk.Offset2D{X: 0, Y: 0}, Extent: vkExtent}, + []vk.ClearValue{ + vk.ClearColor(0.0, 0.4, 0.7, 1.0), + vk.ClearDepthStencil(1.0, 0), + }, + ) + cmd.EndRenderPass() + if err := cmd.End(); err != nil { + panic(err) + } + // Submit: wait on imageAvailable, signal RenderFinished + inFlight + if err := vkQueue.Submit(vk.SubmitConfig{ + Wait: imageAvailable, + WaitStage: vk.StageColorAttachmentOutput, + Command: cmd, + Signal: renderFinished, + Fence: inFlight, + }); err != nil { + panic(err) + } + // Present. Wait on renderFinished + r = vkQueue.Present(vkSwapchain, imageIndex, renderFinished) + if r == vk.ErrorOutOfDateKHR || r == vk.SuboptimalKHR { + break + } + if !r.Ok() { + panic(fmt.Errorf("present: %v", r)) + } + // Make sure GPU is idle before any deferred destruction runs + if err := vkDevice.WaitIdle(); err != nil { + panic(err) + } } }