From 8999747ad23214e1ced7469f3e9c4be02e57a6b0 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 30 Aug 2021 15:06:43 +0200 Subject: [PATCH] app,gpu,internal/vk: add Vulkan port for Wayland, X11, Android This change implements a Vulkan port for the two renderers, old and compute. Run with GIORENDERER=forcecompute to test the compute renderer. To shake out bugs faster, it is also made the default on systems that support it. To disable Vulkan and force the use of OpenGL, use the `novulkan` tag: $ go run -tags novulkan gioui.org/example/kitchen Don't forget to file an issue describing the issue that prompted the use of the tag. Signed-off-by: Elias Naur --- .builds/freebsd.yml | 2 + .builds/linux.yml | 1 + app/egl_android.go | 18 +- app/egl_wayland.go | 14 +- app/egl_x11.go | 14 +- app/os_android.go | 45 +- app/os_wayland.go | 27 + app/os_x11.go | 27 + app/vulkan.go | 206 +++ app/vulkan_android.go | 81 + app/vulkan_wayland.go | 78 + app/vulkan_x11.go | 78 + gpu/api.go | 8 +- gpu/gpu.go | 1 + gpu/headless/headless.go | 28 + gpu/headless/headless_darwin.go | 22 +- gpu/headless/headless_egl.go | 6 +- gpu/headless/headless_js.go | 28 +- gpu/headless/headless_vulkan.go | 74 + gpu/headless/headless_windows.go | 22 +- gpu/internal/driver/api.go | 31 + gpu/internal/metal/metal_darwin.go | 2 +- gpu/internal/vulkan/vulkan.go | 1119 +++++++++++++ gpu/internal/vulkan/vulkan_nosupport.go | 5 + internal/vk/vulkan.go | 2051 +++++++++++++++++++++++ internal/vk/vulkan_android.go | 45 + internal/vk/vulkan_wayland.go | 46 + internal/vk/vulkan_x11.go | 46 + 28 files changed, 4059 insertions(+), 66 deletions(-) create mode 100644 app/vulkan.go create mode 100644 app/vulkan_android.go create mode 100644 app/vulkan_wayland.go create mode 100644 app/vulkan_x11.go create mode 100644 gpu/headless/headless_vulkan.go create mode 100644 gpu/internal/vulkan/vulkan.go create mode 100644 gpu/internal/vulkan/vulkan_nosupport.go create mode 100644 internal/vk/vulkan.go create mode 100644 internal/vk/vulkan_android.go create mode 100644 internal/vk/vulkan_wayland.go create mode 100644 internal/vk/vulkan_x11.go diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 3d9eb402..7a2ea831 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -5,6 +5,8 @@ packages: - libxkbcommon - libXcursor - libXfixes + - vulkan-loader + - vulkan-headers - wayland - mesa-libs - xorg-vfbserver diff --git a/.builds/linux.yml b/.builds/linux.yml index 0ecf1d97..86395fd6 100644 --- a/.builds/linux.yml +++ b/.builds/linux.yml @@ -11,6 +11,7 @@ packages: - libgles2-mesa-dev - libegl1-mesa-dev - libffi-dev + - libvulkan-dev - libxcursor-dev - libxrandr-dev - libxinerama-dev diff --git a/app/egl_android.go b/app/egl_android.go index 42544fff..2c311302 100644 --- a/app/egl_android.go +++ b/app/egl_android.go @@ -21,12 +21,14 @@ type androidContext struct { *egl.Context } -func (w *window) NewContext() (context, error) { - ctx, err := egl.NewContext(nil) - if err != nil { - return nil, err +func init() { + newAndroidGLESContext = func(w *window) (context, error) { + ctx, err := egl.NewContext(nil) + if err != nil { + return nil, err + } + return &androidContext{win: w, Context: ctx}, nil } - return &androidContext{win: w, Context: ctx}, nil } func (c *androidContext) Release() { @@ -38,10 +40,10 @@ func (c *androidContext) Release() { func (c *androidContext) Refresh() error { c.Context.ReleaseSurface() - win, width, height := c.win.nativeWindow(c.Context.VisualID()) - if win == nil { - return nil + if err := c.win.setVisual(c.Context.VisualID()); err != nil { + return err } + win, width, height := c.win.nativeWindow() c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win)) c.width, c.height = width, height return nil diff --git a/app/egl_wayland.go b/app/egl_wayland.go index 2f5aab4a..c2406a0a 100644 --- a/app/egl_wayland.go +++ b/app/egl_wayland.go @@ -30,13 +30,15 @@ type wlContext struct { eglWin *C.struct_wl_egl_window } -func (w *window) NewContext() (context, error) { - disp := egl.NativeDisplayType(unsafe.Pointer(w.display())) - ctx, err := egl.NewContext(disp) - if err != nil { - return nil, err +func init() { + newWaylandEGLContext = func(w *window) (context, error) { + disp := egl.NativeDisplayType(unsafe.Pointer(w.display())) + ctx, err := egl.NewContext(disp) + if err != nil { + return nil, err + } + return &wlContext{Context: ctx, win: w}, nil } - return &wlContext{Context: ctx, win: w}, nil } func (c *wlContext) Release() { diff --git a/app/egl_x11.go b/app/egl_x11.go index b25886d7..72348084 100644 --- a/app/egl_x11.go +++ b/app/egl_x11.go @@ -17,13 +17,15 @@ type x11Context struct { *egl.Context } -func (w *x11Window) NewContext() (context, error) { - disp := egl.NativeDisplayType(unsafe.Pointer(w.display())) - ctx, err := egl.NewContext(disp) - if err != nil { - return nil, err +func init() { + newX11EGLContext = func(w *x11Window) (context, error) { + disp := egl.NativeDisplayType(unsafe.Pointer(w.display())) + ctx, err := egl.NewContext(disp) + if err != nil { + return nil, err + } + return &x11Context{win: w, Context: ctx}, nil } - return &x11Context{win: w, Context: ctx}, nil } func (c *x11Context) Release() { diff --git a/app/os_android.go b/app/os_android.go index a0d36f28..f5be07f3 100644 --- a/app/os_android.go +++ b/app/os_android.go @@ -211,6 +211,33 @@ var ( dataPath string ) +var ( + newAndroidVulkanContext func(w *window) (context, error) + newAndroidGLESContext func(w *window) (context, error) +) + +func (w *window) NewContext() (context, error) { + funcs := []func(w *window) (context, error){newAndroidVulkanContext, newAndroidGLESContext} + var firstErr error + for _, f := range funcs { + if f == nil { + continue + } + c, err := f(w) + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + return c, nil + } + if firstErr != nil { + return nil, firstErr + } + return nil, errors.New("x11: no available GPU backends") +} + func dataDir() (string, error) { dataDirOnce.Do(func() { dataPath = <-dataDirChan @@ -471,16 +498,16 @@ func (w *window) setStage(stage system.Stage) { w.callbacks.Event(system.StageEvent{stage}) } -func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) { - var width, height int - if w.win != nil { - if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 { - panic(errors.New("ANativeWindow_setBuffersGeometry failed")) - } - w, h := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win) - width, height = int(w), int(h) +func (w *window) setVisual(visID int) error { + if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 { + return errors.New("ANativeWindow_setBuffersGeometry failed") } - return w.win, width, height + return nil +} + +func (w *window) nativeWindow() (*C.ANativeWindow, int, int) { + width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win) + return w.win, int(width), int(height) } func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) { diff --git a/app/os_wayland.go b/app/os_wayland.go index 3a6112d6..8f03f6a3 100644 --- a/app/os_wayland.go +++ b/app/os_wayland.go @@ -220,6 +220,11 @@ var callbackMap sync.Map // order of preference. var clipboardMimeTypes = []string{"text/plain;charset=utf8", "UTF8_STRING", "text/plain", "TEXT", "STRING"} +var ( + newWaylandEGLContext func(w *window) (context, error) + newWaylandVulkanContext func(w *window) (context, error) +) + func init() { wlDriver = newWLWindow } @@ -1476,6 +1481,28 @@ func (w *window) SetInputHint(_ key.InputHint) {} // Close the window. Not implemented for Wayland. func (w *window) Close() {} +func (w *window) NewContext() (context, error) { + var firstErr error + if f := newWaylandVulkanContext; f != nil { + c, err := f(w) + if err == nil { + return c, nil + } + firstErr = err + } + if f := newWaylandEGLContext; f != nil { + c, err := f(w) + if err == nil { + return c, nil + } + firstErr = err + } + if firstErr != nil { + return nil, firstErr + } + return nil, errors.New("wayland: no available GPU backends") +} + // detectUIScale reports the system UI scale, or 1.0 if it fails. func detectUIScale() float32 { // TODO: What about other window environments? diff --git a/app/os_x11.go b/app/os_x11.go index 1b01020c..b432fc9f 100644 --- a/app/os_x11.go +++ b/app/os_x11.go @@ -102,6 +102,33 @@ type x11Window struct { wakeups chan struct{} } +var ( + newX11EGLContext func(w *x11Window) (context, error) + newX11VulkanContext func(w *x11Window) (context, error) +) + +func (w *x11Window) NewContext() (context, error) { + var firstErr error + if f := newX11VulkanContext; f != nil { + c, err := f(w) + if err == nil { + return c, nil + } + firstErr = err + } + if f := newX11EGLContext; f != nil { + c, err := f(w) + if err == nil { + return c, nil + } + firstErr = err + } + if firstErr != nil { + return nil, firstErr + } + return nil, errors.New("x11: no available GPU backends") +} + func (w *x11Window) SetAnimating(anim bool) { w.animating = anim } diff --git a/app/vulkan.go b/app/vulkan.go new file mode 100644 index 00000000..91d345f5 --- /dev/null +++ b/app/vulkan.go @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build (linux || freebsd) && !novulkan +// +build linux freebsd +// +build !novulkan + +package app + +import ( + "errors" + "unsafe" + + "gioui.org/gpu" + "gioui.org/internal/vk" +) + +type vkContext struct { + physDev vk.PhysicalDevice + inst vk.Instance + surf vk.Surface + dev vk.Device + queueFam int + queue vk.Queue + acquireSem vk.Semaphore + presentSem vk.Semaphore + + swchain vk.Swapchain + imgs []vk.Image + views []vk.ImageView + fbos []vk.Framebuffer + format vk.Format + presentIdx int +} + +func newVulkanContext(inst vk.Instance, surf vk.Surface) (*vkContext, error) { + physDev, qFam, err := vk.ChoosePhysicalDevice(inst, surf) + if err != nil { + vk.DestroySurface(inst, surf) + return nil, err + } + dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain") + if err != nil { + vk.DestroySurface(inst, surf) + return nil, err + } + if err != nil { + vk.DestroySurface(inst, surf) + vk.DestroyDevice(dev) + return nil, err + } + acquireSem, err := vk.CreateSemaphore(dev) + if err != nil { + vk.DestroySurface(inst, surf) + vk.DestroyDevice(dev) + return nil, err + } + presentSem, err := vk.CreateSemaphore(dev) + if err != nil { + vk.DestroySemaphore(dev, acquireSem) + vk.DestroySurface(inst, surf) + vk.DestroyDevice(dev) + return nil, err + } + c := &vkContext{ + physDev: physDev, + inst: inst, + surf: surf, + dev: dev, + queueFam: qFam, + queue: vk.GetDeviceQueue(dev, qFam, 0), + acquireSem: acquireSem, + presentSem: presentSem, + } + return c, nil +} + +func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) { + vk.DeviceWaitIdle(c.dev) + + imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0) + if err != nil { + return nil, mapErr(err) + } + c.presentIdx = imgIdx + return gpu.VulkanRenderTarget{ + WaitSem: uint64(c.acquireSem), + SignalSem: uint64(c.presentSem), + Framebuffer: uint64(c.fbos[imgIdx]), + Image: uint64(c.imgs[imgIdx]), + }, nil +} + +func (c *vkContext) api() gpu.API { + return gpu.Vulkan{ + PhysDevice: unsafe.Pointer(c.physDev), + Device: unsafe.Pointer(c.dev), + Format: int(c.format), + QueueFamily: c.queueFam, + QueueIndex: 0, + } +} + +func mapErr(err error) error { + var vkErr vk.Error + if !errors.As(err, &vkErr) { + return err + } + switch { + case vkErr == vk.SUBOPTIMAL_KHR: + // Android reports VK_SUBOPTIMAL_KHR when presenting to a rotated + // swapchain (preTransform != currentTransform). However, we don't + // support transforming the output ourselves, so we'll live with it. + return nil + case vkErr.IsDeviceLost(): + return gpu.ErrDeviceLost + } + return err +} + +func (c *vkContext) release() { + vk.DeviceWaitIdle(c.dev) + + c.destroySurface() + vk.DestroySemaphore(c.dev, c.acquireSem) + vk.DestroySemaphore(c.dev, c.presentSem) + vk.DestroyDevice(c.dev) + *c = vkContext{} +} + +func (c *vkContext) present() error { + return mapErr(vk.PresentQueue(c.queue, c.swchain, c.presentSem, c.presentIdx)) +} + +func (c *vkContext) destroyImageViews() { + for _, f := range c.fbos { + vk.DestroyFramebuffer(c.dev, f) + } + c.fbos = nil + for _, view := range c.views { + vk.DestroyImageView(c.dev, view) + } + c.views = nil +} + +func (c *vkContext) destroySurface() { + vk.DeviceWaitIdle(c.dev) + + c.destroyImageViews() + if c.swchain != 0 { + vk.DestroySwapchain(c.dev, c.swchain) + c.swchain = 0 + } + if c.surf != 0 { + vk.DestroySurface(c.inst, c.surf) + c.surf = 0 + } +} + +func (c *vkContext) setSurface(surf vk.Surface) { + if c.surf != 0 { + panic("another surface is active") + } + c.surf = surf +} + +func (c *vkContext) refresh(width, height int) error { + vk.DeviceWaitIdle(c.dev) + + c.destroyImageViews() + swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, c.surf, width, height, c.swchain) + if c.swchain != 0 { + vk.DestroySwapchain(c.dev, c.swchain) + c.swchain = 0 + } + if err != nil { + return mapErr(err) + } + c.swchain = swchain + c.imgs = imgs + c.format = format + pass, err := vk.CreateRenderPass( + c.dev, + format, + vk.ATTACHMENT_LOAD_OP_CLEAR, + vk.IMAGE_LAYOUT_UNDEFINED, + vk.IMAGE_LAYOUT_PRESENT_SRC_KHR, + nil, + ) + if err != nil { + return mapErr(err) + } + defer vk.DestroyRenderPass(c.dev, pass) + for _, img := range imgs { + view, err := vk.CreateImageView(c.dev, img, format) + if err != nil { + return mapErr(err) + } + c.views = append(c.views, view) + fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height) + if err != nil { + return mapErr(err) + } + c.fbos = append(c.fbos, fbo) + } + return nil +} diff --git a/app/vulkan_android.go b/app/vulkan_android.go new file mode 100644 index 00000000..a3f54fe6 --- /dev/null +++ b/app/vulkan_android.go @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build !novulkan +// +build !novulkan + +package app + +import ( + "unsafe" + + "gioui.org/gpu" + "gioui.org/internal/vk" +) + +type wlVkContext struct { + win *window + inst vk.Instance + ctx *vkContext +} + +func init() { + newAndroidVulkanContext = func(w *window) (context, error) { + inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_android_surface") + if err != nil { + return nil, err + } + window, _, _ := w.nativeWindow() + surf, err := vk.CreateAndroidSurface(inst, unsafe.Pointer(window)) + if err != nil { + vk.DestroyInstance(inst) + return nil, err + } + ctx, err := newVulkanContext(inst, surf) + if err != nil { + vk.DestroySurface(inst, surf) + vk.DestroyInstance(inst) + return nil, err + } + c := &wlVkContext{ + win: w, + inst: inst, + ctx: ctx, + } + return c, nil + } +} + +func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) { + return c.ctx.RenderTarget() +} + +func (c *wlVkContext) API() gpu.API { + return c.ctx.api() +} + +func (c *wlVkContext) Release() { + c.ctx.release() + vk.DestroyInstance(c.inst) + *c = wlVkContext{} +} + +func (c *wlVkContext) Present() error { + return c.ctx.present() +} + +func (c *wlVkContext) Lock() error { + return nil +} + +func (c *wlVkContext) Unlock() {} + +func (c *wlVkContext) Refresh() error { + win, w, h := c.win.nativeWindow() + c.ctx.destroySurface() + surf, err := vk.CreateAndroidSurface(c.inst, unsafe.Pointer(win)) + if err != nil { + return err + } + c.ctx.setSurface(surf) + return c.ctx.refresh(w, h) +} diff --git a/app/vulkan_wayland.go b/app/vulkan_wayland.go new file mode 100644 index 00000000..c142e99c --- /dev/null +++ b/app/vulkan_wayland.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build ((linux && !android) || freebsd) && !nowayland && !novulkan +// +build linux,!android freebsd +// +build !nowayland +// +build !novulkan + +package app + +import ( + "unsafe" + + "gioui.org/gpu" + "gioui.org/internal/vk" +) + +type wlVkContext struct { + win *window + inst vk.Instance + ctx *vkContext +} + +func init() { + newWaylandVulkanContext = func(w *window) (context, error) { + inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_wayland_surface") + if err != nil { + return nil, err + } + disp := w.display() + wlSurf, _, _ := w.surface() + surf, err := vk.CreateWaylandSurface(inst, unsafe.Pointer(disp), unsafe.Pointer(wlSurf)) + if err != nil { + vk.DestroyInstance(inst) + return nil, err + } + ctx, err := newVulkanContext(inst, surf) + if err != nil { + vk.DestroySurface(inst, surf) + vk.DestroyInstance(inst) + return nil, err + } + c := &wlVkContext{ + win: w, + inst: inst, + ctx: ctx, + } + return c, nil + } +} + +func (c *wlVkContext) RenderTarget() (gpu.RenderTarget, error) { + return c.ctx.RenderTarget() +} + +func (c *wlVkContext) API() gpu.API { + return c.ctx.api() +} + +func (c *wlVkContext) Release() { + c.ctx.release() + vk.DestroyInstance(c.inst) + *c = wlVkContext{} +} + +func (c *wlVkContext) Present() error { + return c.ctx.present() +} + +func (c *wlVkContext) Lock() error { + return nil +} + +func (c *wlVkContext) Unlock() {} + +func (c *wlVkContext) Refresh() error { + _, w, h := c.win.surface() + return c.ctx.refresh(w, h) +} diff --git a/app/vulkan_x11.go b/app/vulkan_x11.go new file mode 100644 index 00000000..c1a5d75c --- /dev/null +++ b/app/vulkan_x11.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build ((linux && !android) || freebsd) && !nox11 && !novulkan +// +build linux,!android freebsd +// +build !nox11 +// +build !novulkan + +package app + +import ( + "unsafe" + + "gioui.org/gpu" + "gioui.org/internal/vk" +) + +type x11VkContext struct { + win *x11Window + inst vk.Instance + ctx *vkContext +} + +func init() { + newX11VulkanContext = func(w *x11Window) (context, error) { + inst, err := vk.CreateInstance("VK_KHR_surface", "VK_KHR_xlib_surface") + if err != nil { + return nil, err + } + disp := w.display() + window, _, _ := w.window() + surf, err := vk.CreateXlibSurface(inst, unsafe.Pointer(disp), uintptr(window)) + if err != nil { + vk.DestroyInstance(inst) + return nil, err + } + ctx, err := newVulkanContext(inst, surf) + if err != nil { + vk.DestroySurface(inst, surf) + vk.DestroyInstance(inst) + return nil, err + } + c := &x11VkContext{ + win: w, + inst: inst, + ctx: ctx, + } + return c, nil + } +} + +func (c *x11VkContext) RenderTarget() (gpu.RenderTarget, error) { + return c.ctx.RenderTarget() +} + +func (c *x11VkContext) API() gpu.API { + return c.ctx.api() +} + +func (c *x11VkContext) Release() { + c.ctx.release() + vk.DestroyInstance(c.inst) + *c = x11VkContext{} +} + +func (c *x11VkContext) Present() error { + return c.ctx.present() +} + +func (c *x11VkContext) Lock() error { + return nil +} + +func (c *x11VkContext) Unlock() {} + +func (c *x11VkContext) Refresh() error { + _, w, h := c.win.window() + return c.ctx.refresh(w, h) +} diff --git a/gpu/api.go b/gpu/api.go index 1a390899..d347e5a0 100644 --- a/gpu/api.go +++ b/gpu/api.go @@ -8,7 +8,7 @@ import "gioui.org/gpu/internal/driver" // There is an API type for each supported GPU API such as OpenGL and Direct3D. type API = driver.API -// A RenderTarget denotest the destination framebuffer for a frame. +// A RenderTarget denotes the destination framebuffer for a frame. type RenderTarget = driver.RenderTarget // OpenGLRenderTarget is a render target suitable for the OpenGL backend. @@ -20,6 +20,9 @@ type Direct3D11RenderTarget = driver.Direct3D11RenderTarget // MetalRenderTarget is a render target suitable for the Metal backend. type MetalRenderTarget = driver.MetalRenderTarget +// VulkanRenderTarget is a render target suitable for the Vulkan backend. +type VulkanRenderTarget = driver.VulkanRenderTarget + // OpenGL denotes the OpenGL or OpenGL ES API. type OpenGL = driver.OpenGL @@ -29,6 +32,9 @@ type Direct3D11 = driver.Direct3D11 // Metal denotes the Apple Metal API. type Metal = driver.Metal +// Vulkan denotes the Vulkan API. +type Vulkan = driver.Vulkan + // ErrDeviceLost is returned from GPU operations when the underlying GPU device // is lost and should be recreated. var ErrDeviceLost = driver.ErrDeviceLost diff --git a/gpu/gpu.go b/gpu/gpu.go index 9e444176..30af0f00 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -36,6 +36,7 @@ import ( _ "gioui.org/gpu/internal/d3d11" _ "gioui.org/gpu/internal/metal" _ "gioui.org/gpu/internal/opengl" + _ "gioui.org/gpu/internal/vulkan" ) type GPU interface { diff --git a/gpu/headless/headless.go b/gpu/headless/headless.go index 5122d600..f5cae3bf 100644 --- a/gpu/headless/headless.go +++ b/gpu/headless/headless.go @@ -5,6 +5,7 @@ package headless import ( + "errors" "image" "image/color" "runtime" @@ -30,6 +31,33 @@ type context interface { Release() } +var ( + newContextPrimary func() (context, error) + newContextFallback func() (context, error) +) + +func newContext() (context, error) { + funcs := []func() (context, error){newContextPrimary, newContextFallback} + var firstErr error + for _, f := range funcs { + if f == nil { + continue + } + c, err := f() + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + return c, nil + } + if firstErr != nil { + return nil, firstErr + } + return nil, errors.New("x11: no available GPU backends") +} + // NewWindow creates a new headless window. func NewWindow(width, height int) (*Window, error) { ctx, err := newContext() diff --git a/gpu/headless/headless_darwin.go b/gpu/headless/headless_darwin.go index 4b376b8e..9a61f95f 100644 --- a/gpu/headless/headless_darwin.go +++ b/gpu/headless/headless_darwin.go @@ -37,17 +37,19 @@ type mtlContext struct { queue C.CFTypeRef } -func newContext() (context, error) { - dev := C.createDevice() - if dev == 0 { - return nil, errors.New("headless: failed to create Metal device") +func init() { + newContextPrimary = func() (context, error) { + dev := C.createDevice() + if dev == 0 { + return nil, errors.New("headless: failed to create Metal device") + } + queue := C.newCommandQueue(dev) + if queue == 0 { + C.CFRelease(dev) + return nil, errors.New("headless: failed to create MTLQueue") + } + return &mtlContext{dev: dev, queue: queue}, nil } - queue := C.newCommandQueue(dev) - if queue == 0 { - C.CFRelease(dev) - return nil, errors.New("headless: failed to create MTLQueue") - } - return &mtlContext{dev: dev, queue: queue}, nil } func (c *mtlContext) API() gpu.API { diff --git a/gpu/headless/headless_egl.go b/gpu/headless/headless_egl.go index 3119ac12..886254f8 100644 --- a/gpu/headless/headless_egl.go +++ b/gpu/headless/headless_egl.go @@ -9,6 +9,8 @@ import ( "gioui.org/internal/egl" ) -func newContext() (context, error) { - return egl.NewContext(egl.EGL_DEFAULT_DISPLAY) +func init() { + newContextFallback = func() (context, error) { + return egl.NewContext(egl.EGL_DEFAULT_DISPLAY) + } } diff --git a/gpu/headless/headless_js.go b/gpu/headless/headless_js.go index 7b0a1fda..198a2731 100644 --- a/gpu/headless/headless_js.go +++ b/gpu/headless/headless_js.go @@ -14,20 +14,22 @@ type jsContext struct { ctx js.Value } -func newContext() (context, error) { - doc := js.Global().Get("document") - cnv := doc.Call("createElement", "canvas") - ctx := cnv.Call("getContext", "webgl2") - if ctx.IsNull() { - ctx = cnv.Call("getContext", "webgl") +func init() { + newContextPrimary = func() (context, error) { + doc := js.Global().Get("document") + cnv := doc.Call("createElement", "canvas") + ctx := cnv.Call("getContext", "webgl2") + if ctx.IsNull() { + ctx = cnv.Call("getContext", "webgl") + } + if ctx.IsNull() { + return nil, errors.New("headless: webgl is not supported") + } + c := &jsContext{ + ctx: ctx, + } + return c, nil } - if ctx.IsNull() { - return nil, errors.New("headless: webgl is not supported") - } - c := &jsContext{ - ctx: ctx, - } - return c, nil } func (c *jsContext) API() gpu.API { diff --git a/gpu/headless/headless_vulkan.go b/gpu/headless/headless_vulkan.go new file mode 100644 index 00000000..2b9413a3 --- /dev/null +++ b/gpu/headless/headless_vulkan.go @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build (linux || freebsd) && !novulkan +// +build linux freebsd +// +build !novulkan + +package headless + +import ( + "unsafe" + + "gioui.org/gpu" + "gioui.org/internal/vk" +) + +type vkContext struct { + physDev vk.PhysicalDevice + inst vk.Instance + dev vk.Device + queueFam int +} + +func init() { + newContextPrimary = newVulkanContext +} + +func newVulkanContext() (context, error) { + inst, err := vk.CreateInstance() + if err != nil { + return nil, err + } + physDev, qFam, err := vk.ChoosePhysicalDevice(inst, 0) + if err != nil { + vk.DestroyInstance(inst) + return nil, err + } + dev, err := vk.CreateDeviceAndQueue(physDev, qFam) + if err != nil { + vk.DestroyInstance(inst) + return nil, err + } + ctx := &vkContext{ + physDev: physDev, + inst: inst, + dev: dev, + queueFam: qFam, + } + return ctx, nil +} + +func (c *vkContext) API() gpu.API { + return gpu.Vulkan{ + PhysDevice: unsafe.Pointer(c.physDev), + Device: unsafe.Pointer(c.dev), + Format: int(vk.FORMAT_R8G8B8A8_SRGB), + QueueFamily: c.queueFam, + QueueIndex: 0, + } +} + +func (c *vkContext) MakeCurrent() error { + return nil +} + +func (c *vkContext) ReleaseCurrent() { +} + +func (c *vkContext) Release() { + vk.DeviceWaitIdle(c.dev) + + vk.DestroyDevice(c.dev) + vk.DestroyInstance(c.inst) + *c = vkContext{} +} diff --git a/gpu/headless/headless_windows.go b/gpu/headless/headless_windows.go index b82195a0..158aa5d8 100644 --- a/gpu/headless/headless_windows.go +++ b/gpu/headless/headless_windows.go @@ -13,17 +13,19 @@ type d3d11Context struct { dev *d3d11.Device } -func newContext() (context, error) { - dev, ctx, _, err := d3d11.CreateDevice( - d3d11.DRIVER_TYPE_HARDWARE, - 0, - ) - if err != nil { - return nil, err +func init() { + newContextPrimary = func() (context, error) { + dev, ctx, _, err := d3d11.CreateDevice( + d3d11.DRIVER_TYPE_HARDWARE, + 0, + ) + if err != nil { + return nil, err + } + // Don't need it. + d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release) + return &d3d11Context{dev: dev}, nil } - // Don't need it. - d3d11.IUnknownRelease(unsafe.Pointer(ctx), ctx.Vtbl.Release) - return &d3d11Context{dev: dev}, nil } func (c *d3d11Context) API() gpu.API { diff --git a/gpu/internal/driver/api.go b/gpu/internal/driver/api.go index 4afae134..a0297430 100644 --- a/gpu/internal/driver/api.go +++ b/gpu/internal/driver/api.go @@ -31,6 +31,17 @@ type MetalRenderTarget struct { Texture unsafe.Pointer } +type VulkanRenderTarget struct { + // WaitSem is a VkSemaphore that must signaled before accessing Framebuffer. + WaitSem uint64 + // SignalSem is a VkSemaphore that signal access to Framebuffer is complete. + SignalSem uint64 + // Image is the VkImage to render into. + Image uint64 + // Framebuffer is a VkFramebuffer for Image. + Framebuffer uint64 +} + type OpenGL struct { // ES forces the use of ANGLE OpenGL ES libraries on macOS. It is // ignored on all other platforms. @@ -55,11 +66,25 @@ type Metal struct { PixelFormat int } +type Vulkan struct { + // PhysDevice is a VkPhysicalDevice. + PhysDevice unsafe.Pointer + // Device is a VkDevice. + Device unsafe.Pointer + // QueueFamily is the queue familily index of the queue. + QueueFamily int + // QueueIndex is the logical queue index of the queue. + QueueIndex int + // Format is a VkFormat that matches render targets. + Format int +} + // API specific device constructors. var ( NewOpenGLDevice func(api OpenGL) (Device, error) NewDirect3D11Device func(api Direct3D11) (Device, error) NewMetalDevice func(api Metal) (Device, error) + NewVulkanDevice func(api Vulkan) (Device, error) ) // NewDevice creates a new Device given the api. @@ -81,6 +106,10 @@ func NewDevice(api API) (Device, error) { if NewMetalDevice != nil { return NewMetalDevice(api) } + case Vulkan: + if NewVulkanDevice != nil { + return NewVulkanDevice(api) + } } return nil, fmt.Errorf("driver: no driver available for the API %T", api) } @@ -88,6 +117,8 @@ func NewDevice(api API) (Device, error) { func (OpenGL) implementsAPI() {} func (Direct3D11) implementsAPI() {} func (Metal) implementsAPI() {} +func (Vulkan) implementsAPI() {} func (OpenGLRenderTarget) ImplementsRenderTarget() {} func (Direct3D11RenderTarget) ImplementsRenderTarget() {} func (MetalRenderTarget) ImplementsRenderTarget() {} +func (VulkanRenderTarget) ImplementsRenderTarget() {} diff --git a/gpu/internal/metal/metal_darwin.go b/gpu/internal/metal/metal_darwin.go index b915ce5e..c180731e 100644 --- a/gpu/internal/metal/metal_darwin.go +++ b/gpu/internal/metal/metal_darwin.go @@ -986,7 +986,7 @@ func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) { } } -func (b *Backend) BindVertexUniforms(buf driver.Buffer) { +func (b *Backend) BindUniforms(buf driver.Buffer) { bf := buf.(*Buffer) enc := b.renderEnc if enc == 0 { diff --git a/gpu/internal/vulkan/vulkan.go b/gpu/internal/vulkan/vulkan.go new file mode 100644 index 00000000..55726983 --- /dev/null +++ b/gpu/internal/vulkan/vulkan.go @@ -0,0 +1,1119 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build linux +// +build linux + +package vulkan + +import ( + "errors" + "fmt" + "image" + + "gioui.org/gpu/internal/driver" + "gioui.org/internal/vk" + "gioui.org/shader" +) + +type Backend struct { + physDev vk.PhysicalDevice + dev vk.Device + queue vk.Queue + cmdPool struct { + current vk.CommandBuffer + pool vk.CommandPool + used int + buffers []vk.CommandBuffer + } + outFormat vk.Format + staging struct { + buf *Buffer + mem []byte + size int + cap int + } + defers []func(d vk.Device) + frameSig vk.Semaphore + waitSems []vk.Semaphore + waitStages []vk.PipelineStageFlags + sigSems []vk.Semaphore + fence vk.Fence + + allPipes []*Pipeline + + pipe *Pipeline + + passes map[passKey]vk.RenderPass + + // bindings and offset are temporary storage for BindVertexBuffer. + bindings []vk.Buffer + offsets []vk.DeviceSize + + desc struct { + dirty bool + texBinds [texUnits]*Texture + bufBinds [storageUnits]*Buffer + } + + caps driver.Features +} + +type passKey struct { + fmt vk.Format + loadAct vk.AttachmentLoadOp + initLayout vk.ImageLayout + finalLayout vk.ImageLayout +} + +type Texture struct { + backend *Backend + img vk.Image + mem vk.DeviceMemory + view vk.ImageView + sampler vk.Sampler + fbo vk.Framebuffer + format vk.Format + layout vk.ImageLayout + passLayout vk.ImageLayout + width int + height int + acquire vk.Semaphore + foreign bool + + scope struct { + stage vk.PipelineStageFlags + access vk.AccessFlags + } +} + +type Shader struct { + dev vk.Device + module vk.ShaderModule + pushRange vk.PushConstantRange + src shader.Sources +} + +type Pipeline struct { + backend *Backend + pipe vk.Pipeline + pushRanges []vk.PushConstantRange + ninputs int + desc *descPool +} + +type descPool struct { + layout vk.PipelineLayout + descLayout vk.DescriptorSetLayout + pool vk.DescriptorPool + size int + cap int + texBinds []int + imgBinds []int + bufBinds []int +} + +type Buffer struct { + backend *Backend + buf vk.Buffer + store []byte + mem vk.DeviceMemory + usage vk.BufferUsageFlags + + scope struct { + stage vk.PipelineStageFlags + access vk.AccessFlags + } +} + +const ( + texUnits = 4 + storageUnits = 4 +) + +func init() { + driver.NewVulkanDevice = newVulkanDevice +} + +func newVulkanDevice(api driver.Vulkan) (driver.Device, error) { + b := &Backend{ + physDev: vk.PhysicalDevice(api.PhysDevice), + dev: vk.Device(api.Device), + outFormat: vk.Format(api.Format), + caps: driver.FeatureCompute, + passes: make(map[passKey]vk.RenderPass), + } + b.queue = vk.GetDeviceQueue(b.dev, api.QueueFamily, api.QueueIndex) + cmdPool, err := vk.CreateCommandPool(b.dev, api.QueueFamily) + if err != nil { + return nil, err + } + b.cmdPool.pool = cmdPool + props := vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R16_SFLOAT) + reqs := vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT + if props&reqs == reqs { + b.caps |= driver.FeatureFloatRenderTargets + } + reqs = vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT + props = vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R8G8B8A8_SRGB) + if props&reqs == reqs { + b.caps |= driver.FeatureSRGB + } + fence, err := vk.CreateFence(b.dev) + if err != nil { + return nil, mapErr(err) + } + b.fence = fence + return b, nil +} + +func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture { + vk.QueueWaitIdle(b.queue) + b.staging.size = 0 + b.cmdPool.used = 0 + b.runDefers() + b.resetPipes() + + if target == nil { + return nil + } + switch t := target.(type) { + case driver.VulkanRenderTarget: + layout := vk.IMAGE_LAYOUT_UNDEFINED + if !clear { + layout = vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + } + b.frameSig = vk.Semaphore(t.SignalSem) + tex := &Texture{ + img: vk.Image(t.Image), + fbo: vk.Framebuffer(t.Framebuffer), + width: viewport.X, + height: viewport.Y, + layout: layout, + passLayout: vk.IMAGE_LAYOUT_PRESENT_SRC_KHR, + format: b.outFormat, + acquire: vk.Semaphore(t.WaitSem), + foreign: true, + } + return tex + case *Texture: + return t + default: + panic(fmt.Sprintf("vulkan: unsupported render target type: %T", t)) + } +} + +func (b *Backend) deferFunc(f func(d vk.Device)) { + b.defers = append(b.defers, f) +} + +func (b *Backend) runDefers() { + for _, f := range b.defers { + f(b.dev) + } + b.defers = b.defers[:0] +} + +func (b *Backend) resetPipes() { + for i := len(b.allPipes) - 1; i >= 0; i-- { + p := b.allPipes[i] + if p.pipe == 0 { + // Released pipeline. + b.allPipes = append(b.allPipes[:i], b.allPipes[:i+1]...) + continue + } + if p.desc.size > 0 { + vk.ResetDescriptorPool(b.dev, p.desc.pool) + p.desc.size = 0 + } + } +} + +func (b *Backend) EndFrame() { + if b.frameSig != 0 { + b.sigSems = append(b.sigSems, b.frameSig) + b.frameSig = 0 + } + b.submitCmdBuf(false) +} + +func (b *Backend) Caps() driver.Caps { + return driver.Caps{ + MaxTextureSize: 4096, + Features: b.caps, + } +} + +func (b *Backend) NewTimer() driver.Timer { + panic("timers not supported") +} + +func (b *Backend) IsTimeContinuous() bool { + panic("timers not supported") +} + +func (b *Backend) Release() { + vk.DeviceWaitIdle(b.dev) + if buf := b.staging.buf; buf != nil { + vk.UnmapMemory(b.dev, b.staging.buf.mem) + buf.Release() + } + b.runDefers() + for _, rp := range b.passes { + vk.DestroyRenderPass(b.dev, rp) + } + vk.DestroyFence(b.dev, b.fence) + vk.FreeCommandBuffers(b.dev, b.cmdPool.pool, b.cmdPool.buffers...) + vk.DestroyCommandPool(b.dev, b.cmdPool.pool) + *b = Backend{} +} + +func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) { + vkfmt := formatFor(format) + usage := vk.IMAGE_USAGE_TRANSFER_DST_BIT | vk.IMAGE_USAGE_TRANSFER_SRC_BIT + passLayout := vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + if bindings&driver.BufferBindingTexture != 0 { + usage |= vk.IMAGE_USAGE_SAMPLED_BIT + passLayout = vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + } + if bindings&driver.BufferBindingFramebuffer != 0 { + usage |= vk.IMAGE_USAGE_COLOR_ATTACHMENT_BIT + } + if bindings&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 { + usage |= vk.IMAGE_USAGE_STORAGE_BIT + } + filterFor := func(f driver.TextureFilter) vk.Filter { + switch minFilter { + case driver.FilterLinear: + return vk.FILTER_LINEAR + case driver.FilterNearest: + return vk.FILTER_NEAREST + } + panic("unknown filter") + } + sampler, err := vk.CreateSampler(b.dev, filterFor(minFilter), filterFor(magFilter)) + if err != nil { + return nil, mapErr(err) + } + img, mem, err := vk.CreateImage(b.physDev, b.dev, vkfmt, width, height, usage) + if err != nil { + vk.DestroySampler(b.dev, sampler) + return nil, mapErr(err) + } + view, err := vk.CreateImageView(b.dev, img, vkfmt) + if err != nil { + vk.DestroySampler(b.dev, sampler) + vk.DestroyImage(b.dev, img) + vk.FreeMemory(b.dev, mem) + return nil, mapErr(err) + } + t := &Texture{backend: b, img: img, mem: mem, view: view, sampler: sampler, layout: vk.IMAGE_LAYOUT_UNDEFINED, passLayout: passLayout, width: width, height: height, format: vkfmt} + if bindings&driver.BufferBindingFramebuffer != 0 { + pass, err := vk.CreateRenderPass(b.dev, vkfmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE, + vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil) + if err != nil { + return nil, mapErr(err) + } + defer vk.DestroyRenderPass(b.dev, pass) + fbo, err := vk.CreateFramebuffer(b.dev, pass, view, width, height) + if err != nil { + return nil, mapErr(err) + } + t.fbo = fbo + } + return t, nil +} + +func (b *Backend) NewBuffer(bindings driver.BufferBinding, size int) (driver.Buffer, error) { + if bindings&driver.BufferBindingUniforms != 0 { + // Implement uniform buffers as inline push constants. + return &Buffer{store: make([]byte, size)}, nil + } + usage := vk.BUFFER_USAGE_TRANSFER_DST_BIT | vk.BUFFER_USAGE_TRANSFER_SRC_BIT + if bindings&driver.BufferBindingIndices != 0 { + usage |= vk.BUFFER_USAGE_INDEX_BUFFER_BIT + } + if bindings&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 { + usage |= vk.BUFFER_USAGE_STORAGE_BUFFER_BIT + } + if bindings&driver.BufferBindingVertices != 0 { + usage |= vk.BUFFER_USAGE_VERTEX_BUFFER_BIT + } + buf, err := b.newBuffer(size, usage, vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + return buf, mapErr(err) +} + +func (b *Backend) newBuffer(size int, usage vk.BufferUsageFlags, props vk.MemoryPropertyFlags) (*Buffer, error) { + buf, mem, err := vk.CreateBuffer(b.physDev, b.dev, size, usage, props) + return &Buffer{backend: b, buf: buf, mem: mem, usage: usage}, err +} + +func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) { + buf, err := b.NewBuffer(typ, len(data)) + if err != nil { + return nil, err + } + buf.Upload(data) + return buf, nil +} + +func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) { + sh, err := b.newShader(src, vk.SHADER_STAGE_VERTEX_BIT) + return sh, mapErr(err) +} + +func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) { + sh, err := b.newShader(src, vk.SHADER_STAGE_FRAGMENT_BIT) + return sh, mapErr(err) +} + +func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) { + vs := desc.VertexShader.(*Shader) + fs := desc.FragmentShader.(*Shader) + var ranges []vk.PushConstantRange + if r := vs.pushRange; r != (vk.PushConstantRange{}) { + ranges = append(ranges, r) + } + if r := fs.pushRange; r != (vk.PushConstantRange{}) { + ranges = append(ranges, r) + } + descPool, err := createPipelineLayout(b.dev, fs.src, ranges) + if err != nil { + return nil, mapErr(err) + } + blend := desc.BlendDesc + factorFor := func(f driver.BlendFactor) vk.BlendFactor { + switch f { + case driver.BlendFactorZero: + return vk.BLEND_FACTOR_ZERO + case driver.BlendFactorOne: + return vk.BLEND_FACTOR_ONE + case driver.BlendFactorOneMinusSrcAlpha: + return vk.BLEND_FACTOR_ONE_MINUS_SRC_ALPHA + case driver.BlendFactorDstColor: + return vk.BLEND_FACTOR_DST_COLOR + default: + panic("unknown blend factor") + } + } + var top vk.PrimitiveTopology + switch desc.Topology { + case driver.TopologyTriangles: + top = vk.PRIMITIVE_TOPOLOGY_TRIANGLE_LIST + case driver.TopologyTriangleStrip: + top = vk.PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP + default: + panic("unknown topology") + } + var binds []vk.VertexInputBindingDescription + var attrs []vk.VertexInputAttributeDescription + inputs := desc.VertexLayout.Inputs + for i, inp := range inputs { + binds = append(binds, vk.VertexInputBindingDescription{ + Binding: i, + Stride: desc.VertexLayout.Stride, + }) + attrs = append(attrs, vk.VertexInputAttributeDescription{ + Binding: i, + Location: vs.src.Inputs[i].Location, + Format: vertFormatFor(vs.src.Inputs[i]), + Offset: inp.Offset, + }) + } + fmt := b.outFormat + if f := desc.PixelFormat; f != driver.TextureFormatOutput { + fmt = formatFor(f) + } + pass, err := vk.CreateRenderPass(b.dev, fmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE, + vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil) + if err != nil { + return nil, mapErr(err) + } + defer vk.DestroyRenderPass(b.dev, pass) + pipe, err := vk.CreateGraphicsPipeline(b.dev, pass, vs.module, fs.module, blend.Enable, factorFor(blend.SrcFactor), factorFor(blend.DstFactor), top, binds, attrs, descPool.layout) + if err != nil { + descPool.release(b.dev) + return nil, mapErr(err) + } + p := &Pipeline{backend: b, pipe: pipe, desc: descPool, pushRanges: ranges, ninputs: len(inputs)} + b.allPipes = append(b.allPipes, p) + return p, nil +} + +func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) { + sh, err := b.newShader(src, vk.SHADER_STAGE_COMPUTE_BIT) + if err != nil { + return nil, mapErr(err) + } + defer sh.Release() + descPool, err := createPipelineLayout(b.dev, src, nil) + if err != nil { + return nil, mapErr(err) + } + pipe, err := vk.CreateComputePipeline(b.dev, sh.module, descPool.layout) + if err != nil { + descPool.release(b.dev) + return nil, mapErr(err) + } + return &Pipeline{backend: b, pipe: pipe, desc: descPool}, nil +} + +func vertFormatFor(f shader.InputLocation) vk.Format { + t := f.Type + s := f.Size + switch { + case t == shader.DataTypeFloat && s == 1: + return vk.FORMAT_R32_SFLOAT + case t == shader.DataTypeFloat && s == 2: + return vk.FORMAT_R32G32_SFLOAT + case t == shader.DataTypeFloat && s == 3: + return vk.FORMAT_R32G32B32_SFLOAT + case t == shader.DataTypeFloat && s == 4: + return vk.FORMAT_R32G32B32A32_SFLOAT + default: + panic("unsupported data type") + } +} + +func createPipelineLayout(d vk.Device, src shader.Sources, ranges []vk.PushConstantRange) (*descPool, error) { + var ( + descLayouts []vk.DescriptorSetLayout + descLayout vk.DescriptorSetLayout + ) + texBinds := make([]int, len(src.Textures)) + imgBinds := make([]int, len(src.Images)) + bufBinds := make([]int, len(src.StorageBuffers)) + var descBinds []vk.DescriptorSetLayoutBinding + for i, t := range src.Textures { + descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{ + Binding: t.Binding, + StageFlags: vk.SHADER_STAGE_FRAGMENT_BIT, + DescriptorType: vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + }) + texBinds[i] = t.Binding + } + for i, img := range src.Images { + descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{ + Binding: img.Binding, + StageFlags: vk.SHADER_STAGE_COMPUTE_BIT, + DescriptorType: vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, + }) + imgBinds[i] = img.Binding + } + for i, buf := range src.StorageBuffers { + descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{ + Binding: buf.Binding, + StageFlags: vk.SHADER_STAGE_COMPUTE_BIT, + DescriptorType: vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, + }) + bufBinds[i] = buf.Binding + } + if len(descBinds) > 0 { + var err error + descLayout, err = vk.CreateDescriptorSetLayout(d, descBinds) + if err != nil { + return nil, err + } + descLayouts = append(descLayouts, descLayout) + } + layout, err := vk.CreatePipelineLayout(d, ranges, descLayouts) + if err != nil { + if descLayout != 0 { + vk.DestroyDescriptorSetLayout(d, descLayout) + } + return nil, err + } + descPool := &descPool{ + texBinds: texBinds, + bufBinds: bufBinds, + imgBinds: imgBinds, + layout: layout, + descLayout: descLayout, + } + return descPool, nil +} + +func (b *Backend) newShader(src shader.Sources, stage vk.ShaderStageFlags) (*Shader, error) { + mod, err := vk.CreateShaderModule(b.dev, src.SPIRV) + if err != nil { + return nil, err + } + + sh := &Shader{dev: b.dev, module: mod, src: src} + if locs := src.Uniforms.Locations; len(locs) > 0 { + pushOffset := 0x7fffffff + for _, l := range locs { + if l.Offset < pushOffset { + pushOffset = l.Offset + } + } + sh.pushRange = vk.BuildPushConstantRange(stage, pushOffset, src.Uniforms.Size) + } + return sh, nil +} + +func (b *Backend) CopyTexture(dstTex driver.Texture, dorig image.Point, srcFBO driver.Texture, srect image.Rectangle) { + dst := dstTex.(*Texture) + src := srcFBO.(*Texture) + cmdBuf := b.ensureCmdBuf() + op := vk.BuildImageCopy(srect.Min.X, srect.Min.Y, dorig.X, dorig.Y, srect.Dx(), srect.Dy()) + src.imageBarrier(cmdBuf, + vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vk.PIPELINE_STAGE_TRANSFER_BIT, + vk.ACCESS_TRANSFER_READ_BIT, + ) + dst.imageBarrier(cmdBuf, + vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + vk.PIPELINE_STAGE_TRANSFER_BIT, + vk.ACCESS_TRANSFER_WRITE_BIT, + ) + vk.CmdCopyImage(cmdBuf, src.img, src.layout, dst.img, dst.layout, []vk.ImageCopy{op}) +} + +func (b *Backend) Viewport(x, y, width, height int) { + cmdBuf := b.currentCmdBuf() + vp := vk.BuildViewport(float32(x), float32(y), float32(width), float32(height)) + vk.CmdSetViewport(cmdBuf, 0, vp) +} + +func (b *Backend) DrawArrays(off, count int) { + cmdBuf := b.currentCmdBuf() + if b.desc.dirty { + b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_GRAPHICS, b.desc.texBinds, b.desc.bufBinds) + b.desc.dirty = false + } + vk.CmdDraw(cmdBuf, count, 1, off, 0) +} + +func (b *Backend) DrawElements(off, count int) { + cmdBuf := b.currentCmdBuf() + if b.desc.dirty { + b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_GRAPHICS, b.desc.texBinds, b.desc.bufBinds) + b.desc.dirty = false + } + vk.CmdDrawIndexed(cmdBuf, count, 1, off, 0, 0) +} + +func (b *Backend) BindImageTexture(unit int, tex driver.Texture) { + t := tex.(*Texture) + b.desc.texBinds[unit] = t + b.desc.dirty = true + t.imageBarrier(b.currentCmdBuf(), + vk.IMAGE_LAYOUT_GENERAL, + vk.PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk.ACCESS_SHADER_READ_BIT|vk.ACCESS_SHADER_WRITE_BIT, + ) +} + +func (b *Backend) DispatchCompute(x, y, z int) { + cmdBuf := b.currentCmdBuf() + if b.desc.dirty { + b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_COMPUTE, b.desc.texBinds, b.desc.bufBinds) + b.desc.dirty = false + } + vk.CmdDispatch(cmdBuf, x, y, z) +} + +func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { + if stride == 0 { + stride = size.X * 4 + } + cmdBuf := t.backend.ensureCmdBuf() + dstStride := size.X * 4 + n := size.Y * dstStride + stage, mem, off := t.backend.stagingBuffer(n) + var srcOff, dstOff int + for y := 0; y < size.Y; y++ { + srcRow := pixels[srcOff : srcOff+dstStride] + dstRow := mem[dstOff : dstOff+dstStride] + copy(dstRow, srcRow) + dstOff += dstStride + srcOff += stride + } + op := vk.BuildBufferImageCopy(off, dstStride/4, offset.X, offset.Y, size.X, size.Y) + t.imageBarrier(cmdBuf, + vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + vk.PIPELINE_STAGE_TRANSFER_BIT, + vk.ACCESS_TRANSFER_WRITE_BIT, + ) + vk.CmdCopyBufferToImage(cmdBuf, stage.buf, t.img, t.layout, op) +} + +func (t *Texture) Release() { + if t.foreign { + panic("external textures cannot be released") + } + freet := *t + t.backend.deferFunc(func(d vk.Device) { + if freet.fbo != 0 { + vk.DestroyFramebuffer(d, freet.fbo) + } + vk.DestroySampler(d, freet.sampler) + vk.DestroyImageView(d, freet.view) + vk.DestroyImage(d, freet.img) + vk.FreeMemory(d, freet.mem) + }) + *t = Texture{} +} + +func (p *Pipeline) Release() { + freep := *p + p.backend.deferFunc(func(d vk.Device) { + freep.desc.release(d) + vk.DestroyPipeline(d, freep.pipe) + }) + *p = Pipeline{} +} + +func (p *descPool) release(d vk.Device) { + if p := p.pool; p != 0 { + vk.DestroyDescriptorPool(d, p) + } + if l := p.descLayout; l != 0 { + vk.DestroyDescriptorSetLayout(d, l) + } + vk.DestroyPipelineLayout(d, p.layout) +} + +func (p *descPool) bindDescriptorSet(b *Backend, cmdBuf vk.CommandBuffer, bindPoint vk.PipelineBindPoint, texBinds [texUnits]*Texture, bufBinds [storageUnits]*Buffer) { + realloced := false + destroyPool := func() { + pool := p.pool + b.deferFunc(func(d vk.Device) { + vk.DestroyDescriptorPool(d, pool) + }) + p.pool = 0 + p.cap = 0 + } + for { + if p.size == p.cap { + if realloced { + panic("vulkan: vkAllocateDescriptorSet failed on a newly allocated descriptor pool") + } + destroyPool() + realloced = true + newCap := p.cap * 2 + const initialPoolSize = 100 + if newCap < initialPoolSize { + newCap = initialPoolSize + } + var poolSizes []vk.DescriptorPoolSize + if n := len(p.texBinds); n > 0 { + poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, newCap*n)) + } + if n := len(p.imgBinds); n > 0 { + poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, newCap*n)) + } + if n := len(p.bufBinds); n > 0 { + poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, newCap*n)) + } + pool, err := vk.CreateDescriptorPool(b.dev, newCap, poolSizes) + if err != nil { + panic(fmt.Errorf("vulkan: failed to allocate descriptor pool with %d descriptors", newCap)) + } + p.pool = pool + p.cap = newCap + p.size = 0 + } + l := p.descLayout + if l == 0 { + panic("vulkan: descriptor set is dirty, but pipeline has empty layout") + } + descSet, err := vk.AllocateDescriptorSet(b.dev, p.pool, l) + if err != nil { + destroyPool() + continue + } + p.size++ + for _, bind := range p.texBinds { + tex := texBinds[bind] + write := vk.BuildWriteDescriptorSetImage(descSet, bind, vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, tex.sampler, tex.view, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + vk.UpdateDescriptorSet(b.dev, write) + } + for _, bind := range p.imgBinds { + tex := texBinds[bind] + write := vk.BuildWriteDescriptorSetImage(descSet, bind, vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, tex.view, vk.IMAGE_LAYOUT_GENERAL) + vk.UpdateDescriptorSet(b.dev, write) + } + for _, bind := range p.bufBinds { + buf := bufBinds[bind] + write := vk.BuildWriteDescriptorSetBuffer(descSet, bind, vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, buf.buf) + vk.UpdateDescriptorSet(b.dev, write) + } + vk.CmdBindDescriptorSets(cmdBuf, bindPoint, p.layout, 0, []vk.DescriptorSet{descSet}) + break + } +} + +func (t *Texture) imageBarrier(cmdBuf vk.CommandBuffer, layout vk.ImageLayout, stage vk.PipelineStageFlags, access vk.AccessFlags) { + srcStage := t.scope.stage + if srcStage == 0 && t.layout == layout { + t.scope.stage = stage + t.scope.access = access + return + } + if srcStage == 0 { + srcStage = vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT + } + b := vk.BuildImageMemoryBarrier( + t.img, + t.scope.access, access, + t.layout, layout, + ) + vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b}) + t.layout = layout + t.scope.stage = stage + t.scope.access = access +} + +func (b *Backend) PrepareTexture(tex driver.Texture) { + t := tex.(*Texture) + cmdBuf := b.ensureCmdBuf() + t.imageBarrier(cmdBuf, + vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + vk.ACCESS_SHADER_READ_BIT, + ) +} + +func (b *Backend) BindTexture(unit int, tex driver.Texture) { + t := tex.(*Texture) + b.desc.texBinds[unit] = t + b.desc.dirty = true +} + +func (b *Backend) BindPipeline(pipe driver.Pipeline) { + b.bindPipeline(pipe.(*Pipeline), vk.PIPELINE_BIND_POINT_GRAPHICS) +} + +func (b *Backend) BindProgram(prog driver.Program) { + b.bindPipeline(prog.(*Pipeline), vk.PIPELINE_BIND_POINT_COMPUTE) +} + +func (b *Backend) bindPipeline(p *Pipeline, point vk.PipelineBindPoint) { + b.pipe = p + b.desc.dirty = p.desc.descLayout != 0 + cmdBuf := b.currentCmdBuf() + vk.CmdBindPipeline(cmdBuf, point, p.pipe) +} + +func (s *Shader) Release() { + vk.DestroyShaderModule(s.dev, s.module) + *s = Shader{} +} + +func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) { + buf := buffer.(*Buffer) + b.desc.bufBinds[binding] = buf + b.desc.dirty = true + buf.barrier(b.currentCmdBuf(), + vk.PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk.ACCESS_SHADER_READ_BIT|vk.ACCESS_SHADER_WRITE_BIT, + ) +} + +func (b *Backend) BindUniforms(buffer driver.Buffer) { + buf := buffer.(*Buffer) + cmdBuf := b.currentCmdBuf() + for _, s := range b.pipe.pushRanges { + off := s.Offset() + vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()]) + } +} + +func (b *Backend) BindVertexBuffer(buffer driver.Buffer, offset int) { + buf := buffer.(*Buffer) + cmdBuf := b.currentCmdBuf() + b.bindings = b.bindings[:0] + b.offsets = b.offsets[:0] + for i := 0; i < b.pipe.ninputs; i++ { + b.bindings = append(b.bindings, buf.buf) + b.offsets = append(b.offsets, vk.DeviceSize(offset)) + } + vk.CmdBindVertexBuffers(cmdBuf, 0, b.bindings, b.offsets) +} + +func (b *Backend) BindIndexBuffer(buffer driver.Buffer) { + buf := buffer.(*Buffer) + cmdBuf := b.currentCmdBuf() + vk.CmdBindIndexBuffer(cmdBuf, buf.buf, 0, vk.INDEX_TYPE_UINT16) +} + +func (b *Buffer) Download(data []byte) error { + if b.buf == 0 { + copy(data, b.store) + return nil + } + stage, mem, off := b.backend.stagingBuffer(len(data)) + cmdBuf := b.backend.ensureCmdBuf() + b.barrier(cmdBuf, + vk.PIPELINE_STAGE_TRANSFER_BIT, + vk.ACCESS_TRANSFER_READ_BIT, + ) + vk.CmdCopyBuffer(cmdBuf, b.buf, stage.buf, 0, off, len(data)) + stage.scope.stage = vk.PIPELINE_STAGE_TRANSFER_BIT + stage.scope.access = vk.ACCESS_TRANSFER_WRITE_BIT + stage.barrier(cmdBuf, + vk.PIPELINE_STAGE_HOST_BIT, + vk.ACCESS_HOST_READ_BIT, + ) + b.backend.submitCmdBuf(true) + copy(data, mem) + return nil +} + +func (b *Buffer) Upload(data []byte) { + if b.buf == 0 { + copy(b.store, data) + return + } + stage, mem, off := b.backend.stagingBuffer(len(data)) + copy(mem, data) + cmdBuf := b.backend.ensureCmdBuf() + b.barrier(cmdBuf, + vk.PIPELINE_STAGE_TRANSFER_BIT, + vk.ACCESS_TRANSFER_WRITE_BIT, + ) + vk.CmdCopyBuffer(cmdBuf, stage.buf, b.buf, off, 0, len(data)) + var access vk.AccessFlags + if b.usage&vk.BUFFER_USAGE_INDEX_BUFFER_BIT != 0 { + access |= vk.ACCESS_INDEX_READ_BIT + } + if b.usage&vk.BUFFER_USAGE_VERTEX_BUFFER_BIT != 0 { + access |= vk.ACCESS_VERTEX_ATTRIBUTE_READ_BIT + } + if access != 0 { + b.barrier(cmdBuf, + vk.PIPELINE_STAGE_VERTEX_INPUT_BIT, + access, + ) + } +} + +func (b *Buffer) barrier(cmdBuf vk.CommandBuffer, stage vk.PipelineStageFlags, access vk.AccessFlags) { + srcStage := b.scope.stage + if srcStage == 0 { + b.scope.stage = stage + b.scope.access = access + return + } + barrier := vk.BuildBufferMemoryBarrier( + b.buf, + b.scope.access, access, + ) + vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, []vk.BufferMemoryBarrier{barrier}, nil) + b.scope.stage = stage + b.scope.access = access +} + +func (b *Buffer) Release() { + freeb := *b + if freeb.buf != 0 { + b.backend.deferFunc(func(d vk.Device) { + vk.DestroyBuffer(d, freeb.buf) + vk.FreeMemory(d, freeb.mem) + }) + } + *b = Buffer{} +} + +func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { + if len(pixels) == 0 { + return nil + } + sz := src.Size() + stageStride := sz.X * 4 + n := sz.Y * stageStride + stage, mem, off := t.backend.stagingBuffer(n) + cmdBuf := t.backend.ensureCmdBuf() + region := vk.BuildBufferImageCopy(off, stageStride/4, src.Min.X, src.Min.Y, sz.X, sz.Y) + t.imageBarrier(cmdBuf, + vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vk.PIPELINE_STAGE_TRANSFER_BIT, + vk.ACCESS_TRANSFER_READ_BIT, + ) + vk.CmdCopyImageToBuffer(cmdBuf, t.img, t.layout, stage.buf, []vk.BufferImageCopy{region}) + stage.scope.stage = vk.PIPELINE_STAGE_TRANSFER_BIT + stage.scope.access = vk.ACCESS_TRANSFER_WRITE_BIT + stage.barrier(cmdBuf, + vk.PIPELINE_STAGE_HOST_BIT, + vk.ACCESS_HOST_READ_BIT, + ) + t.backend.submitCmdBuf(true) + var srcOff, dstOff int + for y := 0; y < sz.Y; y++ { + dstRow := pixels[srcOff : srcOff+stageStride] + srcRow := mem[dstOff : dstOff+stageStride] + copy(dstRow, srcRow) + dstOff += stageStride + srcOff += stride + } + return nil +} + +func (b *Backend) currentCmdBuf() vk.CommandBuffer { + cur := b.cmdPool.current + if cur == nil { + panic("vulkan: invalid operation outside a render or compute pass") + } + return cur +} + +func (b *Backend) ensureCmdBuf() vk.CommandBuffer { + if b.cmdPool.current != nil { + return b.cmdPool.current + } + if b.cmdPool.used < len(b.cmdPool.buffers) { + buf := b.cmdPool.buffers[b.cmdPool.used] + b.cmdPool.current = buf + } else { + buf, err := vk.AllocateCommandBuffer(b.dev, b.cmdPool.pool) + if err != nil { + panic(err) + } + b.cmdPool.buffers = append(b.cmdPool.buffers, buf) + b.cmdPool.current = buf + } + b.cmdPool.used++ + buf := b.cmdPool.current + if err := vk.BeginCommandBuffer(buf); err != nil { + panic(err) + } + return buf +} + +func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) { + t := tex.(*Texture) + var vkop vk.AttachmentLoadOp + switch d.Action { + case driver.LoadActionClear: + vkop = vk.ATTACHMENT_LOAD_OP_CLEAR + case driver.LoadActionInvalidate: + vkop = vk.ATTACHMENT_LOAD_OP_DONT_CARE + case driver.LoadActionKeep: + vkop = vk.ATTACHMENT_LOAD_OP_LOAD + } + cmdBuf := b.ensureCmdBuf() + if sem := t.acquire; sem != 0 { + // The render pass targets a framebuffer that has an associated acquire semaphore. + // Wait for it by forming an execution barrier. + b.waitSems = append(b.waitSems, sem) + b.waitStages = append(b.waitStages, vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT) + // But only for the first pass in a frame. + t.acquire = 0 + } + t.imageBarrier(cmdBuf, + vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + vk.ACCESS_COLOR_ATTACHMENT_READ_BIT|vk.ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + ) + pass := b.lookupPass(t.format, vkop, t.layout, t.passLayout) + col := d.ClearColor + vk.CmdBeginRenderPass(cmdBuf, pass, t.fbo, t.width, t.height, [4]float32{col.R, col.G, col.B, col.A}) + t.layout = t.passLayout + // If the render pass describes an automatic image layout transition to its final layout, there + // is an implicit image barrier with destination PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT. Make + // sure any subsequent barrier includes the transition. + // See also https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#VkSubpassDependency. + t.scope.stage |= vk.PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT +} + +func (b *Backend) EndRenderPass() { + vk.CmdEndRenderPass(b.cmdPool.current) +} + +func (b *Backend) BeginCompute() { + b.ensureCmdBuf() +} + +func (b *Backend) EndCompute() { +} + +func (b *Backend) lookupPass(fmt vk.Format, loadAct vk.AttachmentLoadOp, initLayout, finalLayout vk.ImageLayout) vk.RenderPass { + key := passKey{fmt: fmt, loadAct: loadAct, initLayout: initLayout, finalLayout: finalLayout} + if pass, ok := b.passes[key]; ok { + return pass + } + pass, err := vk.CreateRenderPass(b.dev, fmt, loadAct, initLayout, finalLayout, nil) + if err != nil { + panic(err) + } + b.passes[key] = pass + return pass +} + +func (b *Backend) submitCmdBuf(sync bool) { + buf := b.cmdPool.current + if buf == nil { + return + } + b.cmdPool.current = nil + if err := vk.EndCommandBuffer(buf); err != nil { + panic(err) + } + var fence vk.Fence + if sync { + fence = b.fence + } + if err := vk.QueueSubmit(b.queue, buf, b.waitSems, b.waitStages, b.sigSems, fence); err != nil { + panic(err) + } + b.waitSems = b.waitSems[:0] + b.sigSems = b.sigSems[:0] + b.waitStages = b.waitStages[:0] + if sync { + vk.WaitForFences(b.dev, b.fence) + vk.ResetFences(b.dev, b.fence) + } +} + +func (b *Backend) stagingBuffer(size int) (*Buffer, []byte, int) { + if b.staging.size+size > b.staging.cap { + if b.staging.buf != nil { + vk.UnmapMemory(b.dev, b.staging.buf.mem) + b.staging.buf.Release() + b.staging.cap = 0 + } + cap := 2 * (b.staging.size + size) + buf, err := b.newBuffer(cap, vk.BUFFER_USAGE_TRANSFER_SRC_BIT|vk.BUFFER_USAGE_TRANSFER_DST_BIT, + vk.MEMORY_PROPERTY_HOST_VISIBLE_BIT|vk.MEMORY_PROPERTY_HOST_COHERENT_BIT) + if err != nil { + panic(err) + } + mem, err := vk.MapMemory(b.dev, buf.mem, 0, cap) + if err != nil { + buf.Release() + panic(err) + } + b.staging.buf = buf + b.staging.mem = mem + b.staging.size = 0 + b.staging.cap = cap + } + off := b.staging.size + b.staging.size += size + mem := b.staging.mem[off : off+size] + return b.staging.buf, mem, off +} + +func formatFor(format driver.TextureFormat) vk.Format { + switch format { + case driver.TextureFormatRGBA8: + return vk.FORMAT_R8G8B8A8_UNORM + case driver.TextureFormatSRGBA: + return vk.FORMAT_R8G8B8A8_SRGB + case driver.TextureFormatFloat: + return vk.FORMAT_R16_SFLOAT + default: + panic("unsupported texture format") + } +} + +func mapErr(err error) error { + var vkErr vk.Error + if errors.As(err, &vkErr) && vkErr.IsDeviceLost() { + return driver.ErrDeviceLost + } + return err +} + +func (f *Texture) ImplementsRenderTarget() {} diff --git a/gpu/internal/vulkan/vulkan_nosupport.go b/gpu/internal/vulkan/vulkan_nosupport.go new file mode 100644 index 00000000..4364a43a --- /dev/null +++ b/gpu/internal/vulkan/vulkan_nosupport.go @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package vulkan + +// Empty file to avoid the build error for platforms without Vulkan support. diff --git a/internal/vk/vulkan.go b/internal/vk/vulkan.go new file mode 100644 index 00000000..a6efbcb0 --- /dev/null +++ b/internal/vk/vulkan.go @@ -0,0 +1,2051 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build linux || freebsd +// +build linux freebsd + +package vk + +/* +#cgo linux freebsd LDFLAGS: -ldl +#cgo freebsd CFLAGS: -I/usr/local/include +#cgo CFLAGS: -Werror -Werror=return-type + +#define VK_NO_PROTOTYPES 1 +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#include +#define __USE_GNU +#include +#include + +static VkResult vkCreateInstance(PFN_vkCreateInstance f, VkInstanceCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) { + return f(&pCreateInfo, pAllocator, pInstance); +} + +static void vkDestroyInstance(PFN_vkDestroyInstance f, VkInstance instance, const VkAllocationCallbacks *pAllocator) { + f(instance, pAllocator); +} + +static VkResult vkEnumeratePhysicalDevices(PFN_vkEnumeratePhysicalDevices f, VkInstance instance, uint32_t *pPhysicalDeviceCount, VkPhysicalDevice *pPhysicalDevices) { + return f(instance, pPhysicalDeviceCount, pPhysicalDevices); +} + +static void vkGetPhysicalDeviceQueueFamilyProperties(PFN_vkGetPhysicalDeviceQueueFamilyProperties f, VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties *pQueueFamilyProperties) { + f(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); +} + +static void vkGetPhysicalDeviceFormatProperties(PFN_vkGetPhysicalDeviceFormatProperties f, VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties *pFormatProperties) { + f(physicalDevice, format, pFormatProperties); +} + +static VkResult vkCreateDevice(PFN_vkCreateDevice f, VkPhysicalDevice physicalDevice, VkDeviceCreateInfo pCreateInfo, VkDeviceQueueCreateInfo qinf, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice) { + pCreateInfo.pQueueCreateInfos = &qinf; + return f(physicalDevice, &pCreateInfo, pAllocator, pDevice); +} + +static void vkDestroyDevice(PFN_vkDestroyDevice f, VkDevice device, const VkAllocationCallbacks *pAllocator) { + f(device, pAllocator); +} + +static void vkGetDeviceQueue(PFN_vkGetDeviceQueue f, VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue *pQueue) { + f(device, queueFamilyIndex, queueIndex, pQueue); +} + +static VkResult vkCreateImageView(PFN_vkCreateImageView f, VkDevice device, const VkImageViewCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkImageView *pView) { + return f(device, pCreateInfo, pAllocator, pView); +} + +static void vkDestroyImageView(PFN_vkDestroyImageView f, VkDevice device, VkImageView imageView, const VkAllocationCallbacks *pAllocator) { + f(device, imageView, pAllocator); +} + +static VkResult vkCreateFramebuffer(PFN_vkCreateFramebuffer f, VkDevice device, VkFramebufferCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkFramebuffer *pFramebuffer) { + return f(device, &pCreateInfo, pAllocator, pFramebuffer); +} + +static void vkDestroyFramebuffer(PFN_vkDestroyFramebuffer f, VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks *pAllocator) { + f(device, framebuffer, pAllocator); +} + +static VkResult vkDeviceWaitIdle(PFN_vkDeviceWaitIdle f, VkDevice device) { + return f(device); +} + +static VkResult vkQueueWaitIdle(PFN_vkQueueWaitIdle f, VkQueue queue) { + return f(queue); +} + +static VkResult vkCreateSemaphore(PFN_vkCreateSemaphore f, VkDevice device, const VkSemaphoreCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSemaphore *pSemaphore) { + return f(device, pCreateInfo, pAllocator, pSemaphore); +} + +static void vkDestroySemaphore(PFN_vkDestroySemaphore f, VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks *pAllocator) { + f(device, semaphore, pAllocator); +} + +static VkResult vkCreateRenderPass(PFN_vkCreateRenderPass f, VkDevice device, VkRenderPassCreateInfo pCreateInfo, VkSubpassDescription subpassInf, const VkAllocationCallbacks *pAllocator, VkRenderPass *pRenderPass) { + pCreateInfo.pSubpasses = &subpassInf; + return f(device, &pCreateInfo, pAllocator, pRenderPass); +} + +static void vkDestroyRenderPass(PFN_vkDestroyRenderPass f, VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks *pAllocator) { + f(device, renderPass, pAllocator); +} + +static VkResult vkCreateCommandPool(PFN_vkCreateCommandPool f, VkDevice device, const VkCommandPoolCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkCommandPool *pCommandPool) { + return f(device, pCreateInfo, pAllocator, pCommandPool); +} + +static void vkDestroyCommandPool(PFN_vkDestroyCommandPool f, VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks *pAllocator) { + f(device, commandPool, pAllocator); +} + +static VkResult vkAllocateCommandBuffers(PFN_vkAllocateCommandBuffers f, VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo, VkCommandBuffer *pCommandBuffers) { + return f(device, pAllocateInfo, pCommandBuffers); +} + +static void vkFreeCommandBuffers(PFN_vkFreeCommandBuffers f, VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers) { + f(device, commandPool, commandBufferCount, pCommandBuffers); +} + +static VkResult vkBeginCommandBuffer(PFN_vkBeginCommandBuffer f, VkCommandBuffer commandBuffer, VkCommandBufferBeginInfo pBeginInfo) { + return f(commandBuffer, &pBeginInfo); +} + +static VkResult vkEndCommandBuffer(PFN_vkEndCommandBuffer f, VkCommandBuffer commandBuffer) { + return f(commandBuffer); +} + +static VkResult vkQueueSubmit(PFN_vkQueueSubmit f, VkQueue queue, VkSubmitInfo pSubmits, VkFence fence) { + return f(queue, 1, &pSubmits, fence); +} + +static void vkCmdBeginRenderPass(PFN_vkCmdBeginRenderPass f, VkCommandBuffer commandBuffer, VkRenderPassBeginInfo pRenderPassBegin, VkSubpassContents contents) { + f(commandBuffer, &pRenderPassBegin, contents); +} + +static void vkCmdEndRenderPass(PFN_vkCmdEndRenderPass f, VkCommandBuffer commandBuffer) { + f(commandBuffer); +} + +static void vkCmdCopyBuffer(PFN_vkCmdCopyBuffer f, VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferCopy *pRegions) { + f(commandBuffer, srcBuffer, dstBuffer, regionCount, pRegions); +} + +static void vkCmdCopyBufferToImage(PFN_vkCmdCopyBufferToImage f, VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkBufferImageCopy *pRegions) { + f(commandBuffer, srcBuffer, dstImage, dstImageLayout, regionCount, pRegions); +} + +static void vkCmdPipelineBarrier(PFN_vkCmdPipelineBarrier f, VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers) { + f(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers); +} + +static void vkCmdPushConstants(PFN_vkCmdPushConstants f, VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void *pValues) { + f(commandBuffer, layout, stageFlags, offset, size, pValues); +} + +static void vkCmdBindPipeline(PFN_vkCmdBindPipeline f, VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline) { + f(commandBuffer, pipelineBindPoint, pipeline); +} + +static void vkCmdBindVertexBuffers(PFN_vkCmdBindVertexBuffers f, VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer *pBuffers, const VkDeviceSize *pOffsets) { + f(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets); +} + +static void vkCmdSetViewport(PFN_vkCmdSetViewport f, VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport *pViewports) { + f(commandBuffer, firstViewport, viewportCount, pViewports); +} + +static void vkCmdBindIndexBuffer(PFN_vkCmdBindIndexBuffer f, VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType) { + f(commandBuffer, buffer, offset, indexType); +} + +static void vkCmdDraw(PFN_vkCmdDraw f, VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) { + f(commandBuffer, vertexCount, instanceCount, firstVertex, firstInstance); +} + +static void vkCmdDrawIndexed(PFN_vkCmdDrawIndexed f, VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance) { + f(commandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); +} + +static void vkCmdBindDescriptorSets(PFN_vkCmdBindDescriptorSets f, VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t *pDynamicOffsets) { + f(commandBuffer, pipelineBindPoint, layout, firstSet, descriptorSetCount, pDescriptorSets, dynamicOffsetCount, pDynamicOffsets); +} + +static void vkCmdCopyImageToBuffer(PFN_vkCmdCopyImageToBuffer f, VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy *pRegions) { + f(commandBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions); +} + +static void vkCmdDispatch(PFN_vkCmdDispatch f, VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ) { + f(commandBuffer, groupCountX, groupCountY, groupCountZ); +} + +static VkResult vkCreateImage(PFN_vkCreateImage f, VkDevice device, const VkImageCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkImage *pImage) { + return f(device, pCreateInfo, pAllocator, pImage); +} + +static void vkDestroyImage(PFN_vkDestroyImage f, VkDevice device, VkImage image, const VkAllocationCallbacks *pAllocator) { + f(device, image, pAllocator); +} + +static void vkGetImageMemoryRequirements(PFN_vkGetImageMemoryRequirements f, VkDevice device, VkImage image, VkMemoryRequirements *pMemoryRequirements) { + f(device, image, pMemoryRequirements); +} + +static VkResult vkAllocateMemory(PFN_vkAllocateMemory f, VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo, const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory) { + return f(device, pAllocateInfo, pAllocator, pMemory); +} + +static VkResult vkBindImageMemory(PFN_vkBindImageMemory f, VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) { + return f(device, image, memory, memoryOffset); +} + +static void vkFreeMemory(PFN_vkFreeMemory f, VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator) { + f(device, memory, pAllocator); +} + +static void vkGetPhysicalDeviceMemoryProperties(PFN_vkGetPhysicalDeviceMemoryProperties f, VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties *pMemoryProperties) { + f(physicalDevice, pMemoryProperties); +} + +static VkResult vkCreateSampler(PFN_vkCreateSampler f,VkDevice device, const VkSamplerCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSampler *pSampler) { + return f(device, pCreateInfo, pAllocator, pSampler); +} + +static void vkDestroySampler(PFN_vkDestroySampler f, VkDevice device, VkSampler sampler, const VkAllocationCallbacks *pAllocator) { + f(device, sampler, pAllocator); +} + +static VkResult vkCreateBuffer(PFN_vkCreateBuffer f, VkDevice device, const VkBufferCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer) { + return f(device, pCreateInfo, pAllocator, pBuffer); +} + +static void vkDestroyBuffer(PFN_vkDestroyBuffer f, VkDevice device, VkBuffer buffer, const VkAllocationCallbacks *pAllocator) { + f(device, buffer, pAllocator); +} + +static void vkGetBufferMemoryRequirements(PFN_vkGetBufferMemoryRequirements f, VkDevice device, VkBuffer buffer, VkMemoryRequirements *pMemoryRequirements) { + f(device, buffer, pMemoryRequirements); +} + +static VkResult vkBindBufferMemory(PFN_vkBindBufferMemory f, VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { + return f(device, buffer, memory, memoryOffset); +} + +static VkResult vkCreateShaderModule(PFN_vkCreateShaderModule f, VkDevice device, VkShaderModuleCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkShaderModule *pShaderModule) { + return f(device, &pCreateInfo, pAllocator, pShaderModule); +} + +static void vkDestroyShaderModule(PFN_vkDestroyShaderModule f, VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks *pAllocator) { + f(device, shaderModule, pAllocator); +} + +static VkResult vkCreateGraphicsPipelines(PFN_vkCreateGraphicsPipelines f, VkDevice device, VkPipelineCache pipelineCache, VkGraphicsPipelineCreateInfo pCreateInfo, VkPipelineDynamicStateCreateInfo dynInf, VkPipelineColorBlendStateCreateInfo blendInf, VkPipelineVertexInputStateCreateInfo vertexInf, VkPipelineViewportStateCreateInfo viewportInf, const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines) { + pCreateInfo.pDynamicState = &dynInf; + pCreateInfo.pViewportState = &viewportInf; + pCreateInfo.pColorBlendState = &blendInf; + pCreateInfo.pVertexInputState = &vertexInf; + return f(device, pipelineCache, 1, &pCreateInfo, pAllocator, pPipelines); +} + +static void vkDestroyPipeline(PFN_vkDestroyPipeline f, VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks *pAllocator) { + f(device, pipeline, pAllocator); +} + +static VkResult vkCreatePipelineLayout(PFN_vkCreatePipelineLayout f, VkDevice device, VkPipelineLayoutCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkPipelineLayout *pPipelineLayout) { + return f(device, &pCreateInfo, pAllocator, pPipelineLayout); +} + +static void vkDestroyPipelineLayout(PFN_vkDestroyPipelineLayout f, VkDevice device, VkPipelineLayout pipelineLayout, const VkAllocationCallbacks *pAllocator) { + f(device, pipelineLayout, pAllocator); +} + +static VkResult vkCreateDescriptorSetLayout(PFN_vkCreateDescriptorSetLayout f, VkDevice device, VkDescriptorSetLayoutCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDescriptorSetLayout *pSetLayout) { + return f(device, &pCreateInfo, pAllocator, pSetLayout); +} + +static void vkDestroyDescriptorSetLayout(PFN_vkDestroyDescriptorSetLayout f, VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks *pAllocator) { + f(device, descriptorSetLayout, pAllocator); +} + +static VkResult vkMapMemory(PFN_vkMapMemory f, VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void **ppData) { + return f(device, memory, offset, size, flags, ppData); +} + +static void vkUnmapMemory(PFN_vkUnmapMemory f, VkDevice device, VkDeviceMemory memory) { + f(device, memory); +} + +static VkResult vkResetCommandBuffer(PFN_vkResetCommandBuffer f, VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags) { + return f(commandBuffer, flags); +} + +static VkResult vkCreateDescriptorPool(PFN_vkCreateDescriptorPool f, VkDevice device, VkDescriptorPoolCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDescriptorPool *pDescriptorPool) { + return f(device, &pCreateInfo, pAllocator, pDescriptorPool); +} + +static void vkDestroyDescriptorPool(PFN_vkDestroyDescriptorPool f, VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks *pAllocator) { + f(device, descriptorPool, pAllocator); +} + +static VkResult vkAllocateDescriptorSets(PFN_vkAllocateDescriptorSets f, VkDevice device, VkDescriptorSetAllocateInfo pAllocateInfo, VkDescriptorSet *pDescriptorSets) { + return f(device, &pAllocateInfo, pDescriptorSets); +} + +static VkResult vkFreeDescriptorSets(PFN_vkFreeDescriptorSets f, VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets) { + return f(device, descriptorPool, descriptorSetCount, pDescriptorSets); +} + +static void vkUpdateDescriptorSets(PFN_vkUpdateDescriptorSets f, VkDevice device, VkWriteDescriptorSet pDescriptorWrite, uint32_t descriptorCopyCount, const VkCopyDescriptorSet *pDescriptorCopies) { + f(device, 1, &pDescriptorWrite, descriptorCopyCount, pDescriptorCopies); +} + +static VkResult vkResetDescriptorPool(PFN_vkResetDescriptorPool f, VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags) { + return f(device, descriptorPool, flags); +} + +static void vkCmdCopyImage(PFN_vkCmdCopyImage f, VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy *pRegions) { + f(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions); +} + +static VkResult vkCreateComputePipelines(PFN_vkCreateComputePipelines f, VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo *pCreateInfos, const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines) { + return f(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines); +} + +static VkResult vkCreateFence(PFN_vkCreateFence f, VkDevice device, const VkFenceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkFence *pFence) { + return f(device, pCreateInfo, pAllocator, pFence); +} + +static void vkDestroyFence(PFN_vkDestroyFence f, VkDevice device, VkFence fence, const VkAllocationCallbacks *pAllocator) { + f(device, fence, pAllocator); +} + +static VkResult vkWaitForFences(PFN_vkWaitForFences f, VkDevice device, uint32_t fenceCount, const VkFence *pFences, VkBool32 waitAll, uint64_t timeout) { + return f(device, fenceCount, pFences, waitAll, timeout); +} + +static VkResult vkResetFences(PFN_vkResetFences f, VkDevice device, uint32_t fenceCount, const VkFence *pFences) { + return f(device, fenceCount, pFences); +} + +static VkResult vkGetPhysicalDeviceSurfaceSupportKHR(PFN_vkGetPhysicalDeviceSurfaceSupportKHR f, VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32 *pSupported) { + return f(physicalDevice, queueFamilyIndex, surface, pSupported); +} + +static void vkDestroySurfaceKHR(PFN_vkDestroySurfaceKHR f, VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks *pAllocator) { + f(instance, surface, pAllocator); +} + +static VkResult vkGetPhysicalDeviceSurfaceFormatsKHR(PFN_vkGetPhysicalDeviceSurfaceFormatsKHR f, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t *pSurfaceFormatCount, VkSurfaceFormatKHR *pSurfaceFormats) { + return f(physicalDevice, surface, pSurfaceFormatCount, pSurfaceFormats); +} + +static VkResult vkGetPhysicalDeviceSurfacePresentModesKHR(PFN_vkGetPhysicalDeviceSurfacePresentModesKHR f, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t *pPresentModeCount, VkPresentModeKHR *pPresentModes) { + return f(physicalDevice, surface, pPresentModeCount, pPresentModes); +} + +static VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR f, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) { + return f(physicalDevice, surface, pSurfaceCapabilities); +} + +static VkResult vkCreateSwapchainKHR(PFN_vkCreateSwapchainKHR f, VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain) { + return f(device, pCreateInfo, pAllocator, pSwapchain); +} + +static void vkDestroySwapchainKHR(PFN_vkDestroySwapchainKHR f, VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator) { + f(device, swapchain, pAllocator); +} + +static VkResult vkGetSwapchainImagesKHR(PFN_vkGetSwapchainImagesKHR f, VkDevice device, VkSwapchainKHR swapchain, uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages) { + return f(device, swapchain, pSwapchainImageCount, pSwapchainImages); +} + +// indexAndResult holds both an integer and a result returned by value, to +// avoid Go heap allocation of the integer with Vulkan's return style. +struct intAndResult { + uint32_t uint; + VkResult res; +}; + +static struct intAndResult vkAcquireNextImageKHR(PFN_vkAcquireNextImageKHR f, VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence) { + struct intAndResult res; + res.res = f(device, swapchain, timeout, semaphore, fence, &res.uint); + return res; +} + +static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const VkPresentInfoKHR pPresentInfo) { + return f(queue, &pPresentInfo); +} +*/ +import "C" +import ( + "errors" + "fmt" + "math" + "reflect" + "runtime" + "sync" + "unsafe" +) + +type ( + AttachmentLoadOp = C.VkAttachmentLoadOp + AccessFlags = C.VkAccessFlags + BlendFactor = C.VkBlendFactor + Buffer = C.VkBuffer + BufferImageCopy = C.VkBufferImageCopy + BufferMemoryBarrier = C.VkBufferMemoryBarrier + BufferUsageFlags = C.VkBufferUsageFlags + CommandPool = C.VkCommandPool + CommandBuffer = C.VkCommandBuffer + DependencyFlags = C.VkDependencyFlags + DescriptorPool = C.VkDescriptorPool + DescriptorPoolSize = C.VkDescriptorPoolSize + DescriptorSet = C.VkDescriptorSet + DescriptorSetLayout = C.VkDescriptorSetLayout + DescriptorType = C.VkDescriptorType + Device = C.VkDevice + DeviceMemory = C.VkDeviceMemory + DeviceSize = C.VkDeviceSize + Fence = C.VkFence + Queue = C.VkQueue + IndexType = C.VkIndexType + Image = C.VkImage + ImageCopy = C.VkImageCopy + ImageLayout = C.VkImageLayout + ImageMemoryBarrier = C.VkImageMemoryBarrier + ImageUsageFlags = C.VkImageUsageFlags + ImageView = C.VkImageView + Instance = C.VkInstance + Filter = C.VkFilter + Format = C.VkFormat + FormatFeatureFlags = C.VkFormatFeatureFlags + Framebuffer = C.VkFramebuffer + MemoryBarrier = C.VkMemoryBarrier + MemoryPropertyFlags = C.VkMemoryPropertyFlags + Pipeline = C.VkPipeline + PipelineBindPoint = C.VkPipelineBindPoint + PipelineLayout = C.VkPipelineLayout + PipelineStageFlags = C.VkPipelineStageFlags + PhysicalDevice = C.VkPhysicalDevice + PrimitiveTopology = C.VkPrimitiveTopology + PushConstantRange = C.VkPushConstantRange + QueueFamilyProperties = C.VkQueueFamilyProperties + QueueFlags = C.VkQueueFlags + RenderPass = C.VkRenderPass + Sampler = C.VkSampler + SamplerMipmapMode = C.VkSamplerMipmapMode + Semaphore = C.VkSemaphore + ShaderModule = C.VkShaderModule + ShaderStageFlags = C.VkShaderStageFlags + SubpassDependency = C.VkSubpassDependency + Viewport = C.VkViewport + WriteDescriptorSet = C.VkWriteDescriptorSet + + Surface = C.VkSurfaceKHR + + Swapchain = C.VkSwapchainKHR +) + +type VertexInputBindingDescription struct { + Binding int + Stride int +} + +type VertexInputAttributeDescription struct { + Location int + Binding int + Format Format + Offset int +} + +type DescriptorSetLayoutBinding struct { + Binding int + DescriptorType DescriptorType + StageFlags ShaderStageFlags +} + +type Error C.VkResult + +const ( + FORMAT_R8G8B8A8_UNORM Format = C.VK_FORMAT_R8G8B8A8_UNORM + FORMAT_B8G8R8A8_SRGB Format = C.VK_FORMAT_B8G8R8A8_SRGB + FORMAT_R8G8B8A8_SRGB Format = C.VK_FORMAT_R8G8B8A8_SRGB + FORMAT_R16_SFLOAT Format = C.VK_FORMAT_R16_SFLOAT + FORMAT_R32_SFLOAT Format = C.VK_FORMAT_R32_SFLOAT + FORMAT_R32G32_SFLOAT Format = C.VK_FORMAT_R32G32_SFLOAT + FORMAT_R32G32B32_SFLOAT Format = C.VK_FORMAT_R32G32B32_SFLOAT + FORMAT_R32G32B32A32_SFLOAT Format = C.VK_FORMAT_R32G32B32A32_SFLOAT + + FORMAT_FEATURE_COLOR_ATTACHMENT_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT + FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT + FORMAT_FEATURE_SAMPLED_IMAGE_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + + IMAGE_USAGE_SAMPLED_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_SAMPLED_BIT + IMAGE_USAGE_COLOR_ATTACHMENT_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT + IMAGE_USAGE_STORAGE_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_STORAGE_BIT + IMAGE_USAGE_TRANSFER_DST_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_TRANSFER_DST_BIT + IMAGE_USAGE_TRANSFER_SRC_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_TRANSFER_SRC_BIT + + FILTER_NEAREST Filter = C.VK_FILTER_NEAREST + FILTER_LINEAR Filter = C.VK_FILTER_LINEAR + + ATTACHMENT_LOAD_OP_CLEAR AttachmentLoadOp = C.VK_ATTACHMENT_LOAD_OP_CLEAR + ATTACHMENT_LOAD_OP_DONT_CARE AttachmentLoadOp = C.VK_ATTACHMENT_LOAD_OP_DONT_CARE + ATTACHMENT_LOAD_OP_LOAD AttachmentLoadOp = C.VK_ATTACHMENT_LOAD_OP_LOAD + + IMAGE_LAYOUT_UNDEFINED ImageLayout = C.VK_IMAGE_LAYOUT_UNDEFINED + IMAGE_LAYOUT_PRESENT_SRC_KHR ImageLayout = C.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR + IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ImageLayout = C.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ImageLayout = C.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ImageLayout = C.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ImageLayout = C.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL + IMAGE_LAYOUT_GENERAL ImageLayout = C.VK_IMAGE_LAYOUT_GENERAL + + BUFFER_USAGE_TRANSFER_DST_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_TRANSFER_DST_BIT + BUFFER_USAGE_TRANSFER_SRC_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_TRANSFER_SRC_BIT + BUFFER_USAGE_UNIFORM_BUFFER_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT + BUFFER_USAGE_STORAGE_BUFFER_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + BUFFER_USAGE_INDEX_BUFFER_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_INDEX_BUFFER_BIT + BUFFER_USAGE_VERTEX_BUFFER_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT + + ERROR_OUT_OF_DATE_KHR = Error(C.VK_ERROR_OUT_OF_DATE_KHR) + ERROR_SURFACE_LOST_KHR = Error(C.VK_ERROR_SURFACE_LOST_KHR) + ERROR_DEVICE_LOST = Error(C.VK_ERROR_DEVICE_LOST) + SUBOPTIMAL_KHR = Error(C.VK_SUBOPTIMAL_KHR) + + BLEND_FACTOR_ZERO BlendFactor = C.VK_BLEND_FACTOR_ZERO + BLEND_FACTOR_ONE BlendFactor = C.VK_BLEND_FACTOR_ONE + BLEND_FACTOR_ONE_MINUS_SRC_ALPHA BlendFactor = C.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA + BLEND_FACTOR_DST_COLOR BlendFactor = C.VK_BLEND_FACTOR_DST_COLOR + + PRIMITIVE_TOPOLOGY_TRIANGLE_LIST PrimitiveTopology = C.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST + PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP PrimitiveTopology = C.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP + + SHADER_STAGE_VERTEX_BIT ShaderStageFlags = C.VK_SHADER_STAGE_VERTEX_BIT + SHADER_STAGE_FRAGMENT_BIT ShaderStageFlags = C.VK_SHADER_STAGE_FRAGMENT_BIT + SHADER_STAGE_COMPUTE_BIT ShaderStageFlags = C.VK_SHADER_STAGE_COMPUTE_BIT + + DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER DescriptorType = C.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + DESCRIPTOR_TYPE_UNIFORM_BUFFER DescriptorType = C.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + DESCRIPTOR_TYPE_STORAGE_BUFFER DescriptorType = C.VK_DESCRIPTOR_TYPE_STORAGE_BUFFER + DESCRIPTOR_TYPE_STORAGE_IMAGE DescriptorType = C.VK_DESCRIPTOR_TYPE_STORAGE_IMAGE + + MEMORY_PROPERTY_DEVICE_LOCAL_BIT MemoryPropertyFlags = C.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + MEMORY_PROPERTY_HOST_VISIBLE_BIT MemoryPropertyFlags = C.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + MEMORY_PROPERTY_HOST_COHERENT_BIT MemoryPropertyFlags = C.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + + DEPENDENCY_BY_REGION_BIT DependencyFlags = C.VK_DEPENDENCY_BY_REGION_BIT + + PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + PIPELINE_STAGE_TRANSFER_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_TRANSFER_BIT + PIPELINE_STAGE_FRAGMENT_SHADER_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT + PIPELINE_STAGE_COMPUTE_SHADER_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT + PIPELINE_STAGE_TOP_OF_PIPE_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT + PIPELINE_STAGE_HOST_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_HOST_BIT + PIPELINE_STAGE_VERTEX_INPUT_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_VERTEX_INPUT_BIT + PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT + + ACCESS_MEMORY_READ_BIT AccessFlags = C.VK_ACCESS_MEMORY_READ_BIT + ACCESS_MEMORY_WRITE_BIT AccessFlags = C.VK_ACCESS_MEMORY_WRITE_BIT + ACCESS_TRANSFER_READ_BIT AccessFlags = C.VK_ACCESS_TRANSFER_READ_BIT + ACCESS_TRANSFER_WRITE_BIT AccessFlags = C.VK_ACCESS_TRANSFER_WRITE_BIT + ACCESS_SHADER_READ_BIT AccessFlags = C.VK_ACCESS_SHADER_READ_BIT + ACCESS_SHADER_WRITE_BIT AccessFlags = C.VK_ACCESS_SHADER_WRITE_BIT + ACCESS_COLOR_ATTACHMENT_READ_BIT AccessFlags = C.VK_ACCESS_COLOR_ATTACHMENT_READ_BIT + ACCESS_COLOR_ATTACHMENT_WRITE_BIT AccessFlags = C.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT + ACCESS_HOST_READ_BIT AccessFlags = C.VK_ACCESS_HOST_READ_BIT + ACCESS_HOST_WRITE_BIT AccessFlags = C.VK_ACCESS_HOST_WRITE_BIT + ACCESS_VERTEX_ATTRIBUTE_READ_BIT AccessFlags = C.VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT + ACCESS_INDEX_READ_BIT AccessFlags = C.VK_ACCESS_INDEX_READ_BIT + + PIPELINE_BIND_POINT_COMPUTE PipelineBindPoint = C.VK_PIPELINE_BIND_POINT_COMPUTE + PIPELINE_BIND_POINT_GRAPHICS PipelineBindPoint = C.VK_PIPELINE_BIND_POINT_GRAPHICS + + INDEX_TYPE_UINT16 IndexType = C.VK_INDEX_TYPE_UINT16 + INDEX_TYPE_UINT32 IndexType = C.VK_INDEX_TYPE_UINT32 + + QUEUE_GRAPHICS_BIT QueueFlags = C.VK_QUEUE_GRAPHICS_BIT + QUEUE_COMPUTE_BIT QueueFlags = C.VK_QUEUE_COMPUTE_BIT +) + +var ( + once sync.Once + loadErr error + + loadFuncs []func(dlopen func(name string) *[0]byte) +) + +var funcs struct { + vkCreateInstance C.PFN_vkCreateInstance + vkDestroyInstance C.PFN_vkDestroyInstance + vkEnumeratePhysicalDevices C.PFN_vkEnumeratePhysicalDevices + vkGetPhysicalDeviceQueueFamilyProperties C.PFN_vkGetPhysicalDeviceQueueFamilyProperties + vkGetPhysicalDeviceFormatProperties C.PFN_vkGetPhysicalDeviceFormatProperties + vkCreateDevice C.PFN_vkCreateDevice + vkDestroyDevice C.PFN_vkDestroyDevice + vkGetDeviceQueue C.PFN_vkGetDeviceQueue + vkCreateImageView C.PFN_vkCreateImageView + vkDestroyImageView C.PFN_vkDestroyImageView + vkCreateFramebuffer C.PFN_vkCreateFramebuffer + vkDestroyFramebuffer C.PFN_vkDestroyFramebuffer + vkDeviceWaitIdle C.PFN_vkDeviceWaitIdle + vkQueueWaitIdle C.PFN_vkQueueWaitIdle + vkCreateSemaphore C.PFN_vkCreateSemaphore + vkDestroySemaphore C.PFN_vkDestroySemaphore + vkCreateRenderPass C.PFN_vkCreateRenderPass + vkDestroyRenderPass C.PFN_vkDestroyRenderPass + vkCreateCommandPool C.PFN_vkCreateCommandPool + vkDestroyCommandPool C.PFN_vkDestroyCommandPool + vkAllocateCommandBuffers C.PFN_vkAllocateCommandBuffers + vkFreeCommandBuffers C.PFN_vkFreeCommandBuffers + vkBeginCommandBuffer C.PFN_vkBeginCommandBuffer + vkEndCommandBuffer C.PFN_vkEndCommandBuffer + vkQueueSubmit C.PFN_vkQueueSubmit + vkCmdBeginRenderPass C.PFN_vkCmdBeginRenderPass + vkCmdEndRenderPass C.PFN_vkCmdEndRenderPass + vkCmdCopyBuffer C.PFN_vkCmdCopyBuffer + vkCmdCopyBufferToImage C.PFN_vkCmdCopyBufferToImage + vkCmdPipelineBarrier C.PFN_vkCmdPipelineBarrier + vkCmdPushConstants C.PFN_vkCmdPushConstants + vkCmdBindPipeline C.PFN_vkCmdBindPipeline + vkCmdBindVertexBuffers C.PFN_vkCmdBindVertexBuffers + vkCmdSetViewport C.PFN_vkCmdSetViewport + vkCmdBindIndexBuffer C.PFN_vkCmdBindIndexBuffer + vkCmdDraw C.PFN_vkCmdDraw + vkCmdDrawIndexed C.PFN_vkCmdDrawIndexed + vkCmdBindDescriptorSets C.PFN_vkCmdBindDescriptorSets + vkCmdCopyImageToBuffer C.PFN_vkCmdCopyImageToBuffer + vkCmdDispatch C.PFN_vkCmdDispatch + vkCreateImage C.PFN_vkCreateImage + vkDestroyImage C.PFN_vkDestroyImage + vkGetImageMemoryRequirements C.PFN_vkGetImageMemoryRequirements + vkAllocateMemory C.PFN_vkAllocateMemory + vkBindImageMemory C.PFN_vkBindImageMemory + vkFreeMemory C.PFN_vkFreeMemory + vkGetPhysicalDeviceMemoryProperties C.PFN_vkGetPhysicalDeviceMemoryProperties + vkCreateSampler C.PFN_vkCreateSampler + vkDestroySampler C.PFN_vkDestroySampler + vkCreateBuffer C.PFN_vkCreateBuffer + vkDestroyBuffer C.PFN_vkDestroyBuffer + vkGetBufferMemoryRequirements C.PFN_vkGetBufferMemoryRequirements + vkBindBufferMemory C.PFN_vkBindBufferMemory + vkCreateShaderModule C.PFN_vkCreateShaderModule + vkDestroyShaderModule C.PFN_vkDestroyShaderModule + vkCreateGraphicsPipelines C.PFN_vkCreateGraphicsPipelines + vkDestroyPipeline C.PFN_vkDestroyPipeline + vkCreatePipelineLayout C.PFN_vkCreatePipelineLayout + vkDestroyPipelineLayout C.PFN_vkDestroyPipelineLayout + vkCreateDescriptorSetLayout C.PFN_vkCreateDescriptorSetLayout + vkDestroyDescriptorSetLayout C.PFN_vkDestroyDescriptorSetLayout + vkMapMemory C.PFN_vkMapMemory + vkUnmapMemory C.PFN_vkUnmapMemory + vkResetCommandBuffer C.PFN_vkResetCommandBuffer + vkCreateDescriptorPool C.PFN_vkCreateDescriptorPool + vkDestroyDescriptorPool C.PFN_vkDestroyDescriptorPool + vkAllocateDescriptorSets C.PFN_vkAllocateDescriptorSets + vkFreeDescriptorSets C.PFN_vkFreeDescriptorSets + vkUpdateDescriptorSets C.PFN_vkUpdateDescriptorSets + vkResetDescriptorPool C.PFN_vkResetDescriptorPool + vkCmdCopyImage C.PFN_vkCmdCopyImage + vkCreateComputePipelines C.PFN_vkCreateComputePipelines + vkCreateFence C.PFN_vkCreateFence + vkDestroyFence C.PFN_vkDestroyFence + vkWaitForFences C.PFN_vkWaitForFences + vkResetFences C.PFN_vkResetFences + + vkGetPhysicalDeviceSurfaceSupportKHR C.PFN_vkGetPhysicalDeviceSurfaceSupportKHR + vkDestroySurfaceKHR C.PFN_vkDestroySurfaceKHR + vkGetPhysicalDeviceSurfaceFormatsKHR C.PFN_vkGetPhysicalDeviceSurfaceFormatsKHR + vkGetPhysicalDeviceSurfacePresentModesKHR C.PFN_vkGetPhysicalDeviceSurfacePresentModesKHR + vkGetPhysicalDeviceSurfaceCapabilitiesKHR C.PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR + + vkCreateSwapchainKHR C.PFN_vkCreateSwapchainKHR + vkDestroySwapchainKHR C.PFN_vkDestroySwapchainKHR + vkGetSwapchainImagesKHR C.PFN_vkGetSwapchainImagesKHR + vkAcquireNextImageKHR C.PFN_vkAcquireNextImageKHR + vkQueuePresentKHR C.PFN_vkQueuePresentKHR +} + +var ( + nilSurface C.VkSurfaceKHR + nilSwapchain C.VkSwapchainKHR + nilSemaphore C.VkSemaphore + nilImageView C.VkImageView + nilRenderPass C.VkRenderPass + nilFramebuffer C.VkFramebuffer + nilCommandPool C.VkCommandPool + nilImage C.VkImage + nilDeviceMemory C.VkDeviceMemory + nilSampler C.VkSampler + nilBuffer C.VkBuffer + nilShaderModule C.VkShaderModule + nilPipeline C.VkPipeline + nilPipelineCache C.VkPipelineCache + nilPipelineLayout C.VkPipelineLayout + nilDescriptorSetLayout C.VkDescriptorSetLayout + nilDescriptorPool C.VkDescriptorPool + nilDescriptorSet C.VkDescriptorSet + nilFence C.VkFence +) + +func vkInit() error { + once.Do(func() { + var libName string + switch { + case runtime.GOOS == "android": + libName = "libvulkan.so" + default: + libName = "libvulkan.so.1" + } + lib := dlopen(libName) + if lib == nil { + loadErr = fmt.Errorf("vulkan: %s", C.GoString(C.dlerror())) + return + } + dlopen := func(name string) *[0]byte { + return (*[0]byte)(dlsym(lib, name)) + } + must := func(name string) *[0]byte { + ptr := dlopen(name) + if ptr != nil { + return ptr + } + if loadErr == nil { + loadErr = fmt.Errorf("vulkan: function %q not found: %s", name, C.GoString(C.dlerror())) + } + return nil + } + funcs.vkCreateInstance = must("vkCreateInstance") + funcs.vkDestroyInstance = must("vkDestroyInstance") + funcs.vkEnumeratePhysicalDevices = must("vkEnumeratePhysicalDevices") + funcs.vkGetPhysicalDeviceQueueFamilyProperties = must("vkGetPhysicalDeviceQueueFamilyProperties") + funcs.vkGetPhysicalDeviceFormatProperties = must("vkGetPhysicalDeviceFormatProperties") + funcs.vkCreateDevice = must("vkCreateDevice") + funcs.vkDestroyDevice = must("vkDestroyDevice") + funcs.vkGetDeviceQueue = must("vkGetDeviceQueue") + funcs.vkCreateImageView = must("vkCreateImageView") + funcs.vkDestroyImageView = must("vkDestroyImageView") + funcs.vkCreateFramebuffer = must("vkCreateFramebuffer") + funcs.vkDestroyFramebuffer = must("vkDestroyFramebuffer") + funcs.vkDeviceWaitIdle = must("vkDeviceWaitIdle") + funcs.vkQueueWaitIdle = must("vkQueueWaitIdle") + funcs.vkCreateSemaphore = must("vkCreateSemaphore") + funcs.vkDestroySemaphore = must("vkDestroySemaphore") + funcs.vkCreateRenderPass = must("vkCreateRenderPass") + funcs.vkDestroyRenderPass = must("vkDestroyRenderPass") + funcs.vkCreateCommandPool = must("vkCreateCommandPool") + funcs.vkDestroyCommandPool = must("vkDestroyCommandPool") + funcs.vkAllocateCommandBuffers = must("vkAllocateCommandBuffers") + funcs.vkFreeCommandBuffers = must("vkFreeCommandBuffers") + funcs.vkBeginCommandBuffer = must("vkBeginCommandBuffer") + funcs.vkEndCommandBuffer = must("vkEndCommandBuffer") + funcs.vkQueueSubmit = must("vkQueueSubmit") + funcs.vkCmdBeginRenderPass = must("vkCmdBeginRenderPass") + funcs.vkCmdEndRenderPass = must("vkCmdEndRenderPass") + funcs.vkCmdCopyBuffer = must("vkCmdCopyBuffer") + funcs.vkCmdCopyBufferToImage = must("vkCmdCopyBufferToImage") + funcs.vkCmdPipelineBarrier = must("vkCmdPipelineBarrier") + funcs.vkCmdPushConstants = must("vkCmdPushConstants") + funcs.vkCmdBindPipeline = must("vkCmdBindPipeline") + funcs.vkCmdBindVertexBuffers = must("vkCmdBindVertexBuffers") + funcs.vkCmdSetViewport = must("vkCmdSetViewport") + funcs.vkCmdBindIndexBuffer = must("vkCmdBindIndexBuffer") + funcs.vkCmdDraw = must("vkCmdDraw") + funcs.vkCmdDrawIndexed = must("vkCmdDrawIndexed") + funcs.vkCmdBindDescriptorSets = must("vkCmdBindDescriptorSets") + funcs.vkCmdCopyImageToBuffer = must("vkCmdCopyImageToBuffer") + funcs.vkCmdDispatch = must("vkCmdDispatch") + funcs.vkCreateImage = must("vkCreateImage") + funcs.vkDestroyImage = must("vkDestroyImage") + funcs.vkGetImageMemoryRequirements = must("vkGetImageMemoryRequirements") + funcs.vkAllocateMemory = must("vkAllocateMemory") + funcs.vkBindImageMemory = must("vkBindImageMemory") + funcs.vkFreeMemory = must("vkFreeMemory") + funcs.vkGetPhysicalDeviceMemoryProperties = must("vkGetPhysicalDeviceMemoryProperties") + funcs.vkCreateSampler = must("vkCreateSampler") + funcs.vkDestroySampler = must("vkDestroySampler") + funcs.vkCreateBuffer = must("vkCreateBuffer") + funcs.vkDestroyBuffer = must("vkDestroyBuffer") + funcs.vkGetBufferMemoryRequirements = must("vkGetBufferMemoryRequirements") + funcs.vkBindBufferMemory = must("vkBindBufferMemory") + funcs.vkCreateShaderModule = must("vkCreateShaderModule") + funcs.vkDestroyShaderModule = must("vkDestroyShaderModule") + funcs.vkCreateGraphicsPipelines = must("vkCreateGraphicsPipelines") + funcs.vkDestroyPipeline = must("vkDestroyPipeline") + funcs.vkCreatePipelineLayout = must("vkCreatePipelineLayout") + funcs.vkDestroyPipelineLayout = must("vkDestroyPipelineLayout") + funcs.vkCreateDescriptorSetLayout = must("vkCreateDescriptorSetLayout") + funcs.vkDestroyDescriptorSetLayout = must("vkDestroyDescriptorSetLayout") + funcs.vkMapMemory = must("vkMapMemory") + funcs.vkUnmapMemory = must("vkUnmapMemory") + funcs.vkResetCommandBuffer = must("vkResetCommandBuffer") + funcs.vkCreateDescriptorPool = must("vkCreateDescriptorPool") + funcs.vkDestroyDescriptorPool = must("vkDestroyDescriptorPool") + funcs.vkAllocateDescriptorSets = must("vkAllocateDescriptorSets") + funcs.vkFreeDescriptorSets = must("vkFreeDescriptorSets") + funcs.vkUpdateDescriptorSets = must("vkUpdateDescriptorSets") + funcs.vkResetDescriptorPool = must("vkResetDescriptorPool") + funcs.vkCmdCopyImage = must("vkCmdCopyImage") + funcs.vkCreateComputePipelines = must("vkCreateComputePipelines") + funcs.vkCreateFence = must("vkCreateFence") + funcs.vkDestroyFence = must("vkDestroyFence") + funcs.vkWaitForFences = must("vkWaitForFences") + funcs.vkResetFences = must("vkResetFences") + + funcs.vkGetPhysicalDeviceSurfaceSupportKHR = dlopen("vkGetPhysicalDeviceSurfaceSupportKHR") + funcs.vkDestroySurfaceKHR = dlopen("vkDestroySurfaceKHR") + funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = dlopen("vkGetPhysicalDeviceSurfaceFormatsKHR") + funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = dlopen("vkGetPhysicalDeviceSurfacePresentModesKHR") + funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = dlopen("vkGetPhysicalDeviceSurfaceCapabilitiesKHR") + + funcs.vkCreateSwapchainKHR = dlopen("vkCreateSwapchainKHR") + funcs.vkDestroySwapchainKHR = dlopen("vkDestroySwapchainKHR") + funcs.vkGetSwapchainImagesKHR = dlopen("vkGetSwapchainImagesKHR") + funcs.vkAcquireNextImageKHR = dlopen("vkAcquireNextImageKHR") + funcs.vkQueuePresentKHR = dlopen("vkQueuePresentKHR") + + for _, f := range loadFuncs { + f(dlopen) + } + }) + return loadErr +} + +func CreateInstance(exts ...string) (Instance, error) { + if err := vkInit(); err != nil { + return nil, err + } + inf := C.VkInstanceCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + enabledExtensionCount: C.uint32_t(len(exts)), + } + if len(exts) > 0 { + cexts := mallocCStringArr(exts) + defer freeCStringArr(cexts) + inf.ppEnabledExtensionNames = &cexts[0] + } + var inst Instance + if err := vkErr(C.vkCreateInstance(funcs.vkCreateInstance, inf, nil, &inst)); err != nil { + return nil, fmt.Errorf("vulkan: vkCreateInstance: %w", err) + } + return inst, nil +} + +func mallocCStringArr(s []string) []*C.char { + carr := make([]*C.char, len(s)) + for i, ext := range s { + carr[i] = C.CString(ext) + } + return carr +} + +func freeCStringArr(s []*C.char) { + for i := range s { + C.free(unsafe.Pointer(s[i])) + s[i] = nil + } +} + +func DestroyInstance(inst Instance) { + C.vkDestroyInstance(funcs.vkDestroyInstance, inst, nil) +} + +func GetPhysicalDeviceQueueFamilyProperties(pd PhysicalDevice) []QueueFamilyProperties { + var count C.uint32_t + C.vkGetPhysicalDeviceQueueFamilyProperties(funcs.vkGetPhysicalDeviceQueueFamilyProperties, pd, &count, nil) + if count == 0 { + return nil + } + queues := make([]C.VkQueueFamilyProperties, count) + C.vkGetPhysicalDeviceQueueFamilyProperties(funcs.vkGetPhysicalDeviceQueueFamilyProperties, pd, &count, &queues[0]) + return queues +} + +func ChoosePhysicalDevice(inst Instance, surf Surface) (PhysicalDevice, int, error) { + var count C.uint32_t + if err := vkErr(C.vkEnumeratePhysicalDevices(funcs.vkEnumeratePhysicalDevices, inst, &count, nil)); err != nil { + return nil, 0, fmt.Errorf("vulkan: vkEnumeratePhysicalDevices: %w", err) + } + if count == 0 { + return nil, 0, errors.New("vulkan: no devices available") + } + devs := make([]C.VkPhysicalDevice, count) + if err := vkErr(C.vkEnumeratePhysicalDevices(funcs.vkEnumeratePhysicalDevices, inst, &count, &devs[0])); err != nil { + return nil, 0, fmt.Errorf("vulkan: vkEnumeratePhysicalDevices: %w", err) + } + for _, pd := range devs { + const caps = C.VK_QUEUE_GRAPHICS_BIT | C.VK_QUEUE_COMPUTE_BIT + queueIdx, ok, err := chooseQueue(pd, surf, caps) + if err != nil { + return nil, 0, err + } + if !ok { + continue + } + if surf != nilSurface { + _, fmtFound, err := chooseFormat(pd, surf) + if err != nil { + return nil, 0, err + } + _, modFound, err := choosePresentMode(pd, surf) + if err != nil { + return nil, 0, err + } + if !fmtFound || !modFound { + continue + } + } + return pd, queueIdx, nil + } + return nil, 0, errors.New("vulkan: no suitable device found") +} + +func CreateDeviceAndQueue(pd C.VkPhysicalDevice, queueIdx int, exts ...string) (Device, error) { + priority := C.float(1.0) + qinf := C.VkDeviceQueueCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + queueCount: 1, + queueFamilyIndex: C.uint32_t(queueIdx), + pQueuePriorities: &priority, + } + inf := C.VkDeviceCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + queueCreateInfoCount: 1, + enabledExtensionCount: C.uint32_t(len(exts)), + } + if len(exts) > 0 { + cexts := mallocCStringArr(exts) + defer freeCStringArr(cexts) + inf.ppEnabledExtensionNames = &cexts[0] + } + var dev Device + if err := vkErr(C.vkCreateDevice(funcs.vkCreateDevice, pd, inf, qinf, nil, &dev)); err != nil { + return nil, fmt.Errorf("vulkan: vkCreateDevice: %w", err) + } + return dev, nil +} + +func GetDeviceQueue(d Device, queueFamily, queueIndex int) Queue { + var queue Queue + C.vkGetDeviceQueue(funcs.vkGetDeviceQueue, d, C.uint32_t(queueFamily), C.uint32_t(queueIndex), &queue) + return queue +} + +func CreateSwapchain(pd PhysicalDevice, d Device, surf Surface, width, height int, old Swapchain) (Swapchain, []Image, Format, error) { + var caps C.VkSurfaceCapabilitiesKHR + err := vkErr(C.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR, pd, surf, &caps)) + if err != nil { + return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceCapabilitiesKHR: %w", err) + } + mode, modeOK, err := choosePresentMode(pd, surf) + if err != nil { + return nilSwapchain, nil, 0, err + } + format, fmtOK, err := chooseFormat(pd, surf) + if err != nil { + return nilSwapchain, nil, 0, err + } + if !modeOK || !fmtOK { + // This shouldn't happen because CreateDeviceAndQueue found at least + // one valid format and present mode. + return nilSwapchain, nil, 0, errors.New("vulkan: no valid format and present mode found") + } + // Find supported alpha composite mode. It doesn't matter which one, because rendering is + // always opaque. + alphaComp := C.VkCompositeAlphaFlagBitsKHR(1) + for caps.supportedCompositeAlpha&C.VkCompositeAlphaFlagsKHR(alphaComp) == 0 { + alphaComp <<= 1 + } + trans := C.VkSurfaceTransformFlagBitsKHR(C.VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) + if caps.supportedTransforms&C.VkSurfaceTransformFlagsKHR(trans) == 0 { + return nilSwapchain, nil, 0, errors.New("vulkan: VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR not supported") + } + inf := C.VkSwapchainCreateInfoKHR{ + sType: C.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + surface: surf, + minImageCount: caps.minImageCount, + imageFormat: format.format, + imageColorSpace: format.colorSpace, + imageExtent: C.VkExtent2D{width: C.uint32_t(width), height: C.uint32_t(height)}, + imageArrayLayers: 1, + imageUsage: C.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + imageSharingMode: C.VK_SHARING_MODE_EXCLUSIVE, + preTransform: trans, + presentMode: mode, + compositeAlpha: C.VkCompositeAlphaFlagBitsKHR(alphaComp), + clipped: C.VK_TRUE, + oldSwapchain: old, + } + var swchain Swapchain + if err := vkErr(C.vkCreateSwapchainKHR(funcs.vkCreateSwapchainKHR, d, &inf, nil, &swchain)); err != nil { + return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkCreateSwapchainKHR: %w", err) + } + var count C.uint32_t + if err := vkErr(C.vkGetSwapchainImagesKHR(funcs.vkGetSwapchainImagesKHR, d, swchain, &count, nil)); err != nil { + DestroySwapchain(d, swchain) + return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkGetSwapchainImagesKHR: %w", err) + } + if count == 0 { + DestroySwapchain(d, swchain) + return nilSwapchain, nil, 0, errors.New("vulkan: vkGetSwapchainImagesKHR returned no images") + } + imgs := make([]Image, count) + if err := vkErr(C.vkGetSwapchainImagesKHR(funcs.vkGetSwapchainImagesKHR, d, swchain, &count, &imgs[0])); err != nil { + DestroySwapchain(d, swchain) + return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkGetSwapchainImagesKHR: %w", err) + } + return swchain, imgs, format.format, nil +} + +func DestroySwapchain(d Device, swchain Swapchain) { + C.vkDestroySwapchainKHR(funcs.vkDestroySwapchainKHR, d, swchain, nil) +} + +func AcquireNextImage(d Device, swchain Swapchain, sem Semaphore, fence Fence) (int, error) { + res := C.vkAcquireNextImageKHR(funcs.vkAcquireNextImageKHR, d, swchain, math.MaxUint64, sem, fence) + if err := vkErr(res.res); err != nil { + return 0, fmt.Errorf("vulkan: vkAcquireNextImageKHR: %w", err) + } + return int(res.uint), nil +} + +func PresentQueue(q Queue, swchain Swapchain, sem Semaphore, imgIdx int) error { + cidx := C.uint32_t(imgIdx) + inf := C.VkPresentInfoKHR{ + sType: C.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + swapchainCount: 1, + pSwapchains: &swchain, + pImageIndices: &cidx, + } + if sem != nilSemaphore { + inf.waitSemaphoreCount = 1 + inf.pWaitSemaphores = &sem + } + if err := vkErr(C.vkQueuePresentKHR(funcs.vkQueuePresentKHR, q, inf)); err != nil { + return fmt.Errorf("vulkan: vkQueuePresentKHR: %w", err) + } + return nil +} + +func CreateImageView(d Device, img Image, format Format) (ImageView, error) { + inf := C.VkImageViewCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + image: img, + viewType: C.VK_IMAGE_VIEW_TYPE_2D, + format: format, + subresourceRange: C.VkImageSubresourceRange{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + levelCount: C.VK_REMAINING_MIP_LEVELS, + layerCount: C.VK_REMAINING_ARRAY_LAYERS, + }, + } + var view C.VkImageView + if err := vkErr(C.vkCreateImageView(funcs.vkCreateImageView, d, &inf, nil, &view)); err != nil { + return nilImageView, fmt.Errorf("vulkan: vkCreateImageView: %w", err) + } + return view, nil +} + +func DestroyImageView(d Device, view ImageView) { + C.vkDestroyImageView(funcs.vkDestroyImageView, d, view, nil) +} + +func CreateRenderPass(d Device, format Format, loadOp AttachmentLoadOp, initialLayout, finalLayout ImageLayout, passDeps []SubpassDependency) (RenderPass, error) { + att := C.VkAttachmentDescription{ + format: format, + samples: C.VK_SAMPLE_COUNT_1_BIT, + loadOp: loadOp, + storeOp: C.VK_ATTACHMENT_STORE_OP_STORE, + stencilLoadOp: C.VK_ATTACHMENT_LOAD_OP_DONT_CARE, + stencilStoreOp: C.VK_ATTACHMENT_STORE_OP_DONT_CARE, + initialLayout: initialLayout, + finalLayout: finalLayout, + } + + ref := C.VkAttachmentReference{ + attachment: 0, + layout: C.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + } + + sub := C.VkSubpassDescription{ + pipelineBindPoint: C.VK_PIPELINE_BIND_POINT_GRAPHICS, + colorAttachmentCount: 1, + pColorAttachments: &ref, + } + + inf := C.VkRenderPassCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + attachmentCount: 1, + pAttachments: &att, + subpassCount: 1, + } + if n := len(passDeps); n > 0 { + inf.dependencyCount = C.uint32_t(n) + inf.pDependencies = &passDeps[0] + } + + var pass RenderPass + if err := vkErr(C.vkCreateRenderPass(funcs.vkCreateRenderPass, d, inf, sub, nil, &pass)); err != nil { + return nilRenderPass, fmt.Errorf("vulkan: vkCreateRenderPass: %w", err) + } + return pass, nil +} + +func DestroyRenderPass(d Device, r RenderPass) { + C.vkDestroyRenderPass(funcs.vkDestroyRenderPass, d, r, nil) +} + +func CreateFramebuffer(d Device, rp RenderPass, view ImageView, width, height int) (Framebuffer, error) { + inf := C.VkFramebufferCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + renderPass: rp, + attachmentCount: 1, + pAttachments: &view, + width: C.uint32_t(width), + height: C.uint32_t(height), + layers: 1, + } + var fbo Framebuffer + if err := vkErr(C.vkCreateFramebuffer(funcs.vkCreateFramebuffer, d, inf, nil, &fbo)); err != nil { + return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err) + } + return fbo, nil + +} + +func DestroyFramebuffer(d Device, f Framebuffer) { + C.vkDestroyFramebuffer(funcs.vkDestroyFramebuffer, d, f, nil) +} + +func DeviceWaitIdle(d Device) error { + if err := vkErr(C.vkDeviceWaitIdle(funcs.vkDeviceWaitIdle, d)); err != nil { + return fmt.Errorf("vulkan: vkDeviceWaitIdle: %w", err) + } + return nil +} + +func QueueWaitIdle(q Queue) error { + if err := vkErr(C.vkQueueWaitIdle(funcs.vkQueueWaitIdle, q)); err != nil { + return fmt.Errorf("vulkan: vkQueueWaitIdle: %w", err) + } + return nil +} + +func CreateSemaphore(d Device) (Semaphore, error) { + inf := C.VkSemaphoreCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + } + var sem Semaphore + err := vkErr(C.vkCreateSemaphore(funcs.vkCreateSemaphore, d, &inf, nil, &sem)) + if err != nil { + return nilSemaphore, fmt.Errorf("vulkan: vkCreateSemaphore: %w", err) + } + return sem, err +} + +func DestroySemaphore(d Device, sem Semaphore) { + C.vkDestroySemaphore(funcs.vkDestroySemaphore, d, sem, nil) +} + +func DestroyDevice(dev Device) { + C.vkDestroyDevice(funcs.vkDestroyDevice, dev, nil) +} + +func DestroySurface(inst Instance, s Surface) { + C.vkDestroySurfaceKHR(funcs.vkDestroySurfaceKHR, inst, s, nil) +} + +func CreateCommandPool(d Device, queueIndex int) (CommandPool, error) { + inf := C.VkCommandPoolCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + queueFamilyIndex: C.uint32_t(queueIndex), + flags: C.VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | C.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + } + + var pool CommandPool + if err := vkErr(C.vkCreateCommandPool(funcs.vkCreateCommandPool, d, &inf, nil, &pool)); err != nil { + return nilCommandPool, fmt.Errorf("vulkan: vkCreateCommandPool: %w", err) + } + return pool, nil +} + +func DestroyCommandPool(d Device, pool CommandPool) { + C.vkDestroyCommandPool(funcs.vkDestroyCommandPool, d, pool, nil) +} + +func AllocateCommandBuffer(d Device, pool CommandPool) (CommandBuffer, error) { + inf := C.VkCommandBufferAllocateInfo{ + sType: C.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + commandPool: pool, + level: C.VK_COMMAND_BUFFER_LEVEL_PRIMARY, + commandBufferCount: 1, + } + + var buf CommandBuffer + if err := vkErr(C.vkAllocateCommandBuffers(funcs.vkAllocateCommandBuffers, d, &inf, &buf)); err != nil { + return nil, fmt.Errorf("vulkan: vkAllocateCommandBuffers: %w", err) + } + return buf, nil +} + +func FreeCommandBuffers(d Device, pool CommandPool, bufs ...CommandBuffer) { + if len(bufs) == 0 { + return + } + C.vkFreeCommandBuffers(funcs.vkFreeCommandBuffers, d, pool, C.uint32_t(len(bufs)), &bufs[0]) +} + +func BeginCommandBuffer(buf CommandBuffer) error { + inf := C.VkCommandBufferBeginInfo{ + sType: C.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + flags: C.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + } + if err := vkErr(C.vkBeginCommandBuffer(funcs.vkBeginCommandBuffer, buf, inf)); err != nil { + return fmt.Errorf("vulkan: vkBeginCommandBuffer: %w", err) + } + return nil +} + +func EndCommandBuffer(buf CommandBuffer) error { + if err := vkErr(C.vkEndCommandBuffer(funcs.vkEndCommandBuffer, buf)); err != nil { + return fmt.Errorf("vulkan: vkEndCommandBuffer: %w", err) + } + return nil +} + +func QueueSubmit(q Queue, buf CommandBuffer, waitSems []Semaphore, waitStages []PipelineStageFlags, sigSems []Semaphore, fence Fence) error { + inf := C.VkSubmitInfo{ + sType: C.VK_STRUCTURE_TYPE_SUBMIT_INFO, + commandBufferCount: 1, + pCommandBuffers: &buf, + } + if len(waitSems) > 0 { + if len(waitSems) != len(waitStages) { + panic("len(waitSems) != len(waitStages)") + } + inf.waitSemaphoreCount = C.uint32_t(len(waitSems)) + inf.pWaitSemaphores = &waitSems[0] + inf.pWaitDstStageMask = &waitStages[0] + } + if len(sigSems) > 0 { + inf.signalSemaphoreCount = C.uint32_t(len(sigSems)) + inf.pSignalSemaphores = &sigSems[0] + } + if err := vkErr(C.vkQueueSubmit(funcs.vkQueueSubmit, q, inf, fence)); err != nil { + return fmt.Errorf("vulkan: vkQueueSubmit: %w", err) + } + return nil +} + +func CmdBeginRenderPass(buf CommandBuffer, rp RenderPass, fbo Framebuffer, width, height int, clearCol [4]float32) { + cclearCol := [4]C.float{C.float(clearCol[0]), C.float(clearCol[1]), C.float(clearCol[2]), C.float(clearCol[3])} + inf := C.VkRenderPassBeginInfo{ + sType: C.VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + renderPass: rp, + framebuffer: fbo, + renderArea: C.VkRect2D{extent: C.VkExtent2D{width: C.uint32_t(width), height: C.uint32_t(height)}}, + clearValueCount: 1, + pClearValues: (*C.VkClearValue)(unsafe.Pointer(&cclearCol)), + } + C.vkCmdBeginRenderPass(funcs.vkCmdBeginRenderPass, buf, inf, C.VK_SUBPASS_CONTENTS_INLINE) +} + +func CmdEndRenderPass(buf CommandBuffer) { + C.vkCmdEndRenderPass(funcs.vkCmdEndRenderPass, buf) +} + +func CmdCopyBuffer(cmdBuf CommandBuffer, src, dst Buffer, srcOff, dstOff, size int) { + C.vkCmdCopyBuffer(funcs.vkCmdCopyBuffer, cmdBuf, src, dst, 1, &C.VkBufferCopy{ + srcOffset: C.VkDeviceSize(srcOff), + dstOffset: C.VkDeviceSize(dstOff), + size: C.VkDeviceSize(size), + }) +} + +func CmdCopyBufferToImage(cmdBuf CommandBuffer, src Buffer, dst Image, layout ImageLayout, copy BufferImageCopy) { + C.vkCmdCopyBufferToImage(funcs.vkCmdCopyBufferToImage, cmdBuf, src, dst, layout, 1, ©) +} + +func CmdPipelineBarrier(cmdBuf CommandBuffer, srcStage, dstStage PipelineStageFlags, flags DependencyFlags, memBarriers []MemoryBarrier, bufBarriers []BufferMemoryBarrier, imgBarriers []ImageMemoryBarrier) { + var memPtr *MemoryBarrier + if len(memBarriers) > 0 { + memPtr = &memBarriers[0] + } + var bufPtr *BufferMemoryBarrier + if len(bufBarriers) > 0 { + bufPtr = &bufBarriers[0] + } + var imgPtr *ImageMemoryBarrier + if len(imgBarriers) > 0 { + imgPtr = &imgBarriers[0] + } + C.vkCmdPipelineBarrier(funcs.vkCmdPipelineBarrier, cmdBuf, srcStage, dstStage, flags, + C.uint32_t(len(memBarriers)), memPtr, + C.uint32_t(len(bufBarriers)), bufPtr, + C.uint32_t(len(imgBarriers)), imgPtr) +} + +func CmdPushConstants(cmdBuf CommandBuffer, layout PipelineLayout, stages ShaderStageFlags, offset int, data []byte) { + if len(data) == 0 { + return + } + C.vkCmdPushConstants(funcs.vkCmdPushConstants, cmdBuf, layout, stages, C.uint32_t(offset), C.uint32_t(len(data)), unsafe.Pointer(&data[0])) +} + +func CmdBindPipeline(cmdBuf CommandBuffer, bindPoint PipelineBindPoint, pipe Pipeline) { + C.vkCmdBindPipeline(funcs.vkCmdBindPipeline, cmdBuf, bindPoint, pipe) +} + +func CmdBindVertexBuffers(cmdBuf CommandBuffer, first int, buffers []Buffer, sizes []DeviceSize) { + if len(buffers) == 0 { + return + } + C.vkCmdBindVertexBuffers(funcs.vkCmdBindVertexBuffers, cmdBuf, C.uint32_t(first), C.uint32_t(len(buffers)), &buffers[0], &sizes[0]) +} + +func CmdSetViewport(cmdBuf CommandBuffer, first int, viewports ...Viewport) { + if len(viewports) == 0 { + return + } + C.vkCmdSetViewport(funcs.vkCmdSetViewport, cmdBuf, C.uint32_t(first), C.uint32_t(len(viewports)), &viewports[0]) +} + +func CmdBindIndexBuffer(cmdBuf CommandBuffer, buffer Buffer, offset int, typ IndexType) { + C.vkCmdBindIndexBuffer(funcs.vkCmdBindIndexBuffer, cmdBuf, buffer, C.VkDeviceSize(offset), typ) +} + +func CmdDraw(cmdBuf CommandBuffer, vertCount, instCount, firstVert, firstInst int) { + C.vkCmdDraw(funcs.vkCmdDraw, cmdBuf, C.uint32_t(vertCount), C.uint32_t(instCount), C.uint32_t(firstVert), C.uint32_t(firstInst)) +} + +func CmdDrawIndexed(cmdBuf CommandBuffer, idxCount, instCount, firstIdx, vertOff, firstInst int) { + C.vkCmdDrawIndexed(funcs.vkCmdDrawIndexed, cmdBuf, C.uint32_t(idxCount), C.uint32_t(instCount), C.uint32_t(firstIdx), C.int32_t(vertOff), C.uint32_t(firstInst)) +} + +func GetPhysicalDeviceFormatProperties(physDev PhysicalDevice, format Format) FormatFeatureFlags { + var props C.VkFormatProperties + C.vkGetPhysicalDeviceFormatProperties(funcs.vkGetPhysicalDeviceFormatProperties, physDev, format, &props) + return FormatFeatureFlags(props.optimalTilingFeatures) +} + +func CmdBindDescriptorSets(cmdBuf CommandBuffer, point PipelineBindPoint, layout PipelineLayout, firstSet int, sets []DescriptorSet) { + C.vkCmdBindDescriptorSets(funcs.vkCmdBindDescriptorSets, cmdBuf, point, layout, C.uint32_t(firstSet), C.uint32_t(len(sets)), &sets[0], 0, nil) +} + +func CmdCopyImage(cmdBuf CommandBuffer, src Image, srcLayout ImageLayout, dst Image, dstLayout ImageLayout, regions []ImageCopy) { + if len(regions) == 0 { + return + } + C.vkCmdCopyImage(funcs.vkCmdCopyImage, cmdBuf, src, srcLayout, dst, dstLayout, C.uint32_t(len(regions)), ®ions[0]) +} + +func CmdCopyImageToBuffer(cmdBuf CommandBuffer, src Image, srcLayout ImageLayout, dst Buffer, regions []BufferImageCopy) { + if len(regions) == 0 { + return + } + C.vkCmdCopyImageToBuffer(funcs.vkCmdCopyImageToBuffer, cmdBuf, src, srcLayout, dst, C.uint32_t(len(regions)), ®ions[0]) +} + +func CmdDispatch(cmdBuf CommandBuffer, x, y, z int) { + C.vkCmdDispatch(funcs.vkCmdDispatch, cmdBuf, C.uint32_t(x), C.uint32_t(y), C.uint32_t(z)) +} + +func CreateImage(pd PhysicalDevice, d Device, format Format, width, height int, usage ImageUsageFlags) (Image, DeviceMemory, error) { + inf := C.VkImageCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + imageType: C.VK_IMAGE_TYPE_2D, + format: format, + extent: C.VkExtent3D{ + width: C.uint32_t(width), + height: C.uint32_t(height), + depth: 1, + }, + mipLevels: 1, + arrayLayers: 1, + samples: C.VK_SAMPLE_COUNT_1_BIT, + tiling: C.VK_IMAGE_TILING_OPTIMAL, + usage: usage, + initialLayout: C.VK_IMAGE_LAYOUT_UNDEFINED, + } + var img C.VkImage + if err := vkErr(C.vkCreateImage(funcs.vkCreateImage, d, &inf, nil, &img)); err != nil { + return nilImage, nilDeviceMemory, fmt.Errorf("vulkan: vkCreateImage: %w", err) + } + var memReqs C.VkMemoryRequirements + C.vkGetImageMemoryRequirements(funcs.vkGetImageMemoryRequirements, d, img, &memReqs) + + memIdx, found := findMemoryTypeIndex(pd, memReqs.memoryTypeBits, C.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + if !found { + DestroyImage(d, img) + return nilImage, nilDeviceMemory, errors.New("vulkan: no memory type suitable for images found") + } + + memInf := C.VkMemoryAllocateInfo{ + sType: C.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + allocationSize: memReqs.size, + memoryTypeIndex: C.uint32_t(memIdx), + } + var imgMem C.VkDeviceMemory + if err := vkErr(C.vkAllocateMemory(funcs.vkAllocateMemory, d, &memInf, nil, &imgMem)); err != nil { + DestroyImage(d, img) + return nilImage, nilDeviceMemory, fmt.Errorf("vulkan: vkAllocateMemory: %w", err) + } + + if err := vkErr(C.vkBindImageMemory(funcs.vkBindImageMemory, d, img, imgMem, 0)); err != nil { + FreeMemory(d, imgMem) + DestroyImage(d, img) + return nilImage, nilDeviceMemory, fmt.Errorf("vulkan: vkBindImageMemory: %w", err) + } + return img, imgMem, nil +} + +func DestroyImage(d Device, img Image) { + C.vkDestroyImage(funcs.vkDestroyImage, d, img, nil) +} + +func FreeMemory(d Device, mem DeviceMemory) { + C.vkFreeMemory(funcs.vkFreeMemory, d, mem, nil) +} + +func CreateSampler(d Device, minFilter, magFilter Filter) (Sampler, error) { + inf := C.VkSamplerCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + minFilter: minFilter, + magFilter: magFilter, + addressModeU: C.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + addressModeV: C.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + } + var s C.VkSampler + if err := vkErr(C.vkCreateSampler(funcs.vkCreateSampler, d, &inf, nil, &s)); err != nil { + return nilSampler, fmt.Errorf("vulkan: vkCreateSampler: %w", err) + } + return s, nil +} + +func DestroySampler(d Device, sampler Sampler) { + C.vkDestroySampler(funcs.vkDestroySampler, d, sampler, nil) +} + +func CreateBuffer(pd PhysicalDevice, d Device, size int, usage BufferUsageFlags, props MemoryPropertyFlags) (Buffer, DeviceMemory, error) { + inf := C.VkBufferCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + size: C.VkDeviceSize(size), + usage: usage, + } + var buf C.VkBuffer + if err := vkErr(C.vkCreateBuffer(funcs.vkCreateBuffer, d, &inf, nil, &buf)); err != nil { + return nilBuffer, nilDeviceMemory, fmt.Errorf("vulkan: vkCreateBuffer: %w", err) + } + + var memReqs C.VkMemoryRequirements + C.vkGetBufferMemoryRequirements(funcs.vkGetBufferMemoryRequirements, d, buf, &memReqs) + + memIdx, found := findMemoryTypeIndex(pd, memReqs.memoryTypeBits, props) + if !found { + DestroyBuffer(d, buf) + return nilBuffer, nilDeviceMemory, errors.New("vulkan: no memory suitable for buffers found") + } + memInf := C.VkMemoryAllocateInfo{ + sType: C.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + allocationSize: memReqs.size, + memoryTypeIndex: C.uint32_t(memIdx), + } + + var mem C.VkDeviceMemory + if err := vkErr(C.vkAllocateMemory(funcs.vkAllocateMemory, d, &memInf, nil, &mem)); err != nil { + DestroyBuffer(d, buf) + return nilBuffer, nilDeviceMemory, fmt.Errorf("vulkan: vkAllocateMemory: %w", err) + } + + if err := vkErr(C.vkBindBufferMemory(funcs.vkBindBufferMemory, d, buf, mem, 0)); err != nil { + FreeMemory(d, mem) + DestroyBuffer(d, buf) + return nilBuffer, nilDeviceMemory, fmt.Errorf("vulkan: vkBindBufferMemory: %w", err) + } + return buf, mem, nil +} + +func DestroyBuffer(d Device, buf Buffer) { + C.vkDestroyBuffer(funcs.vkDestroyBuffer, d, buf, nil) +} + +func CreateShaderModule(d Device, spirv string) (ShaderModule, error) { + ptr := unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&spirv)).Data) + inf := C.VkShaderModuleCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + codeSize: C.size_t(len(spirv)), + pCode: (*C.uint32_t)(ptr), + } + + var mod C.VkShaderModule + if err := vkErr(C.vkCreateShaderModule(funcs.vkCreateShaderModule, d, inf, nil, &mod)); err != nil { + return nilShaderModule, fmt.Errorf("vulkan: vkCreateShaderModule: %w", err) + } + return mod, nil +} + +func DestroyShaderModule(d Device, mod ShaderModule) { + C.vkDestroyShaderModule(funcs.vkDestroyShaderModule, d, mod, nil) +} + +func CreateGraphicsPipeline(d Device, pass RenderPass, vmod, fmod ShaderModule, blend bool, srcFactor, dstFactor BlendFactor, topology PrimitiveTopology, bindings []VertexInputBindingDescription, attrs []VertexInputAttributeDescription, layout PipelineLayout) (Pipeline, error) { + main := C.CString("main") + defer C.free(unsafe.Pointer(main)) + stages := []C.VkPipelineShaderStageCreateInfo{ + { + sType: C.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + stage: C.VK_SHADER_STAGE_VERTEX_BIT, + module: vmod, + pName: main, + }, + { + sType: C.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + stage: C.VK_SHADER_STAGE_FRAGMENT_BIT, + module: fmod, + pName: main, + }, + } + dynStates := []C.VkDynamicState{C.VK_DYNAMIC_STATE_VIEWPORT} + dynInf := C.VkPipelineDynamicStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + dynamicStateCount: C.uint32_t(len(dynStates)), + pDynamicStates: &dynStates[0], + } + const maxDim = 0x7fffffff + scissors := []C.VkRect2D{{extent: C.VkExtent2D{width: maxDim, height: maxDim}}} + viewportInf := C.VkPipelineViewportStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + viewportCount: 1, + scissorCount: C.uint32_t(len(scissors)), + pScissors: &scissors[0], + } + enable := C.VkBool32(0) + if blend { + enable = 1 + } + attBlendInf := C.VkPipelineColorBlendAttachmentState{ + blendEnable: enable, + srcColorBlendFactor: srcFactor, + srcAlphaBlendFactor: srcFactor, + dstColorBlendFactor: dstFactor, + dstAlphaBlendFactor: dstFactor, + colorBlendOp: C.VK_BLEND_OP_ADD, + alphaBlendOp: C.VK_BLEND_OP_ADD, + colorWriteMask: C.VK_COLOR_COMPONENT_R_BIT | C.VK_COLOR_COMPONENT_G_BIT | C.VK_COLOR_COMPONENT_B_BIT | C.VK_COLOR_COMPONENT_A_BIT, + } + blendInf := C.VkPipelineColorBlendStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + attachmentCount: 1, + pAttachments: &attBlendInf, + } + var vkBinds []C.VkVertexInputBindingDescription + var vkAttrs []C.VkVertexInputAttributeDescription + for _, b := range bindings { + vkBinds = append(vkBinds, C.VkVertexInputBindingDescription{ + binding: C.uint32_t(b.Binding), + stride: C.uint32_t(b.Stride), + }) + } + for _, a := range attrs { + vkAttrs = append(vkAttrs, C.VkVertexInputAttributeDescription{ + location: C.uint32_t(a.Location), + binding: C.uint32_t(a.Binding), + format: a.Format, + offset: C.uint32_t(a.Offset), + }) + } + vertexInf := C.VkPipelineVertexInputStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + } + if n := len(vkBinds); n > 0 { + vertexInf.vertexBindingDescriptionCount = C.uint32_t(n) + vertexInf.pVertexBindingDescriptions = &vkBinds[0] + } + if n := len(vkAttrs); n > 0 { + vertexInf.vertexAttributeDescriptionCount = C.uint32_t(n) + vertexInf.pVertexAttributeDescriptions = &vkAttrs[0] + } + inf := C.VkGraphicsPipelineCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + stageCount: C.uint32_t(len(stages)), + pStages: &stages[0], + renderPass: pass, + layout: layout, + pRasterizationState: &C.VkPipelineRasterizationStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + lineWidth: 1.0, + }, + pMultisampleState: &C.VkPipelineMultisampleStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + rasterizationSamples: C.VK_SAMPLE_COUNT_1_BIT, + }, + pInputAssemblyState: &C.VkPipelineInputAssemblyStateCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + topology: topology, + }, + } + + var pipe C.VkPipeline + if err := vkErr(C.vkCreateGraphicsPipelines(funcs.vkCreateGraphicsPipelines, d, nilPipelineCache, inf, dynInf, blendInf, vertexInf, viewportInf, nil, &pipe)); err != nil { + return nilPipeline, fmt.Errorf("vulkan: vkCreateGraphicsPipelines: %w", err) + } + return pipe, nil +} + +func DestroyPipeline(d Device, p Pipeline) { + C.vkDestroyPipeline(funcs.vkDestroyPipeline, d, p, nil) +} + +func CreatePipelineLayout(d Device, pushRanges []PushConstantRange, sets []DescriptorSetLayout) (PipelineLayout, error) { + inf := C.VkPipelineLayoutCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + } + if n := len(sets); n > 0 { + inf.setLayoutCount = C.uint32_t(n) + inf.pSetLayouts = &sets[0] + } + if n := len(pushRanges); n > 0 { + inf.pushConstantRangeCount = C.uint32_t(n) + inf.pPushConstantRanges = &pushRanges[0] + } + var l C.VkPipelineLayout + if err := vkErr(C.vkCreatePipelineLayout(funcs.vkCreatePipelineLayout, d, inf, nil, &l)); err != nil { + return nilPipelineLayout, fmt.Errorf("vulkan: vkCreatePipelineLayout: %w", err) + } + return l, nil +} + +func DestroyPipelineLayout(d Device, l PipelineLayout) { + C.vkDestroyPipelineLayout(funcs.vkDestroyPipelineLayout, d, l, nil) +} + +func CreateDescriptorSetLayout(d Device, bindings []DescriptorSetLayoutBinding) (DescriptorSetLayout, error) { + var vkbinds []C.VkDescriptorSetLayoutBinding + for _, b := range bindings { + vkbinds = append(vkbinds, C.VkDescriptorSetLayoutBinding{ + binding: C.uint32_t(b.Binding), + descriptorType: b.DescriptorType, + descriptorCount: 1, + stageFlags: b.StageFlags, + }) + } + inf := C.VkDescriptorSetLayoutCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + } + if n := len(vkbinds); n > 0 { + inf.bindingCount = C.uint32_t(len(vkbinds)) + inf.pBindings = &vkbinds[0] + } + var l C.VkDescriptorSetLayout + if err := vkErr(C.vkCreateDescriptorSetLayout(funcs.vkCreateDescriptorSetLayout, d, inf, nil, &l)); err != nil { + return nilDescriptorSetLayout, fmt.Errorf("vulkan: vkCreateDescriptorSetLayout: %w", err) + } + return l, nil +} + +func DestroyDescriptorSetLayout(d Device, l DescriptorSetLayout) { + C.vkDestroyDescriptorSetLayout(funcs.vkDestroyDescriptorSetLayout, d, l, nil) +} + +func MapMemory(d Device, mem DeviceMemory, offset, size int) ([]byte, error) { + var ptr unsafe.Pointer + if err := vkErr(C.vkMapMemory(funcs.vkMapMemory, d, mem, C.VkDeviceSize(offset), C.VkDeviceSize(size), 0, &ptr)); err != nil { + return nil, fmt.Errorf("vulkan: vkMapMemory: %w", err) + } + return ((*[1 << 30]byte)(ptr))[:size:size], nil +} + +func UnmapMemory(d Device, mem DeviceMemory) { + C.vkUnmapMemory(funcs.vkUnmapMemory, d, mem) +} + +func ResetCommandBuffer(buf CommandBuffer) error { + if err := vkErr(C.vkResetCommandBuffer(funcs.vkResetCommandBuffer, buf, 0)); err != nil { + return fmt.Errorf("vulkan: vkResetCommandBuffer. %w", err) + } + return nil +} + +func CreateDescriptorPool(d Device, maxSets int, sizes []DescriptorPoolSize) (DescriptorPool, error) { + inf := C.VkDescriptorPoolCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + maxSets: C.uint32_t(maxSets), + poolSizeCount: C.uint32_t(len(sizes)), + pPoolSizes: &sizes[0], + } + var pool C.VkDescriptorPool + if err := vkErr(C.vkCreateDescriptorPool(funcs.vkCreateDescriptorPool, d, inf, nil, &pool)); err != nil { + return nilDescriptorPool, fmt.Errorf("vulkan: vkCreateDescriptorPool: %w", err) + } + return pool, nil +} + +func DestroyDescriptorPool(d Device, pool DescriptorPool) { + C.vkDestroyDescriptorPool(funcs.vkDestroyDescriptorPool, d, pool, nil) +} + +func ResetDescriptorPool(d Device, pool DescriptorPool) error { + if err := vkErr(C.vkResetDescriptorPool(funcs.vkResetDescriptorPool, d, pool, 0)); err != nil { + return fmt.Errorf("vulkan: vkResetDescriptorPool: %w", err) + } + return nil +} + +func UpdateDescriptorSet(d Device, write WriteDescriptorSet) { + C.vkUpdateDescriptorSets(funcs.vkUpdateDescriptorSets, d, write, 0, nil) +} + +func AllocateDescriptorSet(d Device, pool DescriptorPool, layout DescriptorSetLayout) (DescriptorSet, error) { + inf := C.VkDescriptorSetAllocateInfo{ + sType: C.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + descriptorPool: pool, + descriptorSetCount: 1, + pSetLayouts: &layout, + } + var set C.VkDescriptorSet + if err := vkErr(C.vkAllocateDescriptorSets(funcs.vkAllocateDescriptorSets, d, inf, &set)); err != nil { + return nilDescriptorSet, fmt.Errorf("vulkan: vkAllocateDescriptorSets: %w", err) + } + return set, nil +} + +func CreateComputePipeline(d Device, mod ShaderModule, layout PipelineLayout) (Pipeline, error) { + main := C.CString("main") + defer C.free(unsafe.Pointer(main)) + inf := C.VkComputePipelineCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + stage: C.VkPipelineShaderStageCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + stage: C.VK_SHADER_STAGE_COMPUTE_BIT, + module: mod, + pName: main, + }, + layout: layout, + } + var pipe C.VkPipeline + if err := vkErr(C.vkCreateComputePipelines(funcs.vkCreateComputePipelines, d, nilPipelineCache, 1, &inf, nil, &pipe)); err != nil { + return nilPipeline, fmt.Errorf("vulkan: vkCreateComputePipelines: %w", err) + } + return pipe, nil +} + +func CreateFence(d Device) (Fence, error) { + inf := C.VkFenceCreateInfo{ + sType: C.VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + } + var f C.VkFence + if err := vkErr(C.vkCreateFence(funcs.vkCreateFence, d, &inf, nil, &f)); err != nil { + return nilFence, fmt.Errorf("vulkan: vkCreateFence: %w", err) + } + return f, nil +} + +func DestroyFence(d Device, f Fence) { + C.vkDestroyFence(funcs.vkDestroyFence, d, f, nil) +} + +func WaitForFences(d Device, fences ...Fence) error { + if len(fences) == 0 { + return nil + } + err := vkErr(C.vkWaitForFences(funcs.vkWaitForFences, d, C.uint32_t(len(fences)), &fences[0], C.VK_TRUE, 0xffffffffffffffff)) + if err != nil { + return fmt.Errorf("vulkan: vkWaitForFences: %w", err) + } + return nil +} + +func ResetFences(d Device, fences ...Fence) error { + if len(fences) == 0 { + return nil + } + err := vkErr(C.vkResetFences(funcs.vkResetFences, d, C.uint32_t(len(fences)), &fences[0])) + if err != nil { + return fmt.Errorf("vulkan: vkResetFences: %w", err) + } + return nil +} + +func BuildSubpassDependency(srcStage, dstStage PipelineStageFlags, srcMask, dstMask AccessFlags, flags DependencyFlags) SubpassDependency { + return C.VkSubpassDependency{ + srcSubpass: C.VK_SUBPASS_EXTERNAL, + srcStageMask: srcStage, + srcAccessMask: srcMask, + dstSubpass: 0, + dstStageMask: dstStage, + dstAccessMask: dstMask, + dependencyFlags: flags, + } +} + +func BuildPushConstantRange(stages ShaderStageFlags, offset, size int) PushConstantRange { + return C.VkPushConstantRange{ + stageFlags: stages, + offset: C.uint32_t(offset), + size: C.uint32_t(size), + } +} + +func BuildDescriptorPoolSize(typ DescriptorType, count int) DescriptorPoolSize { + return C.VkDescriptorPoolSize{ + _type: typ, + descriptorCount: C.uint32_t(count), + } +} + +func BuildWriteDescriptorSetImage(set DescriptorSet, binding int, typ DescriptorType, sampler Sampler, view ImageView, layout ImageLayout) WriteDescriptorSet { + return C.VkWriteDescriptorSet{ + sType: C.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: set, + dstBinding: C.uint32_t(binding), + descriptorCount: 1, + descriptorType: typ, + pImageInfo: &C.VkDescriptorImageInfo{ + sampler: sampler, + imageView: view, + imageLayout: layout, + }, + } +} + +func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ DescriptorType, buf Buffer) WriteDescriptorSet { + return C.VkWriteDescriptorSet{ + sType: C.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: set, + dstBinding: C.uint32_t(binding), + descriptorCount: 1, + descriptorType: typ, + pBufferInfo: &C.VkDescriptorBufferInfo{ + buffer: buf, + _range: C.VK_WHOLE_SIZE, + }, + } +} + +func (r PushConstantRange) StageFlags() ShaderStageFlags { + return r.stageFlags +} + +func (r PushConstantRange) Offset() int { + return int(r.offset) +} + +func (r PushConstantRange) Size() int { + return int(r.size) +} + +func (p QueueFamilyProperties) Flags() QueueFlags { + return p.queueFlags +} + +func BuildViewport(x, y, width, height float32) Viewport { + return C.VkViewport{ + x: C.float(x), + y: C.float(y), + width: C.float(width), + height: C.float(height), + maxDepth: 1.0, + } +} + +func BuildImageMemoryBarrier(img Image, srcMask, dstMask AccessFlags, oldLayout, newLayout ImageLayout) ImageMemoryBarrier { + return C.VkImageMemoryBarrier{ + sType: C.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + srcAccessMask: srcMask, + dstAccessMask: dstMask, + oldLayout: oldLayout, + newLayout: newLayout, + image: img, + subresourceRange: C.VkImageSubresourceRange{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + levelCount: C.VK_REMAINING_MIP_LEVELS, + layerCount: C.VK_REMAINING_ARRAY_LAYERS, + }, + } +} + +func BuildBufferMemoryBarrier(buf Buffer, srcMask, dstMask AccessFlags) BufferMemoryBarrier { + return C.VkBufferMemoryBarrier{ + sType: C.VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + srcAccessMask: srcMask, + dstAccessMask: dstMask, + buffer: buf, + size: C.VK_WHOLE_SIZE, + } +} + +func BuildMemoryBarrier(srcMask, dstMask AccessFlags) MemoryBarrier { + return C.VkMemoryBarrier{ + sType: C.VK_STRUCTURE_TYPE_MEMORY_BARRIER, + srcAccessMask: srcMask, + dstAccessMask: dstMask, + } +} + +func BuildBufferImageCopy(bufOff, bufStride, x, y, width, height int) BufferImageCopy { + return C.VkBufferImageCopy{ + bufferOffset: C.VkDeviceSize(bufOff), + bufferRowLength: C.uint32_t(bufStride), + imageSubresource: C.VkImageSubresourceLayers{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + layerCount: 1, + }, + imageOffset: C.VkOffset3D{ + x: C.int32_t(x), y: C.int32_t(y), z: 0, + }, + imageExtent: C.VkExtent3D{ + width: C.uint32_t(width), height: C.uint32_t(height), depth: 1, + }, + } +} + +func BuildImageCopy(srcX, srcY, dstX, dstY, width, height int) ImageCopy { + return C.VkImageCopy{ + srcSubresource: C.VkImageSubresourceLayers{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + layerCount: 1, + }, + srcOffset: C.VkOffset3D{ + x: C.int32_t(srcX), + y: C.int32_t(srcY), + }, + dstSubresource: C.VkImageSubresourceLayers{ + aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT, + layerCount: 1, + }, + dstOffset: C.VkOffset3D{ + x: C.int32_t(dstX), + y: C.int32_t(dstY), + }, + extent: C.VkExtent3D{ + width: C.uint32_t(width), + height: C.uint32_t(height), + depth: 1, + }, + } +} + +func findMemoryTypeIndex(pd C.VkPhysicalDevice, constraints C.uint32_t, wantProps C.VkMemoryPropertyFlags) (int, bool) { + var memProps C.VkPhysicalDeviceMemoryProperties + C.vkGetPhysicalDeviceMemoryProperties(funcs.vkGetPhysicalDeviceMemoryProperties, pd, &memProps) + + for i := 0; i < int(memProps.memoryTypeCount); i++ { + if (constraints & (1 << i)) == 0 { + continue + } + if (memProps.memoryTypes[i].propertyFlags & wantProps) != wantProps { + continue + } + return i, true + } + + return 0, false +} + +func choosePresentMode(pd C.VkPhysicalDevice, surf Surface) (C.VkPresentModeKHR, bool, error) { + var count C.uint32_t + err := vkErr(C.vkGetPhysicalDeviceSurfacePresentModesKHR(funcs.vkGetPhysicalDeviceSurfacePresentModesKHR, pd, surf, &count, nil)) + if err != nil { + return 0, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfacePresentModesKHR: %w", err) + } + if count == 0 { + return 0, false, nil + } + modes := make([]C.VkPresentModeKHR, count) + err = vkErr(C.vkGetPhysicalDeviceSurfacePresentModesKHR(funcs.vkGetPhysicalDeviceSurfacePresentModesKHR, pd, surf, &count, &modes[0])) + if err != nil { + return 0, false, fmt.Errorf("vulkan: kGetPhysicalDeviceSurfacePresentModesKHR: %w", err) + } + for _, m := range modes { + if m == C.VK_PRESENT_MODE_MAILBOX_KHR || m == C.VK_PRESENT_MODE_FIFO_KHR { + return m, true, nil + } + } + return 0, false, nil +} + +func chooseFormat(pd C.VkPhysicalDevice, surf Surface) (C.VkSurfaceFormatKHR, bool, error) { + var count C.uint32_t + err := vkErr(C.vkGetPhysicalDeviceSurfaceFormatsKHR(funcs.vkGetPhysicalDeviceSurfaceFormatsKHR, pd, surf, &count, nil)) + if err != nil { + return C.VkSurfaceFormatKHR{}, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceFormatsKHR: %w", err) + } + if count == 0 { + return C.VkSurfaceFormatKHR{}, false, nil + } + formats := make([]C.VkSurfaceFormatKHR, count) + err = vkErr(C.vkGetPhysicalDeviceSurfaceFormatsKHR(funcs.vkGetPhysicalDeviceSurfaceFormatsKHR, pd, surf, &count, &formats[0])) + if err != nil { + return C.VkSurfaceFormatKHR{}, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceFormatsKHR: %w", err) + } + // Query for format with sRGB support. + // TODO: Support devices without sRGB. + for _, f := range formats { + if f.colorSpace != C.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR { + continue + } + switch f.format { + case C.VK_FORMAT_B8G8R8A8_SRGB, C.VK_FORMAT_R8G8B8A8_SRGB: + return f, true, nil + } + } + return C.VkSurfaceFormatKHR{}, false, nil +} + +func chooseQueue(pd C.VkPhysicalDevice, surf Surface, flags C.VkQueueFlags) (int, bool, error) { + queues := GetPhysicalDeviceQueueFamilyProperties(pd) + for i, q := range queues { + // Check for presentation and feature support. + if q.queueFlags&flags != flags { + continue + } + if surf != nilSurface { + // Check for presentation support. It is possible that a device has no + // queue with both rendering and presentation support, but not in reality. + // See https://github.com/KhronosGroup/Vulkan-Docs/issues/1234. + var support C.VkBool32 + if err := vkErr(C.vkGetPhysicalDeviceSurfaceSupportKHR(funcs.vkGetPhysicalDeviceSurfaceSupportKHR, pd, C.uint32_t(i), surf, &support)); err != nil { + return 0, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceSupportKHR: %w", err) + } + if support != C.VK_TRUE { + continue + } + } + return i, true, nil + } + return 0, false, nil +} + +func dlsym(handle unsafe.Pointer, s string) unsafe.Pointer { + cs := C.CString(s) + defer C.free(unsafe.Pointer(cs)) + return C.dlsym(handle, cs) +} + +func dlopen(lib string) unsafe.Pointer { + clib := C.CString(lib) + defer C.free(unsafe.Pointer(clib)) + return C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL) +} + +func vkErr(res C.VkResult) error { + switch res { + case C.VK_SUCCESS: + return nil + default: + return Error(res) + } +} + +func (e Error) Error() string { + return fmt.Sprintf("error %d", e) +} + +func (e Error) IsDeviceLost() bool { + switch e { + case ERROR_OUT_OF_DATE_KHR, ERROR_SURFACE_LOST_KHR, ERROR_DEVICE_LOST: + return true + default: + return false + } +} diff --git a/internal/vk/vulkan_android.go b/internal/vk/vulkan_android.go new file mode 100644 index 00000000..143146ef --- /dev/null +++ b/internal/vk/vulkan_android.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build !nowayland +// +build !nowayland + +package vk + +/* +#define VK_USE_PLATFORM_ANDROID_KHR +#define VK_NO_PROTOTYPES 1 +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#include +#include + +static VkResult vkCreateAndroidSurfaceKHR(PFN_vkCreateAndroidSurfaceKHR f, VkInstance instance, const VkAndroidSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + return f(instance, pCreateInfo, pAllocator, pSurface); +} +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +var wlFuncs struct { + vkCreateAndroidSurfaceKHR C.PFN_vkCreateAndroidSurfaceKHR +} + +func init() { + loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) { + wlFuncs.vkCreateAndroidSurfaceKHR = dlopen("vkCreateAndroidSurfaceKHR") + }) +} + +func CreateAndroidSurface(inst Instance, window unsafe.Pointer) (Surface, error) { + inf := C.VkAndroidSurfaceCreateInfoKHR{ + sType: C.VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + window: (*C.ANativeWindow)(window), + } + var surf Surface + if err := vkErr(C.vkCreateAndroidSurfaceKHR(wlFuncs.vkCreateAndroidSurfaceKHR, inst, &inf, nil, &surf)); err != nil { + return 0, fmt.Errorf("vulkan: vkCreateAndroidSurfaceKHR: %w", err) + } + return surf, nil +} diff --git a/internal/vk/vulkan_wayland.go b/internal/vk/vulkan_wayland.go new file mode 100644 index 00000000..cb057bcb --- /dev/null +++ b/internal/vk/vulkan_wayland.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build ((linux && !android) || freebsd) && !nowayland +// +build linux,!android freebsd +// +build !nowayland + +package vk + +/* +#define VK_USE_PLATFORM_WAYLAND_KHR +#define VK_NO_PROTOTYPES 1 +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#include + +static VkResult vkCreateWaylandSurfaceKHR(PFN_vkCreateWaylandSurfaceKHR f, VkInstance instance, const VkWaylandSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + return f(instance, pCreateInfo, pAllocator, pSurface); +} +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +var wlFuncs struct { + vkCreateWaylandSurfaceKHR C.PFN_vkCreateWaylandSurfaceKHR +} + +func init() { + loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) { + wlFuncs.vkCreateWaylandSurfaceKHR = dlopen("vkCreateWaylandSurfaceKHR") + }) +} + +func CreateWaylandSurface(inst Instance, disp unsafe.Pointer, wlSurf unsafe.Pointer) (Surface, error) { + inf := C.VkWaylandSurfaceCreateInfoKHR{ + sType: C.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, + display: (*C.struct_wl_display)(disp), + surface: (*C.struct_wl_surface)(wlSurf), + } + var surf Surface + if err := vkErr(C.vkCreateWaylandSurfaceKHR(wlFuncs.vkCreateWaylandSurfaceKHR, inst, &inf, nil, &surf)); err != nil { + return 0, fmt.Errorf("vulkan: vkCreateWaylandSurfaceKHR: %w", err) + } + return surf, nil +} diff --git a/internal/vk/vulkan_x11.go b/internal/vk/vulkan_x11.go new file mode 100644 index 00000000..780a5d52 --- /dev/null +++ b/internal/vk/vulkan_x11.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build ((linux && !android) || freebsd) && !nox11 +// +build linux,!android freebsd +// +build !nox11 + +package vk + +/* +#define VK_USE_PLATFORM_XLIB_KHR +#define VK_NO_PROTOTYPES 1 +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; +#include + +static VkResult vkCreateXlibSurfaceKHR(PFN_vkCreateXlibSurfaceKHR f, VkInstance instance, const VkXlibSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + return f(instance, pCreateInfo, pAllocator, pSurface); +} +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +var x11Funcs struct { + vkCreateXlibSurfaceKHR C.PFN_vkCreateXlibSurfaceKHR +} + +func init() { + loadFuncs = append(loadFuncs, func(dlopen func(name string) *[0]byte) { + x11Funcs.vkCreateXlibSurfaceKHR = dlopen("vkCreateXlibSurfaceKHR") + }) +} + +func CreateXlibSurface(inst Instance, dpy unsafe.Pointer, window uintptr) (Surface, error) { + inf := C.VkXlibSurfaceCreateInfoKHR{ + sType: C.VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + dpy: (*C.Display)(dpy), + window: (C.Window)(window), + } + var surf Surface + if err := vkErr(C.vkCreateXlibSurfaceKHR(x11Funcs.vkCreateXlibSurfaceKHR, inst, &inf, nil, &surf)); err != nil { + return 0, fmt.Errorf("vulkan: vkCreateXlibSurfaceKHR: %w", err) + } + return surf, nil +}