forked from joejulian/gio
6c1f9c19f5
While here, add a missing nil check. Updates gio#280 Signed-off-by: Elias Naur <mail@eliasnaur.com>
205 lines
4.7 KiB
Go
205 lines
4.7 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 != 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 == 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
|
|
}
|
|
|
|
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 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) 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 != 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
|
|
}
|