zworld/engine/renderapi/swapchain/swapchain.go
2024-01-14 22:56:06 +08:00

209 lines
4.8 KiB
Go

package swapchain
import (
"fmt"
"log"
"time"
"zworld/engine/renderapi/command"
"zworld/engine/renderapi/device"
"zworld/engine/renderapi/image"
"zworld/engine/util"
"github.com/vkngwrapper/core/v2/core1_0"
"github.com/vkngwrapper/extensions/v2/khr_surface"
"github.com/vkngwrapper/extensions/v2/khr_swapchain"
)
type T interface {
device.Resource[khr_swapchain.Swapchain]
Aquire() (*Context, error)
Present(command.Worker, *Context)
Resize(int, int)
Images() []image.T
SurfaceFormat() core1_0.Format
}
type swapchain struct {
device device.T
ptr khr_swapchain.Swapchain
ext khr_swapchain.Extension
surface khr_surface.Surface
surfaceFmt khr_surface.SurfaceFormat
images []image.T
current int
frames int
width int
height int
resized bool
contexts []*Context
}
func New(device device.T, frames, width, height int, surface khr_surface.Surface, surfaceFormat khr_surface.SurfaceFormat) T {
s := &swapchain{
device: device,
ext: khr_swapchain.CreateExtensionFromDevice(device.Ptr()),
surface: surface,
surfaceFmt: surfaceFormat,
frames: frames,
contexts: make([]*Context, frames),
width: width,
height: height,
}
s.create()
return s
}
func (s *swapchain) Ptr() khr_swapchain.Swapchain {
return s.ptr
}
func (s *swapchain) Images() []image.T { return s.images }
func (s *swapchain) SurfaceFormat() core1_0.Format { return core1_0.Format(s.surfaceFmt.Format) }
func (s *swapchain) Resize(width, height int) {
// resizing actually happens the next time a frame is aquired
s.width = width
s.height = height
s.resized = true
}
func (s *swapchain) recreate() {
log.Println("recreating swapchain")
// wait for all in-flight frames
// no need to release locks, they will be destroyed
for _, ctx := range s.contexts {
ctx.Aquire()
}
// wait for device idle
s.device.WaitIdle()
// recreate swapchain resources
s.Destroy()
s.create()
}
func (s *swapchain) create() {
imageFormat := core1_0.Format(s.surfaceFmt.Format)
imageUsage := core1_0.ImageUsageColorAttachment | core1_0.ImageUsageTransferSrc
imageSharing := core1_0.SharingModeExclusive
swapInfo := khr_swapchain.SwapchainCreateInfo{
Surface: s.surface,
MinImageCount: s.frames,
ImageFormat: imageFormat,
ImageColorSpace: khr_surface.ColorSpace(s.surfaceFmt.ColorSpace),
ImageExtent: core1_0.Extent2D{
Width: s.width,
Height: s.height,
},
ImageArrayLayers: 1,
ImageUsage: imageUsage,
ImageSharingMode: imageSharing,
PresentMode: khr_surface.PresentModeFIFO,
PreTransform: khr_surface.TransformIdentity,
CompositeAlpha: khr_surface.CompositeAlphaOpaque,
Clipped: true,
}
var chain khr_swapchain.Swapchain
chain, _, err := s.ext.CreateSwapchain(s.device.Ptr(), nil, swapInfo)
if err != nil {
panic(err)
}
s.ptr = chain
swapimages, result, err := chain.SwapchainImages()
if err != nil {
panic(err)
}
if result != core1_0.VKSuccess {
panic("failed to get swapchain images")
}
if len(swapimages) != s.frames {
panic("failed to get the requested number of swapchain images")
}
// create images from swapchain buffers
s.images = util.Map(swapimages, func(img core1_0.Image) image.T {
return image.Wrap(s.device, img, image.Args{
Type: core1_0.ImageType2D,
Width: s.width,
Height: s.height,
Depth: 1,
Levels: 1,
Format: imageFormat,
Usage: imageUsage,
Sharing: imageSharing,
})
})
// create frame contexts
s.contexts = make([]*Context, len(s.images))
for i := range s.contexts {
s.contexts[i] = newContext(s.device, i)
}
// this ensures the first call to Aquire works properly
s.current = -1
}
func (s *swapchain) Aquire() (*Context, error) {
if s.resized {
s.recreate()
s.resized = false
return nil, fmt.Errorf("swapchain out of date")
}
// get next frame context
s.current = (s.current + 1) % s.frames
ctx := s.contexts[s.current]
// wait for frame context to become available
ctx.Aquire()
idx, r, err := s.ptr.AcquireNextImage(time.Second, ctx.ImageAvailable.Ptr(), nil)
if err != nil {
panic(err)
}
if r == khr_swapchain.VKErrorOutOfDate {
s.recreate()
return nil, fmt.Errorf("swapchain out of date")
}
// update swapchain output index
ctx.image = idx
return ctx, nil
}
func (s *swapchain) Present(worker command.Worker, ctx *Context) {
if ctx.RenderComplete == nil {
panic("context has no RenderComplete semaphore")
}
worker.Invoke(func() {
s.ext.QueuePresent(worker.Ptr(), khr_swapchain.PresentInfo{
WaitSemaphores: []core1_0.Semaphore{ctx.RenderComplete.Ptr()},
Swapchains: []khr_swapchain.Swapchain{s.Ptr()},
ImageIndices: []int{ctx.image},
})
})
}
func (s *swapchain) Destroy() {
for _, context := range s.contexts {
context.Destroy()
}
s.contexts = nil
if s.ptr != nil {
s.ptr.Destroy(nil)
s.ptr = nil
}
}