diff --git a/app/os.go b/app/os.go index 7b6351ba..338797e3 100644 --- a/app/os.go +++ b/app/os.go @@ -22,6 +22,10 @@ type size struct { Height unit.Value } +// errOutOfDate is reported when the GPU surface dimensions or properties no +// longer match the window. +var errOutOfDate = errors.New("app: GPU surface out of date") + // Config describes a Window configuration. type Config struct { // Size is the window dimensions (Width, Height). diff --git a/app/vulkan.go b/app/vulkan.go index 037af9c2..87516963 100644 --- a/app/vulkan.go +++ b/app/vulkan.go @@ -104,7 +104,13 @@ func mapErr(err error) error { // swapchain (preTransform != currentTransform). However, we don't // support transforming the output ourselves, so we'll live with it. return nil - case vkErr.IsDeviceLost(): + case vkErr == vk.ERROR_OUT_OF_DATE_KHR: + return errOutOfDate + case vkErr == vk.ERROR_SURFACE_LOST_KHR: + // Treating a lost surface as a lost device isn't accurate, but + // porbably not worth optimizing. + return gpu.ErrDeviceLost + case vkErr == vk.ERROR_DEVICE_LOST: return gpu.ErrDeviceLost } return err @@ -149,6 +155,16 @@ func (c *vkContext) refresh(surf vk.Surface, width, height int) error { vk.DeviceWaitIdle(c.dev) c.destroyImageViews() + // Check whether size is valid. That's needed on X11, where ConfigureNotify + // is not always synchronized with the window extent. + caps, err := vk.GetPhysicalDeviceSurfaceCapabilities(c.physDev, surf) + if err != nil { + return err + } + minExt, maxExt := caps.MinExtent(), caps.MaxExtent() + if width < minExt.X || maxExt.X < width || height < minExt.Y || maxExt.Y < height { + return errOutOfDate + } swchain, imgs, format, err := vk.CreateSwapchain(c.physDev, c.dev, surf, width, height, c.swchain) if c.swchain != 0 { vk.DestroySwapchain(c.dev, c.swchain) diff --git a/app/vulkan_android.go b/app/vulkan_android.go index 48df013f..cdfbd958 100644 --- a/app/vulkan_android.go +++ b/app/vulkan_android.go @@ -57,7 +57,9 @@ func (c *wlVkContext) API() gpu.API { func (c *wlVkContext) Release() { c.ctx.release() - vk.DestroySurface(c.inst, c.surf) + if c.surf != 0 { + vk.DestroySurface(c.inst, c.surf) + } vk.DestroyInstance(c.inst) *c = wlVkContext{} } diff --git a/app/window.go b/app/window.go index 09b07c90..c0180f62 100644 --- a/app/window.go +++ b/app/window.go @@ -152,6 +152,11 @@ func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync err = w.ctx.Refresh() }) if err != nil { + if errors.Is(err, errOutOfDate) { + // Surface couldn't be created for transient reasons. Skip + // this frame and wait for the next. + return nil + } w.destroyGPU() if errors.Is(err, gpu.ErrDeviceLost) { continue @@ -174,6 +179,11 @@ func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync } if w.gpu != nil { if err := w.render(frame, size); err != nil { + if errors.Is(err, errOutOfDate) { + // GPU surface needs refreshing. + sync = true + continue + } w.destroyGPU() if errors.Is(err, gpu.ErrDeviceLost) { continue diff --git a/gpu/internal/vulkan/vulkan.go b/gpu/internal/vulkan/vulkan.go index 55726983..4d8db056 100644 --- a/gpu/internal/vulkan/vulkan.go +++ b/gpu/internal/vulkan/vulkan.go @@ -1110,7 +1110,7 @@ func formatFor(format driver.TextureFormat) vk.Format { func mapErr(err error) error { var vkErr vk.Error - if errors.As(err, &vkErr) && vkErr.IsDeviceLost() { + if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST { return driver.ErrDeviceLost } return err diff --git a/internal/vk/vulkan.go b/internal/vk/vulkan.go index 6ef59317..7c65bb76 100644 --- a/internal/vk/vulkan.go +++ b/internal/vk/vulkan.go @@ -380,6 +380,7 @@ import "C" import ( "errors" "fmt" + "image" "math" "reflect" "runtime" @@ -441,7 +442,8 @@ type ( Viewport = C.VkViewport WriteDescriptorSet = C.VkWriteDescriptorSet - Surface = C.VkSurfaceKHR + Surface = C.VkSurfaceKHR + SurfaceCapabilities = C.VkSurfaceCapabilitiesKHR Swapchain = C.VkSwapchainKHR ) @@ -939,11 +941,19 @@ func GetDeviceQueue(d Device, queueFamily, queueIndex int) Queue { return queue } -func CreateSwapchain(pd PhysicalDevice, d Device, surf Surface, width, height int, old Swapchain) (Swapchain, []Image, Format, error) { +func GetPhysicalDeviceSurfaceCapabilities(pd PhysicalDevice, surf Surface) (SurfaceCapabilities, 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) + return SurfaceCapabilities{}, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceCapabilitiesKHR: %w", err) + } + return caps, nil +} + +func CreateSwapchain(pd PhysicalDevice, d Device, surf Surface, width, height int, old Swapchain) (Swapchain, []Image, Format, error) { + caps, err := GetPhysicalDeviceSurfaceCapabilities(pd, surf) + if err != nil { + return nilSwapchain, nil, 0, err } mode, modeOK, err := choosePresentMode(pd, surf) if err != nil { @@ -1846,6 +1856,14 @@ func (p QueueFamilyProperties) Flags() QueueFlags { return p.queueFlags } +func (c SurfaceCapabilities) MinExtent() image.Point { + return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height)) +} + +func (c SurfaceCapabilities) MaxExtent() image.Point { + return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height)) +} + func BuildViewport(x, y, width, height float32) Viewport { return C.VkViewport{ x: C.float(x), @@ -2048,12 +2066,3 @@ func vkErr(res C.VkResult) error { 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 - } -}