feat(vulkan): blend translucent client buffers with a premultiplied-alpha pipeline
- commit
- e28c9e7c8e320fc7ec0a420c6e86bc0180164255
- author
- brice · · 2026-09-26 09:19 UTC
- parent
- cc9b1b3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
10 files · +347 -10
M go.mod +1 -1
| @@ -5,7 +5,7 @@ go 1.27 | |||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/bnema/purego v0.11.0-bnema.4 | |
| 7 | 7 | github.com/bnema/purego-libwayland v0.1.1 | |
| 8 | - | github.com/bnema/purego-vulkan v0.4.0 | |
| 8 | + | github.com/bnema/purego-vulkan v0.4.1 | |
| 9 | 9 | github.com/bnema/wlturbo v0.2.0 | |
| 10 | 10 | github.com/bnema/zerowrap v1.4.1 | |
| 11 | 11 | github.com/rs/zerolog v1.35.1 | |
| @@ -5,7 +5,7 @@ go 1.27 | |||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/bnema/purego v0.11.0-bnema.4 | 6 | github.com/bnema/purego v0.11.0-bnema.4 | ||
| 7 | github.com/bnema/purego-libwayland v0.1.1 | 7 | github.com/bnema/purego-libwayland v0.1.1 | ||
| 8 | - | github.com/bnema/purego-vulkan v0.4.0 | 8 | + | github.com/bnema/purego-vulkan v0.4.1 |
| 9 | github.com/bnema/wlturbo v0.2.0 | 9 | github.com/bnema/wlturbo v0.2.0 | ||
| 10 | github.com/bnema/zerowrap v1.4.1 | 10 | github.com/bnema/zerowrap v1.4.1 | ||
| 11 | github.com/rs/zerolog v1.35.1 | 11 | github.com/rs/zerolog v1.35.1 | ||
M go.sum +2 -0 generated file: collapsed
A internal/adapters/vulkan/blend.go +186 -0
| @@ -0,0 +1,186 @@ | |||
| 1 | + | package vulkan | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | _ "embed" | |
| 5 | + | "encoding/binary" | |
| 6 | + | "unsafe" | |
| 7 | + | ||
| 8 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // Translucent client pixels are blended over what is below them; opaque | |
| 12 | + | // ones keep the plain copy. The blend pipeline draws one quad per upload | |
| 13 | + | // and its fragment shader reads the staged pixels straight from the | |
| 14 | + | // staging buffer: no texture, no sampler. Wayland buffers are | |
| 15 | + | // premultiplied, hence ONE, ONE_MINUS_SRC_ALPHA. | |
| 16 | + | ||
| 17 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.vert -o shaders/blend.vert.spv | |
| 18 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.frag -o shaders/blend.frag.spv | |
| 19 | + | ||
| 20 | + | var ( | |
| 21 | + | //go:embed shaders/blend.vert.spv | |
| 22 | + | blendVert []byte | |
| 23 | + | //go:embed shaders/blend.frag.spv | |
| 24 | + | blendFrag []byte | |
| 25 | + | ) | |
| 26 | + | ||
| 27 | + | // drawSize is one entry of the draw table (shaders: struct Draw). | |
| 28 | + | const drawSize = 32 | |
| 29 | + | ||
| 30 | + | // blendPipeline is the graphics state for blended uploads. | |
| 31 | + | type blendPipeline struct { | |
| 32 | + | setLayout vk.DescriptorSetLayout | |
| 33 | + | layout vk.PipelineLayout | |
| 34 | + | pipeline vk.Pipeline | |
| 35 | + | pool vk.DescriptorPool | |
| 36 | + | set vk.DescriptorSet | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | func (r *Renderer) createBlend() error { | |
| 40 | + | d, b := r.dd, &r.blend | |
| 41 | + | bindings := []vk.DescriptorSetLayoutBinding{ | |
| 42 | + | {Binding: 0, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |
| 43 | + | {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit}, | |
| 44 | + | } | |
| 45 | + | sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]} | |
| 46 | + | if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &b.setLayout)); err != nil { | |
| 47 | + | return err | |
| 48 | + | } | |
| 49 | + | pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &b.setLayout} | |
| 50 | + | if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &b.layout)); err != nil { | |
| 51 | + | return err | |
| 52 | + | } | |
| 53 | + | size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 2} | |
| 54 | + | dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size} | |
| 55 | + | if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &b.pool)); err != nil { | |
| 56 | + | return err | |
| 57 | + | } | |
| 58 | + | dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &b.setLayout} | |
| 59 | + | if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &b.set)); err != nil { | |
| 60 | + | return err | |
| 61 | + | } | |
| 62 | + | vert, err := r.shaderModule(blendVert) | |
| 63 | + | if err != nil { | |
| 64 | + | return err | |
| 65 | + | } | |
| 66 | + | defer d.DestroyShaderModule(r.device, vert, nil) | |
| 67 | + | frag, err := r.shaderModule(blendFrag) | |
| 68 | + | if err != nil { | |
| 69 | + | return err | |
| 70 | + | } | |
| 71 | + | defer d.DestroyShaderModule(r.device, frag, nil) | |
| 72 | + | main := []byte("main\x00") | |
| 73 | + | stages := []vk.PipelineShaderStageCreateInfo{ | |
| 74 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]}, | |
| 75 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]}, | |
| 76 | + | } | |
| 77 | + | vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo} | |
| 78 | + | assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList} | |
| 79 | + | // The target size is fixed for the renderer's life: static viewport. | |
| 80 | + | viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1} | |
| 81 | + | scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}} | |
| 82 | + | viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor} | |
| 83 | + | raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1} | |
| 84 | + | multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit} | |
| 85 | + | attachment := vk.PipelineColorBlendAttachmentState{ | |
| 86 | + | BlendEnable: 1, | |
| 87 | + | SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd, | |
| 88 | + | SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd, | |
| 89 | + | ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit, | |
| 90 | + | } | |
| 91 | + | blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment} | |
| 92 | + | format := vk.Format(vk.FormatB8g8r8a8Unorm) | |
| 93 | + | rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |
| 94 | + | gpi := vk.GraphicsPipelineCreateInfo{ | |
| 95 | + | SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), | |
| 96 | + | StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, | |
| 97 | + | ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample, | |
| 98 | + | ColorBlendState: &blendState, Layout: b.layout, BasePipelineIndex: -1, | |
| 99 | + | } | |
| 100 | + | return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &b.pipeline)) | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) { | |
| 104 | + | // SPIR-V is read as words: copy into a uint32-aligned slice. | |
| 105 | + | words := make([]uint32, len(code)/4) | |
| 106 | + | for i := range words { | |
| 107 | + | words[i] = binary.LittleEndian.Uint32(code[i*4:]) | |
| 108 | + | } | |
| 109 | + | info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]} | |
| 110 | + | var m vk.ShaderModule | |
| 111 | + | return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m)) | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | func (r *Renderer) destroyBlend() { | |
| 115 | + | d, b := r.dd, &r.blend | |
| 116 | + | if b.pipeline != 0 { | |
| 117 | + | d.DestroyPipeline(r.device, b.pipeline, nil) | |
| 118 | + | } | |
| 119 | + | if b.pool != 0 { | |
| 120 | + | d.DestroyDescriptorPool(r.device, b.pool, nil) | |
| 121 | + | } | |
| 122 | + | if b.layout != 0 { | |
| 123 | + | d.DestroyPipelineLayout(r.device, b.layout, nil) | |
| 124 | + | } | |
| 125 | + | if b.setLayout != 0 { | |
| 126 | + | d.DestroyDescriptorSetLayout(r.device, b.setLayout, nil) | |
| 127 | + | } | |
| 128 | + | *b = blendPipeline{} | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // bindStaging points the set at the staging buffer: pixels from 0, the | |
| 132 | + | // draw table from tableOffset. | |
| 133 | + | func (r *Renderer) bindStaging(tableOffset int) { | |
| 134 | + | infos := []vk.DescriptorBufferInfo{ | |
| 135 | + | {Buffer: r.staging, Range: vk.DeviceSize(tableOffset)}, | |
| 136 | + | {Buffer: r.staging, Offset: vk.DeviceSize(tableOffset), Range: wholeSize}, | |
| 137 | + | } | |
| 138 | + | writes := []vk.WriteDescriptorSet{ | |
| 139 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[0]}, | |
| 140 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[1]}, | |
| 141 | + | } | |
| 142 | + | r.dd.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil) | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | // putDraw writes draw entry i of the table at data. | |
| 146 | + | func (r *Renderer) putDraw(table []byte, i int, u *upload) { | |
| 147 | + | e := table[i*drawSize : (i+1)*drawSize] | |
| 148 | + | for j, v := range []uint32{ | |
| 149 | + | uint32(u.rect.Min.X), uint32(u.rect.Min.Y), uint32(u.rect.Max.X), uint32(u.rect.Max.Y), | |
| 150 | + | uint32(u.offset / 4), uint32(u.rect.Dx()), uint32(r.width), uint32(r.height), | |
| 151 | + | } { | |
| 152 | + | binary.LittleEndian.PutUint32(e[j*4:], v) | |
| 153 | + | } | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | // drawBlended draws uploads[first:last], all blended, into view: one | |
| 157 | + | // instanced draw. The image is TRANSFER_DST before and after. | |
| 158 | + | func (r *Renderer) drawBlended(image vk.Image, view vk.ImageView, first, last int) { | |
| 159 | + | d := r.dd | |
| 160 | + | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |
| 161 | + | b := vk.ImageMemoryBarrier{ | |
| 162 | + | SType: vk.StructureTypeImageMemoryBarrier, Image: image, SubresourceRange: rangeInfo, | |
| 163 | + | SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit, | |
| 164 | + | OldLayout: vk.ImageLayoutTransferDstOptimal, NewLayout: vk.ImageLayoutColorAttachmentOptimal, | |
| 165 | + | } | |
| 166 | + | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b) | |
| 167 | + | attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpLoad, StoreOp: vk.AttachmentStoreOpStore} | |
| 168 | + | info := vk.RenderingInfo{SType: vk.StructureTypeRenderingInfo, RenderArea: vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |
| 169 | + | d.CmdBeginRendering(r.command, &info) | |
| 170 | + | d.CmdBindPipeline(r.command, vk.PipelineBindPointGraphics, r.blend.pipeline) | |
| 171 | + | d.CmdBindDescriptorSets(r.command, vk.PipelineBindPointGraphics, r.blend.layout, 0, 1, &r.blend.set, 0, nil) | |
| 172 | + | d.CmdDraw(r.command, 6, uint32(last-first), 0, uint32(first)) | |
| 173 | + | d.CmdEndRendering(r.command) | |
| 174 | + | b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, vk.AccessTransferWriteBit|vk.AccessTransferReadBit | |
| 175 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutTransferDstOptimal | |
| 176 | + | d.CmdPipelineBarrier(r.command, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | // createView makes the color attachment view of a target image. | |
| 180 | + | func (r *Renderer) createView(t *target) error { | |
| 181 | + | info := vk.ImageViewCreateInfo{ | |
| 182 | + | SType: vk.StructureTypeImageViewCreateInfo, Image: t.image, ViewType: vk.ImageViewType2d, Format: vk.FormatB8g8r8a8Unorm, | |
| 183 | + | SubresourceRange: vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}, | |
| 184 | + | } | |
| 185 | + | return checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &t.view)) | |
| 186 | + | } | |
| @@ -0,0 +1,186 @@ | |||||
| 1 | + | package vulkan | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | _ "embed" | |||
| 5 | + | "encoding/binary" | |||
| 6 | + | "unsafe" | |||
| 7 | + | ||||
| 8 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // Translucent client pixels are blended over what is below them; opaque | |||
| 12 | + | // ones keep the plain copy. The blend pipeline draws one quad per upload | |||
| 13 | + | // and its fragment shader reads the staged pixels straight from the | |||
| 14 | + | // staging buffer: no texture, no sampler. Wayland buffers are | |||
| 15 | + | // premultiplied, hence ONE, ONE_MINUS_SRC_ALPHA. | |||
| 16 | + | ||||
| 17 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.vert -o shaders/blend.vert.spv | |||
| 18 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.frag -o shaders/blend.frag.spv | |||
| 19 | + | ||||
| 20 | + | var ( | |||
| 21 | + | //go:embed shaders/blend.vert.spv | |||
| 22 | + | blendVert []byte | |||
| 23 | + | //go:embed shaders/blend.frag.spv | |||
| 24 | + | blendFrag []byte | |||
| 25 | + | ) | |||
| 26 | + | ||||
| 27 | + | // drawSize is one entry of the draw table (shaders: struct Draw). | |||
| 28 | + | const drawSize = 32 | |||
| 29 | + | ||||
| 30 | + | // blendPipeline is the graphics state for blended uploads. | |||
| 31 | + | type blendPipeline struct { | |||
| 32 | + | setLayout vk.DescriptorSetLayout | |||
| 33 | + | layout vk.PipelineLayout | |||
| 34 | + | pipeline vk.Pipeline | |||
| 35 | + | pool vk.DescriptorPool | |||
| 36 | + | set vk.DescriptorSet | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | func (r *Renderer) createBlend() error { | |||
| 40 | + | d, b := r.dd, &r.blend | |||
| 41 | + | bindings := []vk.DescriptorSetLayoutBinding{ | |||
| 42 | + | {Binding: 0, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |||
| 43 | + | {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit}, | |||
| 44 | + | } | |||
| 45 | + | sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]} | |||
| 46 | + | if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &b.setLayout)); err != nil { | |||
| 47 | + | return err | |||
| 48 | + | } | |||
| 49 | + | pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &b.setLayout} | |||
| 50 | + | if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &b.layout)); err != nil { | |||
| 51 | + | return err | |||
| 52 | + | } | |||
| 53 | + | size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 2} | |||
| 54 | + | dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size} | |||
| 55 | + | if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &b.pool)); err != nil { | |||
| 56 | + | return err | |||
| 57 | + | } | |||
| 58 | + | dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &b.setLayout} | |||
| 59 | + | if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &b.set)); err != nil { | |||
| 60 | + | return err | |||
| 61 | + | } | |||
| 62 | + | vert, err := r.shaderModule(blendVert) | |||
| 63 | + | if err != nil { | |||
| 64 | + | return err | |||
| 65 | + | } | |||
| 66 | + | defer d.DestroyShaderModule(r.device, vert, nil) | |||
| 67 | + | frag, err := r.shaderModule(blendFrag) | |||
| 68 | + | if err != nil { | |||
| 69 | + | return err | |||
| 70 | + | } | |||
| 71 | + | defer d.DestroyShaderModule(r.device, frag, nil) | |||
| 72 | + | main := []byte("main\x00") | |||
| 73 | + | stages := []vk.PipelineShaderStageCreateInfo{ | |||
| 74 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]}, | |||
| 75 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]}, | |||
| 76 | + | } | |||
| 77 | + | vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo} | |||
| 78 | + | assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList} | |||
| 79 | + | // The target size is fixed for the renderer's life: static viewport. | |||
| 80 | + | viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1} | |||
| 81 | + | scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}} | |||
| 82 | + | viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor} | |||
| 83 | + | raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1} | |||
| 84 | + | multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit} | |||
| 85 | + | attachment := vk.PipelineColorBlendAttachmentState{ | |||
| 86 | + | BlendEnable: 1, | |||
| 87 | + | SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd, | |||
| 88 | + | SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd, | |||
| 89 | + | ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit, | |||
| 90 | + | } | |||
| 91 | + | blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment} | |||
| 92 | + | format := vk.Format(vk.FormatB8g8r8a8Unorm) | |||
| 93 | + | rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |||
| 94 | + | gpi := vk.GraphicsPipelineCreateInfo{ | |||
| 95 | + | SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), | |||
| 96 | + | StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, | |||
| 97 | + | ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample, | |||
| 98 | + | ColorBlendState: &blendState, Layout: b.layout, BasePipelineIndex: -1, | |||
| 99 | + | } | |||
| 100 | + | return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &b.pipeline)) | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) { | |||
| 104 | + | // SPIR-V is read as words: copy into a uint32-aligned slice. | |||
| 105 | + | words := make([]uint32, len(code)/4) | |||
| 106 | + | for i := range words { | |||
| 107 | + | words[i] = binary.LittleEndian.Uint32(code[i*4:]) | |||
| 108 | + | } | |||
| 109 | + | info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]} | |||
| 110 | + | var m vk.ShaderModule | |||
| 111 | + | return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m)) | |||
| 112 | + | } | |||
| 113 | + | ||||
| 114 | + | func (r *Renderer) destroyBlend() { | |||
| 115 | + | d, b := r.dd, &r.blend | |||
| 116 | + | if b.pipeline != 0 { | |||
| 117 | + | d.DestroyPipeline(r.device, b.pipeline, nil) | |||
| 118 | + | } | |||
| 119 | + | if b.pool != 0 { | |||
| 120 | + | d.DestroyDescriptorPool(r.device, b.pool, nil) | |||
| 121 | + | } | |||
| 122 | + | if b.layout != 0 { | |||
| 123 | + | d.DestroyPipelineLayout(r.device, b.layout, nil) | |||
| 124 | + | } | |||
| 125 | + | if b.setLayout != 0 { | |||
| 126 | + | d.DestroyDescriptorSetLayout(r.device, b.setLayout, nil) | |||
| 127 | + | } | |||
| 128 | + | *b = blendPipeline{} | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // bindStaging points the set at the staging buffer: pixels from 0, the | |||
| 132 | + | // draw table from tableOffset. | |||
| 133 | + | func (r *Renderer) bindStaging(tableOffset int) { | |||
| 134 | + | infos := []vk.DescriptorBufferInfo{ | |||
| 135 | + | {Buffer: r.staging, Range: vk.DeviceSize(tableOffset)}, | |||
| 136 | + | {Buffer: r.staging, Offset: vk.DeviceSize(tableOffset), Range: wholeSize}, | |||
| 137 | + | } | |||
| 138 | + | writes := []vk.WriteDescriptorSet{ | |||
| 139 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[0]}, | |||
| 140 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[1]}, | |||
| 141 | + | } | |||
| 142 | + | r.dd.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil) | |||
| 143 | + | } | |||
| 144 | + | ||||
| 145 | + | // putDraw writes draw entry i of the table at data. | |||
| 146 | + | func (r *Renderer) putDraw(table []byte, i int, u *upload) { | |||
| 147 | + | e := table[i*drawSize : (i+1)*drawSize] | |||
| 148 | + | for j, v := range []uint32{ | |||
| 149 | + | uint32(u.rect.Min.X), uint32(u.rect.Min.Y), uint32(u.rect.Max.X), uint32(u.rect.Max.Y), | |||
| 150 | + | uint32(u.offset / 4), uint32(u.rect.Dx()), uint32(r.width), uint32(r.height), | |||
| 151 | + | } { | |||
| 152 | + | binary.LittleEndian.PutUint32(e[j*4:], v) | |||
| 153 | + | } | |||
| 154 | + | } | |||
| 155 | + | ||||
| 156 | + | // drawBlended draws uploads[first:last], all blended, into view: one | |||
| 157 | + | // instanced draw. The image is TRANSFER_DST before and after. | |||
| 158 | + | func (r *Renderer) drawBlended(image vk.Image, view vk.ImageView, first, last int) { | |||
| 159 | + | d := r.dd | |||
| 160 | + | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |||
| 161 | + | b := vk.ImageMemoryBarrier{ | |||
| 162 | + | SType: vk.StructureTypeImageMemoryBarrier, Image: image, SubresourceRange: rangeInfo, | |||
| 163 | + | SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit, | |||
| 164 | + | OldLayout: vk.ImageLayoutTransferDstOptimal, NewLayout: vk.ImageLayoutColorAttachmentOptimal, | |||
| 165 | + | } | |||
| 166 | + | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 167 | + | attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpLoad, StoreOp: vk.AttachmentStoreOpStore} | |||
| 168 | + | info := vk.RenderingInfo{SType: vk.StructureTypeRenderingInfo, RenderArea: vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |||
| 169 | + | d.CmdBeginRendering(r.command, &info) | |||
| 170 | + | d.CmdBindPipeline(r.command, vk.PipelineBindPointGraphics, r.blend.pipeline) | |||
| 171 | + | d.CmdBindDescriptorSets(r.command, vk.PipelineBindPointGraphics, r.blend.layout, 0, 1, &r.blend.set, 0, nil) | |||
| 172 | + | d.CmdDraw(r.command, 6, uint32(last-first), 0, uint32(first)) | |||
| 173 | + | d.CmdEndRendering(r.command) | |||
| 174 | + | b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, vk.AccessTransferWriteBit|vk.AccessTransferReadBit | |||
| 175 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutTransferDstOptimal | |||
| 176 | + | d.CmdPipelineBarrier(r.command, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | // createView makes the color attachment view of a target image. | |||
| 180 | + | func (r *Renderer) createView(t *target) error { | |||
| 181 | + | info := vk.ImageViewCreateInfo{ | |||
| 182 | + | SType: vk.StructureTypeImageViewCreateInfo, Image: t.image, ViewType: vk.ImageViewType2d, Format: vk.FormatB8g8r8a8Unorm, | |||
| 183 | + | SubresourceRange: vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}, | |||
| 184 | + | } | |||
| 185 | + | return checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &t.view)) | |||
| 186 | + | } | |||
M internal/adapters/vulkan/renderer.go +55 -7
| @@ -55,6 +55,10 @@ type Renderer struct { | |||
| 55 | 55 | frame uint64 | |
| 56 | 56 | // fillRegions is reused by Render for the bands of solid fills. | |
| 57 | 57 | fillRegions []vk.BufferImageCopy | |
| 58 | + | // blend draws translucent client pixels (blend.go); align is the | |
| 59 | + | // device's storage buffer offset alignment. | |
| 60 | + | blend blendPipeline | |
| 61 | + | align int | |
| 58 | 62 | } | |
| 59 | 63 | ||
| 60 | 64 | // fillRows is the height of the strip staged for a solid fill: the CPU | |
| @@ -123,6 +127,7 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 123 | 127 | if properties.ApiVersion < vk.MakeVersion(1, 3, 0) { | |
| 124 | 128 | continue | |
| 125 | 129 | } | |
| 130 | + | r.align = int(max(properties.Limits.MinStorageBufferOffsetAlignment, 4)) | |
| 126 | 131 | var n uint32 | |
| 127 | 132 | r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil) | |
| 128 | 133 | if n == 0 { | |
| @@ -181,7 +186,7 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 181 | 186 | } | |
| 182 | 187 | r.dd.GetDeviceQueue(r.device, family, 0, &r.queue) | |
| 183 | 188 | extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1} | |
| 184 | - | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: extent, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |
| 189 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: extent, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: targetUsage, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |
| 185 | 190 | if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil { | |
| 186 | 191 | return | |
| 187 | 192 | } | |
| @@ -226,7 +231,7 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 226 | 231 | // Start with two frame-sized regions; later frames grow staging on demand. | |
| 227 | 232 | stagingSize := size * 2 | |
| 228 | 233 | r.stagingSize = int(stagingSize) | |
| 229 | - | stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive} | |
| 234 | + | stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} | |
| 230 | 235 | if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil { | |
| 231 | 236 | return | |
| 232 | 237 | } | |
| @@ -253,6 +258,12 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 253 | 258 | if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil { | |
| 254 | 259 | return | |
| 255 | 260 | } | |
| 261 | + | if err = r.createView(&r.own); err != nil { | |
| 262 | + | return | |
| 263 | + | } | |
| 264 | + | if err = r.createBlend(); err != nil { | |
| 265 | + | return | |
| 266 | + | } | |
| 256 | 267 | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |
| 257 | 268 | if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil { | |
| 258 | 269 | return | |
| @@ -315,7 +326,7 @@ func (r *Renderer) ensureStaging(size int) error { | |||
| 315 | 326 | } | |
| 316 | 327 | d := r.dd | |
| 317 | 328 | capacity := size | |
| 318 | - | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive} | |
| 329 | + | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} | |
| 319 | 330 | var buffer vk.Buffer | |
| 320 | 331 | if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil { | |
| 321 | 332 | return err | |
| @@ -370,6 +381,8 @@ type upload struct { | |||
| 370 | 381 | dst, src image.Rectangle | |
| 371 | 382 | color [3]uint8 | |
| 372 | 383 | opaque bool | |
| 384 | + | // blend draws the staged pixels over the target instead of copying. | |
| 385 | + | blend bool | |
| 373 | 386 | // dma is a client GPU buffer, blitted instead of copied from staging. | |
| 374 | 387 | dma *imported | |
| 375 | 388 | // fill is a solid color: staging holds one strip of rows. | |
| @@ -443,7 +456,7 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 443 | 456 | if err != nil { | |
| 444 | 457 | return | |
| 445 | 458 | } | |
| 446 | - | uploads = append(uploads, upload{rect: rect, offset: used, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, dst: dst, src: src}) | |
| 459 | + | uploads = append(uploads, upload{rect: rect, offset: used, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, blend: !content.Opaque, dst: dst, src: src}) | |
| 447 | 460 | used += rect.Dx() * rect.Dy() * 4 | |
| 448 | 461 | } | |
| 449 | 462 | // drawSurface draws one surface buffer with its origin at (x, y) | |
| @@ -561,10 +574,31 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 561 | 574 | addPopups(false) | |
| 562 | 575 | addLayers(true) | |
| 563 | 576 | addPopups(true) | |
| 577 | + | // The draw table of blended uploads follows the pixels. | |
| 578 | + | blends := 0 | |
| 579 | + | for _, u := range uploads { | |
| 580 | + | if u.blend { | |
| 581 | + | blends++ | |
| 582 | + | } | |
| 583 | + | } | |
| 584 | + | tableOffset := (used + r.align - 1) / r.align * r.align | |
| 585 | + | if blends > 0 { | |
| 586 | + | used = tableOffset + blends*drawSize | |
| 587 | + | } | |
| 564 | 588 | if err := r.ensureStaging(used); err != nil { | |
| 565 | 589 | return err | |
| 566 | 590 | } | |
| 567 | 591 | data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize) | |
| 592 | + | if blends > 0 { | |
| 593 | + | r.bindStaging(tableOffset) | |
| 594 | + | i := 0 | |
| 595 | + | for k := range uploads { | |
| 596 | + | if uploads[k].blend { | |
| 597 | + | r.putDraw(data[tableOffset:], i, &uploads[k]) | |
| 598 | + | i++ | |
| 599 | + | } | |
| 600 | + | } | |
| 601 | + | } | |
| 568 | 602 | for _, u := range uploads { | |
| 569 | 603 | if u.dma != nil { | |
| 570 | 604 | continue | |
| @@ -581,7 +615,6 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 581 | 615 | // then shows garbage for this frame, never a crash. | |
| 582 | 616 | copyGuarded(func() { u.stage(dst) }) | |
| 583 | 617 | } | |
| 584 | - | // B8G8R8A8 pixels are copied unchanged; blending belongs to the compositing pipeline. | |
| 585 | 618 | rgb := parseColor(s.Background) | |
| 586 | 619 | d := r.dd | |
| 587 | 620 | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | |
| @@ -643,10 +676,24 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 643 | 676 | } | |
| 644 | 677 | } | |
| 645 | 678 | ownership(true) | |
| 646 | - | for i, f := range uploads { | |
| 647 | - | if i > 0 { | |
| 679 | + | drawn := 0 // blended uploads drawn so far: their index in the draw table | |
| 680 | + | for i := 0; i < len(uploads); i++ { | |
| 681 | + | f := uploads[i] | |
| 682 | + | if i > 0 && !f.blend { | |
| 648 | 683 | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |
| 649 | 684 | } | |
| 685 | + | if f.blend { | |
| 686 | + | // Consecutive blended uploads share one draw: blending follows | |
| 687 | + | // primitive order within it. | |
| 688 | + | n := 1 | |
| 689 | + | for i+n < len(uploads) && uploads[i+n].blend { | |
| 690 | + | n++ | |
| 691 | + | } | |
| 692 | + | r.drawBlended(tg.image, tg.view, drawn, drawn+n) | |
| 693 | + | drawn += n | |
| 694 | + | i += n - 1 | |
| 695 | + | continue | |
| 696 | + | } | |
| 650 | 697 | rect := f.rect | |
| 651 | 698 | if f.dma != nil { | |
| 652 | 699 | // Map the visible part of dst back onto the buffer. | |
| @@ -860,6 +907,7 @@ func (r *Renderer) Close() { | |||
| 860 | 907 | d.FreeMemory(r.device, r.bufferMemory, nil) | |
| 861 | 908 | r.bufferMemory = 0 | |
| 862 | 909 | } | |
| 910 | + | r.destroyBlend() | |
| 863 | 911 | r.freeTarget(&r.own) | |
| 864 | 912 | d.DestroyDevice(r.device, nil) | |
| 865 | 913 | r.device = 0 | |
| @@ -55,6 +55,10 @@ type Renderer struct { | |||||
| 55 | frame uint64 | 55 | frame uint64 | ||
| 56 | // fillRegions is reused by Render for the bands of solid fills. | 56 | // fillRegions is reused by Render for the bands of solid fills. | ||
| 57 | fillRegions []vk.BufferImageCopy | 57 | fillRegions []vk.BufferImageCopy | ||
| 58 | + | // blend draws translucent client pixels (blend.go); align is the | |||
| 59 | + | // device's storage buffer offset alignment. | |||
| 60 | + | blend blendPipeline | |||
| 61 | + | align int | |||
| 58 | } | 62 | } | ||
| 59 | 63 | ||||
| 60 | // fillRows is the height of the strip staged for a solid fill: the CPU | 64 | // fillRows is the height of the strip staged for a solid fill: the CPU | ||
| @@ -123,6 +127,7 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 123 | if properties.ApiVersion < vk.MakeVersion(1, 3, 0) { | 127 | if properties.ApiVersion < vk.MakeVersion(1, 3, 0) { | ||
| 124 | continue | 128 | continue | ||
| 125 | } | 129 | } | ||
| 130 | + | r.align = int(max(properties.Limits.MinStorageBufferOffsetAlignment, 4)) | |||
| 126 | var n uint32 | 131 | var n uint32 | ||
| 127 | r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil) | 132 | r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil) | ||
| 128 | if n == 0 { | 133 | if n == 0 { | ||
| @@ -181,7 +186,7 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 181 | } | 186 | } | ||
| 182 | r.dd.GetDeviceQueue(r.device, family, 0, &r.queue) | 187 | r.dd.GetDeviceQueue(r.device, family, 0, &r.queue) | ||
| 183 | extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1} | 188 | extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1} | ||
| 184 | - | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: extent, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | 189 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: extent, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: targetUsage, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} |
| 185 | if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil { | 190 | if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil { | ||
| 186 | return | 191 | return | ||
| 187 | } | 192 | } | ||
| @@ -226,7 +231,7 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 226 | // Start with two frame-sized regions; later frames grow staging on demand. | 231 | // Start with two frame-sized regions; later frames grow staging on demand. | ||
| 227 | stagingSize := size * 2 | 232 | stagingSize := size * 2 | ||
| 228 | r.stagingSize = int(stagingSize) | 233 | r.stagingSize = int(stagingSize) | ||
| 229 | - | stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive} | 234 | + | stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} |
| 230 | if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil { | 235 | if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil { | ||
| 231 | return | 236 | return | ||
| 232 | } | 237 | } | ||
| @@ -253,6 +258,12 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 253 | if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil { | 258 | if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil { | ||
| 254 | return | 259 | return | ||
| 255 | } | 260 | } | ||
| 261 | + | if err = r.createView(&r.own); err != nil { | |||
| 262 | + | return | |||
| 263 | + | } | |||
| 264 | + | if err = r.createBlend(); err != nil { | |||
| 265 | + | return | |||
| 266 | + | } | |||
| 256 | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | 267 | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | ||
| 257 | if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil { | 268 | if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil { | ||
| 258 | return | 269 | return | ||
| @@ -315,7 +326,7 @@ func (r *Renderer) ensureStaging(size int) error { | |||||
| 315 | } | 326 | } | ||
| 316 | d := r.dd | 327 | d := r.dd | ||
| 317 | capacity := size | 328 | capacity := size | ||
| 318 | - | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive} | 329 | + | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} |
| 319 | var buffer vk.Buffer | 330 | var buffer vk.Buffer | ||
| 320 | if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil { | 331 | if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil { | ||
| 321 | return err | 332 | return err | ||
| @@ -370,6 +381,8 @@ type upload struct { | |||||
| 370 | dst, src image.Rectangle | 381 | dst, src image.Rectangle | ||
| 371 | color [3]uint8 | 382 | color [3]uint8 | ||
| 372 | opaque bool | 383 | opaque bool | ||
| 384 | + | // blend draws the staged pixels over the target instead of copying. | |||
| 385 | + | blend bool | |||
| 373 | // dma is a client GPU buffer, blitted instead of copied from staging. | 386 | // dma is a client GPU buffer, blitted instead of copied from staging. | ||
| 374 | dma *imported | 387 | dma *imported | ||
| 375 | // fill is a solid color: staging holds one strip of rows. | 388 | // fill is a solid color: staging holds one strip of rows. | ||
| @@ -443,7 +456,7 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 443 | if err != nil { | 456 | if err != nil { | ||
| 444 | return | 457 | return | ||
| 445 | } | 458 | } | ||
| 446 | - | uploads = append(uploads, upload{rect: rect, offset: used, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, dst: dst, src: src}) | 459 | + | uploads = append(uploads, upload{rect: rect, offset: used, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, blend: !content.Opaque, dst: dst, src: src}) |
| 447 | used += rect.Dx() * rect.Dy() * 4 | 460 | used += rect.Dx() * rect.Dy() * 4 | ||
| 448 | } | 461 | } | ||
| 449 | // drawSurface draws one surface buffer with its origin at (x, y) | 462 | // drawSurface draws one surface buffer with its origin at (x, y) | ||
| @@ -561,10 +574,31 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 561 | addPopups(false) | 574 | addPopups(false) | ||
| 562 | addLayers(true) | 575 | addLayers(true) | ||
| 563 | addPopups(true) | 576 | addPopups(true) | ||
| 577 | + | // The draw table of blended uploads follows the pixels. | |||
| 578 | + | blends := 0 | |||
| 579 | + | for _, u := range uploads { | |||
| 580 | + | if u.blend { | |||
| 581 | + | blends++ | |||
| 582 | + | } | |||
| 583 | + | } | |||
| 584 | + | tableOffset := (used + r.align - 1) / r.align * r.align | |||
| 585 | + | if blends > 0 { | |||
| 586 | + | used = tableOffset + blends*drawSize | |||
| 587 | + | } | |||
| 564 | if err := r.ensureStaging(used); err != nil { | 588 | if err := r.ensureStaging(used); err != nil { | ||
| 565 | return err | 589 | return err | ||
| 566 | } | 590 | } | ||
| 567 | data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize) | 591 | data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize) | ||
| 592 | + | if blends > 0 { | |||
| 593 | + | r.bindStaging(tableOffset) | |||
| 594 | + | i := 0 | |||
| 595 | + | for k := range uploads { | |||
| 596 | + | if uploads[k].blend { | |||
| 597 | + | r.putDraw(data[tableOffset:], i, &uploads[k]) | |||
| 598 | + | i++ | |||
| 599 | + | } | |||
| 600 | + | } | |||
| 601 | + | } | |||
| 568 | for _, u := range uploads { | 602 | for _, u := range uploads { | ||
| 569 | if u.dma != nil { | 603 | if u.dma != nil { | ||
| 570 | continue | 604 | continue | ||
| @@ -581,7 +615,6 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 581 | // then shows garbage for this frame, never a crash. | 615 | // then shows garbage for this frame, never a crash. | ||
| 582 | copyGuarded(func() { u.stage(dst) }) | 616 | copyGuarded(func() { u.stage(dst) }) | ||
| 583 | } | 617 | } | ||
| 584 | - | // B8G8R8A8 pixels are copied unchanged; blending belongs to the compositing pipeline. | |||
| 585 | rgb := parseColor(s.Background) | 618 | rgb := parseColor(s.Background) | ||
| 586 | d := r.dd | 619 | d := r.dd | ||
| 587 | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | 620 | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | ||
| @@ -643,10 +676,24 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 643 | } | 676 | } | ||
| 644 | } | 677 | } | ||
| 645 | ownership(true) | 678 | ownership(true) | ||
| 646 | - | for i, f := range uploads { | 679 | + | drawn := 0 // blended uploads drawn so far: their index in the draw table |
| 647 | - | if i > 0 { | 680 | + | for i := 0; i < len(uploads); i++ { |
| 681 | + | f := uploads[i] | |||
| 682 | + | if i > 0 && !f.blend { | |||
| 648 | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | 683 | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | ||
| 649 | } | 684 | } | ||
| 685 | + | if f.blend { | |||
| 686 | + | // Consecutive blended uploads share one draw: blending follows | |||
| 687 | + | // primitive order within it. | |||
| 688 | + | n := 1 | |||
| 689 | + | for i+n < len(uploads) && uploads[i+n].blend { | |||
| 690 | + | n++ | |||
| 691 | + | } | |||
| 692 | + | r.drawBlended(tg.image, tg.view, drawn, drawn+n) | |||
| 693 | + | drawn += n | |||
| 694 | + | i += n - 1 | |||
| 695 | + | continue | |||
| 696 | + | } | |||
| 650 | rect := f.rect | 697 | rect := f.rect | ||
| 651 | if f.dma != nil { | 698 | if f.dma != nil { | ||
| 652 | // Map the visible part of dst back onto the buffer. | 699 | // Map the visible part of dst back onto the buffer. | ||
| @@ -860,6 +907,7 @@ func (r *Renderer) Close() { | |||||
| 860 | d.FreeMemory(r.device, r.bufferMemory, nil) | 907 | d.FreeMemory(r.device, r.bufferMemory, nil) | ||
| 861 | r.bufferMemory = 0 | 908 | r.bufferMemory = 0 | ||
| 862 | } | 909 | } | ||
| 910 | + | r.destroyBlend() | |||
| 863 | r.freeTarget(&r.own) | 911 | r.freeTarget(&r.own) | ||
| 864 | d.DestroyDevice(r.device, nil) | 912 | d.DestroyDevice(r.device, nil) | ||
| 865 | r.device = 0 | 913 | r.device = 0 | ||
M internal/adapters/vulkan/renderer_test.go +46 -0
| @@ -364,3 +364,49 @@ func TestRendererTallFill(t *testing.T) { | |||
| 364 | 364 | } | |
| 365 | 365 | } | |
| 366 | 366 | } | |
| 367 | + | ||
| 368 | + | // A translucent overlay layer blends over the window below it (premultiplied | |
| 369 | + | // ARGB); a fully transparent one leaves it untouched. A copy between two | |
| 370 | + | // blended draws keeps the paint order. | |
| 371 | + | func TestRendererAlphaBlend(t *testing.T) { | |
| 372 | + | r, err := New(64, 48) | |
| 373 | + | if err != nil { | |
| 374 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 375 | + | } | |
| 376 | + | defer r.Close() | |
| 377 | + | // Half-transparent red, premultiplied: B,G,R,A = 0,0,128,128. | |
| 378 | + | half := make([]byte, 16*8*4) | |
| 379 | + | for i := 0; i < 8*16*4; i += 4 { | |
| 380 | + | copy(half[i:i+4], []byte{0, 0, 128, 128}) | |
| 381 | + | } | |
| 382 | + | // The right half of the buffer is fully transparent. | |
| 383 | + | for y := 0; y < 8; y++ { | |
| 384 | + | for x := 8; x < 16; x++ { | |
| 385 | + | copy(half[(y*16+x)*4:], []byte{0, 0, 0, 0}) | |
| 386 | + | } | |
| 387 | + | } | |
| 388 | + | overlay := shmContent(t, 16, 8, 16*4, half) | |
| 389 | + | contents := map[ports.WindowID]ports.SurfaceContent{2: *overlay} | |
| 390 | + | scene := ports.Scene{Background: "#000000", | |
| 391 | + | Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}, Borderless: true}}, | |
| 392 | + | Layers: []ports.SceneLayer{{ID: 2, Layer: ports.LayerOverlay, Rect: ports.Rect{X: 4, Y: 4, W: 16, H: 8}}}} | |
| 393 | + | white := solid(64, 48, [3]uint8{255, 255, 255}) | |
| 394 | + | win := shmContent(t, 64, 48, 64*4, white) | |
| 395 | + | win.Opaque = true | |
| 396 | + | contents[1] = *win | |
| 397 | + | if err := r.Render(scene, contents); err != nil { | |
| 398 | + | t.Fatal(err) | |
| 399 | + | } | |
| 400 | + | px := r.Pixels() | |
| 401 | + | // White under half red: R = 128 + 255*127/255 = 255, G = B = 127. | |
| 402 | + | near := func(a, b uint8) bool { return a-b <= 1 || b-a <= 1 } | |
| 403 | + | if got := px.RGBAAt(6, 6); !near(got.R, 255) || !near(got.G, 127) || !near(got.B, 127) { | |
| 404 | + | t.Errorf("blended pixel = %v, want ~{255 127 127}", got) | |
| 405 | + | } | |
| 406 | + | if got := px.RGBAAt(14, 6); got != (color.RGBA{255, 255, 255, 255}) { | |
| 407 | + | t.Errorf("transparent pixel = %v, want white", got) | |
| 408 | + | } | |
| 409 | + | if got := px.RGBAAt(40, 30); got != (color.RGBA{255, 255, 255, 255}) { | |
| 410 | + | t.Errorf("uncovered pixel = %v, want white", got) | |
| 411 | + | } | |
| 412 | + | } | |
| @@ -364,3 +364,49 @@ func TestRendererTallFill(t *testing.T) { | |||||
| 364 | } | 364 | } | ||
| 365 | } | 365 | } | ||
| 366 | } | 366 | } | ||
| 367 | + | ||||
| 368 | + | // A translucent overlay layer blends over the window below it (premultiplied | |||
| 369 | + | // ARGB); a fully transparent one leaves it untouched. A copy between two | |||
| 370 | + | // blended draws keeps the paint order. | |||
| 371 | + | func TestRendererAlphaBlend(t *testing.T) { | |||
| 372 | + | r, err := New(64, 48) | |||
| 373 | + | if err != nil { | |||
| 374 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 375 | + | } | |||
| 376 | + | defer r.Close() | |||
| 377 | + | // Half-transparent red, premultiplied: B,G,R,A = 0,0,128,128. | |||
| 378 | + | half := make([]byte, 16*8*4) | |||
| 379 | + | for i := 0; i < 8*16*4; i += 4 { | |||
| 380 | + | copy(half[i:i+4], []byte{0, 0, 128, 128}) | |||
| 381 | + | } | |||
| 382 | + | // The right half of the buffer is fully transparent. | |||
| 383 | + | for y := 0; y < 8; y++ { | |||
| 384 | + | for x := 8; x < 16; x++ { | |||
| 385 | + | copy(half[(y*16+x)*4:], []byte{0, 0, 0, 0}) | |||
| 386 | + | } | |||
| 387 | + | } | |||
| 388 | + | overlay := shmContent(t, 16, 8, 16*4, half) | |||
| 389 | + | contents := map[ports.WindowID]ports.SurfaceContent{2: *overlay} | |||
| 390 | + | scene := ports.Scene{Background: "#000000", | |||
| 391 | + | Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 48}, Borderless: true}}, | |||
| 392 | + | Layers: []ports.SceneLayer{{ID: 2, Layer: ports.LayerOverlay, Rect: ports.Rect{X: 4, Y: 4, W: 16, H: 8}}}} | |||
| 393 | + | white := solid(64, 48, [3]uint8{255, 255, 255}) | |||
| 394 | + | win := shmContent(t, 64, 48, 64*4, white) | |||
| 395 | + | win.Opaque = true | |||
| 396 | + | contents[1] = *win | |||
| 397 | + | if err := r.Render(scene, contents); err != nil { | |||
| 398 | + | t.Fatal(err) | |||
| 399 | + | } | |||
| 400 | + | px := r.Pixels() | |||
| 401 | + | // White under half red: R = 128 + 255*127/255 = 255, G = B = 127. | |||
| 402 | + | near := func(a, b uint8) bool { return a-b <= 1 || b-a <= 1 } | |||
| 403 | + | if got := px.RGBAAt(6, 6); !near(got.R, 255) || !near(got.G, 127) || !near(got.B, 127) { | |||
| 404 | + | t.Errorf("blended pixel = %v, want ~{255 127 127}", got) | |||
| 405 | + | } | |||
| 406 | + | if got := px.RGBAAt(14, 6); got != (color.RGBA{255, 255, 255, 255}) { | |||
| 407 | + | t.Errorf("transparent pixel = %v, want white", got) | |||
| 408 | + | } | |||
| 409 | + | if got := px.RGBAAt(40, 30); got != (color.RGBA{255, 255, 255, 255}) { | |||
| 410 | + | t.Errorf("uncovered pixel = %v, want white", got) | |||
| 411 | + | } | |||
| 412 | + | } | |||
A internal/adapters/vulkan/shaders/blend.frag +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | #version 450 | |
| 2 | + | // Reads the staged B8G8R8A8 pixel under the fragment: premultiplied, as | |
| 3 | + | // Wayland ARGB buffers are, so the pipeline blends ONE, ONE_MINUS_SRC_ALPHA. | |
| 4 | + | ||
| 5 | + | struct Draw { | |
| 6 | + | ivec4 rect; | |
| 7 | + | uvec4 pixels; | |
| 8 | + | }; | |
| 9 | + | ||
| 10 | + | layout(std430, set = 0, binding = 0) readonly buffer Pixels { uint pixels[]; }; | |
| 11 | + | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |
| 12 | + | ||
| 13 | + | layout(location = 0) flat in uint draw; | |
| 14 | + | layout(location = 0) out vec4 color; | |
| 15 | + | ||
| 16 | + | void main() { | |
| 17 | + | Draw d = draws[draw]; | |
| 18 | + | ivec2 p = ivec2(gl_FragCoord.xy) - d.rect.xy; | |
| 19 | + | // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A). | |
| 20 | + | vec4 bgra = unpackUnorm4x8(pixels[d.pixels.x + uint(p.y) * d.pixels.y + uint(p.x)]); | |
| 21 | + | color = bgra.zyxw; | |
| 22 | + | } | |
| @@ -0,0 +1,22 @@ | |||||
| 1 | + | #version 450 | |||
| 2 | + | // Reads the staged B8G8R8A8 pixel under the fragment: premultiplied, as | |||
| 3 | + | // Wayland ARGB buffers are, so the pipeline blends ONE, ONE_MINUS_SRC_ALPHA. | |||
| 4 | + | ||||
| 5 | + | struct Draw { | |||
| 6 | + | ivec4 rect; | |||
| 7 | + | uvec4 pixels; | |||
| 8 | + | }; | |||
| 9 | + | ||||
| 10 | + | layout(std430, set = 0, binding = 0) readonly buffer Pixels { uint pixels[]; }; | |||
| 11 | + | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |||
| 12 | + | ||||
| 13 | + | layout(location = 0) flat in uint draw; | |||
| 14 | + | layout(location = 0) out vec4 color; | |||
| 15 | + | ||||
| 16 | + | void main() { | |||
| 17 | + | Draw d = draws[draw]; | |||
| 18 | + | ivec2 p = ivec2(gl_FragCoord.xy) - d.rect.xy; | |||
| 19 | + | // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A). | |||
| 20 | + | vec4 bgra = unpackUnorm4x8(pixels[d.pixels.x + uint(p.y) * d.pixels.y + uint(p.x)]); | |||
| 21 | + | color = bgra.zyxw; | |||
| 22 | + | } | |||
A internal/adapters/vulkan/shaders/blend.frag.spv +0 -0 binary file: no text diff
binary file: no text diff
A internal/adapters/vulkan/shaders/blend.vert +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | #version 450 | |
| 2 | + | // One quad per instance: the instance indexes the draw table. | |
| 3 | + | ||
| 4 | + | struct Draw { | |
| 5 | + | ivec4 rect; // x0, y0, x1, y1 in target pixels | |
| 6 | + | uvec4 pixels; // first pixel word in staging, row width, target w, target h | |
| 7 | + | }; | |
| 8 | + | ||
| 9 | + | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |
| 10 | + | ||
| 11 | + | layout(location = 0) flat out uint draw; | |
| 12 | + | ||
| 13 | + | void main() { | |
| 14 | + | Draw d = draws[gl_InstanceIndex]; | |
| 15 | + | // Two triangles: 0,1,2 and 2,1,3 of the corners. | |
| 16 | + | const ivec2 corners[6] = ivec2[](ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(0, 1), ivec2(1, 0), ivec2(1, 1)); | |
| 17 | + | ivec2 c = corners[gl_VertexIndex]; | |
| 18 | + | vec2 p = vec2(c.x == 0 ? d.rect.x : d.rect.z, c.y == 0 ? d.rect.y : d.rect.w); | |
| 19 | + | gl_Position = vec4(p / vec2(d.pixels.zw) * 2.0 - 1.0, 0.0, 1.0); | |
| 20 | + | draw = uint(gl_InstanceIndex); | |
| 21 | + | } | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | #version 450 | |||
| 2 | + | // One quad per instance: the instance indexes the draw table. | |||
| 3 | + | ||||
| 4 | + | struct Draw { | |||
| 5 | + | ivec4 rect; // x0, y0, x1, y1 in target pixels | |||
| 6 | + | uvec4 pixels; // first pixel word in staging, row width, target w, target h | |||
| 7 | + | }; | |||
| 8 | + | ||||
| 9 | + | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |||
| 10 | + | ||||
| 11 | + | layout(location = 0) flat out uint draw; | |||
| 12 | + | ||||
| 13 | + | void main() { | |||
| 14 | + | Draw d = draws[gl_InstanceIndex]; | |||
| 15 | + | // Two triangles: 0,1,2 and 2,1,3 of the corners. | |||
| 16 | + | const ivec2 corners[6] = ivec2[](ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(0, 1), ivec2(1, 0), ivec2(1, 1)); | |||
| 17 | + | ivec2 c = corners[gl_VertexIndex]; | |||
| 18 | + | vec2 p = vec2(c.x == 0 ? d.rect.x : d.rect.z, c.y == 0 ? d.rect.y : d.rect.w); | |||
| 19 | + | gl_Position = vec4(p / vec2(d.pixels.zw) * 2.0 - 1.0, 0.0, 1.0); | |||
| 20 | + | draw = uint(gl_InstanceIndex); | |||
| 21 | + | } | |||
A internal/adapters/vulkan/shaders/blend.vert.spv +0 -0 binary file: no text diff
binary file: no text diff
M internal/adapters/vulkan/targets.go +14 -2
| @@ -24,6 +24,7 @@ type target struct { | |||
| 24 | 24 | image vk.Image | |
| 25 | 25 | memory vk.DeviceMemory | |
| 26 | 26 | layout vk.ImageLayout | |
| 27 | + | view vk.ImageView // color attachment for blended draws | |
| 27 | 28 | // exported images are shared with the display: ownership goes to the | |
| 28 | 29 | // foreign queue family after each frame, and KMS reads them. | |
| 29 | 30 | exported bool | |
| @@ -104,7 +105,7 @@ func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) { | |||
| 104 | 105 | mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount) | |
| 105 | 106 | list.DrmFormatModifierProperties = &mods[0] | |
| 106 | 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp) | |
| 107 | - | need := vk.FormatFeatureFlags(formatFeatureTransferDst | formatFeatureTransferSrc | vk.FormatFeatureBlitDstBit) | |
| 108 | + | need := vk.FormatFeatureFlags(formatFeatureTransferDst | formatFeatureTransferSrc | vk.FormatFeatureBlitDstBit | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit) | |
| 108 | 109 | for _, m := range mods[:list.DrmFormatModifierCount] { | |
| 109 | 110 | if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) { | |
| 110 | 111 | r.renderMods = append(r.renderMods, m.DrmFormatModifier) | |
| @@ -131,7 +132,11 @@ const ( | |||
| 131 | 132 | formatFeatureTransferDst = 1 << 15 | |
| 132 | 133 | ) | |
| 133 | 134 | ||
| 134 | - | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | |
| 135 | + | // Targets are copied into and drawn on (blend.go). | |
| 136 | + | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | vk.ImageUsageColorAttachmentBit | |
| 137 | + | ||
| 138 | + | // Staging feeds copies and, as a storage buffer, the blend shaders. | |
| 139 | + | const stagingUsage = vk.BufferUsageTransferSrcBit | vk.BufferUsageStorageBufferBit | |
| 135 | 140 | ||
| 136 | 141 | // exportTarget creates one exported image with a modifier from mods. | |
| 137 | 142 | func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |
| @@ -171,6 +176,9 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||
| 171 | 176 | var layout vk.SubresourceLayout | |
| 172 | 177 | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT} | |
| 173 | 178 | d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout) | |
| 179 | + | if err := r.createView(t); err != nil { | |
| 180 | + | return nil, ports.DMABuf{}, err | |
| 181 | + | } | |
| 174 | 182 | var fd int32 | |
| 175 | 183 | get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | |
| 176 | 184 | if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil { | |
| @@ -186,6 +194,10 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||
| 186 | 194 | } | |
| 187 | 195 | ||
| 188 | 196 | func (r *Renderer) freeTarget(t *target) { | |
| 197 | + | if t.view != 0 { | |
| 198 | + | r.dd.DestroyImageView(r.device, t.view, nil) | |
| 199 | + | t.view = 0 | |
| 200 | + | } | |
| 189 | 201 | if t.image != 0 { | |
| 190 | 202 | r.dd.DestroyImage(r.device, t.image, nil) | |
| 191 | 203 | t.image = 0 | |
| @@ -24,6 +24,7 @@ type target struct { | |||||
| 24 | image vk.Image | 24 | image vk.Image | ||
| 25 | memory vk.DeviceMemory | 25 | memory vk.DeviceMemory | ||
| 26 | layout vk.ImageLayout | 26 | layout vk.ImageLayout | ||
| 27 | + | view vk.ImageView // color attachment for blended draws | |||
| 27 | // exported images are shared with the display: ownership goes to the | 28 | // exported images are shared with the display: ownership goes to the | ||
| 28 | // foreign queue family after each frame, and KMS reads them. | 29 | // foreign queue family after each frame, and KMS reads them. | ||
| 29 | exported bool | 30 | exported bool | ||
| @@ -104,7 +105,7 @@ func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) { | |||||
| 104 | mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount) | 105 | mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount) | ||
| 105 | list.DrmFormatModifierProperties = &mods[0] | 106 | list.DrmFormatModifierProperties = &mods[0] | ||
| 106 | r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp) | 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp) | ||
| 107 | - | need := vk.FormatFeatureFlags(formatFeatureTransferDst | formatFeatureTransferSrc | vk.FormatFeatureBlitDstBit) | 108 | + | need := vk.FormatFeatureFlags(formatFeatureTransferDst | formatFeatureTransferSrc | vk.FormatFeatureBlitDstBit | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit) |
| 108 | for _, m := range mods[:list.DrmFormatModifierCount] { | 109 | for _, m := range mods[:list.DrmFormatModifierCount] { | ||
| 109 | if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) { | 110 | if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) { | ||
| 110 | r.renderMods = append(r.renderMods, m.DrmFormatModifier) | 111 | r.renderMods = append(r.renderMods, m.DrmFormatModifier) | ||
| @@ -131,7 +132,11 @@ const ( | |||||
| 131 | formatFeatureTransferDst = 1 << 15 | 132 | formatFeatureTransferDst = 1 << 15 | ||
| 132 | ) | 133 | ) | ||
| 133 | 134 | ||||
| 134 | - | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | 135 | + | // Targets are copied into and drawn on (blend.go). |
| 136 | + | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | vk.ImageUsageColorAttachmentBit | |||
| 137 | + | ||||
| 138 | + | // Staging feeds copies and, as a storage buffer, the blend shaders. | |||
| 139 | + | const stagingUsage = vk.BufferUsageTransferSrcBit | vk.BufferUsageStorageBufferBit | |||
| 135 | 140 | ||||
| 136 | // exportTarget creates one exported image with a modifier from mods. | 141 | // exportTarget creates one exported image with a modifier from mods. | ||
| 137 | func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | 142 | func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | ||
| @@ -171,6 +176,9 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||||
| 171 | var layout vk.SubresourceLayout | 176 | var layout vk.SubresourceLayout | ||
| 172 | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT} | 177 | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT} | ||
| 173 | d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout) | 178 | d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout) | ||
| 179 | + | if err := r.createView(t); err != nil { | |||
| 180 | + | return nil, ports.DMABuf{}, err | |||
| 181 | + | } | |||
| 174 | var fd int32 | 182 | var fd int32 | ||
| 175 | get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | 183 | get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | ||
| 176 | if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil { | 184 | if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil { | ||
| @@ -186,6 +194,10 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||||
| 186 | } | 194 | } | ||
| 187 | 195 | ||||
| 188 | func (r *Renderer) freeTarget(t *target) { | 196 | func (r *Renderer) freeTarget(t *target) { | ||
| 197 | + | if t.view != 0 { | |||
| 198 | + | r.dd.DestroyImageView(r.device, t.view, nil) | |||
| 199 | + | t.view = 0 | |||
| 200 | + | } | |||
| 189 | if t.image != 0 { | 201 | if t.image != 0 { | ||
| 190 | r.dd.DestroyImage(r.device, t.image, nil) | 202 | r.dd.DestroyImage(r.device, t.image, nil) | ||
| 191 | t.image = 0 | 203 | t.image = 0 | ||