detached e3eeaa9

refactor: remove the unused rect pipeline and shaders; report validation errors on Target.Close

commit
e3eeaa96f81eb8195fe8ac9e848f40381ae28165
author
brice · · 2026-10-01 17:54 UTC
parent
c104571
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
11 files · +72 -172
M internal/presentation/session/target.go +8 -1
@@ -557,7 +557,8 @@ func (t *Target) Readback(id uint64) (*image.NRGBA, error) {
557557
558558 // Close waits for the GPU, then frees every descriptor and image, always
559559 // completing the teardown. The compositor's imports keep their own kernel
560-// references. It returns the joined errors of the steps that failed.
560+// references. It returns the joined errors of the steps that failed and any
561+// Vulkan validation errors recorded during the Target's life.
561562 func (t *Target) Close() error {
562563 if t == nil || t.closed {
563564 return nil
@@ -591,6 +592,12 @@ func (t *Target) Close() error {
591592 }
592593 errs = append(errs, t.Node.Close())
593594 }
595+ if t.Device != nil {
596+ // Read the messages before Close destroys the messenger.
597+ if msgs := t.Device.Validation.Messages(); len(msgs) > 0 {
598+ errs = append(errs, fmt.Errorf("%d Vulkan validation errors: %v", len(msgs), msgs))
599+ }
600+ }
594601 t.Device.Close()
595602 return errors.Join(errs...)
596603 }
@@ -557,7 +557,8 @@ func (t *Target) Readback(id uint64) (*image.NRGBA, error) {
557 557
558 // Close waits for the GPU, then frees every descriptor and image, always558 // Close waits for the GPU, then frees every descriptor and image, always
559 // completing the teardown. The compositor's imports keep their own kernel559 // completing the teardown. The compositor's imports keep their own kernel
560-// references. It returns the joined errors of the steps that failed.560+// references. It returns the joined errors of the steps that failed and any
561+// Vulkan validation errors recorded during the Target's life.
561 func (t *Target) Close() error {562 func (t *Target) Close() error {
562 if t == nil || t.closed {563 if t == nil || t.closed {
563 return nil564 return nil
@@ -591,6 +592,12 @@ func (t *Target) Close() error {
591 }592 }
592 errs = append(errs, t.Node.Close())593 errs = append(errs, t.Node.Close())
593 }594 }
595+ if t.Device != nil {
596+ // Read the messages before Close destroys the messenger.
597+ if msgs := t.Device.Validation.Messages(); len(msgs) > 0 {
598+ errs = append(errs, fmt.Errorf("%d Vulkan validation errors: %v", len(msgs), msgs))
599+ }
600+ }
594 t.Device.Close()601 t.Device.Close()
595 return errors.Join(errs...)602 return errors.Join(errs...)
596 }603 }
M internal/presentation/shaders/freshness_test.go +2 -2
@@ -12,7 +12,7 @@ func TestEmbeddedSPIRV(t *testing.T) {
1212 for _, tc := range []struct {
1313 name string
1414 data []byte
15- }{{"vertex", Vertex}, {"fragment", Fragment}, {"list vertex", ListVertex}, {"list fragment", ListFragment}} {
15+ }{{"list vertex", ListVertex}, {"list fragment", ListFragment}} {
1616 if len(tc.data) < 20 || !bytes.Equal(tc.data[:4], []byte{3, 2, 35, 7}) || len(tc.data)%4 != 0 {
1717 t.Fatalf("%s: not SPIR-V", tc.name)
1818 }
@@ -27,7 +27,7 @@ func TestShaderFreshness(t *testing.T) {
2727 for _, tc := range []struct {
2828 file, stage string
2929 embedded []byte
30- }{{"rect.vert", "vertex", Vertex}, {"rect.frag", "fragment", Fragment}, {"list.vert", "vertex", ListVertex}, {"list.frag", "fragment", ListFragment}} {
30+ }{{"list.vert", "vertex", ListVertex}, {"list.frag", "fragment", ListFragment}} {
3131 out := filepath.Join(t.TempDir(), "shader.spv")
3232 cmd := exec.Command(bin, "-O", "-fshader-stage="+tc.stage, "-o", out, tc.file)
3333 if log, err := cmd.CombinedOutput(); err != nil {
@@ -12,7 +12,7 @@ func TestEmbeddedSPIRV(t *testing.T) {
12 for _, tc := range []struct {12 for _, tc := range []struct {
13 name string13 name string
14 data []byte14 data []byte
15- }{{"vertex", Vertex}, {"fragment", Fragment}, {"list vertex", ListVertex}, {"list fragment", ListFragment}} {15+ }{{"list vertex", ListVertex}, {"list fragment", ListFragment}} {
16 if len(tc.data) < 20 || !bytes.Equal(tc.data[:4], []byte{3, 2, 35, 7}) || len(tc.data)%4 != 0 {16 if len(tc.data) < 20 || !bytes.Equal(tc.data[:4], []byte{3, 2, 35, 7}) || len(tc.data)%4 != 0 {
17 t.Fatalf("%s: not SPIR-V", tc.name)17 t.Fatalf("%s: not SPIR-V", tc.name)
18 }18 }
@@ -27,7 +27,7 @@ func TestShaderFreshness(t *testing.T) {
27 for _, tc := range []struct {27 for _, tc := range []struct {
28 file, stage string28 file, stage string
29 embedded []byte29 embedded []byte
30- }{{"rect.vert", "vertex", Vertex}, {"rect.frag", "fragment", Fragment}, {"list.vert", "vertex", ListVertex}, {"list.frag", "fragment", ListFragment}} {30+ }{{"list.vert", "vertex", ListVertex}, {"list.frag", "fragment", ListFragment}} {
31 out := filepath.Join(t.TempDir(), "shader.spv")31 out := filepath.Join(t.TempDir(), "shader.spv")
32 cmd := exec.Command(bin, "-O", "-fshader-stage="+tc.stage, "-o", out, tc.file)32 cmd := exec.Command(bin, "-O", "-fshader-stage="+tc.stage, "-o", out, tc.file)
33 if log, err := cmd.CombinedOutput(); err != nil {33 if log, err := cmd.CombinedOutput(); err != nil {
D internal/presentation/shaders/rect.frag +0 -9
@@ -1,9 +0,0 @@
1-#version 450
2-layout(push_constant) uniform Rect {
3- vec4 bounds;
4- vec4 color;
5-} pc;
6-layout(location = 0) out vec4 outColor;
7-void main() {
8- outColor = vec4(pc.color.rgb * pc.color.a, pc.color.a);
9-}
@@ -1,9 +0,0 @@
1-#version 450
2-layout(push_constant) uniform Rect {
3- vec4 bounds;
4- vec4 color;
5-} pc;
6-layout(location = 0) out vec4 outColor;
7-void main() {
8- outColor = vec4(pc.color.rgb * pc.color.a, pc.color.a);
9-}
D internal/presentation/shaders/rect.frag.spv +0 -0 binary file: no text diff

binary file: no text diff

D internal/presentation/shaders/rect.vert +0 -12
@@ -1,12 +0,0 @@
1-#version 450
2-layout(push_constant) uniform Rect {
3- vec4 bounds; // normalized x,y,width,height in framebuffer coordinates
4- vec4 color; // straight input alpha, premultiplied by the fragment stage
5-} pc;
6-void main() {
7- vec2 pos[6] = vec2[6](
8- vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0),
9- vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0));
10- vec2 xy = pc.bounds.xy + pos[gl_VertexIndex] * pc.bounds.zw;
11- gl_Position = vec4(xy.x * 2.0 - 1.0, 1.0 - xy.y * 2.0, 0.0, 1.0);
12-}
@@ -1,12 +0,0 @@
1-#version 450
2-layout(push_constant) uniform Rect {
3- vec4 bounds; // normalized x,y,width,height in framebuffer coordinates
4- vec4 color; // straight input alpha, premultiplied by the fragment stage
5-} pc;
6-void main() {
7- vec2 pos[6] = vec2[6](
8- vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0),
9- vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0));
10- vec2 xy = pc.bounds.xy + pos[gl_VertexIndex] * pc.bounds.zw;
11- gl_Position = vec4(xy.x * 2.0 - 1.0, 1.0 - xy.y * 2.0, 0.0, 1.0);
12-}
D internal/presentation/shaders/rect.vert.spv +0 -0 binary file: no text diff

binary file: no text diff

M internal/presentation/shaders/shaders.go +0 -8
@@ -4,17 +4,9 @@ package shaders
44
55 import _ "embed"
66
7-//go:generate glslc -O -fshader-stage=vertex -o rect.vert.spv rect.vert
8-//go:generate glslc -O -fshader-stage=fragment -o rect.frag.spv rect.frag
97 //go:generate glslc -O -fshader-stage=vertex -o list.vert.spv list.vert
108 //go:generate glslc -O -fshader-stage=fragment -o list.frag.spv list.frag
119
12-//go:embed rect.vert.spv
13-var Vertex []byte
14-
15-//go:embed rect.frag.spv
16-var Fragment []byte
17-
1810 //go:embed list.vert.spv
1911 var ListVertex []byte
2012
@@ -4,17 +4,9 @@ package shaders
4 4
5 import _ "embed"5 import _ "embed"
6 6
7-//go:generate glslc -O -fshader-stage=vertex -o rect.vert.spv rect.vert
8-//go:generate glslc -O -fshader-stage=fragment -o rect.frag.spv rect.frag
9 //go:generate glslc -O -fshader-stage=vertex -o list.vert.spv list.vert7 //go:generate glslc -O -fshader-stage=vertex -o list.vert.spv list.vert
10 //go:generate glslc -O -fshader-stage=fragment -o list.frag.spv list.frag8 //go:generate glslc -O -fshader-stage=fragment -o list.frag.spv list.frag
11 9
12-//go:embed rect.vert.spv
13-var Vertex []byte
14-
15-//go:embed rect.frag.spv
16-var Fragment []byte
17-
18 //go:embed list.vert.spv10 //go:embed list.vert.spv
19 var ListVertex []byte11 var ListVertex []byte
20 12
M internal/presentation/vkdevice/pipeline.go +31 -61
@@ -12,13 +12,6 @@ import (
1212 "github.com/bnema/purego-vulkan/vulkan"
1313 )
1414
15-// Rect contains a normalized framebuffer rectangle and straight RGBA color.
16-// The fragment stage outputs premultiplied RGBA, including transparent alpha.
17-type Rect struct {
18- Bounds [4]float32
19- Color [4]float32
20-}
21-
2215 // Instance is a physical-pixel list quad. Color is straight sRGB; the shader
2316 // premultiplies it. Clip uses x/y/width/height, radii are TL/TR/BR/BL.
2417 // KindLayer.x = 6 samples set 1 binding 0 as a 2D image texture.
@@ -59,19 +52,11 @@ type Pipeline struct {
5952 Layout vulkan.PipelineLayout
6053 }
6154
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.
65-func (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.
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.
7059 func (d *Device) NewListPipeline(width, height int32) (p *Pipeline, err error) {
71- return d.newPipeline(width, height, true)
72-}
73-
74-func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err error) {
7560 if width <= 0 || height <= 0 {
7661 return nil, fmt.Errorf("invalid render area %dx%d", width, height)
7762 }
@@ -81,44 +66,32 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e
8166 p.Close()
8267 }
8368 }()
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- }
69+ if d.atlas == nil {
70+ d.atlas, err = d.newGlyphAtlas()
71+ if err != nil {
72+ return nil, err
9873 }
9974 }
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]
75+ if d.images == nil {
76+ d.images, err = d.newImageCache()
77+ if err != nil {
78+ return nil, err
79+ }
10880 }
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}
10986 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)
87+ return nil, fmt.Errorf("create list pipeline layout: %w", err)
11188 }
112- vert, frag := shaders.Vertex, shaders.Fragment
113- if list {
114- vert, frag = shaders.ListVertex, shaders.ListFragment
115- }
116- vertex, err := d.shaderModule(vert)
89+ vertex, err := d.shaderModule(shaders.ListVertex)
11790 if err != nil {
11891 return nil, err
11992 }
12093 defer d.Dispatch.DestroyShaderModule(d.Logical, vertex, nil)
121- fragment, err := d.shaderModule(frag)
94+ fragment, err := d.shaderModule(shaders.ListFragment)
12295 if err != nil {
12396 return nil, err
12497 }
@@ -129,19 +102,16 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e
129102 {SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fragment, Name: &main[0]},
130103 }
131104 vertexInput := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo}
132- var binding vulkan.VertexInputBindingDescription
133105 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- }
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]
145115 assembly := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList}
146116 viewport := vulkan.Viewport{Width: float32(width), Height: float32(height), MaxDepth: 1}
147117 scissor := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(width), Height: uint32(height)}}
@@ -154,7 +124,7 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e
154124 rendering := vulkan.PipelineRenderingCreateInfo{SType: vulkan.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
155125 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}
156126 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)
127+ return nil, fmt.Errorf("create list pipeline with dynamic rendering: %w", err)
158128 }
159129 return p, nil
160130 }
@@ -12,13 +12,6 @@ import (
12 "github.com/bnema/purego-vulkan/vulkan"12 "github.com/bnema/purego-vulkan/vulkan"
13 )13 )
14 14
15-// Rect contains a normalized framebuffer rectangle and straight RGBA color.
16-// The fragment stage outputs premultiplied RGBA, including transparent alpha.
17-type 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 shader15 // 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.16 // 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.17 // KindLayer.x = 6 samples set 1 binding 0 as a 2D image texture.
@@ -59,19 +52,11 @@ type Pipeline struct {
59 Layout vulkan.PipelineLayout52 Layout vulkan.PipelineLayout
60 }53 }
61 54
62-// NewRectPipeline fixes the Vulkan viewport and scissor to the image size.55+// NewListPipeline creates an instanced, premultiplied-alpha quad pipeline. It
63-// The pinned Vulkan binding does not expose vkCmdSetViewport/Scissor, so resize56+// fixes the Vulkan viewport and scissor to the image size: the pinned Vulkan
64-// builds a new pipeline rather than recording unsupported dynamic-state calls.57+// binding does not expose vkCmdSetViewport/Scissor, so resize builds a new
65-func (d *Device) NewRectPipeline(width, height int32) (p *Pipeline, err error) {58+// pipeline rather than recording unsupported dynamic-state calls.
66- return d.newPipeline(width, height, false)
67-}
68-
69-// NewListPipeline creates an instanced, premultiplied-alpha quad pipeline.
70 func (d *Device) NewListPipeline(width, height int32) (p *Pipeline, err error) {59 func (d *Device) NewListPipeline(width, height int32) (p *Pipeline, err error) {
71- return d.newPipeline(width, height, true)
72-}
73-
74-func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err error) {
75 if width <= 0 || height <= 0 {60 if width <= 0 || height <= 0 {
76 return nil, fmt.Errorf("invalid render area %dx%d", width, height)61 return nil, fmt.Errorf("invalid render area %dx%d", width, height)
77 }62 }
@@ -81,44 +66,32 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e
81 p.Close()66 p.Close()
82 }67 }
83 }()68 }()
84- pushSize := uint32(unsafe.Sizeof(Rect{}))69+ if d.atlas == nil {
85- if list {70+ d.atlas, err = d.newGlyphAtlas()
86- pushSize = 871+ if err != nil {
87- if d.atlas == nil {72+ return nil, err
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 }73 }
99 }74 }
100- rangeInfo := vulkan.PushConstantRange{StageFlags: vulkan.ShaderStageVertexBit | vulkan.ShaderStageFragmentBit, Size: pushSize}75+ if d.images == nil {
101- layoutInfo := vulkan.PipelineLayoutCreateInfo{SType: vulkan.StructureTypePipelineLayoutCreateInfo, PushConstantRangeCount: 1, PushConstantRanges: &rangeInfo}76+ d.images, err = d.newImageCache()
102- if list {77+ if err != nil {
103- // Set 0 samples the atlas; set 1 samples the image descriptor78+ return nil, err
104- // bound for the current image batch.79+ }
105- layouts := [2]vulkan.DescriptorSetLayout{d.atlas.Layout, d.images.Layout}
106- layoutInfo.SetLayoutCount = uint32(len(layouts))
107- layoutInfo.SetLayouts = &layouts[0]
108 }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}
109 if err = vulkan.Check(d.Dispatch.CreatePipelineLayout(d.Logical, &layoutInfo, nil, &p.Layout)); err != nil {86 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)87+ return nil, fmt.Errorf("create list pipeline layout: %w", err)
111 }88 }
112- vert, frag := shaders.Vertex, shaders.Fragment89+ vertex, err := d.shaderModule(shaders.ListVertex)
113- if list {
114- vert, frag = shaders.ListVertex, shaders.ListFragment
115- }
116- vertex, err := d.shaderModule(vert)
117 if err != nil {90 if err != nil {
118 return nil, err91 return nil, err
119 }92 }
120 defer d.Dispatch.DestroyShaderModule(d.Logical, vertex, nil)93 defer d.Dispatch.DestroyShaderModule(d.Logical, vertex, nil)
121- fragment, err := d.shaderModule(frag)94+ fragment, err := d.shaderModule(shaders.ListFragment)
122 if err != nil {95 if err != nil {
123 return nil, err96 return nil, err
124 }97 }
@@ -129,19 +102,16 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e
129 {SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fragment, Name: &main[0]},102 {SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fragment, Name: &main[0]},
130 }103 }
131 vertexInput := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo}104 vertexInput := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo}
132- var binding vulkan.VertexInputBindingDescription
133 var attrs [13]vulkan.VertexInputAttributeDescription105 var attrs [13]vulkan.VertexInputAttributeDescription
134- if list {106+ binding := vulkan.VertexInputBindingDescription{Stride: uint32(unsafe.Sizeof(Instance{})), InputRate: vulkan.VertexInputRateInstance}
135- 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))}
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))}108+ for i := range attrs {
137- for i := range attrs {109+ attrs[i] = vulkan.VertexInputAttributeDescription{Location: uint32(i), Format: vulkan.FormatR32g32b32a32Sfloat, Offset: offsets[i]}
138- attrs[i] = vulkan.VertexInputAttributeDescription{Location: uint32(i), Format: vulkan.FormatR32g32b32a32Sfloat, Offset: offsets[i]}110+ }
139- }111+ vertexInput.VertexBindingDescriptionCount = 1
140- vertexInput.VertexBindingDescriptionCount = 1112+ vertexInput.VertexBindingDescriptions = &binding
141- vertexInput.VertexBindingDescriptions = &binding113+ vertexInput.VertexAttributeDescriptionCount = uint32(len(attrs))
142- vertexInput.VertexAttributeDescriptionCount = uint32(len(attrs))114+ vertexInput.VertexAttributeDescriptions = &attrs[0]
143- vertexInput.VertexAttributeDescriptions = &attrs[0]
144- }
145 assembly := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList}115 assembly := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList}
146 viewport := vulkan.Viewport{Width: float32(width), Height: float32(height), MaxDepth: 1}116 viewport := vulkan.Viewport{Width: float32(width), Height: float32(height), MaxDepth: 1}
147 scissor := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(width), Height: uint32(height)}}117 scissor := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(width), Height: uint32(height)}}
@@ -154,7 +124,7 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e
154 rendering := vulkan.PipelineRenderingCreateInfo{SType: vulkan.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}124 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}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}
156 if err = vulkan.Check(d.Dispatch.CreateGraphicsPipelines(d.Logical, 0, 1, &ci, nil, &p.Handle)); err != nil {126 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)127+ return nil, fmt.Errorf("create list pipeline with dynamic rendering: %w", err)
158 }128 }
159 return p, nil129 return p, nil
160 }130 }
D internal/presentation/vkdevice/pipeline_test.go +0 -35
@@ -1,35 +0,0 @@
1-//go:build linux
2-
3-package vkdevice
4-
5-import (
6- "os"
7- "testing"
8-
9- "golang.org/x/sys/unix"
10-)
11-
12-func TestRectPipelineHardware(t *testing.T) {
13- path := os.Getenv("NEFERGUI_RENDER_NODE")
14- if path == "" {
15- t.Skip("opt in with NEFERGUI_RENDER_NODE=/dev/dri/renderD...")
16- }
17- var stat unix.Stat_t
18- if err := unix.Stat(path, &stat); err != nil {
19- t.Fatal(err)
20- }
21- d, err := Open(DeviceIdentity(uint64(stat.Rdev)))
22- if err != nil {
23- t.Fatal(err)
24- }
25- defer d.Close()
26- p, err := d.NewRectPipeline(320, 240)
27- if err != nil {
28- t.Fatal(err)
29- }
30- defer p.Close()
31- if p.Handle == 0 || p.Layout == 0 {
32- t.Fatal("pipeline not created")
33- }
34- t.Logf("created 320x240 rectangle pipeline with dynamic rendering on %s", path)
35-}
@@ -1,35 +0,0 @@
1-//go:build linux
2-
3-package vkdevice
4-
5-import (
6- "os"
7- "testing"
8-
9- "golang.org/x/sys/unix"
10-)
11-
12-func TestRectPipelineHardware(t *testing.T) {
13- path := os.Getenv("NEFERGUI_RENDER_NODE")
14- if path == "" {
15- t.Skip("opt in with NEFERGUI_RENDER_NODE=/dev/dri/renderD...")
16- }
17- var stat unix.Stat_t
18- if err := unix.Stat(path, &stat); err != nil {
19- t.Fatal(err)
20- }
21- d, err := Open(DeviceIdentity(uint64(stat.Rdev)))
22- if err != nil {
23- t.Fatal(err)
24- }
25- defer d.Close()
26- p, err := d.NewRectPipeline(320, 240)
27- if err != nil {
28- t.Fatal(err)
29- }
30- defer p.Close()
31- if p.Handle == 0 || p.Layout == 0 {
32- t.Fatal("pipeline not created")
33- }
34- t.Logf("created 320x240 rectangle pipeline with dynamic rendering on %s", path)
35-}
M internal/presentation/vkdevice/render.go +30 -43
@@ -76,17 +76,13 @@ func (f *Frame) Ready() (bool, error) {
7676 return true, nil
7777 }
7878
79-func (f *Frame) Record(img *Image, pipe *Pipeline, width, height int32, rect Rect, initial bool, capture bool) error {
80- return f.record(img, pipe, width, height, initial, capture, nil, nil, nil, &rect)
81-}
82-
8379 // RecordList records an ordered list of physical-pixel quads. Reuse is gated
8480 // by Ready, including any instance-buffer growth.
8581 func (f *Frame) RecordList(img *Image, pipe *Pipeline, width, height int32, instances []Instance, uploads []text.Upload, batches []Batch, initial, capture bool) error {
86- return f.record(img, pipe, width, height, initial, capture, instances, uploads, batches, nil)
82+ return f.record(img, pipe, width, height, initial, capture, instances, uploads, batches)
8783 }
8884
89-func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, capture bool, instances []Instance, uploads []text.Upload, batches []Batch, rect *Rect) (err error) {
85+func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, capture bool, instances []Instance, uploads []text.Upload, batches []Batch) (err error) {
9086 if width <= 0 || height <= 0 || pipe == nil {
9187 return fmt.Errorf("invalid render dimensions or pipeline")
9288 }
@@ -109,15 +105,13 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
109105 }
110106 f.submitted = false
111107 }
112- if rect == nil && f.device.atlas == nil {
108+ if f.device.atlas == nil {
113109 return fmt.Errorf("list atlas not initialized")
114110 }
115- if rect == nil {
116- if err := f.stageGlyphs(uploads); err != nil {
117- return err
118- }
111+ if err := f.stageGlyphs(uploads); err != nil {
112+ return err
119113 }
120- if rect == nil && f.device.images != nil {
114+ if f.device.images != nil {
121115 defer func() {
122116 if err != nil {
123117 f.device.images.abort(f)
@@ -128,7 +122,7 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
128122 return err
129123 }
130124 }
131- if rect == nil && len(instances) > 0 {
125+ if len(instances) > 0 {
132126 if err := f.uploadInstances(instances); err != nil {
133127 return err
134128 }
@@ -138,12 +132,10 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
138132 if err = vulkan.Check(d.BeginCommandBuffer(f.Commands, &begin)); err != nil {
139133 return err
140134 }
141- f.atlasRecorded = rect == nil && !f.device.atlas.initialized
142- if rect == nil {
143- var staging vulkan.Buffer
144- if len(uploads) != 0 {
145- staging = f.glyphStaging.Buffer
146- }
135+ f.atlasRecorded = !f.device.atlas.initialized
136+ var staging vulkan.Buffer
137+ if len(uploads) != 0 {
138+ staging = f.glyphStaging.Buffer
147139 f.device.atlas.recordUploads(f.Commands, staging, uploads)
148140 if !f.device.images.fallbackReady {
149141 f.recordFallback()
@@ -164,32 +156,27 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
164156 rendering := vulkan.RenderingInfo{SType: vulkan.StructureTypeRenderingInfo, RenderArea: area, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment}
165157 d.CmdBeginRendering(f.Commands, &rendering)
166158 d.CmdBindPipeline(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Handle)
167- if rect != nil {
168- d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, uint32(unsafe.Sizeof(*rect)), unsafe.Pointer(rect))
169- d.CmdDraw(f.Commands, 6, 1, 0, 0)
170- } else {
171- // The fragment shader statically references both descriptor sets even
172- // for non-image draws. Bind a valid initialized image set before any
173- // draw; each image batch then replaces set 1 in painter's order.
174- sets := [2]vulkan.DescriptorSet{f.device.atlas.Set, f.device.images.fallback.Texture.Set}
175- d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 0, 2, &sets[0], 0, nil)
176- screen := [2]float32{float32(width), float32(height)}
177- d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, 8, unsafe.Pointer(&screen))
178- if len(instances) > 0 {
179- offset := vulkan.DeviceSize(0)
180- d.CmdBindVertexBuffers(f.Commands, 0, 1, &f.instances.Buffer, &offset)
181- for _, batch := range batches {
182- if batch.Count == 0 || uint64(batch.First)+uint64(batch.Count) > uint64(len(instances)) {
183- return fmt.Errorf("invalid list batch range")
184- }
185- if batch.Source != nil {
186- if batch.Texture == nil {
187- return fmt.Errorf("image batch has no GPU texture")
188- }
189- d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 1, 1, &batch.Texture.Set, 0, nil)
159+ // The fragment shader statically references both descriptor sets even
160+ // for non-image draws. Bind a valid initialized image set before any
161+ // draw; each image batch then replaces set 1 in painter's order.
162+ sets := [2]vulkan.DescriptorSet{f.device.atlas.Set, f.device.images.fallback.Texture.Set}
163+ d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 0, 2, &sets[0], 0, nil)
164+ screen := [2]float32{float32(width), float32(height)}
165+ d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, 8, unsafe.Pointer(&screen))
166+ if len(instances) > 0 {
167+ offset := vulkan.DeviceSize(0)
168+ d.CmdBindVertexBuffers(f.Commands, 0, 1, &f.instances.Buffer, &offset)
169+ for _, batch := range batches {
170+ if batch.Count == 0 || uint64(batch.First)+uint64(batch.Count) > uint64(len(instances)) {
171+ return fmt.Errorf("invalid list batch range")
172+ }
173+ if batch.Source != nil {
174+ if batch.Texture == nil {
175+ return fmt.Errorf("image batch has no GPU texture")
190176 }
191- d.CmdDraw(f.Commands, 6, batch.Count, 0, batch.First)
177+ d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 1, 1, &batch.Texture.Set, 0, nil)
192178 }
179+ d.CmdDraw(f.Commands, 6, batch.Count, 0, batch.First)
193180 }
194181 }
195182 d.CmdEndRendering(f.Commands)
@@ -76,17 +76,13 @@ func (f *Frame) Ready() (bool, error) {
76 return true, nil76 return true, nil
77 }77 }
78 78
79-func (f *Frame) Record(img *Image, pipe *Pipeline, width, height int32, rect Rect, initial bool, capture bool) error {
80- return f.record(img, pipe, width, height, initial, capture, nil, nil, nil, &rect)
81-}
82-
83 // RecordList records an ordered list of physical-pixel quads. Reuse is gated79 // RecordList records an ordered list of physical-pixel quads. Reuse is gated
84 // by Ready, including any instance-buffer growth.80 // by Ready, including any instance-buffer growth.
85 func (f *Frame) RecordList(img *Image, pipe *Pipeline, width, height int32, instances []Instance, uploads []text.Upload, batches []Batch, initial, capture bool) error {81 func (f *Frame) RecordList(img *Image, pipe *Pipeline, width, height int32, instances []Instance, uploads []text.Upload, batches []Batch, initial, capture bool) error {
86- return f.record(img, pipe, width, height, initial, capture, instances, uploads, batches, nil)82+ return f.record(img, pipe, width, height, initial, capture, instances, uploads, batches)
87 }83 }
88 84
89-func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, capture bool, instances []Instance, uploads []text.Upload, batches []Batch, rect *Rect) (err error) {85+func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, capture bool, instances []Instance, uploads []text.Upload, batches []Batch) (err error) {
90 if width <= 0 || height <= 0 || pipe == nil {86 if width <= 0 || height <= 0 || pipe == nil {
91 return fmt.Errorf("invalid render dimensions or pipeline")87 return fmt.Errorf("invalid render dimensions or pipeline")
92 }88 }
@@ -109,15 +105,13 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
109 }105 }
110 f.submitted = false106 f.submitted = false
111 }107 }
112- if rect == nil && f.device.atlas == nil {108+ if f.device.atlas == nil {
113 return fmt.Errorf("list atlas not initialized")109 return fmt.Errorf("list atlas not initialized")
114 }110 }
115- if rect == nil {111+ if err := f.stageGlyphs(uploads); err != nil {
116- if err := f.stageGlyphs(uploads); err != nil {112+ return err
117- return err
118- }
119 }113 }
120- if rect == nil && f.device.images != nil {114+ if f.device.images != nil {
121 defer func() {115 defer func() {
122 if err != nil {116 if err != nil {
123 f.device.images.abort(f)117 f.device.images.abort(f)
@@ -128,7 +122,7 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
128 return err122 return err
129 }123 }
130 }124 }
131- if rect == nil && len(instances) > 0 {125+ if len(instances) > 0 {
132 if err := f.uploadInstances(instances); err != nil {126 if err := f.uploadInstances(instances); err != nil {
133 return err127 return err
134 }128 }
@@ -138,12 +132,10 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
138 if err = vulkan.Check(d.BeginCommandBuffer(f.Commands, &begin)); err != nil {132 if err = vulkan.Check(d.BeginCommandBuffer(f.Commands, &begin)); err != nil {
139 return err133 return err
140 }134 }
141- f.atlasRecorded = rect == nil && !f.device.atlas.initialized135+ f.atlasRecorded = !f.device.atlas.initialized
142- if rect == nil {136+ var staging vulkan.Buffer
143- var staging vulkan.Buffer137+ if len(uploads) != 0 {
144- if len(uploads) != 0 {138+ staging = f.glyphStaging.Buffer
145- staging = f.glyphStaging.Buffer
146- }
147 f.device.atlas.recordUploads(f.Commands, staging, uploads)139 f.device.atlas.recordUploads(f.Commands, staging, uploads)
148 if !f.device.images.fallbackReady {140 if !f.device.images.fallbackReady {
149 f.recordFallback()141 f.recordFallback()
@@ -164,32 +156,27 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial,
164 rendering := vulkan.RenderingInfo{SType: vulkan.StructureTypeRenderingInfo, RenderArea: area, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment}156 rendering := vulkan.RenderingInfo{SType: vulkan.StructureTypeRenderingInfo, RenderArea: area, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment}
165 d.CmdBeginRendering(f.Commands, &rendering)157 d.CmdBeginRendering(f.Commands, &rendering)
166 d.CmdBindPipeline(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Handle)158 d.CmdBindPipeline(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Handle)
167- if rect != nil {159+ // The fragment shader statically references both descriptor sets even
168- d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, uint32(unsafe.Sizeof(*rect)), unsafe.Pointer(rect))160+ // for non-image draws. Bind a valid initialized image set before any
169- d.CmdDraw(f.Commands, 6, 1, 0, 0)161+ // draw; each image batch then replaces set 1 in painter's order.
170- } else {162+ sets := [2]vulkan.DescriptorSet{f.device.atlas.Set, f.device.images.fallback.Texture.Set}
171- // The fragment shader statically references both descriptor sets even163+ d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 0, 2, &sets[0], 0, nil)
172- // for non-image draws. Bind a valid initialized image set before any164+ screen := [2]float32{float32(width), float32(height)}
173- // draw; each image batch then replaces set 1 in painter's order.165+ d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, 8, unsafe.Pointer(&screen))
174- sets := [2]vulkan.DescriptorSet{f.device.atlas.Set, f.device.images.fallback.Texture.Set}166+ if len(instances) > 0 {
175- d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 0, 2, &sets[0], 0, nil)167+ offset := vulkan.DeviceSize(0)
176- screen := [2]float32{float32(width), float32(height)}168+ d.CmdBindVertexBuffers(f.Commands, 0, 1, &f.instances.Buffer, &offset)
177- d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, 8, unsafe.Pointer(&screen))169+ for _, batch := range batches {
178- if len(instances) > 0 {170+ if batch.Count == 0 || uint64(batch.First)+uint64(batch.Count) > uint64(len(instances)) {
179- offset := vulkan.DeviceSize(0)171+ return fmt.Errorf("invalid list batch range")
180- d.CmdBindVertexBuffers(f.Commands, 0, 1, &f.instances.Buffer, &offset)172+ }
181- for _, batch := range batches {173+ if batch.Source != nil {
182- if batch.Count == 0 || uint64(batch.First)+uint64(batch.Count) > uint64(len(instances)) {174+ if batch.Texture == nil {
183- return fmt.Errorf("invalid list batch range")175+ return fmt.Errorf("image batch has no GPU texture")
184- }
185- if batch.Source != nil {
186- if batch.Texture == nil {
187- return fmt.Errorf("image batch has no GPU texture")
188- }
189- d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 1, 1, &batch.Texture.Set, 0, nil)
190 }176 }
191- d.CmdDraw(f.Commands, 6, batch.Count, 0, batch.First)177+ d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 1, 1, &batch.Texture.Set, 0, nil)
192 }178 }
179+ d.CmdDraw(f.Commands, 6, batch.Count, 0, batch.First)
193 }180 }
194 }181 }
195 d.CmdEndRendering(f.Commands)182 d.CmdEndRendering(f.Commands)
M internal/presentation/vkdevice/validation_test.go +1 -1
@@ -29,7 +29,7 @@ func TestValidationLayer(t *testing.T) {
2929 }
3030 t.Fatal(err)
3131 }
32- pipeline, err := d.NewRectPipeline(64, 64)
32+ pipeline, err := d.NewListPipeline(64, 64)
3333 if err != nil {
3434 d.Close()
3535 t.Fatal(err)
@@ -29,7 +29,7 @@ func TestValidationLayer(t *testing.T) {
29 }29 }
30 t.Fatal(err)30 t.Fatal(err)
31 }31 }
32- pipeline, err := d.NewRectPipeline(64, 64)32+ pipeline, err := d.NewListPipeline(64, 64)
33 if err != nil {33 if err != nil {
34 d.Close()34 d.Close()
35 t.Fatal(err)35 t.Fatal(err)

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