detached 942d2a2

merge: alpha-blend (premultiplied blending of translucent SHM buffers)

commit
942d2a2771caca272a87524b9f1c4b45d6961136
author
brice · · 2026-09-26 09:29 UTC
merge of
cc9b1b3 + c9165de · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
10 files · +395 -14
M go.mod +1 -1
@@ -5,7 +5,7 @@ go 1.27
55 require (
66 github.com/bnema/purego v0.11.0-bnema.4
77 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
99 github.com/bnema/wlturbo v0.2.0
1010 github.com/bnema/zerowrap v1.4.1
1111 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.46 github.com/bnema/purego v0.11.0-bnema.4
7 github.com/bnema/purego-libwayland v0.1.17 github.com/bnema/purego-libwayland v0.1.1
8- github.com/bnema/purego-vulkan v0.4.08+ github.com/bnema/purego-vulkan v0.4.1
9 github.com/bnema/wlturbo v0.2.09 github.com/bnema/wlturbo v0.2.0
10 github.com/bnema/zerowrap v1.4.110 github.com/bnema/zerowrap v1.4.1
11 github.com/rs/zerolog v1.35.111 github.com/rs/zerolog v1.35.1
M go.sum +2 -0 generated file: collapsed
A internal/adapters/vulkan/blend.go +190 -0
@@ -0,0 +1,190 @@
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+// Limits: dmabuf buffers are still blitted, alpha ignored (blending them
18+// needs a sampled-image variant). The opaque region is not honoured, so
19+// an ARGB buffer blends even where it is opaque.
20+
21+//go:generate glslc -O --target-env=vulkan1.3 shaders/blend.vert -o shaders/blend.vert.spv
22+//go:generate glslc -O --target-env=vulkan1.3 shaders/blend.frag -o shaders/blend.frag.spv
23+
24+var (
25+ //go:embed shaders/blend.vert.spv
26+ blendVert []byte
27+ //go:embed shaders/blend.frag.spv
28+ blendFrag []byte
29+)
30+
31+// drawSize is one entry of the draw table (shaders: struct Draw).
32+const drawSize = 32
33+
34+// blendPipeline is the graphics state for blended uploads.
35+type blendPipeline struct {
36+ setLayout vk.DescriptorSetLayout
37+ layout vk.PipelineLayout
38+ pipeline vk.Pipeline
39+ pool vk.DescriptorPool
40+ set vk.DescriptorSet
41+}
42+
43+func (r *Renderer) createBlend() error {
44+ d, b := r.dd, &r.blend
45+ bindings := []vk.DescriptorSetLayoutBinding{
46+ {Binding: 0, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit},
47+ {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit},
48+ }
49+ sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]}
50+ if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &b.setLayout)); err != nil {
51+ return err
52+ }
53+ pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &b.setLayout}
54+ if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &b.layout)); err != nil {
55+ return err
56+ }
57+ size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 2}
58+ dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size}
59+ if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &b.pool)); err != nil {
60+ return err
61+ }
62+ dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &b.setLayout}
63+ if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &b.set)); err != nil {
64+ return err
65+ }
66+ vert, err := r.shaderModule(blendVert)
67+ if err != nil {
68+ return err
69+ }
70+ defer d.DestroyShaderModule(r.device, vert, nil)
71+ frag, err := r.shaderModule(blendFrag)
72+ if err != nil {
73+ return err
74+ }
75+ defer d.DestroyShaderModule(r.device, frag, nil)
76+ main := []byte("main\x00")
77+ stages := []vk.PipelineShaderStageCreateInfo{
78+ {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]},
79+ {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]},
80+ }
81+ vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo}
82+ assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList}
83+ // The target size is fixed for the renderer's life: static viewport.
84+ viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1}
85+ scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}
86+ viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor}
87+ raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1}
88+ multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit}
89+ attachment := vk.PipelineColorBlendAttachmentState{
90+ BlendEnable: 1,
91+ SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd,
92+ SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd,
93+ ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit,
94+ }
95+ blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment}
96+ format := vk.Format(vk.FormatB8g8r8a8Unorm)
97+ rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
98+ gpi := vk.GraphicsPipelineCreateInfo{
99+ SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering),
100+ StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly,
101+ ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample,
102+ ColorBlendState: &blendState, Layout: b.layout, BasePipelineIndex: -1,
103+ }
104+ return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &b.pipeline))
105+}
106+
107+func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) {
108+ // SPIR-V is read as words: copy into a uint32-aligned slice.
109+ words := make([]uint32, len(code)/4)
110+ for i := range words {
111+ words[i] = binary.LittleEndian.Uint32(code[i*4:])
112+ }
113+ info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]}
114+ var m vk.ShaderModule
115+ return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m))
116+}
117+
118+func (r *Renderer) destroyBlend() {
119+ d, b := r.dd, &r.blend
120+ if b.pipeline != 0 {
121+ d.DestroyPipeline(r.device, b.pipeline, nil)
122+ }
123+ if b.pool != 0 {
124+ d.DestroyDescriptorPool(r.device, b.pool, nil)
125+ }
126+ if b.layout != 0 {
127+ d.DestroyPipelineLayout(r.device, b.layout, nil)
128+ }
129+ if b.setLayout != 0 {
130+ d.DestroyDescriptorSetLayout(r.device, b.setLayout, nil)
131+ }
132+ *b = blendPipeline{}
133+}
134+
135+// bindStaging points the set at the staging buffer: pixels blended
136+// pixel bytes from 0, and the draw table of n entries at tableOffset.
137+func (r *Renderer) bindStaging(pixels, tableOffset, n int) {
138+ infos := []vk.DescriptorBufferInfo{
139+ {Buffer: r.staging, Range: vk.DeviceSize(max(pixels, 4))},
140+ {Buffer: r.staging, Offset: vk.DeviceSize(tableOffset), Range: vk.DeviceSize(n * drawSize)},
141+ }
142+ writes := []vk.WriteDescriptorSet{
143+ {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[0]},
144+ {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[1]},
145+ }
146+ r.dd.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil)
147+}
148+
149+// putDraw writes draw entry i of table.
150+func (r *Renderer) putDraw(table []byte, i int, u *upload) {
151+ e := table[i*drawSize : (i+1)*drawSize]
152+ for j, v := range []uint32{
153+ uint32(u.rect.Min.X), uint32(u.rect.Min.Y), uint32(u.rect.Max.X), uint32(u.rect.Max.Y),
154+ uint32(u.offset / 4), uint32(u.rect.Dx()), uint32(r.width), uint32(r.height),
155+ } {
156+ binary.LittleEndian.PutUint32(e[j*4:], v)
157+ }
158+}
159+
160+// drawBlended draws uploads[first:last], all blended, into view: one
161+// instanced draw. The image is TRANSFER_DST before and after.
162+func (r *Renderer) drawBlended(image vk.Image, view vk.ImageView, first, last int) {
163+ d := r.dd
164+ rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
165+ b := vk.ImageMemoryBarrier{
166+ SType: vk.StructureTypeImageMemoryBarrier, Image: image, SubresourceRange: rangeInfo,
167+ SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit,
168+ OldLayout: vk.ImageLayoutTransferDstOptimal, NewLayout: vk.ImageLayoutColorAttachmentOptimal,
169+ }
170+ d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b)
171+ attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpLoad, StoreOp: vk.AttachmentStoreOpStore}
172+ 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}
173+ d.CmdBeginRendering(r.command, &info)
174+ d.CmdBindPipeline(r.command, vk.PipelineBindPointGraphics, r.blend.pipeline)
175+ d.CmdBindDescriptorSets(r.command, vk.PipelineBindPointGraphics, r.blend.layout, 0, 1, &r.blend.set, 0, nil)
176+ d.CmdDraw(r.command, 6, uint32(last-first), 0, uint32(first))
177+ d.CmdEndRendering(r.command)
178+ b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, vk.AccessTransferWriteBit|vk.AccessTransferReadBit
179+ b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutTransferDstOptimal
180+ d.CmdPipelineBarrier(r.command, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b)
181+}
182+
183+// createView makes the color attachment view of a target image.
184+func (r *Renderer) createView(t *target) error {
185+ info := vk.ImageViewCreateInfo{
186+ SType: vk.StructureTypeImageViewCreateInfo, Image: t.image, ViewType: vk.ImageViewType2d, Format: vk.FormatB8g8r8a8Unorm,
187+ SubresourceRange: vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1},
188+ }
189+ return checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &t.view))
190+}
@@ -0,0 +1,190 @@
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+// Limits: dmabuf buffers are still blitted, alpha ignored (blending them
18+// needs a sampled-image variant). The opaque region is not honoured, so
19+// an ARGB buffer blends even where it is opaque.
20+
21+//go:generate glslc -O --target-env=vulkan1.3 shaders/blend.vert -o shaders/blend.vert.spv
22+//go:generate glslc -O --target-env=vulkan1.3 shaders/blend.frag -o shaders/blend.frag.spv
23+
24+var (
25+ //go:embed shaders/blend.vert.spv
26+ blendVert []byte
27+ //go:embed shaders/blend.frag.spv
28+ blendFrag []byte
29+)
30+
31+// drawSize is one entry of the draw table (shaders: struct Draw).
32+const drawSize = 32
33+
34+// blendPipeline is the graphics state for blended uploads.
35+type blendPipeline struct {
36+ setLayout vk.DescriptorSetLayout
37+ layout vk.PipelineLayout
38+ pipeline vk.Pipeline
39+ pool vk.DescriptorPool
40+ set vk.DescriptorSet
41+}
42+
43+func (r *Renderer) createBlend() error {
44+ d, b := r.dd, &r.blend
45+ bindings := []vk.DescriptorSetLayoutBinding{
46+ {Binding: 0, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit},
47+ {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit},
48+ }
49+ sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]}
50+ if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &b.setLayout)); err != nil {
51+ return err
52+ }
53+ pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &b.setLayout}
54+ if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &b.layout)); err != nil {
55+ return err
56+ }
57+ size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 2}
58+ dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size}
59+ if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &b.pool)); err != nil {
60+ return err
61+ }
62+ dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &b.setLayout}
63+ if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &b.set)); err != nil {
64+ return err
65+ }
66+ vert, err := r.shaderModule(blendVert)
67+ if err != nil {
68+ return err
69+ }
70+ defer d.DestroyShaderModule(r.device, vert, nil)
71+ frag, err := r.shaderModule(blendFrag)
72+ if err != nil {
73+ return err
74+ }
75+ defer d.DestroyShaderModule(r.device, frag, nil)
76+ main := []byte("main\x00")
77+ stages := []vk.PipelineShaderStageCreateInfo{
78+ {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]},
79+ {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]},
80+ }
81+ vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo}
82+ assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList}
83+ // The target size is fixed for the renderer's life: static viewport.
84+ viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1}
85+ scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}
86+ viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor}
87+ raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1}
88+ multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit}
89+ attachment := vk.PipelineColorBlendAttachmentState{
90+ BlendEnable: 1,
91+ SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd,
92+ SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd,
93+ ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit,
94+ }
95+ blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment}
96+ format := vk.Format(vk.FormatB8g8r8a8Unorm)
97+ rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
98+ gpi := vk.GraphicsPipelineCreateInfo{
99+ SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering),
100+ StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly,
101+ ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample,
102+ ColorBlendState: &blendState, Layout: b.layout, BasePipelineIndex: -1,
103+ }
104+ return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &b.pipeline))
105+}
106+
107+func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) {
108+ // SPIR-V is read as words: copy into a uint32-aligned slice.
109+ words := make([]uint32, len(code)/4)
110+ for i := range words {
111+ words[i] = binary.LittleEndian.Uint32(code[i*4:])
112+ }
113+ info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]}
114+ var m vk.ShaderModule
115+ return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m))
116+}
117+
118+func (r *Renderer) destroyBlend() {
119+ d, b := r.dd, &r.blend
120+ if b.pipeline != 0 {
121+ d.DestroyPipeline(r.device, b.pipeline, nil)
122+ }
123+ if b.pool != 0 {
124+ d.DestroyDescriptorPool(r.device, b.pool, nil)
125+ }
126+ if b.layout != 0 {
127+ d.DestroyPipelineLayout(r.device, b.layout, nil)
128+ }
129+ if b.setLayout != 0 {
130+ d.DestroyDescriptorSetLayout(r.device, b.setLayout, nil)
131+ }
132+ *b = blendPipeline{}
133+}
134+
135+// bindStaging points the set at the staging buffer: pixels blended
136+// pixel bytes from 0, and the draw table of n entries at tableOffset.
137+func (r *Renderer) bindStaging(pixels, tableOffset, n int) {
138+ infos := []vk.DescriptorBufferInfo{
139+ {Buffer: r.staging, Range: vk.DeviceSize(max(pixels, 4))},
140+ {Buffer: r.staging, Offset: vk.DeviceSize(tableOffset), Range: vk.DeviceSize(n * drawSize)},
141+ }
142+ writes := []vk.WriteDescriptorSet{
143+ {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[0]},
144+ {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[1]},
145+ }
146+ r.dd.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil)
147+}
148+
149+// putDraw writes draw entry i of table.
150+func (r *Renderer) putDraw(table []byte, i int, u *upload) {
151+ e := table[i*drawSize : (i+1)*drawSize]
152+ for j, v := range []uint32{
153+ uint32(u.rect.Min.X), uint32(u.rect.Min.Y), uint32(u.rect.Max.X), uint32(u.rect.Max.Y),
154+ uint32(u.offset / 4), uint32(u.rect.Dx()), uint32(r.width), uint32(r.height),
155+ } {
156+ binary.LittleEndian.PutUint32(e[j*4:], v)
157+ }
158+}
159+
160+// drawBlended draws uploads[first:last], all blended, into view: one
161+// instanced draw. The image is TRANSFER_DST before and after.
162+func (r *Renderer) drawBlended(image vk.Image, view vk.ImageView, first, last int) {
163+ d := r.dd
164+ rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
165+ b := vk.ImageMemoryBarrier{
166+ SType: vk.StructureTypeImageMemoryBarrier, Image: image, SubresourceRange: rangeInfo,
167+ SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit,
168+ OldLayout: vk.ImageLayoutTransferDstOptimal, NewLayout: vk.ImageLayoutColorAttachmentOptimal,
169+ }
170+ d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b)
171+ attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpLoad, StoreOp: vk.AttachmentStoreOpStore}
172+ 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}
173+ d.CmdBeginRendering(r.command, &info)
174+ d.CmdBindPipeline(r.command, vk.PipelineBindPointGraphics, r.blend.pipeline)
175+ d.CmdBindDescriptorSets(r.command, vk.PipelineBindPointGraphics, r.blend.layout, 0, 1, &r.blend.set, 0, nil)
176+ d.CmdDraw(r.command, 6, uint32(last-first), 0, uint32(first))
177+ d.CmdEndRendering(r.command)
178+ b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, vk.AccessTransferWriteBit|vk.AccessTransferReadBit
179+ b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutTransferDstOptimal
180+ d.CmdPipelineBarrier(r.command, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b)
181+}
182+
183+// createView makes the color attachment view of a target image.
184+func (r *Renderer) createView(t *target) error {
185+ info := vk.ImageViewCreateInfo{
186+ SType: vk.StructureTypeImageViewCreateInfo, Image: t.image, ViewType: vk.ImageViewType2d, Format: vk.FormatB8g8r8a8Unorm,
187+ SubresourceRange: vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1},
188+ }
189+ return checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &t.view))
190+}
M internal/adapters/vulkan/renderer.go +81 -11
@@ -55,6 +55,11 @@ type Renderer struct {
5555 frame uint64
5656 // fillRegions is reused by Render for the bands of solid fills.
5757 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
62+ maxRange int // bytes of blended pixels one frame may bind
5863 }
5964
6065 // fillRows is the height of the strip staged for a solid fill: the CPU
@@ -146,6 +151,10 @@ func New(width, height int) (r *Renderer, err error) {
146151 return
147152 }
148153 r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)
154+ var properties vk.PhysicalDeviceProperties
155+ r.id.GetPhysicalDeviceProperties(physical, &properties)
156+ r.align = int(max(properties.Limits.MinStorageBufferOffsetAlignment, 4))
157+ r.maxRange = int(properties.Limits.MaxStorageBufferRange)
149158 r.family = family
150159 priority := float32(1)
151160 qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}
@@ -181,7 +190,7 @@ func New(width, height int) (r *Renderer, err error) {
181190 }
182191 r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)
183192 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}
193+ 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}
185194 if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil {
186195 return
187196 }
@@ -226,7 +235,7 @@ func New(width, height int) (r *Renderer, err error) {
226235 // Start with two frame-sized regions; later frames grow staging on demand.
227236 stagingSize := size * 2
228237 r.stagingSize = int(stagingSize)
229- stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive}
238+ stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: stagingUsage, SharingMode: vk.SharingModeExclusive}
230239 if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil {
231240 return
232241 }
@@ -253,6 +262,12 @@ func New(width, height int) (r *Renderer, err error) {
253262 if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil {
254263 return
255264 }
265+ if err = r.createView(&r.own); err != nil {
266+ return
267+ }
268+ if err = r.createBlend(); err != nil {
269+ return
270+ }
256271 fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo}
257272 if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil {
258273 return
@@ -315,7 +330,7 @@ func (r *Renderer) ensureStaging(size int) error {
315330 }
316331 d := r.dd
317332 capacity := size
318- info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive}
333+ info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: stagingUsage, SharingMode: vk.SharingModeExclusive}
319334 var buffer vk.Buffer
320335 if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil {
321336 return err
@@ -370,12 +385,22 @@ type upload struct {
370385 dst, src image.Rectangle
371386 color [3]uint8
372387 opaque bool
388+ // blend draws the staged pixels over the target instead of copying.
389+ blend bool
373390 // dma is a client GPU buffer, blitted instead of copied from staging.
374391 dma *imported
375392 // fill is a solid color: staging holds one strip of rows.
376393 fill bool
377394 }
378395
396+// size is the staging bytes of an upload: a strip for a fill.
397+func (u *upload) size() int {
398+ if u.fill {
399+ return u.rect.Dx() * min(u.rect.Dy(), fillRows) * 4
400+ }
401+ return u.rect.Dx() * u.rect.Dy() * 4
402+}
403+
379404 // stage copies the visible pixels of a client buffer into dst, resampling
380405 // when the buffer is not drawn at its size.
381406 func (u *upload) stage(dst []byte) {
@@ -411,7 +436,6 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
411436 phys := func(v int) int { return int(math.Round(float64(v) * scale)) }
412437 physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) }
413438 var uploads []upload
414- used := 0
415439 bounds := image.Rect(0, 0, r.width, r.height)
416440 add := func(rect image.Rectangle, c [3]uint8) {
417441 rect = rect.Intersect(bounds)
@@ -420,8 +444,7 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
420444 }
421445 // A fill stages a strip of up to fillRows rows; the GPU copies the
422446 // strip down the rect.
423- uploads = append(uploads, upload{rect: rect, offset: used, color: c, fill: true})
424- used += rect.Dx() * min(rect.Dy(), fillRows) * 4
447+ uploads = append(uploads, upload{rect: rect, color: c, fill: true})
425448 }
426449 // addContent maps src (buffer pixels) onto dst (physical pixels).
427450 addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) {
@@ -443,8 +466,7 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
443466 if err != nil {
444467 return
445468 }
446- uploads = append(uploads, upload{rect: rect, offset: used, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, dst: dst, src: src})
447- used += rect.Dx() * rect.Dy() * 4
469+ uploads = append(uploads, upload{rect: rect, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, blend: !content.Opaque, dst: dst, src: src})
448470 }
449471 // drawSurface draws one surface buffer with its origin at (x, y)
450472 // logical, clipped to clip (logical).
@@ -561,10 +583,44 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
561583 addPopups(false)
562584 addLayers(true)
563585 addPopups(true)
586+ // Blended pixels are staged first, so the shaders bind only them,
587+ // within the device's storage buffer range; past it they are copied.
588+ // The draw table follows them, then the other uploads.
589+ blends, used := 0, 0
590+ for k := range uploads {
591+ u := &uploads[k]
592+ if u.blend && used+u.size() > r.maxRange {
593+ u.blend = false
594+ }
595+ if u.blend {
596+ u.offset = used
597+ used += u.size()
598+ blends++
599+ }
600+ }
601+ pixelBytes := used
602+ tableOffset := (used + r.align - 1) / r.align * r.align
603+ used = tableOffset + blends*drawSize
604+ for k := range uploads {
605+ if u := &uploads[k]; !u.blend && u.dma == nil {
606+ u.offset = used
607+ used += u.size()
608+ }
609+ }
564610 if err := r.ensureStaging(used); err != nil {
565611 return err
566612 }
567613 data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize)
614+ if blends > 0 {
615+ r.bindStaging(pixelBytes, tableOffset, blends)
616+ i := 0
617+ for k := range uploads {
618+ if uploads[k].blend {
619+ r.putDraw(data[tableOffset:], i, &uploads[k])
620+ i++
621+ }
622+ }
623+ }
568624 for _, u := range uploads {
569625 if u.dma != nil {
570626 continue
@@ -581,7 +637,6 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
581637 // then shows garbage for this frame, never a crash.
582638 copyGuarded(func() { u.stage(dst) })
583639 }
584- // B8G8R8A8 pixels are copied unchanged; blending belongs to the compositing pipeline.
585640 rgb := parseColor(s.Background)
586641 d := r.dd
587642 if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil {
@@ -643,10 +698,24 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
643698 }
644699 }
645700 ownership(true)
646- for i, f := range uploads {
647- if i > 0 {
701+ drawn := 0 // blended uploads drawn so far: their index in the draw table
702+ for i := 0; i < len(uploads); i++ {
703+ f := uploads[i]
704+ if i > 0 && !f.blend {
648705 d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier)
649706 }
707+ if f.blend {
708+ // Consecutive blended uploads share one draw: blending follows
709+ // primitive order within it.
710+ n := 1
711+ for i+n < len(uploads) && uploads[i+n].blend {
712+ n++
713+ }
714+ r.drawBlended(tg.image, tg.view, drawn, drawn+n)
715+ drawn += n
716+ i += n - 1
717+ continue
718+ }
650719 rect := f.rect
651720 if f.dma != nil {
652721 // Map the visible part of dst back onto the buffer.
@@ -860,6 +929,7 @@ func (r *Renderer) Close() {
860929 d.FreeMemory(r.device, r.bufferMemory, nil)
861930 r.bufferMemory = 0
862931 }
932+ r.destroyBlend()
863933 r.freeTarget(&r.own)
864934 d.DestroyDevice(r.device, nil)
865935 r.device = 0
@@ -55,6 +55,11 @@ type Renderer struct {
55 frame uint6455 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.BufferImageCopy57 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
62+ maxRange int // bytes of blended pixels one frame may bind
58 }63 }
59 64
60 // fillRows is the height of the strip staged for a solid fill: the CPU65 // fillRows is the height of the strip staged for a solid fill: the CPU
@@ -146,6 +151,10 @@ func New(width, height int) (r *Renderer, err error) {
146 return151 return
147 }152 }
148 r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)153 r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)
154+ var properties vk.PhysicalDeviceProperties
155+ r.id.GetPhysicalDeviceProperties(physical, &properties)
156+ r.align = int(max(properties.Limits.MinStorageBufferOffsetAlignment, 4))
157+ r.maxRange = int(properties.Limits.MaxStorageBufferRange)
149 r.family = family158 r.family = family
150 priority := float32(1)159 priority := float32(1)
151 qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}160 qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}
@@ -181,7 +190,7 @@ func New(width, height int) (r *Renderer, err error) {
181 }190 }
182 r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)191 r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)
183 extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1}192 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}193+ 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 {194 if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil {
186 return195 return
187 }196 }
@@ -226,7 +235,7 @@ func New(width, height int) (r *Renderer, err error) {
226 // Start with two frame-sized regions; later frames grow staging on demand.235 // Start with two frame-sized regions; later frames grow staging on demand.
227 stagingSize := size * 2236 stagingSize := size * 2
228 r.stagingSize = int(stagingSize)237 r.stagingSize = int(stagingSize)
229- stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive}238+ 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 {239 if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil {
231 return240 return
232 }241 }
@@ -253,6 +262,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 {262 if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil {
254 return263 return
255 }264 }
265+ if err = r.createView(&r.own); err != nil {
266+ return
267+ }
268+ if err = r.createBlend(); err != nil {
269+ return
270+ }
256 fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo}271 fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo}
257 if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil {272 if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil {
258 return273 return
@@ -315,7 +330,7 @@ func (r *Renderer) ensureStaging(size int) error {
315 }330 }
316 d := r.dd331 d := r.dd
317 capacity := size332 capacity := size
318- info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: vk.BufferUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive}333+ info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: stagingUsage, SharingMode: vk.SharingModeExclusive}
319 var buffer vk.Buffer334 var buffer vk.Buffer
320 if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil {335 if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil {
321 return err336 return err
@@ -370,12 +385,22 @@ type upload struct {
370 dst, src image.Rectangle385 dst, src image.Rectangle
371 color [3]uint8386 color [3]uint8
372 opaque bool387 opaque bool
388+ // blend draws the staged pixels over the target instead of copying.
389+ blend bool
373 // dma is a client GPU buffer, blitted instead of copied from staging.390 // dma is a client GPU buffer, blitted instead of copied from staging.
374 dma *imported391 dma *imported
375 // fill is a solid color: staging holds one strip of rows.392 // fill is a solid color: staging holds one strip of rows.
376 fill bool393 fill bool
377 }394 }
378 395
396+// size is the staging bytes of an upload: a strip for a fill.
397+func (u *upload) size() int {
398+ if u.fill {
399+ return u.rect.Dx() * min(u.rect.Dy(), fillRows) * 4
400+ }
401+ return u.rect.Dx() * u.rect.Dy() * 4
402+}
403+
379 // stage copies the visible pixels of a client buffer into dst, resampling404 // stage copies the visible pixels of a client buffer into dst, resampling
380 // when the buffer is not drawn at its size.405 // when the buffer is not drawn at its size.
381 func (u *upload) stage(dst []byte) {406 func (u *upload) stage(dst []byte) {
@@ -411,7 +436,6 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
411 phys := func(v int) int { return int(math.Round(float64(v) * scale)) }436 phys := func(v int) int { return int(math.Round(float64(v) * scale)) }
412 physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) }437 physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) }
413 var uploads []upload438 var uploads []upload
414- used := 0
415 bounds := image.Rect(0, 0, r.width, r.height)439 bounds := image.Rect(0, 0, r.width, r.height)
416 add := func(rect image.Rectangle, c [3]uint8) {440 add := func(rect image.Rectangle, c [3]uint8) {
417 rect = rect.Intersect(bounds)441 rect = rect.Intersect(bounds)
@@ -420,8 +444,7 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
420 }444 }
421 // A fill stages a strip of up to fillRows rows; the GPU copies the445 // A fill stages a strip of up to fillRows rows; the GPU copies the
422 // strip down the rect.446 // strip down the rect.
423- uploads = append(uploads, upload{rect: rect, offset: used, color: c, fill: true})447+ uploads = append(uploads, upload{rect: rect, color: c, fill: true})
424- used += rect.Dx() * min(rect.Dy(), fillRows) * 4
425 }448 }
426 // addContent maps src (buffer pixels) onto dst (physical pixels).449 // addContent maps src (buffer pixels) onto dst (physical pixels).
427 addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) {450 addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) {
@@ -443,8 +466,7 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
443 if err != nil {466 if err != nil {
444 return467 return
445 }468 }
446- uploads = append(uploads, upload{rect: rect, offset: used, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, dst: dst, src: src})469+ uploads = append(uploads, upload{rect: rect, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, blend: !content.Opaque, dst: dst, src: src})
447- used += rect.Dx() * rect.Dy() * 4
448 }470 }
449 // drawSurface draws one surface buffer with its origin at (x, y)471 // drawSurface draws one surface buffer with its origin at (x, y)
450 // logical, clipped to clip (logical).472 // logical, clipped to clip (logical).
@@ -561,10 +583,44 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
561 addPopups(false)583 addPopups(false)
562 addLayers(true)584 addLayers(true)
563 addPopups(true)585 addPopups(true)
586+ // Blended pixels are staged first, so the shaders bind only them,
587+ // within the device's storage buffer range; past it they are copied.
588+ // The draw table follows them, then the other uploads.
589+ blends, used := 0, 0
590+ for k := range uploads {
591+ u := &uploads[k]
592+ if u.blend && used+u.size() > r.maxRange {
593+ u.blend = false
594+ }
595+ if u.blend {
596+ u.offset = used
597+ used += u.size()
598+ blends++
599+ }
600+ }
601+ pixelBytes := used
602+ tableOffset := (used + r.align - 1) / r.align * r.align
603+ used = tableOffset + blends*drawSize
604+ for k := range uploads {
605+ if u := &uploads[k]; !u.blend && u.dma == nil {
606+ u.offset = used
607+ used += u.size()
608+ }
609+ }
564 if err := r.ensureStaging(used); err != nil {610 if err := r.ensureStaging(used); err != nil {
565 return err611 return err
566 }612 }
567 data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize)613 data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize)
614+ if blends > 0 {
615+ r.bindStaging(pixelBytes, tableOffset, blends)
616+ i := 0
617+ for k := range uploads {
618+ if uploads[k].blend {
619+ r.putDraw(data[tableOffset:], i, &uploads[k])
620+ i++
621+ }
622+ }
623+ }
568 for _, u := range uploads {624 for _, u := range uploads {
569 if u.dma != nil {625 if u.dma != nil {
570 continue626 continue
@@ -581,7 +637,6 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
581 // then shows garbage for this frame, never a crash.637 // then shows garbage for this frame, never a crash.
582 copyGuarded(func() { u.stage(dst) })638 copyGuarded(func() { u.stage(dst) })
583 }639 }
584- // B8G8R8A8 pixels are copied unchanged; blending belongs to the compositing pipeline.
585 rgb := parseColor(s.Background)640 rgb := parseColor(s.Background)
586 d := r.dd641 d := r.dd
587 if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil {642 if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil {
@@ -643,10 +698,24 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa
643 }698 }
644 }699 }
645 ownership(true)700 ownership(true)
646- for i, f := range uploads {701+ drawn := 0 // blended uploads drawn so far: their index in the draw table
647- if i > 0 {702+ for i := 0; i < len(uploads); i++ {
703+ f := uploads[i]
704+ if i > 0 && !f.blend {
648 d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier)705 d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier)
649 }706 }
707+ if f.blend {
708+ // Consecutive blended uploads share one draw: blending follows
709+ // primitive order within it.
710+ n := 1
711+ for i+n < len(uploads) && uploads[i+n].blend {
712+ n++
713+ }
714+ r.drawBlended(tg.image, tg.view, drawn, drawn+n)
715+ drawn += n
716+ i += n - 1
717+ continue
718+ }
650 rect := f.rect719 rect := f.rect
651 if f.dma != nil {720 if f.dma != nil {
652 // Map the visible part of dst back onto the buffer.721 // Map the visible part of dst back onto the buffer.
@@ -860,6 +929,7 @@ func (r *Renderer) Close() {
860 d.FreeMemory(r.device, r.bufferMemory, nil)929 d.FreeMemory(r.device, r.bufferMemory, nil)
861 r.bufferMemory = 0930 r.bufferMemory = 0
862 }931 }
932+ r.destroyBlend()
863 r.freeTarget(&r.own)933 r.freeTarget(&r.own)
864 d.DestroyDevice(r.device, nil)934 d.DestroyDevice(r.device, nil)
865 r.device = 0935 r.device = 0
M internal/adapters/vulkan/renderer_test.go +62 -0
@@ -364,3 +364,65 @@ func TestRendererTallFill(t *testing.T) {
364364 }
365365 }
366366 }
367+
368+// fill returns a w×h premultiplied B8G8R8A8 buffer of one pixel.
369+func fill(w, h int, bgra [4]byte) []byte {
370+ p := make([]byte, w*h*4)
371+ for i := 0; i < len(p); i += 4 {
372+ copy(p[i:i+4], bgra[:])
373+ }
374+ return p
375+}
376+
377+// Translucent buffers blend over what is below them, in paint order: a
378+// bottom layer (one draw), an opaque window copied over it, then two
379+// overlapping overlays sharing one draw. A transparent pixel changes nothing.
380+func TestRendererAlphaBlend(t *testing.T) {
381+ r, err := New(64, 48)
382+ if err != nil {
383+ t.Skipf("Vulkan unavailable: %v", err)
384+ }
385+ defer r.Close()
386+ halfRed, halfBlue := [4]byte{0, 0, 128, 128}, [4]byte{128, 0, 0, 128}
387+ clear := fill(16, 8, halfRed)
388+ for y := range 8 {
389+ copy(clear[y*64+48:y*64+64], make([]byte, 16)) // last 4 pixels: transparent
390+ }
391+ white := shmContent(t, 32, 48, 32*4, solid(32, 48, [3]uint8{255, 255, 255}))
392+ white.Opaque = true
393+ contents := map[ports.WindowID]ports.SurfaceContent{
394+ 1: *white,
395+ 2: *shmContent(t, 64, 48, 64*4, fill(64, 48, halfRed)),
396+ 3: *shmContent(t, 16, 8, 16*4, clear),
397+ 4: *shmContent(t, 16, 8, 16*4, fill(16, 8, halfBlue)),
398+ }
399+ scene := ports.Scene{Background: "#000000",
400+ Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 32, H: 48}, Borderless: true}},
401+ Layers: []ports.SceneLayer{
402+ {ID: 2, Layer: ports.LayerBottom, Rect: ports.Rect{W: 64, H: 48}},
403+ {ID: 3, Layer: ports.LayerOverlay, Rect: ports.Rect{W: 16, H: 8}},
404+ {ID: 4, Layer: ports.LayerOverlay, Rect: ports.Rect{X: 8, W: 16, H: 8}},
405+ }}
406+ if err := r.Render(scene, contents); err != nil {
407+ t.Fatal(err)
408+ }
409+ px := r.Pixels()
410+ near := func(a, b uint8) bool { d := int(a) - int(b); return d >= -1 && d <= 1 }
411+ for _, tc := range []struct {
412+ name string
413+ x, y int
414+ r, g, b uint8
415+ }{
416+ {"bottom layer over background", 40, 20, 128, 0, 0},
417+ {"opaque window over bottom layer", 20, 20, 255, 255, 255},
418+ {"red overlay over window", 4, 4, 255, 127, 127},
419+ {"blue over red over window", 10, 4, 127, 63, 191},
420+ {"blue over window", 20, 4, 127, 127, 255},
421+ {"transparent red pixels: blue over window only", 14, 4, 127, 127, 255},
422+ } {
423+ got := px.RGBAAt(tc.x, tc.y)
424+ if !near(got.R, tc.r) || !near(got.G, tc.g) || !near(got.B, tc.b) {
425+ t.Errorf("%s: pixel (%d,%d) = %v, want ~{%d %d %d}", tc.name, tc.x, tc.y, got, tc.r, tc.g, tc.b)
426+ }
427+ }
428+}
@@ -364,3 +364,65 @@ func TestRendererTallFill(t *testing.T) {
364 }364 }
365 }365 }
366 }366 }
367+
368+// fill returns a w×h premultiplied B8G8R8A8 buffer of one pixel.
369+func fill(w, h int, bgra [4]byte) []byte {
370+ p := make([]byte, w*h*4)
371+ for i := 0; i < len(p); i += 4 {
372+ copy(p[i:i+4], bgra[:])
373+ }
374+ return p
375+}
376+
377+// Translucent buffers blend over what is below them, in paint order: a
378+// bottom layer (one draw), an opaque window copied over it, then two
379+// overlapping overlays sharing one draw. A transparent pixel changes nothing.
380+func TestRendererAlphaBlend(t *testing.T) {
381+ r, err := New(64, 48)
382+ if err != nil {
383+ t.Skipf("Vulkan unavailable: %v", err)
384+ }
385+ defer r.Close()
386+ halfRed, halfBlue := [4]byte{0, 0, 128, 128}, [4]byte{128, 0, 0, 128}
387+ clear := fill(16, 8, halfRed)
388+ for y := range 8 {
389+ copy(clear[y*64+48:y*64+64], make([]byte, 16)) // last 4 pixels: transparent
390+ }
391+ white := shmContent(t, 32, 48, 32*4, solid(32, 48, [3]uint8{255, 255, 255}))
392+ white.Opaque = true
393+ contents := map[ports.WindowID]ports.SurfaceContent{
394+ 1: *white,
395+ 2: *shmContent(t, 64, 48, 64*4, fill(64, 48, halfRed)),
396+ 3: *shmContent(t, 16, 8, 16*4, clear),
397+ 4: *shmContent(t, 16, 8, 16*4, fill(16, 8, halfBlue)),
398+ }
399+ scene := ports.Scene{Background: "#000000",
400+ Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 32, H: 48}, Borderless: true}},
401+ Layers: []ports.SceneLayer{
402+ {ID: 2, Layer: ports.LayerBottom, Rect: ports.Rect{W: 64, H: 48}},
403+ {ID: 3, Layer: ports.LayerOverlay, Rect: ports.Rect{W: 16, H: 8}},
404+ {ID: 4, Layer: ports.LayerOverlay, Rect: ports.Rect{X: 8, W: 16, H: 8}},
405+ }}
406+ if err := r.Render(scene, contents); err != nil {
407+ t.Fatal(err)
408+ }
409+ px := r.Pixels()
410+ near := func(a, b uint8) bool { d := int(a) - int(b); return d >= -1 && d <= 1 }
411+ for _, tc := range []struct {
412+ name string
413+ x, y int
414+ r, g, b uint8
415+ }{
416+ {"bottom layer over background", 40, 20, 128, 0, 0},
417+ {"opaque window over bottom layer", 20, 20, 255, 255, 255},
418+ {"red overlay over window", 4, 4, 255, 127, 127},
419+ {"blue over red over window", 10, 4, 127, 63, 191},
420+ {"blue over window", 20, 4, 127, 127, 255},
421+ {"transparent red pixels: blue over window only", 14, 4, 127, 127, 255},
422+ } {
423+ got := px.RGBAAt(tc.x, tc.y)
424+ if !near(got.R, tc.r) || !near(got.G, tc.g) || !near(got.B, tc.b) {
425+ t.Errorf("%s: pixel (%d,%d) = %v, want ~{%d %d %d}", tc.name, tc.x, tc.y, got, tc.r, tc.g, tc.b)
426+ }
427+ }
428+}
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 +16 -2
@@ -24,6 +24,7 @@ type target struct {
2424 image vk.Image
2525 memory vk.DeviceMemory
2626 layout vk.ImageLayout
27+ view vk.ImageView // color attachment for blended draws
2728 // exported images are shared with the display: ownership goes to the
2829 // foreign queue family after each frame, and KMS reads them.
2930 exported bool
@@ -104,7 +105,9 @@ func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {
104105 mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
105106 list.DrmFormatModifierProperties = &mods[0]
106107 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)
109+ // Targets are drawn on too (blend.go): a transfer-only modifier is
110+ // not a target, and without any the display falls back to readback.
108111 for _, m := range mods[:list.DrmFormatModifierCount] {
109112 if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) {
110113 r.renderMods = append(r.renderMods, m.DrmFormatModifier)
@@ -131,7 +134,11 @@ const (
131134 formatFeatureTransferDst = 1 << 15
132135 )
133136
134-const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit
137+// Targets are copied into and drawn on (blend.go).
138+const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | vk.ImageUsageColorAttachmentBit
139+
140+// Staging feeds copies and, as a storage buffer, the blend shaders.
141+const stagingUsage = vk.BufferUsageTransferSrcBit | vk.BufferUsageStorageBufferBit
135142
136143 // exportTarget creates one exported image with a modifier from mods.
137144 func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
@@ -171,6 +178,9 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
171178 var layout vk.SubresourceLayout
172179 sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}
173180 d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)
181+ if err := r.createView(t); err != nil {
182+ return nil, ports.DMABuf{}, err
183+ }
174184 var fd int32
175185 get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
176186 if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
@@ -186,6 +196,10 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
186196 }
187197
188198 func (r *Renderer) freeTarget(t *target) {
199+ if t.view != 0 {
200+ r.dd.DestroyImageView(r.device, t.view, nil)
201+ t.view = 0
202+ }
189203 if t.image != 0 {
190204 r.dd.DestroyImage(r.device, t.image, nil)
191205 t.image = 0
@@ -24,6 +24,7 @@ type target struct {
24 image vk.Image24 image vk.Image
25 memory vk.DeviceMemory25 memory vk.DeviceMemory
26 layout vk.ImageLayout26 layout vk.ImageLayout
27+ view vk.ImageView // color attachment for blended draws
27 // exported images are shared with the display: ownership goes to the28 // 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 bool30 exported bool
@@ -104,7 +105,9 @@ 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)
109+ // Targets are drawn on too (blend.go): a transfer-only modifier is
110+ // not a target, and without any the display falls back to readback.
108 for _, m := range mods[:list.DrmFormatModifierCount] {111 for _, m := range mods[:list.DrmFormatModifierCount] {
109 if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) {112 if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) {
110 r.renderMods = append(r.renderMods, m.DrmFormatModifier)113 r.renderMods = append(r.renderMods, m.DrmFormatModifier)
@@ -131,7 +134,11 @@ const (
131 formatFeatureTransferDst = 1 << 15134 formatFeatureTransferDst = 1 << 15
132 )135 )
133 136
134-const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit137+// Targets are copied into and drawn on (blend.go).
138+const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | vk.ImageUsageColorAttachmentBit
139+
140+// Staging feeds copies and, as a storage buffer, the blend shaders.
141+const stagingUsage = vk.BufferUsageTransferSrcBit | vk.BufferUsageStorageBufferBit
135 142
136 // exportTarget creates one exported image with a modifier from mods.143 // exportTarget creates one exported image with a modifier from mods.
137 func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {144 func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
@@ -171,6 +178,9 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
171 var layout vk.SubresourceLayout178 var layout vk.SubresourceLayout
172 sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}179 sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}
173 d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)180 d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)
181+ if err := r.createView(t); err != nil {
182+ return nil, ports.DMABuf{}, err
183+ }
174 var fd int32184 var fd int32
175 get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}185 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 {186 if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
@@ -186,6 +196,10 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
186 }196 }
187 197
188 func (r *Renderer) freeTarget(t *target) {198 func (r *Renderer) freeTarget(t *target) {
199+ if t.view != 0 {
200+ r.dd.DestroyImageView(r.device, t.view, nil)
201+ t.view = 0
202+ }
189 if t.image != 0 {203 if t.image != 0 {
190 r.dd.DestroyImage(r.device, t.image, nil)204 r.dd.DestroyImage(r.device, t.image, nil)
191 t.image = 0205 t.image = 0

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close