github.com/utopiagio/gio@v0.0.8/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  	"github.com/utopiagio/gio/gpu"
    14  	"github.com/utopiagio/gio/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  }