Files
gio/gpu/internal/vulkan/vulkan.go
T
Elias Naur 8999747ad2 app,gpu,internal/vk: add Vulkan port for Wayland, X11, Android
This change implements a Vulkan port for the two renderers, old and
compute. Run with GIORENDERER=forcecompute to test the compute renderer.

To shake out bugs faster, it is also made the default on systems that
support it. To disable Vulkan and force the use of OpenGL, use the
`novulkan` tag:

$ go run -tags novulkan gioui.org/example/kitchen

Don't forget to file an issue describing the issue that prompted the use
of the tag.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2021-09-21 18:40:05 +02:00

1120 lines
30 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
//go:build linux
// +build linux
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() {
pool := p.pool
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.IsDeviceLost() {
return driver.ErrDeviceLost
}
return err
}
func (f *Texture) ImplementsRenderTarget() {}