v0.1.0 d9218d6

compose.go RawHistoryFolder

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}

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 26

Tags 10

↑↓ moveenter openesc close