1package vulkan
2
3import (
4 _ "embed"
5 "encoding/binary"
6 "image"
7 "math"
8 "os"
9 "unsafe"
10
11 vk "github.com/bnema/purego-vulkan/vulkan"
12)
13
14// Composition is one graphics pipeline drawing every piece of a frame as
15// a quad, in paint order, inside one dynamic rendering pass: solid fills,
16// client dmabufs sampled as images, and client wl_shm pixels read from a
17// GPU buffer (shm.go). Push constants carry each draw; the descriptor set
18// holds its image and buffer. Client pixels are premultiplied, hence the
19// blend ONE, ONE_MINUS_SRC_ALPHA; opaque draws force alpha to 1.
20
21//go:generate glslc -O --target-env=vulkan1.3 shaders/compose.vert -o shaders/compose.vert.spv
22//go:generate glslc -O --target-env=vulkan1.3 shaders/compose.frag -o shaders/compose.frag.spv
23
24var (
25 //go:embed shaders/compose.vert.spv
26 composeVert []byte
27 //go:embed shaders/compose.frag.spv
28 composeFrag []byte
29)
30
31// Draw modes and flags (shaders: modeSolid, modeImage, flag*).
32const (
33 modeSolid = 0
34 modeImage = 1
35 modeBuffer = 2
36
37 flagOpaque = 1
38 flagExact = 2
39)
40
41// pushConstants is struct Draw of the shaders (std430 push constant block).
42type pushConstants struct {
43 rect [4]int32
44 mapv [4]float32
45 color [4]float32
46 buf [4]uint32
47 misc [4]uint32
48}
49
50// draw is one quad of a frame.
51type draw struct {
52 pc pushConstants
53 set vk.DescriptorSet
54 im *imported // sampled client dmabuf, for ownership barriers
55 // acquire is the content's explicit-sync fence (nil: implicit).
56 acquire *os.File
57}
58
59// composer is the pipeline state and the objects every draw can bind.
60type composer struct {
61 setLayout vk.DescriptorSetLayout
62 layout vk.PipelineLayout
63 pipeline vk.Pipeline
64 sampler vk.Sampler
65 // Placeholders for the binding a draw does not use: every binding
66 // the shader declares must be valid.
67 dummyImage vk.Image
68 dummyMemory vk.DeviceMemory
69 dummyView vk.ImageView
70 dummyBuffer gpuBuffer
71 dummyPool vk.DescriptorPool
72 dummySet vk.DescriptorSet
73}
74
75func (r *Renderer) createComposer() error {
76 d, c := r.dd, &r.compose
77 bindings := []vk.DescriptorSetLayoutBinding{
78 {Binding: 0, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit},
79 {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit},
80 }
81 sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]}
82 if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &c.setLayout)); err != nil {
83 return err
84 }
85 pcr := vk.PushConstantRange{StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit, Size: uint32(unsafe.Sizeof(pushConstants{}))}
86 pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &c.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &pcr}
87 if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &c.layout)); err != nil {
88 return err
89 }
90 si := vk.SamplerCreateInfo{SType: vk.StructureTypeSamplerCreateInfo, MagFilter: vk.FilterLinear, MinFilter: vk.FilterLinear, AddressModeU: vk.SamplerAddressModeClampToEdge, AddressModeV: vk.SamplerAddressModeClampToEdge, AddressModeW: vk.SamplerAddressModeClampToEdge}
91 if err := checked("vkCreateSampler", d.CreateSampler(r.device, &si, nil, &c.sampler)); err != nil {
92 return err
93 }
94 if err := r.createPipeline(); err != nil {
95 return err
96 }
97 return r.createDummies()
98}
99
100func (r *Renderer) createPipeline() error {
101 d, c := r.dd, &r.compose
102 vert, err := r.shaderModule(composeVert)
103 if err != nil {
104 return err
105 }
106 defer d.DestroyShaderModule(r.device, vert, nil)
107 frag, err := r.shaderModule(composeFrag)
108 if err != nil {
109 return err
110 }
111 defer d.DestroyShaderModule(r.device, frag, nil)
112 main := []byte("main\x00")
113 stages := []vk.PipelineShaderStageCreateInfo{
114 {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]},
115 {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]},
116 }
117 vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo}
118 assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList}
119 // The target size is fixed for the renderer's life: static viewport.
120 viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1}
121 scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}
122 viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor}
123 raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1}
124 multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit}
125 attachment := vk.PipelineColorBlendAttachmentState{
126 BlendEnable: 1,
127 SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd,
128 SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd,
129 ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit,
130 }
131 blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment}
132 format := vk.Format(vk.FormatB8g8r8a8Unorm)
133 rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
134 gpi := vk.GraphicsPipelineCreateInfo{
135 SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering),
136 StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly,
137 ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample,
138 ColorBlendState: &blendState, Layout: c.layout, BasePipelineIndex: -1,
139 }
140 return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &c.pipeline))
141}
142
143// createDummies makes the placeholder image (1×1, shader-read layout) and
144// buffer, and the set fills bind.
145func (r *Renderer) createDummies() error {
146 d, c := r.dd, &r.compose
147 ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: 1, Height: 1, Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
148 if err := checked("vkCreateImage(dummy)", d.CreateImage(r.device, &ii, nil, &c.dummyImage)); err != nil {
149 return err
150 }
151 var req vk.MemoryRequirements
152 d.GetImageMemoryRequirements(r.device, c.dummyImage, &req)
153 kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
154 if err != nil {
155 return err
156 }
157 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
158 if err := checked("vkAllocateMemory(dummy)", d.AllocateMemory(r.device, &alloc, nil, &c.dummyMemory)); err != nil {
159 return err
160 }
161 if err := checked("vkBindImageMemory(dummy)", d.BindImageMemory(r.device, c.dummyImage, c.dummyMemory, 0)); err != nil {
162 return err
163 }
164 if c.dummyView, err = r.imageView(c.dummyImage, vk.FormatB8g8r8a8Unorm); err != nil {
165 return err
166 }
167 if err := r.newGPUBuffer(&c.dummyBuffer, 16); err != nil {
168 return err
169 }
170 if c.dummyPool, c.dummySet, err = r.newSet(c.dummyView, c.dummyBuffer.buffer); err != nil {
171 return err
172 }
173 // Move the placeholder image to the layout every draw declares.
174 return r.oneShot(func(cmd vk.CommandBuffer) {
175 b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutShaderReadOnlyOptimal, DstAccessMask: vk.AccessShaderReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: c.dummyImage, SubresourceRange: colorRange}
176 d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &b)
177 })
178}
179
180// newSet allocates a descriptor set from its own pool (the bindings lack
181// vkFreeDescriptorSets: the pool is destroyed with the object).
182func (r *Renderer) newSet(view vk.ImageView, buffer vk.Buffer) (vk.DescriptorPool, vk.DescriptorSet, error) {
183 d := r.dd
184 sizes := []vk.DescriptorPoolSize{{Type: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}, {Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1}}
185 dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 2, PoolSizes: &sizes[0]}
186 var pool vk.DescriptorPool
187 if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &pool)); err != nil {
188 return 0, 0, err
189 }
190 var set vk.DescriptorSet
191 dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: pool, DescriptorSetCount: 1, SetLayouts: &r.compose.setLayout}
192 if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &set)); err != nil {
193 d.DestroyDescriptorPool(r.device, pool, nil)
194 return 0, 0, err
195 }
196 img := vk.DescriptorImageInfo{Sampler: r.compose.sampler, ImageView: view, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal}
197 buf := vk.DescriptorBufferInfo{Buffer: buffer, Range: wholeSize}
198 writes := []vk.WriteDescriptorSet{
199 {SType: vk.StructureTypeWriteDescriptorSet, DstSet: set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &img},
200 {SType: vk.StructureTypeWriteDescriptorSet, DstSet: set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &buf},
201 }
202 d.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil)
203 return pool, set, nil
204}
205
206func (r *Renderer) destroyComposer() {
207 d, c := r.dd, &r.compose
208 if c.dummyPool != 0 {
209 d.DestroyDescriptorPool(r.device, c.dummyPool, nil)
210 }
211 r.freeGPUBuffer(&c.dummyBuffer)
212 if c.dummyView != 0 {
213 d.DestroyImageView(r.device, c.dummyView, nil)
214 }
215 if c.dummyImage != 0 {
216 d.DestroyImage(r.device, c.dummyImage, nil)
217 }
218 if c.dummyMemory != 0 {
219 d.FreeMemory(r.device, c.dummyMemory, nil)
220 }
221 if c.sampler != 0 {
222 d.DestroySampler(r.device, c.sampler, nil)
223 }
224 if c.pipeline != 0 {
225 d.DestroyPipeline(r.device, c.pipeline, nil)
226 }
227 if c.layout != 0 {
228 d.DestroyPipelineLayout(r.device, c.layout, nil)
229 }
230 if c.setLayout != 0 {
231 d.DestroyDescriptorSetLayout(r.device, c.setLayout, nil)
232 }
233 *c = composer{}
234}
235
236func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) {
237 // SPIR-V is read as words: copy into a uint32-aligned slice.
238 words := make([]uint32, len(code)/4)
239 for i := range words {
240 words[i] = binary.LittleEndian.Uint32(code[i*4:])
241 }
242 info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]}
243 var m vk.ShaderModule
244 return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m))
245}
246
247var colorRange = vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
248
249// imageView makes a 2D color view of an image.
250func (r *Renderer) imageView(img vk.Image, format vk.Format) (vk.ImageView, error) {
251 info := vk.ImageViewCreateInfo{SType: vk.StructureTypeImageViewCreateInfo, Image: img, ViewType: vk.ImageViewType2d, Format: format, SubresourceRange: colorRange}
252 var v vk.ImageView
253 return v, checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &v))
254}
255
256// createView makes the color attachment view of a target image.
257func (r *Renderer) createView(t *target) (err error) {
258 t.view, err = r.imageView(t.image, vk.FormatB8g8r8a8Unorm)
259 return err
260}
261
262// fillDraw is a solid rect of c.
263func (r *Renderer) fillDraw(rect image.Rectangle, c [3]uint8) draw {
264 dr := draw{set: r.compose.dummySet}
265 dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)}
266 dr.pc.color = [4]float32{float32(c[0]) / 255, float32(c[1]) / 255, float32(c[2]) / 255, 1}
267 dr.pc.misc = [4]uint32{modeSolid, 0, uint32(r.width), uint32(r.height)}
268 return dr
269}
270
271// contentDraw draws the visible rect of a w×h buffer mapped onto full
272// (target pixels). exact: the buffer is drawn at its size.
273func (r *Renderer) contentDraw(rect, full image.Rectangle, w, h int, mode uint32, opaque bool) draw {
274 var dr draw
275 sx, sy := float32(w)/float32(full.Dx()), float32(h)/float32(full.Dy())
276 dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)}
277 dr.pc.mapv = [4]float32{-float32(full.Min.X) * sx, -float32(full.Min.Y) * sy, sx, sy}
278 flags := uint32(0)
279 if opaque {
280 flags |= flagOpaque
281 }
282 if full.Dx() == w && full.Dy() == h {
283 flags |= flagExact
284 }
285 dr.pc.misc = [4]uint32{mode, flags, uint32(r.width), uint32(r.height)}
286 return dr
287}
288
289// recordDraws draws ds in one rendering pass that clears view to bg, or
290// keeps its contents (keep).
291func (r *Renderer) recordDraws(cmd vk.CommandBuffer, view vk.ImageView, bg [3]uint8, ds []draw, keep bool) {
292 d, c := r.dd, &r.compose
293 clear := vk.ClearValue{math.Float32bits(float32(bg[0]) / 255), math.Float32bits(float32(bg[1]) / 255), math.Float32bits(float32(bg[2]) / 255), math.Float32bits(1)}
294 attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpClear, StoreOp: vk.AttachmentStoreOpStore, ClearValue: clear}
295 if keep {
296 attachment.LoadOp = vk.AttachmentLoadOpLoad
297 }
298 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}
299 d.CmdBeginRendering(cmd, &info)
300 d.CmdBindPipeline(cmd, vk.PipelineBindPointGraphics, c.pipeline)
301 var bound vk.DescriptorSet
302 for i := range ds {
303 dr := &ds[i]
304 if dr.set != bound {
305 d.CmdBindDescriptorSets(cmd, vk.PipelineBindPointGraphics, c.layout, 0, 1, &dr.set, 0, nil)
306 bound = dr.set
307 }
308 d.CmdPushConstants(cmd, c.layout, vk.ShaderStageVertexBit|vk.ShaderStageFragmentBit, 0, uint32(unsafe.Sizeof(dr.pc)), unsafe.Pointer(&dr.pc))
309 d.CmdDraw(cmd, 6, 1, 0, 0)
310 }
311 d.CmdEndRendering(cmd)
312}