diff options
Diffstat (limited to 'vendor/gioui.org/gpu/internal/vulkan/vulkan.go')
-rw-r--r-- | vendor/gioui.org/gpu/internal/vulkan/vulkan.go | 1121 |
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() {} |