v0.5.1 657f658

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

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