1package vulkan
2
3import (
4 _ "embed"
5 "encoding/binary"
6 "image"
7 "math"
8 "os"
9 "unsafe"
10
11 "github.com/bnema/neferwl/internal/ports"
12 vk "github.com/bnema/purego-vulkan/vulkan"
13)
14
15// Composition draws quads in paint order inside one dynamic rendering
16// pass. Each batch binds a frame-slot-owned draw buffer and resource table,
17// then draws all its quads as instances. Client pixels are premultiplied,
18// hence the blend ONE, ONE_MINUS_SRC_ALPHA; opaque draws force alpha to 1.
19
20//go:generate glslc -O --target-env=vulkan1.3 shaders/compose.vert -o shaders/compose.vert.spv
21//go:generate glslc -O --target-env=vulkan1.3 shaders/compose.frag -o shaders/compose.frag.spv
22//go:generate glslc -O --target-env=vulkan1.3 shaders/compose_hdr.frag -o shaders/compose_hdr.frag.spv
23//go:generate glslc -O --target-env=vulkan1.3 -DSINGLE_RESOURCE shaders/compose.frag -o shaders/compose_single.frag.spv
24//go:generate glslc -O --target-env=vulkan1.3 -DSINGLE_RESOURCE shaders/compose_hdr.frag -o shaders/compose_hdr_single.frag.spv
25//go:generate glslc -O --target-env=vulkan1.3 shaders/hdr.vert -o shaders/hdr.vert.spv
26//go:generate glslc -O --target-env=vulkan1.3 shaders/hdr.frag -o shaders/hdr.frag.spv
27
28var (
29 //go:embed shaders/compose.vert.spv
30 composeVert []byte
31 //go:embed shaders/compose.frag.spv
32 composeFrag []byte
33 //go:embed shaders/compose_hdr.frag.spv
34 composeHDRFrag []byte
35 //go:embed shaders/compose_single.frag.spv
36 composeSingleFrag []byte
37 //go:embed shaders/compose_hdr_single.frag.spv
38 composeHDRSingleFrag []byte
39)
40
41// Draw modes and flags (shaders: modeSolid, modeImage, flag*).
42const (
43 modeSolid = 0
44 modeImage = 1
45 modeBuffer = 2
46
47 flagOpaque = 1
48 flagExact = 2
49 flagPQ = 4
50 flagExtendedLinear = 8
51 flagYUV = 16
52 flagP010 = 32
53 // flagSmooth supersamples a buffer shrunk under half size (overview
54 // previews): four bilinear samples per pixel instead of one.
55 flagSmooth = 64
56 flagRounded = 128
57 flagRing = 256
58)
59
60// drawParameters is the parameter portion of the shaders' std430 Draw.
61type drawParameters struct {
62 rect [4]int32
63 mapv [4]float32
64 mapy [4]float32
65 color [4]float32
66 crop [4]float32
67 buf [4]uint32
68 misc [4]uint32
69}
70
71// draw is one quad of a frame.
72type draw struct {
73 round, roundInner [4]float32
74 radii [2]uint32
75 pc drawParameters
76 shm *shmCopy
77 im *imported // sampled client dmabuf, for ownership barriers
78 // acquire is the content's explicit-sync fence (nil: implicit).
79 acquire *os.File
80}
81
82// composer is the pipeline state and the objects every draw can bind.
83type composer struct {
84 setLayout vk.DescriptorSetLayout
85 layout vk.PipelineLayout
86 pipeline vk.Pipeline
87 hdrPipeline vk.Pipeline
88 sampler vk.Sampler
89 // Placeholders for the binding a draw does not use: every binding
90 // the shader declares must be valid.
91 dummyImage vk.Image
92 dummyMemory vk.DeviceMemory
93 dummyView vk.ImageView
94 dummyBuffer gpuBuffer
95 // resources is the number of resource slots per batch (one without
96 // nonuniform descriptor indexing); indexing selects the shader variant.
97 resources uint32
98 indexing bool
99}
100
101func (r *Renderer) createComposer() error {
102 d, c := r.dd, &r.compose
103 bindings := []vk.DescriptorSetLayoutBinding{
104 {Binding: 0, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: c.resources, StageFlags: vk.ShaderStageFragmentBit},
105 {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: c.resources, StageFlags: vk.ShaderStageFragmentBit},
106 {Binding: 2, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: c.resources, StageFlags: vk.ShaderStageFragmentBit},
107 {Binding: 3, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit},
108 }
109 sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: uint32(len(bindings)), Bindings: &bindings[0]}
110 if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &c.setLayout)); err != nil {
111 return err
112 }
113 pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &c.setLayout}
114 if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &c.layout)); err != nil {
115 return err
116 }
117 si := vk.SamplerCreateInfo{SType: vk.StructureTypeSamplerCreateInfo, MagFilter: vk.FilterLinear, MinFilter: vk.FilterLinear, AddressModeU: vk.SamplerAddressModeClampToEdge, AddressModeV: vk.SamplerAddressModeClampToEdge, AddressModeW: vk.SamplerAddressModeClampToEdge}
118 if err := checked("vkCreateSampler", d.CreateSampler(r.device, &si, nil, &c.sampler)); err != nil {
119 return err
120 }
121 if err := r.createPipeline(); err != nil {
122 return err
123 }
124 return r.createDummies()
125}
126
127func (r *Renderer) createPipeline() error {
128 fragment := composeFrag
129 if !r.compose.indexing {
130 fragment = composeSingleFrag
131 }
132 if err := r.createComposePipeline(fragment, vk.FormatB8g8r8a8Unorm, &r.compose.pipeline); err != nil {
133 return err
134 }
135 return nil
136}
137
138func (r *Renderer) createComposePipeline(fragment []byte, format vk.Format, pipeline *vk.Pipeline) error {
139 resourceCount := r.compose.resources
140 entry := vk.SpecializationMapEntry{ConstantID: 0, Size: 4}
141 specialization := vk.SpecializationInfo{MapEntryCount: 1, MapEntries: &entry, DataSize: 4, Data: unsafe.Pointer(&resourceCount)}
142 return r.createGraphicsPipeline(composeVert, fragment, format, r.compose.layout, true, &specialization, pipeline)
143}
144
145func (r *Renderer) createGraphicsPipeline(vertex, fragment []byte, format vk.Format, layout vk.PipelineLayout, blend bool, specialization *vk.SpecializationInfo, pipeline *vk.Pipeline) error {
146 d := r.dd
147 vert, err := r.shaderModule(vertex)
148 if err != nil {
149 return err
150 }
151 defer d.DestroyShaderModule(r.device, vert, nil)
152 frag, err := r.shaderModule(fragment)
153 if err != nil {
154 return err
155 }
156 defer d.DestroyShaderModule(r.device, frag, nil)
157 main := []byte("main\x00")
158 stages := []vk.PipelineShaderStageCreateInfo{
159 {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]},
160 {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0], SpecializationInfo: specialization},
161 }
162 vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo}
163 assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList}
164 // The target size is fixed for the renderer's life: static viewport.
165 viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1}
166 scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}
167 viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor}
168 raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1}
169 multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit}
170 attachment := vk.PipelineColorBlendAttachmentState{
171 BlendEnable: 1,
172 SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd,
173 SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd,
174 ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit,
175 }
176 if !blend {
177 attachment.BlendEnable = 0
178 }
179 blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment}
180 rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
181 gpi := vk.GraphicsPipelineCreateInfo{
182 SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering),
183 StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly,
184 ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample,
185 ColorBlendState: &blendState, Layout: layout, BasePipelineIndex: -1,
186 }
187 return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, pipeline))
188}
189
190// createDummies makes the placeholder image (1×1, shader-read layout) and
191// buffer used by resource-table entries a draw does not sample.
192func (r *Renderer) createDummies() error {
193 d, c := r.dd, &r.compose
194 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}
195 if err := checked("vkCreateImage(dummy)", d.CreateImage(r.device, &ii, nil, &c.dummyImage)); err != nil {
196 return err
197 }
198 var req vk.MemoryRequirements
199 d.GetImageMemoryRequirements(r.device, c.dummyImage, &req)
200 kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
201 if err != nil {
202 return err
203 }
204 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
205 if err := checked("vkAllocateMemory(dummy)", d.AllocateMemory(r.device, &alloc, nil, &c.dummyMemory)); err != nil {
206 return err
207 }
208 if err := checked("vkBindImageMemory(dummy)", d.BindImageMemory(r.device, c.dummyImage, c.dummyMemory, 0)); err != nil {
209 return err
210 }
211 if c.dummyView, err = r.imageView(c.dummyImage, vk.FormatB8g8r8a8Unorm); err != nil {
212 return err
213 }
214 if err := r.newGPUBuffer(&c.dummyBuffer, 16); err != nil {
215 return err
216 }
217 // Move the placeholder image to the layout every draw declares.
218 return r.oneShot(func(cmd vk.CommandBuffer) {
219 b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutShaderReadOnlyOptimal, DstAccessMask: vk.AccessShaderReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: c.dummyImage, SubresourceRange: colorRange}
220 d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &b)
221 })
222}
223
224func (r *Renderer) destroyComposer() {
225 d, c := r.dd, &r.compose
226 r.freeGPUBuffer(&c.dummyBuffer)
227 if c.dummyView != 0 {
228 d.DestroyImageView(r.device, c.dummyView, nil)
229 }
230 if c.dummyImage != 0 {
231 d.DestroyImage(r.device, c.dummyImage, nil)
232 }
233 if c.dummyMemory != 0 {
234 d.FreeMemory(r.device, c.dummyMemory, nil)
235 }
236 if c.sampler != 0 {
237 d.DestroySampler(r.device, c.sampler, nil)
238 }
239 if c.pipeline != 0 {
240 d.DestroyPipeline(r.device, c.pipeline, nil)
241 }
242 if c.hdrPipeline != 0 {
243 d.DestroyPipeline(r.device, c.hdrPipeline, nil)
244 }
245 if c.layout != 0 {
246 d.DestroyPipelineLayout(r.device, c.layout, nil)
247 }
248 if c.setLayout != 0 {
249 d.DestroyDescriptorSetLayout(r.device, c.setLayout, nil)
250 }
251 *c = composer{}
252}
253
254func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) {
255 // SPIR-V is read as words: copy into a uint32-aligned slice.
256 words := make([]uint32, len(code)/4)
257 for i := range words {
258 words[i] = binary.LittleEndian.Uint32(code[i*4:])
259 }
260 info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]}
261 var m vk.ShaderModule
262 return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m))
263}
264
265var colorRange = vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
266
267// imageView makes a 2D color view of an image.
268func (r *Renderer) imageView(img vk.Image, format vk.Format) (vk.ImageView, error) {
269 info := vk.ImageViewCreateInfo{SType: vk.StructureTypeImageViewCreateInfo, Image: img, ViewType: vk.ImageViewType2d, Format: format, SubresourceRange: colorRange}
270 var v vk.ImageView
271 return v, checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &v))
272}
273
274// createView makes the color attachment view of a target image.
275func (r *Renderer) createView(t *target) (err error) {
276 t.view, err = r.imageView(t.image, vk.FormatB8g8r8a8Unorm)
277 return err
278}
279
280// fillDraw is a solid rect of c.
281func (r *Renderer) fillDraw(rect image.Rectangle, c [3]uint8) draw {
282 dr := draw{}
283 dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)}
284 dr.pc.color = [4]float32{float32(c[0]) / 255, float32(c[1]) / 255, float32(c[2]) / 255, 1}
285 dr.pc.misc = [4]uint32{modeSolid, 0, uint32(r.width), uint32(r.height)}
286 return dr
287}
288
289// solidDraw is the quad of a single-pixel buffer. Opaque forces alpha to 1
290// keeping rgb, like the shader's flagOpaque does for client pixels; Fade
291// scales the four premultiplied channels. The shader decodes the rgb of a
292// solid on HDR outputs, so there it is pre-encoded such that the decode
293// yields the linear premultiplied color.
294func (r *Renderer) solidDraw(rect image.Rectangle, content *ports.SurfaceContent) draw {
295 c := *content.Solid
296 if content.Opaque {
297 c.A = 1
298 }
299 k := 1 - max(0, min(1, content.Fade))
300 c.R, c.G, c.B, c.A = c.R*k, c.G*k, c.B*k, c.A*k
301 if a := float64(c.A); r.hdrNits > 0 && a > 0 {
302 enc := func(ch float32) float32 {
303 v := max(0, min(1, float64(ch)/a))
304 return float32(linearToSRGB(srgbToLinear(v) * a))
305 }
306 c.R, c.G, c.B = enc(c.R), enc(c.G), enc(c.B)
307 }
308 dr := r.fillDraw(rect, [3]uint8{})
309 dr.pc.color = [4]float32{c.R, c.G, c.B, c.A}
310 return dr
311}
312
313// fadeSolid scales a solid draw's premultiplied color by k (a window's
314// fade). The shader decodes a solid's rgb on HDR outputs: there the rgb is
315// re-encoded so the decode yields the scaled linear premultiplied color, as
316// solidDraw does.
317func (r *Renderer) fadeSolid(dr *draw, k float32) {
318 c := &dr.pc.color
319 if r.hdrNits > 0 {
320 for i := range 3 {
321 c[i] = float32(linearToSRGB(srgbToLinear(float64(c[i])) * float64(k)))
322 }
323 } else {
324 c[0], c[1], c[2] = c[0]*k, c[1]*k, c[2]*k
325 }
326 c[3] *= k
327}
328
329// dimDraw is premultiplied black: blending ONE, ONE_MINUS_SRC_ALPHA
330// leaves the destination at (1-alpha) of its previous value.
331func (r *Renderer) dimDraw(rect image.Rectangle, alpha float64) draw {
332 dr := r.fillDraw(rect, [3]uint8{})
333 dr.pc.color[3] = float32(dimAlpha(alpha, r.hdrNits > 0))
334 return dr
335}
336
337// dimAlpha is the blend alpha of a dim. SDR blends sRGB-encoded values:
338// white becomes (1-alpha) encoded. HDR blends linear light, where the same
339// alpha looks far weaker; it gets the alpha that turns reference white into
340// the linear light of that SDR result. Brighter HDR highlights stay a
341// little brighter than an exact perceptual match.
342func dimAlpha(alpha float64, hdr bool) float64 {
343 alpha = max(0, min(1, alpha))
344 if hdr {
345 alpha = 1 - srgbToLinear(1-alpha)
346 }
347 return alpha
348}
349
350// contentDraw draws the visible rect of a w×h buffer mapped onto full
351// (target pixels) through the inverse of its buffer transform t. exact: the
352// buffer is drawn at its size.
353func (r *Renderer) contentDraw(rect, full image.Rectangle, w, h int, source [4]float32, t ports.BufferTransform, mode uint32, opaque bool) draw {
354 var dr draw
355 if source[2] <= 0 {
356 source = [4]float32{0, 0, float32(w), float32(h)}
357 }
358 // The crop in the surface's axes: its sides swap for a rotation.
359 iw, ih := t.Size(int(source[2]), int(source[3]))
360 cw, ch := float64(iw), float64(ih)
361 // src maps a target point to buffer pixels: affine, so its value at
362 // the target origin and its steps along x and y define it.
363 src := func(x, y float64) (float64, float64) {
364 bx, by := t.ToBuffer((x-float64(full.Min.X))*cw/float64(full.Dx()), (y-float64(full.Min.Y))*ch/float64(full.Dy()), cw, ch)
365 return float64(source[0]) + bx, float64(source[1]) + by
366 }
367 ox, oy := src(0, 0)
368 xx, xy := src(1, 0)
369 yx, yy := src(0, 1)
370 dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)}
371 dr.pc.mapv = [4]float32{float32(ox), float32(oy), float32(xx - ox), float32(xy - oy)}
372 dr.pc.mapy = [4]float32{float32(yx - ox), float32(yy - oy), 0, 0}
373 dr.pc.crop = [4]float32{source[0], source[1], source[0] + source[2], source[1] + source[3]}
374 flags := uint32(0)
375 if opaque {
376 flags |= flagOpaque
377 }
378 if float64(full.Dx()) == cw && float64(full.Dy()) == ch && cw == float64(int(cw)) && ch == float64(int(ch)) {
379 flags |= flagExact
380 }
381 if float64(full.Dx()) < cw/2 || float64(full.Dy()) < ch/2 {
382 flags |= flagSmooth
383 }
384 dr.pc.misc = [4]uint32{mode, flags, uint32(r.width), uint32(r.height)}
385 // color.a is the opacity of a content draw (wp_alpha_modifier_v1).
386 dr.pc.color[3] = 1
387 return dr
388}
389
390// recordDraws draws batches in one rendering pass that clears view to bg, or
391// keeps its contents (keep).
392func (r *Renderer) recordDraws(cmd vk.CommandBuffer, view vk.ImageView, bg [3]uint8, batches []*composeBatch, keep bool) {
393 d, c := r.dd, &r.compose
394 clear := vk.ClearValue{math.Float32bits(float32(bg[0]) / 255), math.Float32bits(float32(bg[1]) / 255), math.Float32bits(float32(bg[2]) / 255), math.Float32bits(1)}
395 if r.hdrNits > 0 {
396 for i := 0; i < 3; i++ {
397 clear[i] = math.Float32bits(float32(srgbToLinear(float64(bg[i]) / 255)))
398 }
399 }
400 attachment, info := &r.call.attach, &r.call.render
401 *attachment = vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpClear, StoreOp: vk.AttachmentStoreOpStore, ClearValue: clear}
402 if keep {
403 attachment.LoadOp = vk.AttachmentLoadOpLoad
404 }
405 *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}
406 d.CmdBeginRendering(cmd, info)
407 pipeline := c.pipeline
408 if r.hdrNits > 0 {
409 pipeline = c.hdrPipeline
410 }
411 d.CmdBindPipeline(cmd, vk.PipelineBindPointGraphics, pipeline)
412 for i := range batches {
413 batch := batches[i]
414 d.CmdBindDescriptorSets(cmd, vk.PipelineBindPointGraphics, c.layout, 0, 1, &batch.set, 0, nil)
415 d.CmdDraw(cmd, 6, batch.count, 0, 0)
416 }
417 d.CmdEndRendering(cmd)
418}