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