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