forked from joejulian/gio
ebad5afdf3
The mapErr helper may map the error to nil, in which case the caller should continue, not exit. This change split up error mapping into mapErr which never maps to nil, and mapSurfaceErr which handles the VK_KHR_swapchain errors and may map to nil. Maybe fixes gio#287 Signed-off-by: Elias Naur <mail@eliasnaur.com>
211 lines
4.8 KiB
Go
211 lines
4.8 KiB
Go
// 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
|
|
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 {
|
|
return nil, err
|
|
}
|
|
dev, err := vk.CreateDeviceAndQueue(physDev, qFam, "VK_KHR_swapchain")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err != nil {
|
|
vk.DestroyDevice(dev)
|
|
return nil, err
|
|
}
|
|
acquireSem, err := vk.CreateSemaphore(dev)
|
|
if err != nil {
|
|
vk.DestroyDevice(dev)
|
|
return nil, err
|
|
}
|
|
presentSem, err := vk.CreateSemaphore(dev)
|
|
if err != nil {
|
|
vk.DestroySemaphore(dev, acquireSem)
|
|
vk.DestroyDevice(dev)
|
|
return nil, err
|
|
}
|
|
c := &vkContext{
|
|
physDev: physDev,
|
|
inst: inst,
|
|
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 := mapSurfaceErr(err); err != nil {
|
|
return nil, 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) && vkErr == vk.ERROR_DEVICE_LOST {
|
|
return gpu.ErrDeviceLost
|
|
}
|
|
return err
|
|
}
|
|
|
|
func mapSurfaceErr(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 == 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
|
|
// probably not worth optimizing.
|
|
return gpu.ErrDeviceLost
|
|
}
|
|
return mapErr(err)
|
|
}
|
|
|
|
func (c *vkContext) release() {
|
|
vk.DeviceWaitIdle(c.dev)
|
|
|
|
c.destroySwapchain()
|
|
vk.DestroySemaphore(c.dev, c.acquireSem)
|
|
vk.DestroySemaphore(c.dev, c.presentSem)
|
|
vk.DestroyDevice(c.dev)
|
|
*c = vkContext{}
|
|
}
|
|
|
|
func (c *vkContext) present() error {
|
|
return mapSurfaceErr(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) destroySwapchain() {
|
|
vk.DeviceWaitIdle(c.dev)
|
|
|
|
c.destroyImageViews()
|
|
if c.swchain != 0 {
|
|
vk.DestroySwapchain(c.dev, c.swchain)
|
|
c.swchain = 0
|
|
}
|
|
}
|
|
|
|
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)
|
|
c.swchain = 0
|
|
}
|
|
if err := mapSurfaceErr(err); err != nil {
|
|
return 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 := mapErr(err); err != nil {
|
|
return err
|
|
}
|
|
defer vk.DestroyRenderPass(c.dev, pass)
|
|
for _, img := range imgs {
|
|
view, err := vk.CreateImageView(c.dev, img, format)
|
|
if err := mapErr(err); err != nil {
|
|
return err
|
|
}
|
|
c.views = append(c.views, view)
|
|
fbo, err := vk.CreateFramebuffer(c.dev, pass, view, width, height)
|
|
if err := mapErr(err); err != nil {
|
|
return err
|
|
}
|
|
c.fbos = append(c.fbos, fbo)
|
|
}
|
|
return nil
|
|
}
|