v0.1.0 47aaa0f

pipeline.go RawHistoryFolder

1//go:build linux
2
3package vkdevice
4
5import (
6	"encoding/binary"
7	"fmt"
8	"image"
9	"unsafe"
10
11	"github.com/bnema/nefergui/internal/presentation/shaders"
12	"github.com/bnema/purego-vulkan/vulkan"
13)
14
15// Rect contains a normalized framebuffer rectangle and straight RGBA color.
16// The fragment stage outputs premultiplied RGBA, including transparent alpha.
17type Rect struct {
18	Bounds [4]float32
19	Color  [4]float32
20}
21
22// Instance is a physical-pixel list quad. Color is straight sRGB; the shader
23// premultiplies it. Clip uses x/y/width/height, radii are TL/TR/BR/BL.
24// KindLayer.x = 6 samples set 1 binding 0 as a 2D image texture.
25type Instance struct {
26	Bounds [4]float32
27	Color  [4]float32
28	Clip   [4]float32
29	Radii  [4]float32
30	// UV stores normalized atlas min/max. KindLayer.x is 1 for glyphs;
31	// KindLayer.y selects the array layer. Rectangles keep both zero.
32	UV        [4]float32
33	KindLayer [4]float32
34	Widths    [4]float32    // top, right, bottom, left
35	Sides     [4][4]float32 // top, right, bottom, left straight RGBA
36	Shape     [4]float32    // original box for outline/shadow
37	Shadow    [4]float32    // offset x/y, blur, spread
38}
39
40// Batch preserves the painter's order. Non-image runs use set 0 (atlas);
41// image draws bind Texture at set 1. Source is borrowed until cache resolution.
42// The image's interface value must be comparable and its pixels immutable.
43// First indexes the shared instance buffer; Count is the instance count.
44type Batch struct {
45	First, Count uint32
46	Source       image.Image
47	Texture      *ImageTexture
48}
49
50// ImageTexture owns a GPU image and the descriptor slot assigned by the cache.
51// Its lifetime is gated by the last frame fence that sampled it.
52type ImageTexture struct {
53	Set vulkan.DescriptorSet
54}
55
56type Pipeline struct {
57	device *Device
58	Handle vulkan.Pipeline
59	Layout vulkan.PipelineLayout
60}
61
62// NewRectPipeline fixes the Vulkan viewport and scissor to the image size.
63// The pinned Vulkan binding does not expose vkCmdSetViewport/Scissor, so resize
64// builds a new pipeline rather than recording unsupported dynamic-state calls.
65func (d *Device) NewRectPipeline(width, height int32) (p *Pipeline, err error) {
66	return d.newPipeline(width, height, false)
67}
68
69// NewListPipeline creates an instanced, premultiplied-alpha quad pipeline.
70func (d *Device) NewListPipeline(width, height int32) (p *Pipeline, err error) {
71	return d.newPipeline(width, height, true)
72}
73
74func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err error) {
75	if width <= 0 || height <= 0 {
76		return nil, fmt.Errorf("invalid render area %dx%d", width, height)
77	}
78	p = &Pipeline{device: d}
79	defer func() {
80		if err != nil {
81			p.Close()
82		}
83	}()
84	pushSize := uint32(unsafe.Sizeof(Rect{}))
85	if list {
86		pushSize = 8
87		if d.atlas == nil {
88			d.atlas, err = d.newGlyphAtlas()
89			if err != nil {
90				return nil, err
91			}
92		}
93		if d.images == nil {
94			d.images, err = d.newImageCache()
95			if err != nil {
96				return nil, err
97			}
98		}
99	}
100	rangeInfo := vulkan.PushConstantRange{StageFlags: vulkan.ShaderStageVertexBit | vulkan.ShaderStageFragmentBit, Size: pushSize}
101	layoutInfo := vulkan.PipelineLayoutCreateInfo{SType: vulkan.StructureTypePipelineLayoutCreateInfo, PushConstantRangeCount: 1, PushConstantRanges: &rangeInfo}
102	if list {
103		// Set 0 samples the atlas; set 1 samples the image descriptor
104		// bound for the current image batch.
105		layouts := [2]vulkan.DescriptorSetLayout{d.atlas.Layout, d.images.Layout}
106		layoutInfo.SetLayoutCount = uint32(len(layouts))
107		layoutInfo.SetLayouts = &layouts[0]
108	}
109	if err = vulkan.Check(d.Dispatch.CreatePipelineLayout(d.Logical, &layoutInfo, nil, &p.Layout)); err != nil {
110		return nil, fmt.Errorf("create rect pipeline layout: %w", err)
111	}
112	vert, frag := shaders.Vertex, shaders.Fragment
113	if list {
114		vert, frag = shaders.ListVertex, shaders.ListFragment
115	}
116	vertex, err := d.shaderModule(vert)
117	if err != nil {
118		return nil, err
119	}
120	defer d.Dispatch.DestroyShaderModule(d.Logical, vertex, nil)
121	fragment, err := d.shaderModule(frag)
122	if err != nil {
123		return nil, err
124	}
125	defer d.Dispatch.DestroyShaderModule(d.Logical, fragment, nil)
126	main := []byte("main\x00")
127	stages := [2]vulkan.PipelineShaderStageCreateInfo{
128		{SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageVertexBit, Module: vertex, Name: &main[0]},
129		{SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fragment, Name: &main[0]},
130	}
131	vertexInput := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo}
132	var binding vulkan.VertexInputBindingDescription
133	var attrs [13]vulkan.VertexInputAttributeDescription
134	if list {
135		binding = vulkan.VertexInputBindingDescription{Stride: uint32(unsafe.Sizeof(Instance{})), InputRate: vulkan.VertexInputRateInstance}
136		offsets := [13]uint32{uint32(unsafe.Offsetof(Instance{}.Bounds)), uint32(unsafe.Offsetof(Instance{}.Color)), uint32(unsafe.Offsetof(Instance{}.Clip)), uint32(unsafe.Offsetof(Instance{}.Radii)), uint32(unsafe.Offsetof(Instance{}.UV)), uint32(unsafe.Offsetof(Instance{}.KindLayer)), uint32(unsafe.Offsetof(Instance{}.Widths)), uint32(unsafe.Offsetof(Instance{}.Sides)), uint32(unsafe.Offsetof(Instance{}.Sides)) + 16, uint32(unsafe.Offsetof(Instance{}.Sides)) + 32, uint32(unsafe.Offsetof(Instance{}.Sides)) + 48, uint32(unsafe.Offsetof(Instance{}.Shape)), uint32(unsafe.Offsetof(Instance{}.Shadow))}
137		for i := range attrs {
138			attrs[i] = vulkan.VertexInputAttributeDescription{Location: uint32(i), Format: vulkan.FormatR32g32b32a32Sfloat, Offset: offsets[i]}
139		}
140		vertexInput.VertexBindingDescriptionCount = 1
141		vertexInput.VertexBindingDescriptions = &binding
142		vertexInput.VertexAttributeDescriptionCount = uint32(len(attrs))
143		vertexInput.VertexAttributeDescriptions = &attrs[0]
144	}
145	assembly := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList}
146	viewport := vulkan.Viewport{Width: float32(width), Height: float32(height), MaxDepth: 1}
147	scissor := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(width), Height: uint32(height)}}
148	vp := vulkan.PipelineViewportStateCreateInfo{SType: vulkan.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor}
149	raster := vulkan.PipelineRasterizationStateCreateInfo{SType: vulkan.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vulkan.PolygonModeFill, CullMode: vulkan.CullModeNone, FrontFace: vulkan.FrontFaceCounterClockwise, LineWidth: 1}
150	ms := vulkan.PipelineMultisampleStateCreateInfo{SType: vulkan.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vulkan.SampleCount1Bit}
151	blendAttachment := vulkan.PipelineColorBlendAttachmentState{BlendEnable: 1, SrcColorBlendFactor: vulkan.BlendFactorOne, DstColorBlendFactor: vulkan.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vulkan.BlendOpAdd, SrcAlphaBlendFactor: vulkan.BlendFactorOne, DstAlphaBlendFactor: vulkan.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vulkan.BlendOpAdd, ColorWriteMask: vulkan.ColorComponentRBit | vulkan.ColorComponentGBit | vulkan.ColorComponentBBit | vulkan.ColorComponentABit}
152	blend := vulkan.PipelineColorBlendStateCreateInfo{SType: vulkan.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &blendAttachment}
153	format := vulkan.Format(vulkan.FormatB8g8r8a8Unorm)
154	rendering := vulkan.PipelineRenderingCreateInfo{SType: vulkan.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
155	ci := vulkan.GraphicsPipelineCreateInfo{SType: vulkan.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, ViewportState: &vp, RasterizationState: &raster, MultisampleState: &ms, ColorBlendState: &blend, Layout: p.Layout}
156	if err = vulkan.Check(d.Dispatch.CreateGraphicsPipelines(d.Logical, 0, 1, &ci, nil, &p.Handle)); err != nil {
157		return nil, fmt.Errorf("create rectangle pipeline with dynamic rendering: %w", err)
158	}
159	return p, nil
160}
161
162func (d *Device) shaderModule(data []byte) (vulkan.ShaderModule, error) {
163	if len(data) < 20 || len(data)%4 != 0 || binary.LittleEndian.Uint32(data) != 0x07230203 {
164		return 0, fmt.Errorf("invalid embedded SPIR-V")
165	}
166	words := make([]uint32, len(data)/4)
167	for i := range words {
168		words[i] = binary.LittleEndian.Uint32(data[i*4:])
169	}
170	ci := vulkan.ShaderModuleCreateInfo{SType: vulkan.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(data)), Code: &words[0]}
171	var module vulkan.ShaderModule
172	if err := vulkan.Check(d.Dispatch.CreateShaderModule(d.Logical, &ci, nil, &module)); err != nil {
173		return 0, err
174	}
175	return module, nil
176}
177
178func (p *Pipeline) Close() {
179	if p == nil || p.device == nil {
180		return
181	}
182	if p.Handle != 0 {
183		p.device.Dispatch.DestroyPipeline(p.device.Logical, p.Handle, nil)
184		p.Handle = 0
185	}
186	if p.Layout != 0 {
187		p.device.Dispatch.DestroyPipelineLayout(p.device.Logical, p.Layout, nil)
188		p.Layout = 0
189	}
190}

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 1

Tags 12

↑↓ moveenter openesc close