gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/vulkan.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 //go:build (linux || freebsd) && !novulkan 4 // +build linux freebsd 5 // +build !novulkan 6 7 package app 8 9 import ( 10 "errors" 11 "unsafe" 12 13 "gioui.org/gpu" 14 "gioui.org/internal/vk" 15 ) 16 17 type vkContext struct { 18 physDev vk.PhysicalDevice 19 inst vk.Instance 20 dev vk.Device 21 queueFam int 22 queue vk.Queue 23 acquireSem vk.Semaphore 24 presentSem vk.Semaphore 25 fence vk.Fence 26 27 swchain vk.Swapchain 28 imgs []vk.Image 29 views []vk.ImageView 30 fbos []vk.Framebuffer 31 format vk.Format 32 presentIdx int 33 } 34 35 func newVulkanContext(inst vk.Instance, surf vk.Surface) (*vkContext, error) { 36 physDev, qFam, err := vk.ChoosePhysicalDevice(inst, surf) 37 if err != nil { 38 return nil, err 39 } 40 dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain") 41 if err != nil { 42 return nil, err 43 } 44 acquireSem, err := vk.CreateSemaphore(dev) 45 if err != nil { 46 vk.DestroyDevice(dev) 47 return nil, err 48 } 49 presentSem, err := vk.CreateSemaphore(dev) 50 if err != nil { 51 vk.DestroySemaphore(dev, acquireSem) 52 vk.DestroyDevice(dev) 53 return nil, err 54 } 55 fence, err := vk.CreateFence(dev, vk.FENCE_CREATE_SIGNALED_BIT) 56 if err != nil { 57 vk.DestroySemaphore(dev, presentSem) 58 vk.DestroySemaphore(dev, acquireSem) 59 vk.DestroyDevice(dev) 60 return nil, err 61 } 62 c := &vkContext{ 63 physDev: physDev, 64 inst: inst, 65 dev: dev, 66 queueFam: qFam, 67 queue: vk.GetDeviceQueue(dev, qFam, 0), 68 acquireSem: acquireSem, 69 presentSem: presentSem, 70 fence: fence, 71 } 72 return c, nil 73 } 74 75 func (c *vkContext) RenderTarget() (gpu.RenderTarget, error) { 76 vk.WaitForFences(c.dev, c.fence) 77 vk.ResetFences(c.dev, c.fence) 78 79 imgIdx, err := vk.AcquireNextImage(c.dev, c.swchain, c.acquireSem, 0) 80 if err := mapSurfaceErr(err); err != nil { 81 return nil, err 82 } 83 c.presentIdx = imgIdx 84 return gpu.VulkanRenderTarget{ 85 WaitSem: uint64(c.acquireSem), 86 SignalSem: uint64(c.presentSem), 87 Fence: uint64(c.fence), 88 Framebuffer: uint64(c.fbos[imgIdx]), 89 Image: uint64(c.imgs[imgIdx]), 90 }, nil 91 } 92 93 func (c *vkContext) api() gpu.API { 94 return gpu.Vulkan{ 95 PhysDevice: unsafe.Pointer(c.physDev), 96 Device: unsafe.Pointer(c.dev), 97 Format: int(c.format), 98 QueueFamily: c.queueFam, 99 QueueIndex: 0, 100 } 101 } 102 103 func mapErr(err error) error { 104 var vkErr vk.Error 105 if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST { 106 return gpu.ErrDeviceLost 107 } 108 return err 109 } 110 111 func mapSurfaceErr(err error) error { 112 var vkErr vk.Error 113 if !errors.As(err, &vkErr) { 114 return err 115 } 116 switch { 117 case vkErr == vk.SUBOPTIMAL_KHR: 118 // Android reports VK_SUBOPTIMAL_KHR when presenting to a rotated 119 // swapchain (preTransform != currentTransform). However, we don't 120 // support transforming the output ourselves, so we'll live with it. 121 return nil 122 case vkErr == vk.ERROR_OUT_OF_DATE_KHR: 123 return errOutOfDate 124 case vkErr == vk.ERROR_SURFACE_LOST_KHR: 125 // Treating a lost surface as a lost device isn't accurate, but 126 // probably not worth optimizing. 127 return gpu.ErrDeviceLost 128 } 129 return mapErr(err) 130 } 131 132 func (c *vkContext) release() { 133 vk.DeviceWaitIdle(c.dev) 134 135 c.destroySwapchain() 136 vk.DestroyFence(c.dev, c.fence) 137 vk.DestroySemaphore(c.dev, c.acquireSem) 138 vk.DestroySemaphore(c.dev, c.presentSem) 139 vk.DestroyDevice(c.dev) 140 *c = vkContext{} 141 } 142 143 func (c *vkContext) present() error { 144 return mapSurfaceErr(vk.PresentQueue(c.queue, c.swchain, c.presentSem, c.presentIdx)) 145 } 146 147 func (c *vkContext) destroyImageViews() { 148 for _, f := range c.fbos { 149 vk.DestroyFramebuffer(c.dev, f) 150 } 151 c.fbos = nil 152 for _, view := range c.views { 153 vk.DestroyImageView(c.dev, view) 154 } 155 c.views = nil 156 } 157 158 func (c *vkContext) destroySwapchain() { 159 vk.DeviceWaitIdle(c.dev) 160 161 c.destroyImageViews() 162 if c.swchain != 0 { 163 vk.DestroySwapchain(c.dev, c.swchain) 164 c.swchain = 0 165 } 166 } 167 168 func (c *vkContext) refresh(surf vk.Surface, width, height int) error { 169 vk.DeviceWaitIdle(c.dev) 170 171 c.destroyImageViews() 172 // Check whether size is valid. That's needed on X11, where ConfigureNotify 173 // is not always synchronized with the window extent. 174 caps, err := vk.GetPhysicalDeviceSurfaceCapabilities(c.physDev, surf) 175 if err != nil { 176 return err 177 } 178 minExt, maxExt := caps.MinExtent(), caps.MaxExtent() 179 if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height { 180 return errOutOfDate 181 } 182 swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, surf, width, height, c.swchain) 183 if c.swchain != 0 { 184 vk.DestroySwapchain(c.dev, c.swchain) 185 c.swchain = 0 186 } 187 if err := mapSurfaceErr(err); err != nil { 188 return err 189 } 190 c.swchain = swchain 191 c.imgs = imgs 192 c.format = format 193 pass, err := vk.CreateRenderPass( 194 c.dev, 195 format, 196 vk.ATTACHMENT_LOAD_OP_CLEAR, 197 vk.IMAGE_LAYOUT_UNDEFINED, 198 vk.IMAGE_LAYOUT_PRESENT_SRC_KHR, 199 nil, 200 ) 201 if err := mapErr(err); err != nil { 202 return err 203 } 204 defer vk.DestroyRenderPass(c.dev, pass) 205 for _, img := range imgs { 206 view, err := vk.CreateImageView(c.dev, img, format) 207 if err := mapErr(err); err != nil { 208 return err 209 } 210 c.views = append(c.views, view) 211 fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height) 212 if err := mapErr(err); err != nil { 213 return err 214 } 215 c.fbos = append(c.fbos, fbo) 216 } 217 return nil 218 }