comparison semiconginev2/text/textbox.nim @ 1236:176383220123

add: first font-rendering test
author sam <sam@basx.dev>
date Sat, 20 Jul 2024 17:45:44 +0700
parents 841e12f33c47
children 03634915bbdb
comparison
equal deleted inserted replaced
1235:c70fee6568f6 1236:176383220123
12 dirtyShaderdata: bool # is true if any of the attributes changed 12 dirtyShaderdata: bool # is true if any of the attributes changed
13 processedText: seq[Rune] # used to store processed (word-wrapper) text to preserve original 13 processedText: seq[Rune] # used to store processed (word-wrapper) text to preserve original
14 lastRenderedText: seq[Rune] # stores the last rendered text, to prevent unnecessary updates 14 lastRenderedText: seq[Rune] # stores the last rendered text, to prevent unnecessary updates
15 15
16 # rendering data 16 # rendering data
17 position: GPUArray[Vec3f, VertexBuffer] 17 position*: GPUArray[Vec3f, VertexBuffer]
18 uv: GPUArray[Vec2f, VertexBuffer] 18 uv*: GPUArray[Vec2f, VertexBuffer]
19 indices: GPUArray[uint16, IndexBuffer] 19 indices*: GPUArray[uint16, IndexBuffer]
20 shaderdata: DescriptorSet[TextboxDescriptorSet] 20 shaderdata*: DescriptorSet[TextboxDescriptorSet]
21 21
22 func `$`*(text: Textbox): string = 22 func `$`*(textbox: Textbox): string =
23 "\"" & $text.text[0 ..< min(text.text.len, 16)] & "\"" 23 "\"" & $textbox.text[0 ..< min(textbox.text.len, 16)] & "\""
24 24
25 proc RefreshShaderdata(text: Textbox) = 25 proc RefreshShaderdata(textbox: Textbox) =
26 if not text.dirtyShaderdata: 26 textbox.shaderdata.data.textbox.UpdateGPUBuffer()
27 return 27
28 text.shaderdata.data.textbox.UpdateGPUBuffer() 28 proc RefreshGeometry(textbox: var Textbox) =
29
30 proc RefreshGeometry(text: var Textbox) =
31 if not text.dirtyGeometry and text.processedText == text.lastRenderedText:
32 return
33
34 # pre-calculate text-width 29 # pre-calculate text-width
35 var width = 0'f32 30 var width = 0'f32
36 var lineWidths: seq[float32] 31 var lineWidths: seq[float32]
37 for i in 0 ..< text.processedText.len: 32 for i in 0 ..< textbox.processedText.len:
38 if text.processedText[i] == NEWLINE: 33 if textbox.processedText[i] == NEWLINE:
39 lineWidths.add width 34 lineWidths.add width
40 width = 0'f32 35 width = 0'f32
41 else: 36 else:
42 if not (i == text.processedText.len - 1 and text.processedText[i].isWhiteSpace): 37 if not (i == textbox.processedText.len - 1 and textbox.processedText[i].isWhiteSpace):
43 width += text.font.glyphs[text.processedText[i]].advance 38 width += textbox.font.glyphs[textbox.processedText[i]].advance
44 if i < text.processedText.len - 1: 39 if i < textbox.processedText.len - 1:
45 width += text.font.kerning[(text.processedText[i], text.processedText[i + 1])] 40 width += textbox.font.kerning[(textbox.processedText[i], textbox.processedText[i + 1])]
46 lineWidths.add width 41 lineWidths.add width
47 var height = float32(lineWidths.len - 1) * text.font.lineAdvance + text.font.capHeight 42 var height = float32(lineWidths.len - 1) * textbox.font.lineAdvance + textbox.font.capHeight
48 if lineWidths[^1] == 0 and lineWidths.len > 1: 43 if lineWidths[^1] == 0 and lineWidths.len > 1:
49 height -= 1 44 height -= 1
50 45
51 let anchorY = (case text.verticalAlignment 46 let anchorY = (case textbox.verticalAlignment
52 of Top: 0'f32 47 of Top: 0'f32
53 of Center: height / 2 48 of Center: height / 2
54 of Bottom: height) - text.font.capHeight 49 of Bottom: height) - textbox.font.capHeight
55 50
56 var 51 var
57 offsetX = 0'f32 52 offsetX = 0'f32
58 offsetY = 0'f32 53 offsetY = 0'f32
59 lineIndex = 0 54 lineIndex = 0
60 anchorX = case text.horizontalAlignment 55 anchorX = case textbox.horizontalAlignment
61 of Left: 0'f32 56 of Left: 0'f32
62 of Center: lineWidths[lineIndex] / 2 57 of Center: lineWidths[lineIndex] / 2
63 of Right: lineWidths[lineIndex] 58 of Right: lineWidths[lineIndex]
64 for i in 0 ..< text.maxLen: 59 for i in 0 ..< textbox.maxLen:
65 let vertexOffset = i * 4 60 let vertexOffset = i * 4
66 if i < text.processedText.len: 61 if i < textbox.processedText.len:
67 if text.processedText[i] == Rune('\n'): 62 if textbox.processedText[i] == Rune('\n'):
68 offsetX = 0 63 offsetX = 0
69 offsetY += text.font.lineAdvance 64 offsetY += textbox.font.lineAdvance
70 text.position.data[vertexOffset + 0] = NewVec3f() 65 textbox.position.data[vertexOffset + 0] = NewVec3f()
71 text.position.data[vertexOffset + 1] = NewVec3f() 66 textbox.position.data[vertexOffset + 1] = NewVec3f()
72 text.position.data[vertexOffset + 2] = NewVec3f() 67 textbox.position.data[vertexOffset + 2] = NewVec3f()
73 text.position.data[vertexOffset + 3] = NewVec3f() 68 textbox.position.data[vertexOffset + 3] = NewVec3f()
74 inc lineIndex 69 inc lineIndex
75 anchorX = case text.horizontalAlignment 70 anchorX = case textbox.horizontalAlignment
76 of Left: 0'f32 71 of Left: 0'f32
77 of Center: lineWidths[lineIndex] / 2 72 of Center: lineWidths[lineIndex] / 2
78 of Right: lineWidths[lineIndex] 73 of Right: lineWidths[lineIndex]
79 else: 74 else:
80 let 75 let
81 glyph = text.font.glyphs[text.processedText[i]] 76 glyph = textbox.font.glyphs[textbox.processedText[i]]
82 left = offsetX + glyph.leftOffset 77 left = offsetX + glyph.leftOffset
83 right = offsetX + glyph.leftOffset + glyph.dimension.x 78 right = offsetX + glyph.leftOffset + glyph.dimension.x
84 top = offsetY + glyph.topOffset 79 top = offsetY + glyph.topOffset
85 bottom = offsetY + glyph.topOffset + glyph.dimension.y 80 bottom = offsetY + glyph.topOffset + glyph.dimension.y
86 81
87 text.position.data[vertexOffset + 0] = NewVec3f(left - anchorX, bottom - anchorY) 82 textbox.position.data[vertexOffset + 1] = NewVec3f(left - anchorX, bottom - anchorY)
88 text.position.data[vertexOffset + 1] = NewVec3f(left - anchorX, top - anchorY) 83 textbox.position.data[vertexOffset + 0] = NewVec3f(left - anchorX, top - anchorY)
89 text.position.data[vertexOffset + 2] = NewVec3f(right - anchorX, top - anchorY) 84 textbox.position.data[vertexOffset + 3] = NewVec3f(right - anchorX, top - anchorY)
90 text.position.data[vertexOffset + 3] = NewVec3f(right - anchorX, bottom - anchorY) 85 textbox.position.data[vertexOffset + 2] = NewVec3f(right - anchorX, bottom - anchorY)
91 86
92 text.uv.data[vertexOffset + 0] = glyph.uvs[0] 87 textbox.uv.data[vertexOffset + 0] = glyph.uvs[0]
93 text.uv.data[vertexOffset + 1] = glyph.uvs[1] 88 textbox.uv.data[vertexOffset + 1] = glyph.uvs[1]
94 text.uv.data[vertexOffset + 2] = glyph.uvs[2] 89 textbox.uv.data[vertexOffset + 2] = glyph.uvs[2]
95 text.uv.data[vertexOffset + 3] = glyph.uvs[3] 90 textbox.uv.data[vertexOffset + 3] = glyph.uvs[3]
96 91
97 offsetX += glyph.advance 92 offsetX += glyph.advance
98 if i < text.processedText.len - 1: 93 if i < textbox.processedText.len - 1:
99 offsetX += text.font.kerning[(text.processedText[i], text.processedText[i + 1])] 94 offsetX += textbox.font.kerning[(textbox.processedText[i], textbox.processedText[i + 1])]
100 else: 95 else:
101 text.position.data[vertexOffset + 0] = NewVec3f() 96 textbox.position.data[vertexOffset + 0] = NewVec3f()
102 text.position.data[vertexOffset + 1] = NewVec3f() 97 textbox.position.data[vertexOffset + 1] = NewVec3f()
103 text.position.data[vertexOffset + 2] = NewVec3f() 98 textbox.position.data[vertexOffset + 2] = NewVec3f()
104 text.position.data[vertexOffset + 3] = NewVec3f() 99 textbox.position.data[vertexOffset + 3] = NewVec3f()
105 text.lastRenderedText = text.processedText 100 textbox.lastRenderedText = textbox.processedText
106 text.dirtyGeometry = false 101
107 102 func text*(textbox: Textbox): seq[Rune] =
108 proc Refresh*(textbox: var Textbox) = 103 textbox.text
109 textbox.RefreshShaderdata() 104
110 textbox.RefreshGeometry() 105 proc `text=`*(textbox: var Textbox, newText: seq[Rune]) =
111 106 if newText[0 ..< min(newText.len, textbox.maxLen)] == textbox.text:
112 func text*(text: Textbox): seq[Rune] = 107 return
113 text.text 108
114 109 textbox.text = newText[0 ..< min(newText.len, textbox.maxLen)]
115 proc `text=`*(text: var Textbox, newText: seq[Rune]) = 110
116 text.text = newText[0 ..< min(newText.len, text.maxLen)] 111 textbox.processedText = textbox.text
117 112 if textbox.maxWidth > 0:
118 text.processedText = text.text 113 textbox.processedText = WordWrapped(
119 if text.maxWidth > 0: 114 textbox.processedText,
120 text.processedText = WordWrapped( 115 textbox.font[],
121 text.processedText, 116 textbox.maxWidth / textbox.shaderdata.data.textbox.data.scale,
122 text.font[],
123 text.maxWidth / text.shaderdata.data.textbox.data.scale,
124 ) 117 )
125 118
126 proc `text=`*(text: var Textbox, newText: string) = 119 proc `text=`*(textbox: var Textbox, newText: string) =
127 `text=`(text, newText.toRunes) 120 `text=`(textbox, newText.toRunes)
128 121
129 proc Color*(text: Textbox): Vec4f = 122 proc Color*(textbox: Textbox): Vec4f =
130 text.shaderdata.data.textbox.data.color 123 textbox.shaderdata.data.textbox.data.color
131 124
132 proc `Color=`*(text: var Textbox, value: Vec4f) = 125 proc `Color=`*(textbox: var Textbox, value: Vec4f) =
133 if text.shaderdata.data.textbox.data.color != value: 126 if textbox.shaderdata.data.textbox.data.color != value:
134 text.dirtyShaderdata = true 127 textbox.dirtyShaderdata = true
135 text.shaderdata.data.textbox.data.color = value 128 textbox.shaderdata.data.textbox.data.color = value
136 129
137 proc Scale*(text: Textbox): float32 = 130 proc Scale*(textbox: Textbox): float32 =
138 text.shaderdata.data.textbox.data.scale 131 textbox.shaderdata.data.textbox.data.scale
139 132
140 proc `Scale=`*(text: var Textbox, value: float32) = 133 proc `Scale=`*(textbox: var Textbox, value: float32) =
141 if text.shaderdata.data.textbox.data.scale != value: 134 if textbox.shaderdata.data.textbox.data.scale != value:
142 text.dirtyShaderdata = true 135 textbox.dirtyShaderdata = true
143 text.shaderdata.data.textbox.data.scale = value 136 textbox.shaderdata.data.textbox.data.scale = value
144 137
145 proc Position*(text: Textbox): Vec3f = 138 proc AspectRatio*(textbox: Textbox): float32 =
146 text.shaderdata.data.textbox.data.position 139 textbox.shaderdata.data.textbox.data.aspectratio
147 140
148 proc `Position=`*(text: var Textbox, value: Vec3f) = 141 proc `AspectRatio=`*(textbox: var Textbox, value: float32) =
149 if text.shaderdata.data.textbox.data.position != value: 142 if textbox.shaderdata.data.textbox.data.aspectratio != value:
150 text.dirtyShaderdata = true 143 textbox.dirtyShaderdata = true
151 text.shaderdata.data.textbox.data.position = value 144 textbox.shaderdata.data.textbox.data.aspectratio = value
152 145
153 proc horizontalAlignment*(text: Textbox): HorizontalAlignment = 146 proc Position*(textbox: Textbox): Vec3f =
154 text.horizontalAlignment 147 textbox.shaderdata.data.textbox.data.position
155 proc `horizontalAlignment=`*(text: var Textbox, value: HorizontalAlignment) = 148
156 if value != text.horizontalAlignment: 149 proc `Position=`*(textbox: var Textbox, value: Vec3f) =
157 text.horizontalAlignment = value 150 if textbox.shaderdata.data.textbox.data.position != value:
158 text.dirtyGeometry = true 151 textbox.dirtyShaderdata = true
159 152 textbox.shaderdata.data.textbox.data.position = value
160 proc verticalAlignment*(text: Textbox): VerticalAlignment = 153
161 text.verticalAlignment 154 proc horizontalAlignment*(textbox: Textbox): HorizontalAlignment =
162 proc `verticalAlignment=`*(text: var Textbox, value: VerticalAlignment) = 155 textbox.horizontalAlignment
163 if value != text.verticalAlignment: 156 proc `horizontalAlignment=`*(textbox: var Textbox, value: HorizontalAlignment) =
164 text.verticalAlignment = value 157 if value != textbox.horizontalAlignment:
165 text.dirtyGeometry = true 158 textbox.horizontalAlignment = value
166 159 textbox.dirtyGeometry = true
167 proc Draw(text: Textbox, commandbuffer: VkCommandBuffer, pipeline: Pipeline, currentFiF: int) = 160
161 proc verticalAlignment*(textbox: Textbox): VerticalAlignment =
162 textbox.verticalAlignment
163 proc `verticalAlignment=`*(textbox: var Textbox, value: VerticalAlignment) =
164 if value != textbox.verticalAlignment:
165 textbox.verticalAlignment = value
166 textbox.dirtyGeometry = true
167
168 proc Refresh*(textbox: var Textbox, aspectratio: float32) =
169 `AspectRatio=`(textbox, aspectratio)
170
171 if textbox.dirtyShaderdata:
172 textbox.RefreshShaderdata()
173 textbox.dirtyShaderdata = false
174
175 if textbox.dirtyGeometry or textbox.processedText != textbox.lastRenderedText:
176 textbox.RefreshGeometry()
177 textbox.dirtyGeometry = false
178
179 proc Render*(textbox: Textbox, commandbuffer: VkCommandBuffer, pipeline: Pipeline, currentFiF: int) =
168 WithBind(commandbuffer, (textbox.shaderdata, ), pipeline, currentFiF): 180 WithBind(commandbuffer, (textbox.shaderdata, ), pipeline, currentFiF):
169 Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = text) 181 Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = textbox)
170 182
171 proc InitTextbox*( 183 proc InitTextbox*[T: string | seq[Rune]](
172 renderdata: var RenderData, 184 renderdata: var RenderData,
173 descriptorSetLayout: VkDescriptorSetLayout, 185 descriptorSetLayout: VkDescriptorSetLayout,
174 font: Font, 186 font: Font,
175 text = "".toRunes, 187 text: T = default(T),
176 scale: float32 = 1, 188 scale: float32 = 1,
177 position: Vec3f = NewVec3f(), 189 position: Vec3f = NewVec3f(),
178 color: Vec4f = NewVec4f(0, 0, 0, 1), 190 color: Vec4f = NewVec4f(0, 0, 0, 1),
179 maxLen: int = text.len, 191 maxLen: int = text.len,
180 verticalAlignment: VerticalAlignment = Center, 192 verticalAlignment: VerticalAlignment = Center,
184 196
185 result = Textbox( 197 result = Textbox(
186 maxLen: maxLen, 198 maxLen: maxLen,
187 font: font, 199 font: font,
188 dirtyGeometry: true, 200 dirtyGeometry: true,
201 dirtyShaderdata: true,
189 horizontalAlignment: horizontalAlignment, 202 horizontalAlignment: horizontalAlignment,
190 verticalAlignment: verticalAlignment, 203 verticalAlignment: verticalAlignment,
191 maxWidth: maxWidth, 204 maxWidth: maxWidth,
192 position: asGPUArray(newSeq[Vec3f](int(maxLen * 4)), VertexBuffer), 205 position: asGPUArray(newSeq[Vec3f](int(maxLen * 4)), VertexBuffer),
193 uv: asGPUArray(newSeq[Vec2f](int(maxLen * 4)), VertexBuffer), 206 uv: asGPUArray(newSeq[Vec2f](int(maxLen * 4)), VertexBuffer),
196 TextboxDescriptorSet( 209 TextboxDescriptorSet(
197 textbox: asGPUValue(TextboxData( 210 textbox: asGPUValue(TextboxData(
198 scale: scale, 211 scale: scale,
199 position: position, 212 position: position,
200 color: color, 213 color: color,
214 aspectratio: 1,
201 ), UniformBufferMapped), 215 ), UniformBufferMapped),
202 fontAtlas: font.fontAtlas 216 fontAtlas: font.fontAtlas
203 ) 217 )
204 ) 218 )
205 ) 219 )
211 result.indices.data[i * 6 + 2] = vertexIndex + 2 225 result.indices.data[i * 6 + 2] = vertexIndex + 2
212 result.indices.data[i * 6 + 3] = vertexIndex + 2 226 result.indices.data[i * 6 + 3] = vertexIndex + 2
213 result.indices.data[i * 6 + 4] = vertexIndex + 3 227 result.indices.data[i * 6 + 4] = vertexIndex + 3
214 result.indices.data[i * 6 + 5] = vertexIndex + 0 228 result.indices.data[i * 6 + 5] = vertexIndex + 0
215 229
216 `text=`(result, text) 230 when T is string:
217 231 `text=`(result, text.toRunes())
218 AssignBuffers(renderdata, result) 232 else:
233 `text=`(result, text)
234
235 AssignBuffers(renderdata, result, uploadData = false)
219 UploadImages(renderdata, result.shaderdata) 236 UploadImages(renderdata, result.shaderdata)
220 InitDescriptorSet(renderdata, descriptorSetLayout, result.shaderdata) 237 InitDescriptorSet(renderdata, descriptorSetLayout, result.shaderdata)
221 238
222 result.Refresh() 239 result.Refresh(1)
240 UpdateAllGPUBuffers(result, flush = true)
241 UpdateAllGPUBuffers(result.shaderdata.data, flush = true)