hdr.go RawHistoryFolder

1package vulkan
2
3import (
4	_ "embed"
5	"fmt"
6	"unsafe"
7
8	vk "github.com/bnema/purego-vulkan/vulkan"
9)
10
11var (
12	//go:embed shaders/hdr.vert.spv
13	hdrVert []byte
14	//go:embed shaders/hdr.frag.spv
15	hdrFrag []byte
16)
17
18type hdrPass struct {
19	setLayout vk.DescriptorSetLayout
20	layout    vk.PipelineLayout
21	pipeline  vk.Pipeline
22	sampler   vk.Sampler
23	pool      vk.DescriptorPool
24	set       vk.DescriptorSet
25}
26
27// SetHDR prepares the output transform before ExportTargets. The renderer
28// remains owner-goroutine confined; all GPU objects are reused each frame.
29func (r *Renderer) SetHDR(nits float64) {
30	if nits != r.hdrNits {
31		r.cursorCache.Reset()
32		if r.hdrNits > 0 && nits > 0 {
33			r.hdrOwn.valid = false
34		}
35		if (nits > 0) != (r.hdrNits > 0) && r.device != 0 {
36			_ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device))
37			r.idle()
38			r.hdrOwn.valid = false
39			r.own.valid = false
40			for _, t := range r.targets {
41				t.valid = false
42			}
43			if r.last == &r.hdrOwn {
44				r.last = nil
45			}
46		}
47	}
48	r.hdrNits = nits
49	if nits <= 0 {
50		r.cursorCache.Reset()
51		r.hdrError = nil
52		return
53	}
54	if r.hdrOwn.image == 0 {
55		r.hdrError = r.createHDROwn()
56		if r.hdrError != nil {
57			r.dropHDROwn()
58			return
59		}
60	}
61	if r.compose.hdrPipeline == 0 {
62		fragment := composeHDRFrag
63		if !r.compose.indexing {
64			fragment = composeHDRSingleFrag
65		}
66		r.hdrError = r.createComposePipeline(fragment, vk.FormatR16g16b16a16Sfloat, &r.compose.hdrPipeline)
67		if r.hdrError != nil {
68			r.dropHDROwn()
69			return
70		}
71	}
72	if r.hdr.pipeline != 0 {
73		return
74	}
75	// The port has no error return: ExportTargets fails if preparation fails.
76	r.hdrError = r.createHDR()
77	if r.hdrError != nil {
78		r.destroyHDR()
79		r.dropHDROwn()
80	}
81}
82
83// createHDROwn allocates the fp16 linear-light composition image once per
84// renderer; toggling HDR reuses it after the previous frames complete.
85func (r *Renderer) createHDROwn() error {
86	// An HDR compositor needs an fp16 blendable color attachment that can
87	// also be sampled by the PQ pass and copied only for capture.
88	if r.physical == 0 {
89		return fmt.Errorf("HDR fp16 device unavailable")
90	}
91	props := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2}
92	r.id.GetPhysicalDeviceFormatProperties2(r.physical, vk.FormatR16g16b16a16Sfloat, &props)
93	need := vk.FormatFeatureFlags(vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit | vk.FormatFeatureSampledImageBit | formatFeatureTransferSrc)
94	if props.FormatProperties.OptimalTilingFeatures&need != need {
95		return fmt.Errorf("HDR fp16 composition format lacks attachment/blend/sample/readback features")
96	}
97	d, t := r.dd, &r.hdrOwn
98	ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatR16g16b16a16Sfloat, Extent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: targetUsage | vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
99	if err := checked("vkCreateImage(HDR composition)", d.CreateImage(r.device, &ii, nil, &t.image)); err != nil {
100		return err
101	}
102	var req vk.MemoryRequirements
103	d.GetImageMemoryRequirements(r.device, t.image, &req)
104	kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
105	if err != nil {
106		return err
107	}
108	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
109	if err := checked("vkAllocateMemory(HDR composition)", d.AllocateMemory(r.device, &alloc, nil, &t.memory)); err != nil {
110		return err
111	}
112	if err := checked("vkBindImageMemory(HDR composition)", d.BindImageMemory(r.device, t.image, t.memory, 0)); err != nil {
113		return err
114	}
115	t.view, err = r.imageView(t.image, vk.FormatR16g16b16a16Sfloat)
116	return err
117}
118
119func (r *Renderer) createHDR() error {
120	d, h := r.dd, &r.hdr
121	binding := vk.DescriptorSetLayoutBinding{Binding: 0, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}
122	sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 1, Bindings: &binding}
123	if err := checked("vkCreateDescriptorSetLayout(HDR)", d.CreateDescriptorSetLayout(r.device, &sli, nil, &h.setLayout)); err != nil {
124		return err
125	}
126	push := vk.PushConstantRange{StageFlags: vk.ShaderStageFragmentBit, Size: 4}
127	pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &h.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &push}
128	if err := checked("vkCreatePipelineLayout(HDR)", d.CreatePipelineLayout(r.device, &pli, nil, &h.layout)); err != nil {
129		return err
130	}
131	if err := r.createGraphicsPipeline(hdrVert, hdrFrag, vk.FormatA2r10g10b10UnormPack32, h.layout, false, nil, &h.pipeline); err != nil {
132		return err
133	}
134	si := vk.SamplerCreateInfo{SType: vk.StructureTypeSamplerCreateInfo, MagFilter: vk.FilterNearest, MinFilter: vk.FilterNearest, AddressModeU: vk.SamplerAddressModeClampToEdge, AddressModeV: vk.SamplerAddressModeClampToEdge, AddressModeW: vk.SamplerAddressModeClampToEdge}
135	if err := checked("vkCreateSampler(HDR)", d.CreateSampler(r.device, &si, nil, &h.sampler)); err != nil {
136		return err
137	}
138	size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}
139	dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size}
140	if err := checked("vkCreateDescriptorPool(HDR)", d.CreateDescriptorPool(r.device, &dpi, nil, &h.pool)); err != nil {
141		return err
142	}
143	dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: h.pool, DescriptorSetCount: 1, SetLayouts: &h.setLayout}
144	if err := checked("vkAllocateDescriptorSets(HDR)", d.AllocateDescriptorSets(r.device, &dai, &h.set)); err != nil {
145		return err
146	}
147	img := vk.DescriptorImageInfo{Sampler: h.sampler, ImageView: r.hdrOwn.view, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal}
148	write := vk.WriteDescriptorSet{SType: vk.StructureTypeWriteDescriptorSet, DstSet: h.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &img}
149	d.UpdateDescriptorSets(r.device, 1, &write, 0, nil)
150	return nil
151}
152
153func (r *Renderer) destroyHDR() {
154	if r.dd == nil {
155		return
156	}
157	d, h := r.dd, &r.hdr
158	if h.pool != 0 {
159		d.DestroyDescriptorPool(r.device, h.pool, nil)
160	}
161	if h.sampler != 0 {
162		d.DestroySampler(r.device, h.sampler, nil)
163	}
164	if h.pipeline != 0 {
165		d.DestroyPipeline(r.device, h.pipeline, nil)
166	}
167	if h.layout != 0 {
168		d.DestroyPipelineLayout(r.device, h.layout, nil)
169	}
170	if h.setLayout != 0 {
171		d.DestroyDescriptorSetLayout(r.device, h.setLayout, nil)
172	}
173	*h = hdrPass{}
174}
175
176// recordHDR converts the fully composed linear HDR image in the same command buffer.
177func (r *Renderer) recordHDR(cmd vk.CommandBuffer, out *target) {
178	d, h := r.dd, &r.hdr
179	in, b := &r.call.hdrIn, &r.call.hdrOut
180	attachment, info := &r.call.attach, &r.call.render
181	*in = vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, SrcAccessMask: vk.AccessColorAttachmentWriteBit, DstAccessMask: vk.AccessShaderReadBit, OldLayout: vk.ImageLayoutColorAttachmentOptimal, NewLayout: vk.ImageLayoutShaderReadOnlyOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: r.hdrOwn.image, SubresourceRange: colorRange}
182	d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, in)
183	*b = vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessColorAttachmentWriteBit, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutColorAttachmentOptimal, SrcQueueFamilyIndex: vk.QueueFamilyForeignEXT, DstQueueFamilyIndex: r.family, Image: out.image, SubresourceRange: colorRange}
184	d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, b)
185	*attachment = vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: out.view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpDontCare, StoreOp: vk.AttachmentStoreOpStore}
186	*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}
187	d.CmdBeginRendering(cmd, info)
188	d.CmdBindPipeline(cmd, vk.PipelineBindPointGraphics, h.pipeline)
189	d.CmdBindDescriptorSets(cmd, vk.PipelineBindPointGraphics, h.layout, 0, 1, &h.set, 0, nil)
190	r.call.hdrNits = float32(r.hdrNits)
191	d.CmdPushConstants(cmd, h.layout, vk.ShaderStageFragmentBit, 0, 4, unsafe.Pointer(&r.call.hdrNits))
192	d.CmdDraw(cmd, 3, 1, 0, 0)
193	d.CmdEndRendering(cmd)
194	b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, 0
195	b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutGeneral
196	b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT
197	d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, b)
198	in.SrcAccessMask, in.DstAccessMask = vk.AccessShaderReadBit, vk.AccessTransferReadBit
199	in.OldLayout, in.NewLayout = vk.ImageLayoutShaderReadOnlyOptimal, vk.ImageLayoutTransferSrcOptimal
200	d.CmdPipelineBarrier(cmd, vk.PipelineStageFragmentShaderBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, in)
201}
202
203// dropHDROwn frees the HDR composition image with the capture pass that
204// samples it (its descriptor set refers to the view). Pending captures
205// finish first: this runs on SetHDR failures, never per frame.
206func (r *Renderer) dropHDROwn() {
207	if r.capturePass.pipeline != 0 {
208		for _, s := range r.captures {
209			if s.pending {
210				_ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device))
211				r.idle()
212				break
213			}
214		}
215		r.destroyCapturePass()
216	}
217	r.freeTarget(&r.hdrOwn)
218}

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