// 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() {}