feat/xdg-foreign fdbb95e

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 = cursorConversion{}
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 = cursorConversion{}
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		r.hdrError = r.createGraphicsPipeline(composeVert, composeHDRFrag, vk.FormatR16g16b16a16Sfloat, r.compose.layout, true, &r.compose.hdrPipeline)
63		if r.hdrError != nil {
64			r.dropHDROwn()
65			return
66		}
67	}
68	if r.hdr.pipeline != 0 {
69		return
70	}
71	// The port has no error return: ExportTargets fails if preparation fails.
72	r.hdrError = r.createHDR()
73	if r.hdrError != nil {
74		r.destroyHDR()
75		r.dropHDROwn()
76	}
77}
78
79// createHDROwn allocates the fp16 linear-light composition image once per
80// renderer; toggling HDR reuses it after the previous frames complete.
81func (r *Renderer) createHDROwn() error {
82	// An HDR compositor needs an fp16 blendable color attachment that can
83	// also be sampled by the PQ pass and copied only for capture.
84	if r.physical == 0 {
85		return fmt.Errorf("HDR fp16 device unavailable")
86	}
87	props := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2}
88	r.id.GetPhysicalDeviceFormatProperties2(r.physical, vk.FormatR16g16b16a16Sfloat, &props)
89	need := vk.FormatFeatureFlags(vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit | vk.FormatFeatureSampledImageBit | formatFeatureTransferSrc)
90	if props.FormatProperties.OptimalTilingFeatures&need != need {
91		return fmt.Errorf("HDR fp16 composition format lacks attachment/blend/sample/readback features")
92	}
93	d, t := r.dd, &r.hdrOwn
94	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}
95	if err := checked("vkCreateImage(HDR composition)", d.CreateImage(r.device, &ii, nil, &t.image)); err != nil {
96		return err
97	}
98	var req vk.MemoryRequirements
99	d.GetImageMemoryRequirements(r.device, t.image, &req)
100	kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
101	if err != nil {
102		return err
103	}
104	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
105	if err := checked("vkAllocateMemory(HDR composition)", d.AllocateMemory(r.device, &alloc, nil, &t.memory)); err != nil {
106		return err
107	}
108	if err := checked("vkBindImageMemory(HDR composition)", d.BindImageMemory(r.device, t.image, t.memory, 0)); err != nil {
109		return err
110	}
111	t.view, err = r.imageView(t.image, vk.FormatR16g16b16a16Sfloat)
112	return err
113}
114
115func (r *Renderer) createHDR() error {
116	d, h := r.dd, &r.hdr
117	binding := vk.DescriptorSetLayoutBinding{Binding: 0, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}
118	sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 1, Bindings: &binding}
119	if err := checked("vkCreateDescriptorSetLayout(HDR)", d.CreateDescriptorSetLayout(r.device, &sli, nil, &h.setLayout)); err != nil {
120		return err
121	}
122	push := vk.PushConstantRange{StageFlags: vk.ShaderStageFragmentBit, Size: 4}
123	pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &h.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &push}
124	if err := checked("vkCreatePipelineLayout(HDR)", d.CreatePipelineLayout(r.device, &pli, nil, &h.layout)); err != nil {
125		return err
126	}
127	if err := r.createGraphicsPipeline(hdrVert, hdrFrag, vk.FormatA2r10g10b10UnormPack32, h.layout, false, &h.pipeline); err != nil {
128		return err
129	}
130	si := vk.SamplerCreateInfo{SType: vk.StructureTypeSamplerCreateInfo, MagFilter: vk.FilterNearest, MinFilter: vk.FilterNearest, AddressModeU: vk.SamplerAddressModeClampToEdge, AddressModeV: vk.SamplerAddressModeClampToEdge, AddressModeW: vk.SamplerAddressModeClampToEdge}
131	if err := checked("vkCreateSampler(HDR)", d.CreateSampler(r.device, &si, nil, &h.sampler)); err != nil {
132		return err
133	}
134	size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}
135	dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size}
136	if err := checked("vkCreateDescriptorPool(HDR)", d.CreateDescriptorPool(r.device, &dpi, nil, &h.pool)); err != nil {
137		return err
138	}
139	dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: h.pool, DescriptorSetCount: 1, SetLayouts: &h.setLayout}
140	if err := checked("vkAllocateDescriptorSets(HDR)", d.AllocateDescriptorSets(r.device, &dai, &h.set)); err != nil {
141		return err
142	}
143	img := vk.DescriptorImageInfo{Sampler: h.sampler, ImageView: r.hdrOwn.view, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal}
144	write := vk.WriteDescriptorSet{SType: vk.StructureTypeWriteDescriptorSet, DstSet: h.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &img}
145	d.UpdateDescriptorSets(r.device, 1, &write, 0, nil)
146	return nil
147}
148
149func (r *Renderer) destroyHDR() {
150	if r.dd == nil {
151		return
152	}
153	d, h := r.dd, &r.hdr
154	if h.pool != 0 {
155		d.DestroyDescriptorPool(r.device, h.pool, nil)
156	}
157	if h.sampler != 0 {
158		d.DestroySampler(r.device, h.sampler, nil)
159	}
160	if h.pipeline != 0 {
161		d.DestroyPipeline(r.device, h.pipeline, nil)
162	}
163	if h.layout != 0 {
164		d.DestroyPipelineLayout(r.device, h.layout, nil)
165	}
166	if h.setLayout != 0 {
167		d.DestroyDescriptorSetLayout(r.device, h.setLayout, nil)
168	}
169	*h = hdrPass{}
170}
171
172// recordHDR converts the fully composed linear HDR image in the same command buffer.
173func (r *Renderer) recordHDR(cmd vk.CommandBuffer, out *target) {
174	d, h := r.dd, &r.hdr
175	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}
176	d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &in)
177	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}
178	d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b)
179	attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: out.view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpDontCare, StoreOp: vk.AttachmentStoreOpStore}
180	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}
181	d.CmdBeginRendering(cmd, &info)
182	d.CmdBindPipeline(cmd, vk.PipelineBindPointGraphics, h.pipeline)
183	d.CmdBindDescriptorSets(cmd, vk.PipelineBindPointGraphics, h.layout, 0, 1, &h.set, 0, nil)
184	nits := float32(r.hdrNits)
185	d.CmdPushConstants(cmd, h.layout, vk.ShaderStageFragmentBit, 0, 4, unsafe.Pointer(&nits))
186	d.CmdDraw(cmd, 3, 1, 0, 0)
187	d.CmdEndRendering(cmd)
188	b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, 0
189	b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutGeneral
190	b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT
191	d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b)
192	in.SrcAccessMask, in.DstAccessMask = vk.AccessShaderReadBit, vk.AccessTransferReadBit
193	in.OldLayout, in.NewLayout = vk.ImageLayoutShaderReadOnlyOptimal, vk.ImageLayoutTransferSrcOptimal
194	d.CmdPipelineBarrier(cmd, vk.PipelineStageFragmentShaderBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &in)
195}
196
197// dropHDROwn frees the HDR composition image with the capture pass that
198// samples it (its descriptor set refers to the view). Pending captures
199// finish first: this runs on SetHDR failures, never per frame.
200func (r *Renderer) dropHDROwn() {
201	if r.capturePass.pipeline != 0 {
202		for _, s := range r.captures {
203			if s.pending {
204				_ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device))
205				r.idle()
206				break
207			}
208		}
209		r.destroyCapturePass()
210	}
211	r.freeTarget(&r.hdrOwn)
212}

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