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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) { | |||
| 557 | 557 | ||
| 558 | 558 | // Close waits for the GPU, then frees every descriptor and image, always | |
| 559 | 559 | // 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 | 562 | func (t *Target) Close() error { | |
| 562 | 563 | if t == nil || t.closed { | |
| 563 | 564 | return nil | |
| @@ -591,6 +592,12 @@ func (t *Target) Close() error { | |||
| 591 | 592 | } | |
| 592 | 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 | 601 | t.Device.Close() | |
| 595 | 602 | return errors.Join(errs...) | |
| 596 | 603 | } | |
| @@ -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, always | 558 | // Close waits for the GPU, then frees every descriptor and image, always | ||
| 559 | // completing the teardown. The compositor's imports keep their own kernel | 559 | // 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 nil | 564 | 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) { | |||
| 12 | 12 | for _, tc := range []struct { | |
| 13 | 13 | name string | |
| 14 | 14 | data []byte | |
| 15 | - | }{{"vertex", Vertex}, {"fragment", Fragment}, {"list vertex", ListVertex}, {"list fragment", ListFragment}} { | |
| 15 | + | }{{"list vertex", ListVertex}, {"list fragment", ListFragment}} { | |
| 16 | 16 | if len(tc.data) < 20 || !bytes.Equal(tc.data[:4], []byte{3, 2, 35, 7}) || len(tc.data)%4 != 0 { | |
| 17 | 17 | t.Fatalf("%s: not SPIR-V", tc.name) | |
| 18 | 18 | } | |
| @@ -27,7 +27,7 @@ func TestShaderFreshness(t *testing.T) { | |||
| 27 | 27 | for _, tc := range []struct { | |
| 28 | 28 | file, stage string | |
| 29 | 29 | 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 | 31 | out := filepath.Join(t.TempDir(), "shader.spv") | |
| 32 | 32 | cmd := exec.Command(bin, "-O", "-fshader-stage="+tc.stage, "-o", out, tc.file) | |
| 33 | 33 | 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 string | 13 | name string | ||
| 14 | data []byte | 14 | 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 string | 28 | file, stage string | ||
| 29 | embedded []byte | 29 | 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 | |||
| 4 | 4 | ||
| 5 | 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 | 7 | //go:generate glslc -O -fshader-stage=vertex -o list.vert.spv list.vert | |
| 10 | 8 | //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 | 10 | //go:embed list.vert.spv | |
| 19 | 11 | var ListVertex []byte | |
| 20 | 12 | ||
| @@ -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.vert | 7 | //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.frag | 8 | //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.spv | 10 | //go:embed list.vert.spv | ||
| 19 | var ListVertex []byte | 11 | var ListVertex []byte | ||
| 20 | 12 | ||||
M internal/presentation/vkdevice/pipeline.go +31 -61
| @@ -12,13 +12,6 @@ import ( | |||
| 12 | 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 | 15 | // Instance is a physical-pixel list quad. Color is straight sRGB; the shader | |
| 23 | 16 | // premultiplies it. Clip uses x/y/width/height, radii are TL/TR/BR/BL. | |
| 24 | 17 | // KindLayer.x = 6 samples set 1 binding 0 as a 2D image texture. | |
| @@ -59,19 +52,11 @@ type Pipeline struct { | |||
| 59 | 52 | Layout vulkan.PipelineLayout | |
| 60 | 53 | } | |
| 61 | 54 | ||
| 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. | |
| 70 | 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 | 60 | if width <= 0 || height <= 0 { | |
| 76 | 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 | 66 | p.Close() | |
| 82 | 67 | } | |
| 83 | 68 | }() | |
| 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 | |
| 98 | 73 | } | |
| 99 | 74 | } | |
| 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 | + | } | |
| 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 | 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.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) | |
| 117 | 90 | if err != nil { | |
| 118 | 91 | return nil, err | |
| 119 | 92 | } | |
| 120 | 93 | defer d.Dispatch.DestroyShaderModule(d.Logical, vertex, nil) | |
| 121 | - | fragment, err := d.shaderModule(frag) | |
| 94 | + | fragment, err := d.shaderModule(shaders.ListFragment) | |
| 122 | 95 | if err != nil { | |
| 123 | 96 | return nil, err | |
| 124 | 97 | } | |
| @@ -129,19 +102,16 @@ func (d *Device) newPipeline(width, height int32, list bool) (p *Pipeline, err e | |||
| 129 | 102 | {SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fragment, Name: &main[0]}, | |
| 130 | 103 | } | |
| 131 | 104 | vertexInput := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo} | |
| 132 | - | var binding vulkan.VertexInputBindingDescription | |
| 133 | 105 | 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] | |
| 145 | 115 | assembly := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList} | |
| 146 | 116 | viewport := vulkan.Viewport{Width: float32(width), Height: float32(height), MaxDepth: 1} | |
| 147 | 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 | 124 | rendering := vulkan.PipelineRenderingCreateInfo{SType: vulkan.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |
| 155 | 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 | 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 | 129 | return p, nil | |
| 160 | 130 | } | |
| @@ -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 shader | 15 | // 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.PipelineLayout | 52 | 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 resize | 56 | + | // 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 = 8 | 71 | + | 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 descriptor | 78 | + | 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.Fragment | 89 | + | 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, err | 91 | 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, err | 96 | 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.VertexInputAttributeDescription | 105 | 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 = 1 | 112 | + | vertexInput.VertexBindingDescriptions = &binding |
| 141 | - | vertexInput.VertexBindingDescriptions = &binding | 113 | + | 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, nil | 129 | 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) { | |||
| 76 | 76 | 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 | 79 | // RecordList records an ordered list of physical-pixel quads. Reuse is gated | |
| 84 | 80 | // by Ready, including any instance-buffer growth. | |
| 85 | 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 | 86 | if width <= 0 || height <= 0 || pipe == nil { | |
| 91 | 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 | 106 | f.submitted = false | |
| 111 | 107 | } | |
| 112 | - | if rect == nil && f.device.atlas == nil { | |
| 108 | + | if f.device.atlas == nil { | |
| 113 | 109 | return fmt.Errorf("list atlas not initialized") | |
| 114 | 110 | } | |
| 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 | |
| 119 | 113 | } | |
| 120 | - | if rect == nil && f.device.images != nil { | |
| 114 | + | if f.device.images != nil { | |
| 121 | 115 | defer func() { | |
| 122 | 116 | if err != nil { | |
| 123 | 117 | f.device.images.abort(f) | |
| @@ -128,7 +122,7 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, | |||
| 128 | 122 | return err | |
| 129 | 123 | } | |
| 130 | 124 | } | |
| 131 | - | if rect == nil && len(instances) > 0 { | |
| 125 | + | if len(instances) > 0 { | |
| 132 | 126 | if err := f.uploadInstances(instances); err != nil { | |
| 133 | 127 | return err | |
| 134 | 128 | } | |
| @@ -138,12 +132,10 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, | |||
| 138 | 132 | if err = vulkan.Check(d.BeginCommandBuffer(f.Commands, &begin)); err != nil { | |
| 139 | 133 | return err | |
| 140 | 134 | } | |
| 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 | |
| 147 | 139 | f.device.atlas.recordUploads(f.Commands, staging, uploads) | |
| 148 | 140 | if !f.device.images.fallbackReady { | |
| 149 | 141 | f.recordFallback() | |
| @@ -164,32 +156,27 @@ func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, | |||
| 164 | 156 | rendering := vulkan.RenderingInfo{SType: vulkan.StructureTypeRenderingInfo, RenderArea: area, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |
| 165 | 157 | d.CmdBeginRendering(f.Commands, &rendering) | |
| 166 | 158 | 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") | |
| 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 | 182 | d.CmdEndRendering(f.Commands) | |
| @@ -76,17 +76,13 @@ func (f *Frame) Ready() (bool, error) { | |||||
| 76 | return true, nil | 76 | 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 gated | 79 | // 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 = false | 106 | 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 err | 122 | 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 err | 127 | 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 err | 133 | return err | ||
| 140 | } | 134 | } | ||
| 141 | - | f.atlasRecorded = rect == nil && !f.device.atlas.initialized | 135 | + | f.atlasRecorded = !f.device.atlas.initialized |
| 142 | - | if rect == nil { | 136 | + | var staging vulkan.Buffer |
| 143 | - | var staging vulkan.Buffer | 137 | + | 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 even | 163 | + | 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 any | 164 | + | 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) { | |||
| 29 | 29 | } | |
| 30 | 30 | t.Fatal(err) | |
| 31 | 31 | } | |
| 32 | - | pipeline, err := d.NewRectPipeline(64, 64) | |
| 32 | + | pipeline, err := d.NewListPipeline(64, 64) | |
| 33 | 33 | if err != nil { | |
| 34 | 34 | d.Close() | |
| 35 | 35 | 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) | ||