aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/gpu/internal/vulkan/vulkan.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/gpu/internal/vulkan/vulkan.go')
-rw-r--r--vendor/gioui.org/gpu/internal/vulkan/vulkan.go1121
1 files changed, 1121 insertions, 0 deletions
diff --git a/vendor/gioui.org/gpu/internal/vulkan/vulkan.go b/vendor/gioui.org/gpu/internal/vulkan/vulkan.go
new file mode 100644
index 0000000..7d3791e
--- /dev/null
+++ b/vendor/gioui.org/gpu/internal/vulkan/vulkan.go
@@ -0,0 +1,1121 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build (linux || freebsd) && !novulkan
+// +build linux freebsd
+// +build !novulkan
+
+package vulkan
+
+import (
+ "errors"
+ "fmt"
+ "image"
+
+ "gioui.org/gpu/internal/driver"
+ "gioui.org/internal/vk"
+ "gioui.org/shader"
+)
+
+type Backend struct {
+ physDev vk.PhysicalDevice
+ dev vk.Device
+ queue vk.Queue
+ cmdPool struct {
+ current vk.CommandBuffer
+ pool vk.CommandPool
+ used int
+ buffers []vk.CommandBuffer
+ }
+ outFormat vk.Format
+ staging struct {
+ buf *Buffer
+ mem []byte
+ size int
+ cap int
+ }
+ defers []func(d vk.Device)
+ frameSig vk.Semaphore
+ waitSems []vk.Semaphore
+ waitStages []vk.PipelineStageFlags
+ sigSems []vk.Semaphore
+ fence vk.Fence
+
+ allPipes []*Pipeline
+
+ pipe *Pipeline
+
+ passes map[passKey]vk.RenderPass
+
+ // bindings and offset are temporary storage for BindVertexBuffer.
+ bindings []vk.Buffer
+ offsets []vk.DeviceSize
+
+ desc struct {
+ dirty bool
+ texBinds [texUnits]*Texture
+ bufBinds [storageUnits]*Buffer
+ }
+
+ caps driver.Features
+}
+
+type passKey struct {
+ fmt vk.Format
+ loadAct vk.AttachmentLoadOp
+ initLayout vk.ImageLayout
+ finalLayout vk.ImageLayout
+}
+
+type Texture struct {
+ backend *Backend
+ img vk.Image
+ mem vk.DeviceMemory
+ view vk.ImageView
+ sampler vk.Sampler
+ fbo vk.Framebuffer
+ format vk.Format
+ layout vk.ImageLayout
+ passLayout vk.ImageLayout
+ width int
+ height int
+ acquire vk.Semaphore
+ foreign bool
+
+ scope struct {
+ stage vk.PipelineStageFlags
+ access vk.AccessFlags
+ }
+}
+
+type Shader struct {
+ dev vk.Device
+ module vk.ShaderModule
+ pushRange vk.PushConstantRange
+ src shader.Sources
+}
+
+type Pipeline struct {
+ backend *Backend
+ pipe vk.Pipeline
+ pushRanges []vk.PushConstantRange
+ ninputs int
+ desc *descPool
+}
+
+type descPool struct {
+ layout vk.PipelineLayout
+ descLayout vk.DescriptorSetLayout
+ pool vk.DescriptorPool
+ size int
+ cap int
+ texBinds []int
+ imgBinds []int
+ bufBinds []int
+}
+
+type Buffer struct {
+ backend *Backend
+ buf vk.Buffer
+ store []byte
+ mem vk.DeviceMemory
+ usage vk.BufferUsageFlags
+
+ scope struct {
+ stage vk.PipelineStageFlags
+ access vk.AccessFlags
+ }
+}
+
+const (
+ texUnits = 4
+ storageUnits = 4
+)
+
+func init() {
+ driver.NewVulkanDevice = newVulkanDevice
+}
+
+func newVulkanDevice(api driver.Vulkan) (driver.Device, error) {
+ b := &Backend{
+ physDev: vk.PhysicalDevice(api.PhysDevice),
+ dev: vk.Device(api.Device),
+ outFormat: vk.Format(api.Format),
+ caps: driver.FeatureCompute,
+ passes: make(map[passKey]vk.RenderPass),
+ }
+ b.queue = vk.GetDeviceQueue(b.dev, api.QueueFamily, api.QueueIndex)
+ cmdPool, err := vk.CreateCommandPool(b.dev, api.QueueFamily)
+ if err != nil {
+ return nil, err
+ }
+ b.cmdPool.pool = cmdPool
+ props := vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R16_SFLOAT)
+ reqs := vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT
+ if props&reqs == reqs {
+ b.caps |= driver.FeatureFloatRenderTargets
+ }
+ reqs = vk.FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | vk.FORMAT_FEATURE_SAMPLED_IMAGE_BIT
+ props = vk.GetPhysicalDeviceFormatProperties(b.physDev, vk.FORMAT_R8G8B8A8_SRGB)
+ if props&reqs == reqs {
+ b.caps |= driver.FeatureSRGB
+ }
+ fence, err := vk.CreateFence(b.dev)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ b.fence = fence
+ return b, nil
+}
+
+func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
+ vk.QueueWaitIdle(b.queue)
+ b.staging.size = 0
+ b.cmdPool.used = 0
+ b.runDefers()
+ b.resetPipes()
+
+ if target == nil {
+ return nil
+ }
+ switch t := target.(type) {
+ case driver.VulkanRenderTarget:
+ layout := vk.IMAGE_LAYOUT_UNDEFINED
+ if !clear {
+ layout = vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+ }
+ b.frameSig = vk.Semaphore(t.SignalSem)
+ tex := &Texture{
+ img: vk.Image(t.Image),
+ fbo: vk.Framebuffer(t.Framebuffer),
+ width: viewport.X,
+ height: viewport.Y,
+ layout: layout,
+ passLayout: vk.IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ format: b.outFormat,
+ acquire: vk.Semaphore(t.WaitSem),
+ foreign: true,
+ }
+ return tex
+ case *Texture:
+ return t
+ default:
+ panic(fmt.Sprintf("vulkan: unsupported render target type: %T", t))
+ }
+}
+
+func (b *Backend) deferFunc(f func(d vk.Device)) {
+ b.defers = append(b.defers, f)
+}
+
+func (b *Backend) runDefers() {
+ for _, f := range b.defers {
+ f(b.dev)
+ }
+ b.defers = b.defers[:0]
+}
+
+func (b *Backend) resetPipes() {
+ for i := len(b.allPipes) - 1; i >= 0; i-- {
+ p := b.allPipes[i]
+ if p.pipe == 0 {
+ // Released pipeline.
+ b.allPipes = append(b.allPipes[:i], b.allPipes[:i+1]...)
+ continue
+ }
+ if p.desc.size > 0 {
+ vk.ResetDescriptorPool(b.dev, p.desc.pool)
+ p.desc.size = 0
+ }
+ }
+}
+
+func (b *Backend) EndFrame() {
+ if b.frameSig != 0 {
+ b.sigSems = append(b.sigSems, b.frameSig)
+ b.frameSig = 0
+ }
+ b.submitCmdBuf(false)
+}
+
+func (b *Backend) Caps() driver.Caps {
+ return driver.Caps{
+ MaxTextureSize: 4096,
+ Features: b.caps,
+ }
+}
+
+func (b *Backend) NewTimer() driver.Timer {
+ panic("timers not supported")
+}
+
+func (b *Backend) IsTimeContinuous() bool {
+ panic("timers not supported")
+}
+
+func (b *Backend) Release() {
+ vk.DeviceWaitIdle(b.dev)
+ if buf := b.staging.buf; buf != nil {
+ vk.UnmapMemory(b.dev, b.staging.buf.mem)
+ buf.Release()
+ }
+ b.runDefers()
+ for _, rp := range b.passes {
+ vk.DestroyRenderPass(b.dev, rp)
+ }
+ vk.DestroyFence(b.dev, b.fence)
+ vk.FreeCommandBuffers(b.dev, b.cmdPool.pool, b.cmdPool.buffers...)
+ vk.DestroyCommandPool(b.dev, b.cmdPool.pool)
+ *b = Backend{}
+}
+
+func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
+ vkfmt := formatFor(format)
+ usage := vk.IMAGE_USAGE_TRANSFER_DST_BIT | vk.IMAGE_USAGE_TRANSFER_SRC_BIT
+ passLayout := vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+ if bindings&driver.BufferBindingTexture != 0 {
+ usage |= vk.IMAGE_USAGE_SAMPLED_BIT
+ passLayout = vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+ }
+ if bindings&driver.BufferBindingFramebuffer != 0 {
+ usage |= vk.IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+ }
+ if bindings&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
+ usage |= vk.IMAGE_USAGE_STORAGE_BIT
+ }
+ filterFor := func(f driver.TextureFilter) vk.Filter {
+ switch minFilter {
+ case driver.FilterLinear:
+ return vk.FILTER_LINEAR
+ case driver.FilterNearest:
+ return vk.FILTER_NEAREST
+ }
+ panic("unknown filter")
+ }
+ sampler, err := vk.CreateSampler(b.dev, filterFor(minFilter), filterFor(magFilter))
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ img, mem, err := vk.CreateImage(b.physDev, b.dev, vkfmt, width, height, usage)
+ if err != nil {
+ vk.DestroySampler(b.dev, sampler)
+ return nil, mapErr(err)
+ }
+ view, err := vk.CreateImageView(b.dev, img, vkfmt)
+ if err != nil {
+ vk.DestroySampler(b.dev, sampler)
+ vk.DestroyImage(b.dev, img)
+ vk.FreeMemory(b.dev, mem)
+ return nil, mapErr(err)
+ }
+ t := &Texture{backend: b, img: img, mem: mem, view: view, sampler: sampler, layout: vk.IMAGE_LAYOUT_UNDEFINED, passLayout: passLayout, width: width, height: height, format: vkfmt}
+ if bindings&driver.BufferBindingFramebuffer != 0 {
+ pass, err := vk.CreateRenderPass(b.dev, vkfmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE,
+ vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ defer vk.DestroyRenderPass(b.dev, pass)
+ fbo, err := vk.CreateFramebuffer(b.dev, pass, view, width, height)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ t.fbo = fbo
+ }
+ return t, nil
+}
+
+func (b *Backend) NewBuffer(bindings driver.BufferBinding, size int) (driver.Buffer, error) {
+ if bindings&driver.BufferBindingUniforms != 0 {
+ // Implement uniform buffers as inline push constants.
+ return &Buffer{store: make([]byte, size)}, nil
+ }
+ usage := vk.BUFFER_USAGE_TRANSFER_DST_BIT | vk.BUFFER_USAGE_TRANSFER_SRC_BIT
+ if bindings&driver.BufferBindingIndices != 0 {
+ usage |= vk.BUFFER_USAGE_INDEX_BUFFER_BIT
+ }
+ if bindings&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
+ usage |= vk.BUFFER_USAGE_STORAGE_BUFFER_BIT
+ }
+ if bindings&driver.BufferBindingVertices != 0 {
+ usage |= vk.BUFFER_USAGE_VERTEX_BUFFER_BIT
+ }
+ buf, err := b.newBuffer(size, usage, vk.MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
+ return buf, mapErr(err)
+}
+
+func (b *Backend) newBuffer(size int, usage vk.BufferUsageFlags, props vk.MemoryPropertyFlags) (*Buffer, error) {
+ buf, mem, err := vk.CreateBuffer(b.physDev, b.dev, size, usage, props)
+ return &Buffer{backend: b, buf: buf, mem: mem, usage: usage}, err
+}
+
+func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
+ buf, err := b.NewBuffer(typ, len(data))
+ if err != nil {
+ return nil, err
+ }
+ buf.Upload(data)
+ return buf, nil
+}
+
+func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
+ sh, err := b.newShader(src, vk.SHADER_STAGE_VERTEX_BIT)
+ return sh, mapErr(err)
+}
+
+func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
+ sh, err := b.newShader(src, vk.SHADER_STAGE_FRAGMENT_BIT)
+ return sh, mapErr(err)
+}
+
+func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
+ vs := desc.VertexShader.(*Shader)
+ fs := desc.FragmentShader.(*Shader)
+ var ranges []vk.PushConstantRange
+ if r := vs.pushRange; r != (vk.PushConstantRange{}) {
+ ranges = append(ranges, r)
+ }
+ if r := fs.pushRange; r != (vk.PushConstantRange{}) {
+ ranges = append(ranges, r)
+ }
+ descPool, err := createPipelineLayout(b.dev, fs.src, ranges)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ blend := desc.BlendDesc
+ factorFor := func(f driver.BlendFactor) vk.BlendFactor {
+ switch f {
+ case driver.BlendFactorZero:
+ return vk.BLEND_FACTOR_ZERO
+ case driver.BlendFactorOne:
+ return vk.BLEND_FACTOR_ONE
+ case driver.BlendFactorOneMinusSrcAlpha:
+ return vk.BLEND_FACTOR_ONE_MINUS_SRC_ALPHA
+ case driver.BlendFactorDstColor:
+ return vk.BLEND_FACTOR_DST_COLOR
+ default:
+ panic("unknown blend factor")
+ }
+ }
+ var top vk.PrimitiveTopology
+ switch desc.Topology {
+ case driver.TopologyTriangles:
+ top = vk.PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
+ case driver.TopologyTriangleStrip:
+ top = vk.PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
+ default:
+ panic("unknown topology")
+ }
+ var binds []vk.VertexInputBindingDescription
+ var attrs []vk.VertexInputAttributeDescription
+ inputs := desc.VertexLayout.Inputs
+ for i, inp := range inputs {
+ binds = append(binds, vk.VertexInputBindingDescription{
+ Binding: i,
+ Stride: desc.VertexLayout.Stride,
+ })
+ attrs = append(attrs, vk.VertexInputAttributeDescription{
+ Binding: i,
+ Location: vs.src.Inputs[i].Location,
+ Format: vertFormatFor(vs.src.Inputs[i]),
+ Offset: inp.Offset,
+ })
+ }
+ fmt := b.outFormat
+ if f := desc.PixelFormat; f != driver.TextureFormatOutput {
+ fmt = formatFor(f)
+ }
+ pass, err := vk.CreateRenderPass(b.dev, fmt, vk.ATTACHMENT_LOAD_OP_DONT_CARE,
+ vk.IMAGE_LAYOUT_UNDEFINED, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, nil)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ defer vk.DestroyRenderPass(b.dev, pass)
+ pipe, err := vk.CreateGraphicsPipeline(b.dev, pass, vs.module, fs.module, blend.Enable, factorFor(blend.SrcFactor), factorFor(blend.DstFactor), top, binds, attrs, descPool.layout)
+ if err != nil {
+ descPool.release(b.dev)
+ return nil, mapErr(err)
+ }
+ p := &Pipeline{backend: b, pipe: pipe, desc: descPool, pushRanges: ranges, ninputs: len(inputs)}
+ b.allPipes = append(b.allPipes, p)
+ return p, nil
+}
+
+func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
+ sh, err := b.newShader(src, vk.SHADER_STAGE_COMPUTE_BIT)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ defer sh.Release()
+ descPool, err := createPipelineLayout(b.dev, src, nil)
+ if err != nil {
+ return nil, mapErr(err)
+ }
+ pipe, err := vk.CreateComputePipeline(b.dev, sh.module, descPool.layout)
+ if err != nil {
+ descPool.release(b.dev)
+ return nil, mapErr(err)
+ }
+ return &Pipeline{backend: b, pipe: pipe, desc: descPool}, nil
+}
+
+func vertFormatFor(f shader.InputLocation) vk.Format {
+ t := f.Type
+ s := f.Size
+ switch {
+ case t == shader.DataTypeFloat && s == 1:
+ return vk.FORMAT_R32_SFLOAT
+ case t == shader.DataTypeFloat && s == 2:
+ return vk.FORMAT_R32G32_SFLOAT
+ case t == shader.DataTypeFloat && s == 3:
+ return vk.FORMAT_R32G32B32_SFLOAT
+ case t == shader.DataTypeFloat && s == 4:
+ return vk.FORMAT_R32G32B32A32_SFLOAT
+ default:
+ panic("unsupported data type")
+ }
+}
+
+func createPipelineLayout(d vk.Device, src shader.Sources, ranges []vk.PushConstantRange) (*descPool, error) {
+ var (
+ descLayouts []vk.DescriptorSetLayout
+ descLayout vk.DescriptorSetLayout
+ )
+ texBinds := make([]int, len(src.Textures))
+ imgBinds := make([]int, len(src.Images))
+ bufBinds := make([]int, len(src.StorageBuffers))
+ var descBinds []vk.DescriptorSetLayoutBinding
+ for i, t := range src.Textures {
+ descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{
+ Binding: t.Binding,
+ StageFlags: vk.SHADER_STAGE_FRAGMENT_BIT,
+ DescriptorType: vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ })
+ texBinds[i] = t.Binding
+ }
+ for i, img := range src.Images {
+ descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{
+ Binding: img.Binding,
+ StageFlags: vk.SHADER_STAGE_COMPUTE_BIT,
+ DescriptorType: vk.DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ })
+ imgBinds[i] = img.Binding
+ }
+ for i, buf := range src.StorageBuffers {
+ descBinds = append(descBinds, vk.DescriptorSetLayoutBinding{
+ Binding: buf.Binding,
+ StageFlags: vk.SHADER_STAGE_COMPUTE_BIT,
+ DescriptorType: vk.DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ })
+ bufBinds[i] = buf.Binding
+ }
+ if len(descBinds) > 0 {
+ var err error
+ descLayout, err = vk.CreateDescriptorSetLayout(d, descBinds)
+ if err != nil {
+ return nil, err
+ }
+ descLayouts = append(descLayouts, descLayout)
+ }
+ layout, err := vk.CreatePipelineLayout(d, ranges, descLayouts)
+ if err != nil {
+ if descLayout != 0 {
+ vk.DestroyDescriptorSetLayout(d, descLayout)
+ }
+ return nil, err
+ }
+ descPool := &descPool{
+ texBinds: texBinds,
+ bufBinds: bufBinds,
+ imgBinds: imgBinds,
+ layout: layout,
+ descLayout: descLayout,
+ }
+ return descPool, nil
+}
+
+func (b *Backend) newShader(src shader.Sources, stage vk.ShaderStageFlags) (*Shader, error) {
+ mod, err := vk.CreateShaderModule(b.dev, src.SPIRV)
+ if err != nil {
+ return nil, err
+ }
+
+ sh := &Shader{dev: b.dev, module: mod, src: src}
+ if locs := src.Uniforms.Locations; len(locs) > 0 {
+ pushOffset := 0x7fffffff
+ for _, l := range locs {
+ if l.Offset < pushOffset {
+ pushOffset = l.Offset
+ }
+ }
+ sh.pushRange = vk.BuildPushConstantRange(stage, pushOffset, src.Uniforms.Size)
+ }
+ return sh, nil
+}
+
+func (b *Backend) CopyTexture(dstTex driver.Texture, dorig image.Point, srcFBO driver.Texture, srect image.Rectangle) {
+ dst := dstTex.(*Texture)
+ src := srcFBO.(*Texture)
+ cmdBuf := b.ensureCmdBuf()
+ op := vk.BuildImageCopy(srect.Min.X, srect.Min.Y, dorig.X, dorig.Y, srect.Dx(), srect.Dy())
+ src.imageBarrier(cmdBuf,
+ vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ vk.PIPELINE_STAGE_TRANSFER_BIT,
+ vk.ACCESS_TRANSFER_READ_BIT,
+ )
+ dst.imageBarrier(cmdBuf,
+ vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ vk.PIPELINE_STAGE_TRANSFER_BIT,
+ vk.ACCESS_TRANSFER_WRITE_BIT,
+ )
+ vk.CmdCopyImage(cmdBuf, src.img, src.layout, dst.img, dst.layout, []vk.ImageCopy{op})
+}
+
+func (b *Backend) Viewport(x, y, width, height int) {
+ cmdBuf := b.currentCmdBuf()
+ vp := vk.BuildViewport(float32(x), float32(y), float32(width), float32(height))
+ vk.CmdSetViewport(cmdBuf, 0, vp)
+}
+
+func (b *Backend) DrawArrays(off, count int) {
+ cmdBuf := b.currentCmdBuf()
+ if b.desc.dirty {
+ b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_GRAPHICS, b.desc.texBinds, b.desc.bufBinds)
+ b.desc.dirty = false
+ }
+ vk.CmdDraw(cmdBuf, count, 1, off, 0)
+}
+
+func (b *Backend) DrawElements(off, count int) {
+ cmdBuf := b.currentCmdBuf()
+ if b.desc.dirty {
+ b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_GRAPHICS, b.desc.texBinds, b.desc.bufBinds)
+ b.desc.dirty = false
+ }
+ vk.CmdDrawIndexed(cmdBuf, count, 1, off, 0, 0)
+}
+
+func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
+ t := tex.(*Texture)
+ b.desc.texBinds[unit] = t
+ b.desc.dirty = true
+ t.imageBarrier(b.currentCmdBuf(),
+ vk.IMAGE_LAYOUT_GENERAL,
+ vk.PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ vk.ACCESS_SHADER_READ_BIT|vk.ACCESS_SHADER_WRITE_BIT,
+ )
+}
+
+func (b *Backend) DispatchCompute(x, y, z int) {
+ cmdBuf := b.currentCmdBuf()
+ if b.desc.dirty {
+ b.pipe.desc.bindDescriptorSet(b, cmdBuf, vk.PIPELINE_BIND_POINT_COMPUTE, b.desc.texBinds, b.desc.bufBinds)
+ b.desc.dirty = false
+ }
+ vk.CmdDispatch(cmdBuf, x, y, z)
+}
+
+func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
+ if stride == 0 {
+ stride = size.X * 4
+ }
+ cmdBuf := t.backend.ensureCmdBuf()
+ dstStride := size.X * 4
+ n := size.Y * dstStride
+ stage, mem, off := t.backend.stagingBuffer(n)
+ var srcOff, dstOff int
+ for y := 0; y < size.Y; y++ {
+ srcRow := pixels[srcOff : srcOff+dstStride]
+ dstRow := mem[dstOff : dstOff+dstStride]
+ copy(dstRow, srcRow)
+ dstOff += dstStride
+ srcOff += stride
+ }
+ op := vk.BuildBufferImageCopy(off, dstStride/4, offset.X, offset.Y, size.X, size.Y)
+ t.imageBarrier(cmdBuf,
+ vk.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ vk.PIPELINE_STAGE_TRANSFER_BIT,
+ vk.ACCESS_TRANSFER_WRITE_BIT,
+ )
+ vk.CmdCopyBufferToImage(cmdBuf, stage.buf, t.img, t.layout, op)
+}
+
+func (t *Texture) Release() {
+ if t.foreign {
+ panic("external textures cannot be released")
+ }
+ freet := *t
+ t.backend.deferFunc(func(d vk.Device) {
+ if freet.fbo != 0 {
+ vk.DestroyFramebuffer(d, freet.fbo)
+ }
+ vk.DestroySampler(d, freet.sampler)
+ vk.DestroyImageView(d, freet.view)
+ vk.DestroyImage(d, freet.img)
+ vk.FreeMemory(d, freet.mem)
+ })
+ *t = Texture{}
+}
+
+func (p *Pipeline) Release() {
+ freep := *p
+ p.backend.deferFunc(func(d vk.Device) {
+ freep.desc.release(d)
+ vk.DestroyPipeline(d, freep.pipe)
+ })
+ *p = Pipeline{}
+}
+
+func (p *descPool) release(d vk.Device) {
+ if p := p.pool; p != 0 {
+ vk.DestroyDescriptorPool(d, p)
+ }
+ if l := p.descLayout; l != 0 {
+ vk.DestroyDescriptorSetLayout(d, l)
+ }
+ vk.DestroyPipelineLayout(d, p.layout)
+}
+
+func (p *descPool) bindDescriptorSet(b *Backend, cmdBuf vk.CommandBuffer, bindPoint vk.PipelineBindPoint, texBinds [texUnits]*Texture, bufBinds [storageUnits]*Buffer) {
+ realloced := false
+ destroyPool := func() {
+ if pool := p.pool; pool != 0 {
+ b.deferFunc(func(d vk.Device) {
+ vk.DestroyDescriptorPool(d, pool)
+ })
+ }
+ p.pool = 0
+ p.cap = 0
+ }
+ for {
+ if p.size == p.cap {
+ if realloced {
+ panic("vulkan: vkAllocateDescriptorSet failed on a newly allocated descriptor pool")
+ }
+ destroyPool()
+ realloced = true
+ newCap := p.cap * 2
+ const initialPoolSize = 100
+ if newCap < initialPoolSize {
+ newCap = initialPoolSize
+ }
+ var poolSizes []vk.DescriptorPoolSize
+ if n := len(p.texBinds); n > 0 {
+ poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, newCap*n))
+ }
+ if n := len(p.imgBinds); n > 0 {
+ poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, newCap*n))
+ }
+ if n := len(p.bufBinds); n > 0 {
+ poolSizes = append(poolSizes, vk.BuildDescriptorPoolSize(vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, newCap*n))
+ }
+ pool, err := vk.CreateDescriptorPool(b.dev, newCap, poolSizes)
+ if err != nil {
+ panic(fmt.Errorf("vulkan: failed to allocate descriptor pool with %d descriptors", newCap))
+ }
+ p.pool = pool
+ p.cap = newCap
+ p.size = 0
+ }
+ l := p.descLayout
+ if l == 0 {
+ panic("vulkan: descriptor set is dirty, but pipeline has empty layout")
+ }
+ descSet, err := vk.AllocateDescriptorSet(b.dev, p.pool, l)
+ if err != nil {
+ destroyPool()
+ continue
+ }
+ p.size++
+ for _, bind := range p.texBinds {
+ tex := texBinds[bind]
+ write := vk.BuildWriteDescriptorSetImage(descSet, bind, vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, tex.sampler, tex.view, vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+ vk.UpdateDescriptorSet(b.dev, write)
+ }
+ for _, bind := range p.imgBinds {
+ tex := texBinds[bind]
+ write := vk.BuildWriteDescriptorSetImage(descSet, bind, vk.DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, tex.view, vk.IMAGE_LAYOUT_GENERAL)
+ vk.UpdateDescriptorSet(b.dev, write)
+ }
+ for _, bind := range p.bufBinds {
+ buf := bufBinds[bind]
+ write := vk.BuildWriteDescriptorSetBuffer(descSet, bind, vk.DESCRIPTOR_TYPE_STORAGE_BUFFER, buf.buf)
+ vk.UpdateDescriptorSet(b.dev, write)
+ }
+ vk.CmdBindDescriptorSets(cmdBuf, bindPoint, p.layout, 0, []vk.DescriptorSet{descSet})
+ break
+ }
+}
+
+func (t *Texture) imageBarrier(cmdBuf vk.CommandBuffer, layout vk.ImageLayout, stage vk.PipelineStageFlags, access vk.AccessFlags) {
+ srcStage := t.scope.stage
+ if srcStage == 0 && t.layout == layout {
+ t.scope.stage = stage
+ t.scope.access = access
+ return
+ }
+ if srcStage == 0 {
+ srcStage = vk.PIPELINE_STAGE_TOP_OF_PIPE_BIT
+ }
+ b := vk.BuildImageMemoryBarrier(
+ t.img,
+ t.scope.access, access,
+ t.layout, layout,
+ )
+ vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, nil, []vk.ImageMemoryBarrier{b})
+ t.layout = layout
+ t.scope.stage = stage
+ t.scope.access = access
+}
+
+func (b *Backend) PrepareTexture(tex driver.Texture) {
+ t := tex.(*Texture)
+ cmdBuf := b.ensureCmdBuf()
+ t.imageBarrier(cmdBuf,
+ vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ vk.ACCESS_SHADER_READ_BIT,
+ )
+}
+
+func (b *Backend) BindTexture(unit int, tex driver.Texture) {
+ t := tex.(*Texture)
+ b.desc.texBinds[unit] = t
+ b.desc.dirty = true
+}
+
+func (b *Backend) BindPipeline(pipe driver.Pipeline) {
+ b.bindPipeline(pipe.(*Pipeline), vk.PIPELINE_BIND_POINT_GRAPHICS)
+}
+
+func (b *Backend) BindProgram(prog driver.Program) {
+ b.bindPipeline(prog.(*Pipeline), vk.PIPELINE_BIND_POINT_COMPUTE)
+}
+
+func (b *Backend) bindPipeline(p *Pipeline, point vk.PipelineBindPoint) {
+ b.pipe = p
+ b.desc.dirty = p.desc.descLayout != 0
+ cmdBuf := b.currentCmdBuf()
+ vk.CmdBindPipeline(cmdBuf, point, p.pipe)
+}
+
+func (s *Shader) Release() {
+ vk.DestroyShaderModule(s.dev, s.module)
+ *s = Shader{}
+}
+
+func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
+ buf := buffer.(*Buffer)
+ b.desc.bufBinds[binding] = buf
+ b.desc.dirty = true
+ buf.barrier(b.currentCmdBuf(),
+ vk.PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ vk.ACCESS_SHADER_READ_BIT|vk.ACCESS_SHADER_WRITE_BIT,
+ )
+}
+
+func (b *Backend) BindUniforms(buffer driver.Buffer) {
+ buf := buffer.(*Buffer)
+ cmdBuf := b.currentCmdBuf()
+ for _, s := range b.pipe.pushRanges {
+ off := s.Offset()
+ vk.CmdPushConstants(cmdBuf, b.pipe.desc.layout, s.StageFlags(), off, buf.store[off:off+s.Size()])
+ }
+}
+
+func (b *Backend) BindVertexBuffer(buffer driver.Buffer, offset int) {
+ buf := buffer.(*Buffer)
+ cmdBuf := b.currentCmdBuf()
+ b.bindings = b.bindings[:0]
+ b.offsets = b.offsets[:0]
+ for i := 0; i < b.pipe.ninputs; i++ {
+ b.bindings = append(b.bindings, buf.buf)
+ b.offsets = append(b.offsets, vk.DeviceSize(offset))
+ }
+ vk.CmdBindVertexBuffers(cmdBuf, 0, b.bindings, b.offsets)
+}
+
+func (b *Backend) BindIndexBuffer(buffer driver.Buffer) {
+ buf := buffer.(*Buffer)
+ cmdBuf := b.currentCmdBuf()
+ vk.CmdBindIndexBuffer(cmdBuf, buf.buf, 0, vk.INDEX_TYPE_UINT16)
+}
+
+func (b *Buffer) Download(data []byte) error {
+ if b.buf == 0 {
+ copy(data, b.store)
+ return nil
+ }
+ stage, mem, off := b.backend.stagingBuffer(len(data))
+ cmdBuf := b.backend.ensureCmdBuf()
+ b.barrier(cmdBuf,
+ vk.PIPELINE_STAGE_TRANSFER_BIT,
+ vk.ACCESS_TRANSFER_READ_BIT,
+ )
+ vk.CmdCopyBuffer(cmdBuf, b.buf, stage.buf, 0, off, len(data))
+ stage.scope.stage = vk.PIPELINE_STAGE_TRANSFER_BIT
+ stage.scope.access = vk.ACCESS_TRANSFER_WRITE_BIT
+ stage.barrier(cmdBuf,
+ vk.PIPELINE_STAGE_HOST_BIT,
+ vk.ACCESS_HOST_READ_BIT,
+ )
+ b.backend.submitCmdBuf(true)
+ copy(data, mem)
+ return nil
+}
+
+func (b *Buffer) Upload(data []byte) {
+ if b.buf == 0 {
+ copy(b.store, data)
+ return
+ }
+ stage, mem, off := b.backend.stagingBuffer(len(data))
+ copy(mem, data)
+ cmdBuf := b.backend.ensureCmdBuf()
+ b.barrier(cmdBuf,
+ vk.PIPELINE_STAGE_TRANSFER_BIT,
+ vk.ACCESS_TRANSFER_WRITE_BIT,
+ )
+ vk.CmdCopyBuffer(cmdBuf, stage.buf, b.buf, off, 0, len(data))
+ var access vk.AccessFlags
+ if b.usage&vk.BUFFER_USAGE_INDEX_BUFFER_BIT != 0 {
+ access |= vk.ACCESS_INDEX_READ_BIT
+ }
+ if b.usage&vk.BUFFER_USAGE_VERTEX_BUFFER_BIT != 0 {
+ access |= vk.ACCESS_VERTEX_ATTRIBUTE_READ_BIT
+ }
+ if access != 0 {
+ b.barrier(cmdBuf,
+ vk.PIPELINE_STAGE_VERTEX_INPUT_BIT,
+ access,
+ )
+ }
+}
+
+func (b *Buffer) barrier(cmdBuf vk.CommandBuffer, stage vk.PipelineStageFlags, access vk.AccessFlags) {
+ srcStage := b.scope.stage
+ if srcStage == 0 {
+ b.scope.stage = stage
+ b.scope.access = access
+ return
+ }
+ barrier := vk.BuildBufferMemoryBarrier(
+ b.buf,
+ b.scope.access, access,
+ )
+ vk.CmdPipelineBarrier(cmdBuf, srcStage, stage, vk.DEPENDENCY_BY_REGION_BIT, nil, []vk.BufferMemoryBarrier{barrier}, nil)
+ b.scope.stage = stage
+ b.scope.access = access
+}
+
+func (b *Buffer) Release() {
+ freeb := *b
+ if freeb.buf != 0 {
+ b.backend.deferFunc(func(d vk.Device) {
+ vk.DestroyBuffer(d, freeb.buf)
+ vk.FreeMemory(d, freeb.mem)
+ })
+ }
+ *b = Buffer{}
+}
+
+func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
+ if len(pixels) == 0 {
+ return nil
+ }
+ sz := src.Size()
+ stageStride := sz.X * 4
+ n := sz.Y * stageStride
+ stage, mem, off := t.backend.stagingBuffer(n)
+ cmdBuf := t.backend.ensureCmdBuf()
+ region := vk.BuildBufferImageCopy(off, stageStride/4, src.Min.X, src.Min.Y, sz.X, sz.Y)
+ t.imageBarrier(cmdBuf,
+ vk.IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ vk.PIPELINE_STAGE_TRANSFER_BIT,
+ vk.ACCESS_TRANSFER_READ_BIT,
+ )
+ vk.CmdCopyImageToBuffer(cmdBuf, t.img, t.layout, stage.buf, []vk.BufferImageCopy{region})
+ stage.scope.stage = vk.PIPELINE_STAGE_TRANSFER_BIT
+ stage.scope.access = vk.ACCESS_TRANSFER_WRITE_BIT
+ stage.barrier(cmdBuf,
+ vk.PIPELINE_STAGE_HOST_BIT,
+ vk.ACCESS_HOST_READ_BIT,
+ )
+ t.backend.submitCmdBuf(true)
+ var srcOff, dstOff int
+ for y := 0; y < sz.Y; y++ {
+ dstRow := pixels[srcOff : srcOff+stageStride]
+ srcRow := mem[dstOff : dstOff+stageStride]
+ copy(dstRow, srcRow)
+ dstOff += stageStride
+ srcOff += stride
+ }
+ return nil
+}
+
+func (b *Backend) currentCmdBuf() vk.CommandBuffer {
+ cur := b.cmdPool.current
+ if cur == nil {
+ panic("vulkan: invalid operation outside a render or compute pass")
+ }
+ return cur
+}
+
+func (b *Backend) ensureCmdBuf() vk.CommandBuffer {
+ if b.cmdPool.current != nil {
+ return b.cmdPool.current
+ }
+ if b.cmdPool.used < len(b.cmdPool.buffers) {
+ buf := b.cmdPool.buffers[b.cmdPool.used]
+ b.cmdPool.current = buf
+ } else {
+ buf, err := vk.AllocateCommandBuffer(b.dev, b.cmdPool.pool)
+ if err != nil {
+ panic(err)
+ }
+ b.cmdPool.buffers = append(b.cmdPool.buffers, buf)
+ b.cmdPool.current = buf
+ }
+ b.cmdPool.used++
+ buf := b.cmdPool.current
+ if err := vk.BeginCommandBuffer(buf); err != nil {
+ panic(err)
+ }
+ return buf
+}
+
+func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
+ t := tex.(*Texture)
+ var vkop vk.AttachmentLoadOp
+ switch d.Action {
+ case driver.LoadActionClear:
+ vkop = vk.ATTACHMENT_LOAD_OP_CLEAR
+ case driver.LoadActionInvalidate:
+ vkop = vk.ATTACHMENT_LOAD_OP_DONT_CARE
+ case driver.LoadActionKeep:
+ vkop = vk.ATTACHMENT_LOAD_OP_LOAD
+ }
+ cmdBuf := b.ensureCmdBuf()
+ if sem := t.acquire; sem != 0 {
+ // The render pass targets a framebuffer that has an associated acquire semaphore.
+ // Wait for it by forming an execution barrier.
+ b.waitSems = append(b.waitSems, sem)
+ b.waitStages = append(b.waitStages, vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)
+ // But only for the first pass in a frame.
+ t.acquire = 0
+ }
+ t.imageBarrier(cmdBuf,
+ vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ vk.ACCESS_COLOR_ATTACHMENT_READ_BIT|vk.ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ )
+ pass := b.lookupPass(t.format, vkop, t.layout, t.passLayout)
+ col := d.ClearColor
+ vk.CmdBeginRenderPass(cmdBuf, pass, t.fbo, t.width, t.height, [4]float32{col.R, col.G, col.B, col.A})
+ t.layout = t.passLayout
+ // If the render pass describes an automatic image layout transition to its final layout, there
+ // is an implicit image barrier with destination PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT. Make
+ // sure any subsequent barrier includes the transition.
+ // See also https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#VkSubpassDependency.
+ t.scope.stage |= vk.PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT
+}
+
+func (b *Backend) EndRenderPass() {
+ vk.CmdEndRenderPass(b.cmdPool.current)
+}
+
+func (b *Backend) BeginCompute() {
+ b.ensureCmdBuf()
+}
+
+func (b *Backend) EndCompute() {
+}
+
+func (b *Backend) lookupPass(fmt vk.Format, loadAct vk.AttachmentLoadOp, initLayout, finalLayout vk.ImageLayout) vk.RenderPass {
+ key := passKey{fmt: fmt, loadAct: loadAct, initLayout: initLayout, finalLayout: finalLayout}
+ if pass, ok := b.passes[key]; ok {
+ return pass
+ }
+ pass, err := vk.CreateRenderPass(b.dev, fmt, loadAct, initLayout, finalLayout, nil)
+ if err != nil {
+ panic(err)
+ }
+ b.passes[key] = pass
+ return pass
+}
+
+func (b *Backend) submitCmdBuf(sync bool) {
+ buf := b.cmdPool.current
+ if buf == nil {
+ return
+ }
+ b.cmdPool.current = nil
+ if err := vk.EndCommandBuffer(buf); err != nil {
+ panic(err)
+ }
+ var fence vk.Fence
+ if sync {
+ fence = b.fence
+ }
+ if err := vk.QueueSubmit(b.queue, buf, b.waitSems, b.waitStages, b.sigSems, fence); err != nil {
+ panic(err)
+ }
+ b.waitSems = b.waitSems[:0]
+ b.sigSems = b.sigSems[:0]
+ b.waitStages = b.waitStages[:0]
+ if sync {
+ vk.WaitForFences(b.dev, b.fence)
+ vk.ResetFences(b.dev, b.fence)
+ }
+}
+
+func (b *Backend) stagingBuffer(size int) (*Buffer, []byte, int) {
+ if b.staging.size+size > b.staging.cap {
+ if b.staging.buf != nil {
+ vk.UnmapMemory(b.dev, b.staging.buf.mem)
+ b.staging.buf.Release()
+ b.staging.cap = 0
+ }
+ cap := 2 * (b.staging.size + size)
+ buf, err := b.newBuffer(cap, vk.BUFFER_USAGE_TRANSFER_SRC_BIT|vk.BUFFER_USAGE_TRANSFER_DST_BIT,
+ vk.MEMORY_PROPERTY_HOST_VISIBLE_BIT|vk.MEMORY_PROPERTY_HOST_COHERENT_BIT)
+ if err != nil {
+ panic(err)
+ }
+ mem, err := vk.MapMemory(b.dev, buf.mem, 0, cap)
+ if err != nil {
+ buf.Release()
+ panic(err)
+ }
+ b.staging.buf = buf
+ b.staging.mem = mem
+ b.staging.size = 0
+ b.staging.cap = cap
+ }
+ off := b.staging.size
+ b.staging.size += size
+ mem := b.staging.mem[off : off+size]
+ return b.staging.buf, mem, off
+}
+
+func formatFor(format driver.TextureFormat) vk.Format {
+ switch format {
+ case driver.TextureFormatRGBA8:
+ return vk.FORMAT_R8G8B8A8_UNORM
+ case driver.TextureFormatSRGBA:
+ return vk.FORMAT_R8G8B8A8_SRGB
+ case driver.TextureFormatFloat:
+ return vk.FORMAT_R16_SFLOAT
+ default:
+ panic("unsupported texture format")
+ }
+}
+
+func mapErr(err error) error {
+ var vkErr vk.Error
+ if errors.As(err, &vkErr) && vkErr == vk.ERROR_DEVICE_LOST {
+ return driver.ErrDeviceLost
+ }
+ return err
+}
+
+func (f *Texture) ImplementsRenderTarget() {}