changeset 1381:c8d1b87cf6c8

did: make progress on new glyph-rendering system
author sam <sam@basx.dev>
date Thu, 12 Dec 2024 23:54:49 +0700
parents 4aa9e703af48
children ec9f19151d44 7e998fce9260
files semicongine/rendering.nim semicongine/text.nim semicongine/text/font.nim tests/test_text.nim
diffstat 4 files changed, 73 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/rendering.nim	Thu Dec 12 21:35:34 2024 +0700
+++ b/semicongine/rendering.nim	Thu Dec 12 23:54:49 2024 +0700
@@ -206,7 +206,7 @@
     elif getBufferType(default(T)) in [StorageBuffer, StorageBufferMapped]:
       VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
     else:
-      {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+      {.error: "Unsupported descriptor type: " & $T.}
   elif T is array:
     when elementType(default(T)) is ImageObject:
       VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
@@ -218,11 +218,11 @@
           [StorageBuffer, StorageBufferMapped]:
         VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
       else:
-        {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+        {.error: "Unsupported descriptor type: " & $T.}
     else:
-      {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+      {.error: "Unsupported descriptor type: " & $T.}
   else:
-    {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+    {.error: "Unsupported descriptor type: " & $T.}
 
 func getDescriptorCount[T](): uint32 {.compileTIme.} =
   when T is array:
@@ -239,8 +239,7 @@
       found = true
     else:
       inc c
-  assert found,
-    "Field '" & field & "' of descriptor '" & typetraits.name(T) & "' not found"
+  assert found, "Field '" & field & "' of descriptor '" & $T & "' not found"
 
 proc currentFiF*(): int =
   assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
--- a/semicongine/text.nim	Thu Dec 12 21:35:34 2024 +0700
+++ b/semicongine/text.nim	Thu Dec 12 23:54:49 2024 +0700
@@ -76,7 +76,7 @@
     glyphIndex*: GPUArray[uint16, VertexBufferMapped]
 
   GlyphData[N: static int] = object
-    pos: array[N, Vec2f] # [left, bottom, right, top]
+    pos: array[N, Vec4f] # [left, bottom, right, top]
     uv: array[N, Vec4f] # [left, bottom, right, top]
 
   GlyphDescriptorSet*[N: static int] = object
@@ -101,26 +101,64 @@
 const vec2[4] pp = vec2[](vec2(-0.1, -0.1), vec2(-0.1, 0.1), vec2(0.1, 0.1), vec2(0.1, -0.1));
 
 void main() {
-  // int vertexI = indices[gl_VertexIndex];
-  // vec3 pos = vec3(glyphData.pos[glyphIndex][i_x[vertexI]], glyphData.pos[glyphIndex][i_y[vertexI]], 0);
-  // vec2 uv = vec2(glyphData.uv[glyphIndex][i_x[vertexI]], glyphData.uv[glyphIndex][i_y[vertexI]]);
-  // gl_Position = vec4(pos * scale + position, 1.0);
-  // fragmentUv = uv;
-  // fragmentColor = color;
-  gl_Position = vec4(pp[indices[gl_VertexIndex]] + glyphIndex * 0.1, 0, 1);
+  int vertexI = indices[gl_VertexIndex];
+  vec3 pos = vec3(glyphData.pos[glyphIndex][i_x[vertexI]], glyphData.pos[glyphIndex][i_y[vertexI]], 0);
+  vec2 uv = vec2(glyphData.uv[glyphIndex][i_x[vertexI]], glyphData.uv[glyphIndex][i_y[vertexI]]);
+  gl_Position = vec4(pos * scale + position, 1.0);
+  fragmentUv = uv;
+  fragmentColor = color;
 }  """
     fragmentCode* =
       """void main() {
-    // float v = texture(fontAtlas, fragmentUv).r;
+    float v = texture(fontAtlas, fragmentUv).r;
     // CARFULL: This can lead to rough edges at times
-    // if(v == 0) {
-      // discard;
-    // }
-    // outColor = vec4(fragmentColor.rgb, fragmentColor.a * v);
-    outColor = vec4(1, 0, 1, 1);
+    if(v == 0) {
+      discard;
+    }
+    outColor = vec4(fragmentColor.rgb, fragmentColor.a * v);
 }"""
 
 proc `=copy`(dest: var FontObj, source: FontObj) {.error.}
 
 include ./text/font
 include ./text/textbox
+
+proc glyphDescriptorSet*(
+    font: Font, maxGlyphs: static int
+): (DescriptorSetData[GlyphDescriptorSet[maxGlyphs]], Table[Rune, uint16]) =
+  assert font.glyphs.len <= maxGlyphs,
+    "font has " & $font.glyphs.len & " glyphs but shader is only configured for " &
+      $maxGlyphs
+
+  var glyphData = GlyphData[maxGlyphs]()
+  var glyphTable: Table[Rune, uint16]
+
+  var i = 0'u16
+  for rune, info in font.glyphs.pairs():
+    let
+      left = -info.leftOffset
+      right = -info.leftOffset + info.dimension.x
+      top = font.lineHeight + info.topOffset
+      bottom = font.lineHeight + info.topOffset - info.dimension.y
+    glyphData.pos[i] = vec4(left, bottom, right, top) * 0.005'f32
+    assert info.uvs[0].x == info.uvs[1].x,
+      "Currently only axis aligned rectangles are allowed for info boxes in font texture maps"
+    assert info.uvs[0].y == info.uvs[3].y,
+      "Currently only axis aligned rectangles are allowed for info boxes in font texture maps"
+    assert info.uvs[2].x == info.uvs[3].x,
+      "Currently only axis aligned rectangles are allowed for info boxes in font texture maps"
+    assert info.uvs[1].y == info.uvs[2].y,
+      "Currently only axis aligned rectangles are allowed for info boxes in font texture maps"
+    glyphData.uv[i] = vec4(info.uvs[0].x, info.uvs[0].y, info.uvs[2].x, info.uvs[2].y)
+    glyphTable[rune] = i
+    inc i
+
+  (
+    asDescriptorSetData(
+      GlyphDescriptorSet[maxGlyphs](
+        fontAtlas: font.fontAtlas.copy(),
+        glyphData: asGPUValue(glyphData, StorageBuffer),
+      )
+    ),
+    glyphTable,
+  )
--- a/semicongine/text/font.nim	Thu Dec 12 21:35:34 2024 +0700
+++ b/semicongine/text/font.nim	Thu Dec 12 23:54:49 2024 +0700
@@ -71,6 +71,7 @@
 
   var
     topOffsets: Table[Rune, int]
+    leftOffsets: Table[Rune, int]
     images: seq[Image[Gray]]
 
   for codePoint in codePoints:
@@ -88,6 +89,7 @@
       addr offY,
     )
     topOffsets[codePoint] = offY
+    leftOffsets[codePoint] = offX
 
     if char(codePoint) in UppercaseLetters:
       result.capHeight = float32(height)
@@ -130,8 +132,9 @@
         vec2((coord.x + iw - 0.5) / w, (coord.y + 0.5) / h),
         vec2((coord.x + iw - 0.5) / w, (coord.y + ih - 0.5) / h),
       ],
-      topOffset: float32(topOffsets[codePoint]),
-      leftOffset: float32(leftBearing) * result.fontscale,
+      topOffset: float32(topOffsets[codePoint]) * result.fontscale,
+      # leftOffset: float32(leftBearing) * result.fontscale,
+      leftOffset: float32(leftOffsets[codePoint] + leftBearing) * result.fontscale,
       advance: float32(advance) * result.fontscale,
     )
 
--- a/tests/test_text.nim	Thu Dec 12 21:35:34 2024 +0700
+++ b/tests/test_text.nim	Thu Dec 12 23:54:49 2024 +0700
@@ -4,8 +4,10 @@
 import std/sequtils
 import std/monotimes
 import std/times
+import std/tables
 import std/options
 import std/random
+import std/unicode
 
 import ../semicongine
 
@@ -14,20 +16,21 @@
 
 type EMPTY = object
 
+const N_GLYPHS = 200
 proc test_01_static_label_new(time: float32) =
   var font = loadFont("Overhaul.ttf", lineHeightPixels = 160)
   var renderdata = initRenderData()
   var pipeline =
-    createPipeline[GlyphShader[200]](renderPass = vulkan.swapchain.renderPass)
+    createPipeline[GlyphShader[N_GLYPHS]](renderPass = vulkan.swapchain.renderPass)
+  var (ds, glyphtable) = glyphDescriptorSet(font, N_GLYPHS)
   var glyphs = Glyphs(
-    position: asGPUArray([vec3()], VertexBufferMapped),
-    scale: asGPUArray([1'f32], VertexBufferMapped),
-    color: asGPUArray([vec4(1, 1, 1, 1)], VertexBufferMapped),
-    glyphIndex: asGPUArray([0'u16], VertexBufferMapped),
+    position: asGPUArray([vec3(), vec3()], VertexBufferMapped),
+    scale: asGPUArray([1'f32, 1'f32], VertexBufferMapped),
+    color: asGPUArray([vec4(1, 1, 1, 1), vec4(1, 1, 1, 1)], VertexBufferMapped),
+    glyphIndex:
+      asGPUArray([glyphtable[Rune('Q')], glyphtable[Rune('H')]], VertexBufferMapped),
   )
 
-  var ds =
-    asDescriptorSetData(GlyphDescriptorSet[200](fontAtlas: font.fontAtlas.copy()))
   assignBuffers(renderdata, glyphs)
   assignBuffers(renderdata, ds)
   uploadImages(renderdata, ds)
@@ -282,7 +285,7 @@
   destroyRenderData(renderdata)
 
 when isMainModule:
-  var time = 1'f32
+  var time = 1000'f32
   initVulkan()
 
   for depthBuffer in [true, false]: