changeset 1409:5a56f8ac328b

did: improve descriptor-set handling + last fixes for new font/text rendering api
author sam <sam@basx.dev>
date Mon, 23 Dec 2024 00:32:07 +0700
parents 17d960ff6a24
children 99d5b42cf32d
files semicongine/rendering/shaders.nim semicongine/text.nim semicongine/text/font.nim tests/test_text.nim
diffstat 4 files changed, 108 insertions(+), 110 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/rendering/shaders.nim	Sun Dec 22 22:32:12 2024 +0700
+++ b/semicongine/rendering/shaders.nim	Mon Dec 23 00:32:07 2024 +0700
@@ -472,6 +472,17 @@
         nil,
         addr(result[value.getCustomPragmaVal(DescriptorSet)]),
       )
+  # create empty descriptor sets for unused sets
+  for i in 0 ..< result.len:
+    if not result[i].Valid:
+      var layoutCreateInfo = VkDescriptorSetLayoutCreateInfo(
+        sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+        bindingCount: 0,
+        pBindings: nil,
+      )
+      checkVkResult vkCreateDescriptorSetLayout(
+        vulkan.device, addr(layoutCreateInfo), nil, addr(result[i])
+      )
 
 proc createPipeline*[TShader](
     renderPass: RenderPass,
@@ -488,10 +499,6 @@
   (result.vertexShaderModule, result.fragmentShaderModule) = compileShader(shader)
 
   result.descriptorSetLayouts = createDescriptorSetLayouts[TShader]()
-  var layouts: seq[VkDescriptorSetLayout]
-  for l in result.descriptorSetLayouts:
-    if l.Valid:
-      layouts.add l
 
   # TODO: only add pushConstants if shader actually uses them
   let pushConstant = VkPushConstantRange(
@@ -502,8 +509,8 @@
 
   let pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
     sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
-    setLayoutCount: layouts.len.uint32,
-    pSetLayouts: layouts.ToCPointer,
+    setLayoutCount: result.descriptorSetLayouts.len.uint32,
+    pSetLayouts: result.descriptorSetLayouts.ToCPointer,
     pushConstantRangeCount: 1,
     pPushConstantRanges: addr(pushConstant),
   )
--- a/semicongine/text.nim	Sun Dec 22 22:32:12 2024 +0700
+++ b/semicongine/text.nim	Mon Dec 23 00:32:07 2024 +0700
@@ -15,63 +15,19 @@
 import ./image
 import ./contrib/algorithms/texture_packing
 
-const
-  NEWLINE = Rune('\n')
-  SPACE = Rune(' ')
-
 type
-  TextAlignment* = enum
-    Left
-    Center
-    Right
-
   GlyphQuad[MaxGlyphs: static int] = object
     pos: array[MaxGlyphs, Vec4f]
       # vertex offsets to glyph center: [left, bottom, right, top]
     uv: array[MaxGlyphs, Vec4f] # [left, bottom, right, top]
 
+  TextRendering* = object
+    aspectRatio*: float32
+
   GlyphDescriptorSet*[MaxGlyphs: static int] = object
     fontAtlas*: Image[Gray]
     glyphquads*: GPUValue[GlyphQuad[MaxGlyphs], StorageBuffer]
 
-  FontObj*[MaxGlyphs: static int] = object
-    advance*: Table[Rune, float32]
-    kerning*: Table[(Rune, Rune), float32]
-    lineAdvance*: float32
-    lineHeight*: float32 # like lineAdvance - lineGap
-    ascent*: float32 # from baseline to highest glyph
-    descent*: float32 # from baseline to highest glyph
-    xHeight*: float32 # from baseline to height of lowercase x
-    descriptorSet*: DescriptorSetData[GlyphDescriptorSet[MaxGlyphs]]
-    descriptorGlyphIndex: Table[Rune, uint16]
-    fallbackCharacter: Rune
-
-  Font*[MaxGlyphs: static int] = ref FontObj[MaxGlyphs]
-
-  TextHandle* = distinct int
-  Text = object
-    bufferOffset: int
-    text: seq[Rune]
-    position: Vec3f = vec3()
-    alignment: TextAlignment = Left
-    anchor: Vec2f = vec2()
-    scale: float32 = 0
-    color: Vec4f = vec4(1, 1, 1, 1)
-    capacity: int
-
-  TextBuffer*[MaxGlyphs: static int] = object
-    cursor: int
-    font*: Font[MaxGlyphs]
-    baseScale*: float32
-    position*: GPUArray[Vec3f, VertexBufferMapped]
-    color*: GPUArray[Vec4f, VertexBufferMapped]
-    scale*: GPUArray[float32, VertexBufferMapped]
-    glyphIndex*: GPUArray[uint16, VertexBufferMapped]
-    texts: seq[Text]
-
-  TextRendering* = object
-    aspectRatio*: float32
-
   GlyphShader*[MaxGlyphs: static int] = object
     position {.InstanceAttribute.}: Vec3f
     color {.InstanceAttribute.}: Vec4f
@@ -82,7 +38,7 @@
     fragmentUv {.Pass.}: Vec2f
     fragmentColor {.PassFlat.}: Vec4f
     outColor {.ShaderOutput.}: Vec4f
-    glyphData {.DescriptorSet: 0.}: GlyphDescriptorSet[MaxGlyphs]
+    glyphData {.DescriptorSet: 3.}: GlyphDescriptorSet[MaxGlyphs]
     vertexCode* =
       """
 const int[6] indices = int[](0, 1, 2, 2, 3, 0);
@@ -109,6 +65,47 @@
     outColor = vec4(fragmentColor.rgb, fragmentColor.a * a);
 }"""
 
+  FontObj*[MaxGlyphs: static int] = object
+    advance*: Table[Rune, float32]
+    kerning*: Table[(Rune, Rune), float32]
+    lineAdvance*: float32
+    lineHeight*: float32 # like lineAdvance - lineGap
+    ascent*: float32 # from baseline to highest glyph
+    descent*: float32 # from baseline to highest glyph
+    xHeight*: float32 # from baseline to height of lowercase x
+    descriptorSet*: DescriptorSetData[GlyphDescriptorSet[MaxGlyphs]]
+    descriptorGlyphIndex: Table[Rune, uint16]
+    fallbackCharacter: Rune
+
+  Font*[MaxGlyphs: static int] = ref FontObj[MaxGlyphs]
+
+  TextHandle* = distinct int
+
+  TextAlignment* = enum
+    Left
+    Center
+    Right
+
+  Text = object
+    bufferOffset: int
+    text: seq[Rune]
+    position: Vec3f = vec3()
+    alignment: TextAlignment = Left
+    anchor: Vec2f = vec2()
+    scale: float32 = 0
+    color: Vec4f = vec4(1, 1, 1, 1)
+    capacity: int
+
+  TextBuffer*[MaxGlyphs: static int] = object
+    cursor: int
+    font*: Font[MaxGlyphs]
+    baseScale*: float32
+    position*: GPUArray[Vec3f, VertexBufferMapped]
+    color*: GPUArray[Vec4f, VertexBufferMapped]
+    scale*: GPUArray[float32, VertexBufferMapped]
+    glyphIndex*: GPUArray[uint16, VertexBufferMapped]
+    texts: seq[Text]
+
 proc `=copy`[MaxGlyphs: static int](
   dest: var FontObj[MaxGlyphs], source: FontObj[MaxGlyphs]
 ) {.error.}
@@ -120,16 +117,16 @@
 include ./text/font
 
 func initTextBuffer*[MaxGlyphs: static int](
-    font: Font[MaxGlyphs], maxCharacters: int, baseScale = 1'f32
+    font: Font[MaxGlyphs], bufferSize: int, baseScale = 1'f32
 ): TextBuffer[MaxGlyphs] =
   result.cursor = 0
   result.font = font
   result.baseScale = baseScale
-  result.position.data.setLen(maxCharacters)
-  result.scale.data.setLen(maxCharacters)
-  result.color.data.setLen(maxCharacters)
-  result.glyphIndex.data.setLen(maxCharacters)
-  result.texts.setLen(maxCharacters) # waste a lot of memory?
+  result.position.data.setLen(bufferSize)
+  result.scale.data.setLen(bufferSize)
+  result.color.data.setLen(bufferSize)
+  result.glyphIndex.data.setLen(bufferSize)
+  result.texts.setLen(bufferSize) # waste a lot of memory?
 
 iterator splitLines(text: seq[Rune]): seq[Rune] =
   var current = newSeq[Rune]()
@@ -141,21 +138,24 @@
       current.add c
   yield current
 
-proc width(font: Font, text: seq[Rune], scale: float32): float32 =
+proc width*(font: Font, text: seq[Rune]): float32 =
   for i in 0 ..< text.len:
     if not (i == text.len - 1 and text[i].isWhiteSpace):
       if text[i] in font.advance:
-        result += font.advance[text[i]] * scale
+        result += font.advance[text[i]]
       else:
-        result += font.advance[font.fallbackCharacter] * scale
+        result += font.advance[font.fallbackCharacter]
     if i < text.len - 1:
-      result += font.kerning.getOrDefault((text[i], text[i + 1]), 0) * scale
+      result += font.kerning.getOrDefault((text[i], text[i + 1]), 0)
   return result
 
-proc textDimension*(font: Font, text: seq[Rune], scale: float32): Vec2f =
+proc width*(font: Font, text: string): float32 =
+  width(font, text.toRunes)
+
+proc textDimension*(font: Font, text: seq[Rune]): Vec2f =
   let nLines = text.countIt(it == Rune('\n')).float32
-  let h = (nLines * font.lineAdvance * scale + font.lineHeight * scale)
-  let w = max(splitLines(text).toSeq.mapIt(width(font, it, scale)))
+  let h = (nLines * font.lineAdvance + font.lineHeight)
+  let w = max(splitLines(text).toSeq.mapIt(width(font, it)))
 
   return vec2(w, h)
 
@@ -172,7 +172,7 @@
     capacity = textbuffer.texts[i].capacity
 
     globalScale = scale * textbuffer.baseScale
-    box = textDimension(textbuffer.font, text, globalScale)
+    box = textDimension(textbuffer.font, text) * globalScale
     xH = textbuffer.font.xHeight * globalScale
     aratio = getAspectRatio()
     origin = vec3(
@@ -181,7 +181,7 @@
         textbuffer.font.lineHeight * globalScale * 0.5,
       position.z,
     )
-    lineWidths = splitLines(text).toSeq.mapIt(width(textbuffer.font, it, globalScale))
+    lineWidths = splitLines(text).toSeq.mapIt(width(textbuffer.font, it) * globalScale)
     maxWidth = box.x
 
   var
--- a/semicongine/text/font.nim	Sun Dec 22 22:32:12 2024 +0700
+++ b/semicongine/text/font.nim	Mon Dec 23 00:32:07 2024 +0700
@@ -176,23 +176,19 @@
     lineHeightPixels,
   )
 
-func textWidth*(theText: seq[Rune] | string, font: FontObj): float32 =
-  var text = when theText is string: theText.toRunes else: theText
-  var currentWidth = 0'f32
-  var lineWidths: seq[float32]
-  for i in 0 ..< text.len:
-    if text[i] == NEWLINE:
-      lineWidths.add currentWidth
-      currentWidth = 0'f32
-    else:
-      if not (i == text.len - 1 and text[i].isWhiteSpace):
-        currentWidth += font.advance[text[i]]
-      if i < text.len - 1:
-        currentWidth += font.kerning[(text[i], text[i + 1])]
-  lineWidths.add currentWidth
-  return lineWidths.max
+proc upload*(font: Font, renderdata: var RenderData) =
+  assignBuffers(renderdata, font.descriptorSet)
+  uploadImages(renderdata, font.descriptorSet)
+
+proc addToPipeline*(font: Font, renderdata: RenderData, pipeline: Pipeline) =
+  initDescriptorSet(renderdata, pipeline.layout(3), font.descriptorSet)
 
-func WordWrapped*(text: seq[Rune], font: FontObj, maxWidth: float32): seq[Rune] =
+proc bindTo*(font: Font, pipeline: Pipeline, commandbuffer: VkCommandBuffer) =
+  bindDescriptorSet(commandbuffer, font.descriptorSet, 3, pipeline)
+
+#[
+# needs to be adjusted to work correctly with new metrics code in text.nim
+func wordWrapped*(text: seq[Rune], font: FontObj, maxWidth: float32): seq[Rune] =
   var remaining: seq[seq[Rune]] = @[@[]]
   for c in text:
     if c == SPACE:
@@ -236,3 +232,4 @@
     result.add currentLine
 
   return result
+  ]#
--- a/tests/test_text.nim	Sun Dec 22 22:32:12 2024 +0700
+++ b/tests/test_text.nim	Mon Dec 23 00:32:07 2024 +0700
@@ -22,11 +22,10 @@
     renderPass = vulkan.swapchain.renderPass
   )
   var textbuffer = font.initTextBuffer(1000, baseScale = 0.1)
+  assignBuffers(renderdata, textbuffer)
 
-  assignBuffers(renderdata, textbuffer)
-  assignBuffers(renderdata, font.descriptorSet)
-  uploadImages(renderdata, font.descriptorSet)
-  initDescriptorSet(renderdata, pipeline.layout(0), font.descriptorSet)
+  font.upload(renderdata)
+  font.addToPipeline(renderdata, pipeline)
 
   discard textbuffer.add("Hello semicongine!", vec3())
 
@@ -37,7 +36,7 @@
       textbuffer.refresh()
 
     withNextFrame(framebuffer, commandbuffer):
-      bindDescriptorSet(commandbuffer, font.descriptorSet, 0, pipeline)
+      font.bindTo(pipeline, commandbuffer)
       withRenderPass(
         vulkan.swapchain.renderPass,
         framebuffer,
@@ -64,15 +63,12 @@
     renderPass = vulkan.swapchain.renderPass
   )
 
-  assignBuffers(renderdata, font1.descriptorSet)
-  assignBuffers(renderdata, font2.descriptorSet)
-  assignBuffers(renderdata, font3.descriptorSet)
-  uploadImages(renderdata, font1.descriptorSet)
-  uploadImages(renderdata, font2.descriptorSet)
-  uploadImages(renderdata, font3.descriptorSet)
-  initDescriptorSet(renderdata, pipeline.layout(0), font1.descriptorSet)
-  initDescriptorSet(renderdata, pipeline.layout(0), font2.descriptorSet)
-  initDescriptorSet(renderdata, pipeline.layout(0), font3.descriptorSet)
+  font1.upload(renderdata)
+  font2.upload(renderdata)
+  font3.upload(renderdata)
+  font1.addToPipeline(renderdata, pipeline)
+  font2.addToPipeline(renderdata, pipeline)
+  font3.addToPipeline(renderdata, pipeline)
 
   var textbuffer1 = font1.initTextBuffer(10, baseScale = 0.1)
   var textbuffer2 = font2.initTextBuffer(10, baseScale = 0.1)
@@ -111,11 +107,11 @@
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
-          bindDescriptorSet(commandbuffer, font1.descriptorSet, 0, pipeline)
+          bindDescriptorSet(commandbuffer, font1.descriptorSet, 3, pipeline)
           renderTextBuffer(commandbuffer, pipeline, textbuffer1)
-          bindDescriptorSet(commandbuffer, font2.descriptorSet, 0, pipeline)
+          bindDescriptorSet(commandbuffer, font2.descriptorSet, 3, pipeline)
           renderTextBuffer(commandbuffer, pipeline, textbuffer2)
-          bindDescriptorSet(commandbuffer, font3.descriptorSet, 0, pipeline)
+          bindDescriptorSet(commandbuffer, font3.descriptorSet, 3, pipeline)
           renderTextBuffer(commandbuffer, pipeline, textbuffer3)
 
       # cleanup
@@ -131,9 +127,8 @@
     renderPass = vulkan.swapchain.renderPass
   )
 
-  assignBuffers(renderdata, font.descriptorSet)
-  uploadImages(renderdata, font.descriptorSet)
-  initDescriptorSet(renderdata, pipeline.layout(0), font.descriptorSet)
+  font.upload(renderdata)
+  font.addToPipeline(renderdata, pipeline)
 
   var textbuffer = font.initTextBuffer(1000, baseScale = 0.1)
   assignBuffers(renderdata, textbuffer)
@@ -161,7 +156,7 @@
       textbuffer.refresh()
 
     withNextFrame(framebuffer, commandbuffer):
-      bindDescriptorSet(commandbuffer, font.descriptorSet, 0, pipeline)
+      bindDescriptorSet(commandbuffer, font.descriptorSet, 3, pipeline)
       withRenderPass(
         vulkan.swapchain.renderPass,
         framebuffer,
@@ -186,9 +181,8 @@
     renderPass = vulkan.swapchain.renderPass
   )
 
-  assignBuffers(renderdata, font.descriptorSet)
-  uploadImages(renderdata, font.descriptorSet)
-  initDescriptorSet(renderdata, pipeline.layout(0), font.descriptorSet)
+  font.upload(renderdata)
+  font.addToPipeline(renderdata, pipeline)
 
   var textbuffer = font.initTextBuffer(3000, baseScale = 0.1)
   assignBuffers(renderdata, textbuffer)
@@ -211,7 +205,7 @@
     withNextFrame(framebuffer, commandbuffer):
       if windowWasResized():
         textbuffer.refresh()
-      bindDescriptorSet(commandbuffer, font.descriptorSet, 0, pipeline)
+      bindDescriptorSet(commandbuffer, font.descriptorSet, 3, pipeline)
       withRenderPass(
         vulkan.swapchain.renderPass,
         framebuffer,