diff 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
line wrap: on
line diff
--- a/semiconginev2/text/textbox.nim	Sat Jul 20 15:45:02 2024 +0700
+++ b/semiconginev2/text/textbox.nim	Sat Jul 20 17:45:44 2024 +0700
@@ -14,165 +14,177 @@
     lastRenderedText: seq[Rune] # stores the last rendered text, to prevent unnecessary updates
 
     # rendering data
-    position: GPUArray[Vec3f, VertexBuffer]
-    uv: GPUArray[Vec2f, VertexBuffer]
-    indices: GPUArray[uint16, IndexBuffer]
-    shaderdata: DescriptorSet[TextboxDescriptorSet]
-
-func `$`*(text: Textbox): string =
-  "\"" & $text.text[0 ..< min(text.text.len, 16)] & "\""
+    position*: GPUArray[Vec3f, VertexBuffer]
+    uv*: GPUArray[Vec2f, VertexBuffer]
+    indices*: GPUArray[uint16, IndexBuffer]
+    shaderdata*: DescriptorSet[TextboxDescriptorSet]
 
-proc RefreshShaderdata(text: Textbox) =
-  if not text.dirtyShaderdata:
-    return
-  text.shaderdata.data.textbox.UpdateGPUBuffer()
+func `$`*(textbox: Textbox): string =
+  "\"" & $textbox.text[0 ..< min(textbox.text.len, 16)] & "\""
 
-proc RefreshGeometry(text: var Textbox) =
-  if not text.dirtyGeometry and text.processedText == text.lastRenderedText:
-    return
+proc RefreshShaderdata(textbox: Textbox) =
+  textbox.shaderdata.data.textbox.UpdateGPUBuffer()
 
+proc RefreshGeometry(textbox: var Textbox) =
   # pre-calculate text-width
   var width = 0'f32
   var lineWidths: seq[float32]
-  for i in 0 ..< text.processedText.len:
-    if text.processedText[i] == NEWLINE:
+  for i in 0 ..< textbox.processedText.len:
+    if textbox.processedText[i] == NEWLINE:
       lineWidths.add width
       width = 0'f32
     else:
-      if not (i == text.processedText.len - 1 and text.processedText[i].isWhiteSpace):
-        width += text.font.glyphs[text.processedText[i]].advance
-      if i < text.processedText.len - 1:
-        width += text.font.kerning[(text.processedText[i], text.processedText[i + 1])]
+      if not (i == textbox.processedText.len - 1 and textbox.processedText[i].isWhiteSpace):
+        width += textbox.font.glyphs[textbox.processedText[i]].advance
+      if i < textbox.processedText.len - 1:
+        width += textbox.font.kerning[(textbox.processedText[i], textbox.processedText[i + 1])]
   lineWidths.add width
-  var height = float32(lineWidths.len - 1) * text.font.lineAdvance + text.font.capHeight
+  var height = float32(lineWidths.len - 1) * textbox.font.lineAdvance + textbox.font.capHeight
   if lineWidths[^1] == 0 and lineWidths.len > 1:
     height -= 1
 
-  let anchorY = (case text.verticalAlignment
+  let anchorY = (case textbox.verticalAlignment
     of Top: 0'f32
     of Center: height / 2
-    of Bottom: height) - text.font.capHeight
+    of Bottom: height) - textbox.font.capHeight
 
   var
     offsetX = 0'f32
     offsetY = 0'f32
     lineIndex = 0
-    anchorX = case text.horizontalAlignment
+    anchorX = case textbox.horizontalAlignment
       of Left: 0'f32
       of Center: lineWidths[lineIndex] / 2
       of Right: lineWidths[lineIndex]
-  for i in 0 ..< text.maxLen:
+  for i in 0 ..< textbox.maxLen:
     let vertexOffset = i * 4
-    if i < text.processedText.len:
-      if text.processedText[i] == Rune('\n'):
+    if i < textbox.processedText.len:
+      if textbox.processedText[i] == Rune('\n'):
         offsetX = 0
-        offsetY += text.font.lineAdvance
-        text.position.data[vertexOffset + 0] = NewVec3f()
-        text.position.data[vertexOffset + 1] = NewVec3f()
-        text.position.data[vertexOffset + 2] = NewVec3f()
-        text.position.data[vertexOffset + 3] = NewVec3f()
+        offsetY += textbox.font.lineAdvance
+        textbox.position.data[vertexOffset + 0] = NewVec3f()
+        textbox.position.data[vertexOffset + 1] = NewVec3f()
+        textbox.position.data[vertexOffset + 2] = NewVec3f()
+        textbox.position.data[vertexOffset + 3] = NewVec3f()
         inc lineIndex
-        anchorX = case text.horizontalAlignment
+        anchorX = case textbox.horizontalAlignment
           of Left: 0'f32
           of Center: lineWidths[lineIndex] / 2
           of Right: lineWidths[lineIndex]
       else:
         let
-          glyph = text.font.glyphs[text.processedText[i]]
+          glyph = textbox.font.glyphs[textbox.processedText[i]]
           left = offsetX + glyph.leftOffset
           right = offsetX + glyph.leftOffset + glyph.dimension.x
           top = offsetY + glyph.topOffset
           bottom = offsetY + glyph.topOffset + glyph.dimension.y
 
-        text.position.data[vertexOffset + 0] = NewVec3f(left - anchorX, bottom - anchorY)
-        text.position.data[vertexOffset + 1] = NewVec3f(left - anchorX, top - anchorY)
-        text.position.data[vertexOffset + 2] = NewVec3f(right - anchorX, top - anchorY)
-        text.position.data[vertexOffset + 3] = NewVec3f(right - anchorX, bottom - anchorY)
+        textbox.position.data[vertexOffset + 1] = NewVec3f(left - anchorX, bottom - anchorY)
+        textbox.position.data[vertexOffset + 0] = NewVec3f(left - anchorX, top - anchorY)
+        textbox.position.data[vertexOffset + 3] = NewVec3f(right - anchorX, top - anchorY)
+        textbox.position.data[vertexOffset + 2] = NewVec3f(right - anchorX, bottom - anchorY)
 
-        text.uv.data[vertexOffset + 0] = glyph.uvs[0]
-        text.uv.data[vertexOffset + 1] = glyph.uvs[1]
-        text.uv.data[vertexOffset + 2] = glyph.uvs[2]
-        text.uv.data[vertexOffset + 3] = glyph.uvs[3]
+        textbox.uv.data[vertexOffset + 0] = glyph.uvs[0]
+        textbox.uv.data[vertexOffset + 1] = glyph.uvs[1]
+        textbox.uv.data[vertexOffset + 2] = glyph.uvs[2]
+        textbox.uv.data[vertexOffset + 3] = glyph.uvs[3]
 
         offsetX += glyph.advance
-        if i < text.processedText.len - 1:
-          offsetX += text.font.kerning[(text.processedText[i], text.processedText[i + 1])]
+        if i < textbox.processedText.len - 1:
+          offsetX += textbox.font.kerning[(textbox.processedText[i], textbox.processedText[i + 1])]
     else:
-      text.position.data[vertexOffset + 0] = NewVec3f()
-      text.position.data[vertexOffset + 1] = NewVec3f()
-      text.position.data[vertexOffset + 2] = NewVec3f()
-      text.position.data[vertexOffset + 3] = NewVec3f()
-  text.lastRenderedText = text.processedText
-  text.dirtyGeometry = false
+      textbox.position.data[vertexOffset + 0] = NewVec3f()
+      textbox.position.data[vertexOffset + 1] = NewVec3f()
+      textbox.position.data[vertexOffset + 2] = NewVec3f()
+      textbox.position.data[vertexOffset + 3] = NewVec3f()
+  textbox.lastRenderedText = textbox.processedText
+
+func text*(textbox: Textbox): seq[Rune] =
+  textbox.text
 
-proc Refresh*(textbox: var Textbox) =
-  textbox.RefreshShaderdata()
-  textbox.RefreshGeometry()
+proc `text=`*(textbox: var Textbox, newText: seq[Rune]) =
+  if newText[0 ..< min(newText.len, textbox.maxLen)] == textbox.text:
+    return
 
-func text*(text: Textbox): seq[Rune] =
-  text.text
+  textbox.text = newText[0 ..< min(newText.len, textbox.maxLen)]
 
-proc `text=`*(text: var Textbox, newText: seq[Rune]) =
-  text.text = newText[0 ..< min(newText.len, text.maxLen)]
-
-  text.processedText = text.text
-  if text.maxWidth > 0:
-    text.processedText = WordWrapped(
-      text.processedText,
-      text.font[],
-      text.maxWidth / text.shaderdata.data.textbox.data.scale,
+  textbox.processedText = textbox.text
+  if textbox.maxWidth > 0:
+    textbox.processedText = WordWrapped(
+      textbox.processedText,
+      textbox.font[],
+      textbox.maxWidth / textbox.shaderdata.data.textbox.data.scale,
     )
 
-proc `text=`*(text: var Textbox, newText: string) =
-  `text=`(text, newText.toRunes)
+proc `text=`*(textbox: var Textbox, newText: string) =
+  `text=`(textbox, newText.toRunes)
+
+proc Color*(textbox: Textbox): Vec4f =
+  textbox.shaderdata.data.textbox.data.color
 
-proc Color*(text: Textbox): Vec4f =
-  text.shaderdata.data.textbox.data.color
+proc `Color=`*(textbox: var Textbox, value: Vec4f) =
+  if textbox.shaderdata.data.textbox.data.color != value:
+    textbox.dirtyShaderdata = true
+    textbox.shaderdata.data.textbox.data.color = value
 
-proc `Color=`*(text: var Textbox, value: Vec4f) =
-  if text.shaderdata.data.textbox.data.color != value:
-    text.dirtyShaderdata = true
-    text.shaderdata.data.textbox.data.color = value
+proc Scale*(textbox: Textbox): float32 =
+  textbox.shaderdata.data.textbox.data.scale
 
-proc Scale*(text: Textbox): float32 =
-  text.shaderdata.data.textbox.data.scale
+proc `Scale=`*(textbox: var Textbox, value: float32) =
+  if textbox.shaderdata.data.textbox.data.scale != value:
+    textbox.dirtyShaderdata = true
+    textbox.shaderdata.data.textbox.data.scale = value
+
+proc AspectRatio*(textbox: Textbox): float32 =
+  textbox.shaderdata.data.textbox.data.aspectratio
 
-proc `Scale=`*(text: var Textbox, value: float32) =
-  if text.shaderdata.data.textbox.data.scale != value:
-    text.dirtyShaderdata = true
-    text.shaderdata.data.textbox.data.scale = value
+proc `AspectRatio=`*(textbox: var Textbox, value: float32) =
+  if textbox.shaderdata.data.textbox.data.aspectratio != value:
+    textbox.dirtyShaderdata = true
+    textbox.shaderdata.data.textbox.data.aspectratio = value
 
-proc Position*(text: Textbox): Vec3f =
-  text.shaderdata.data.textbox.data.position
+proc Position*(textbox: Textbox): Vec3f =
+  textbox.shaderdata.data.textbox.data.position
 
-proc `Position=`*(text: var Textbox, value: Vec3f) =
-  if text.shaderdata.data.textbox.data.position != value:
-    text.dirtyShaderdata = true
-    text.shaderdata.data.textbox.data.position = value
+proc `Position=`*(textbox: var Textbox, value: Vec3f) =
+  if textbox.shaderdata.data.textbox.data.position != value:
+    textbox.dirtyShaderdata = true
+    textbox.shaderdata.data.textbox.data.position = value
 
-proc horizontalAlignment*(text: Textbox): HorizontalAlignment =
-  text.horizontalAlignment
-proc `horizontalAlignment=`*(text: var Textbox, value: HorizontalAlignment) =
-  if value != text.horizontalAlignment:
-    text.horizontalAlignment = value
-    text.dirtyGeometry = true
+proc horizontalAlignment*(textbox: Textbox): HorizontalAlignment =
+  textbox.horizontalAlignment
+proc `horizontalAlignment=`*(textbox: var Textbox, value: HorizontalAlignment) =
+  if value != textbox.horizontalAlignment:
+    textbox.horizontalAlignment = value
+    textbox.dirtyGeometry = true
 
-proc verticalAlignment*(text: Textbox): VerticalAlignment =
-  text.verticalAlignment
-proc `verticalAlignment=`*(text: var Textbox, value: VerticalAlignment) =
-  if value != text.verticalAlignment:
-    text.verticalAlignment = value
-    text.dirtyGeometry = true
+proc verticalAlignment*(textbox: Textbox): VerticalAlignment =
+  textbox.verticalAlignment
+proc `verticalAlignment=`*(textbox: var Textbox, value: VerticalAlignment) =
+  if value != textbox.verticalAlignment:
+    textbox.verticalAlignment = value
+    textbox.dirtyGeometry = true
+
+proc Refresh*(textbox: var Textbox, aspectratio: float32) =
+  `AspectRatio=`(textbox, aspectratio)
 
-proc Draw(text: Textbox, commandbuffer: VkCommandBuffer, pipeline: Pipeline, currentFiF: int) =
+  if textbox.dirtyShaderdata:
+    textbox.RefreshShaderdata()
+    textbox.dirtyShaderdata = false
+
+  if textbox.dirtyGeometry or textbox.processedText != textbox.lastRenderedText:
+    textbox.RefreshGeometry()
+    textbox.dirtyGeometry = false
+
+proc Render*(textbox: Textbox, commandbuffer: VkCommandBuffer, pipeline: Pipeline, currentFiF: int) =
   WithBind(commandbuffer, (textbox.shaderdata, ), pipeline, currentFiF):
-    Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = text)
+    Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = textbox)
 
-proc InitTextbox*(
+proc InitTextbox*[T: string | seq[Rune]](
   renderdata: var RenderData,
   descriptorSetLayout: VkDescriptorSetLayout,
   font: Font,
-  text = "".toRunes,
+  text: T = default(T),
   scale: float32 = 1,
   position: Vec3f = NewVec3f(),
   color: Vec4f = NewVec4f(0, 0, 0, 1),
@@ -186,6 +198,7 @@
     maxLen: maxLen,
     font: font,
     dirtyGeometry: true,
+    dirtyShaderdata: true,
     horizontalAlignment: horizontalAlignment,
     verticalAlignment: verticalAlignment,
     maxWidth: maxWidth,
@@ -198,6 +211,7 @@
           scale: scale,
           position: position,
           color: color,
+          aspectratio: 1,
     ), UniformBufferMapped),
     fontAtlas: font.fontAtlas
   )
@@ -213,10 +227,15 @@
     result.indices.data[i * 6 + 4] = vertexIndex + 3
     result.indices.data[i * 6 + 5] = vertexIndex + 0
 
-  `text=`(result, text)
+  when T is string:
+    `text=`(result, text.toRunes())
+  else:
+    `text=`(result, text)
 
-  AssignBuffers(renderdata, result)
+  AssignBuffers(renderdata, result, uploadData = false)
   UploadImages(renderdata, result.shaderdata)
   InitDescriptorSet(renderdata, descriptorSetLayout, result.shaderdata)
 
-  result.Refresh()
+  result.Refresh(1)
+  UpdateAllGPUBuffers(result, flush = true)
+  UpdateAllGPUBuffers(result.shaderdata.data, flush = true)