changeset 724:70d9147415b8

add: some api improvments, preparing for font-loading
author Sam <sam@basx.dev>
date Sat, 27 May 2023 13:43:46 +0700
parents 9610f5cc64f3
children 5c08c45b51b9
files src/semicongine.nim src/semicongine/core/fonttypes.nim src/semicongine/core/imagetypes.nim src/semicongine/renderer.nim src/semicongine/resources.nim src/semicongine/resources/font.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/text.nim
diffstat 9 files changed, 155 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine.nim	Sat May 27 13:43:46 2023 +0700
@@ -9,6 +9,7 @@
 import semicongine/renderer
 import semicongine/resources
 import semicongine/settings
+import semicongine/text
 import semicongine/platform/window
 import semicongine/vulkan
 
@@ -20,5 +21,6 @@
 export renderer
 export resources
 export settings
+export text
 export window
 export vulkan
--- a/src/semicongine/core/fonttypes.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/core/fonttypes.nim	Sat May 27 13:43:46 2023 +0700
@@ -2,7 +2,11 @@
 import std/unicode
 
 import ./imagetypes
+import ./vector
 
 type
   Font* = object
-    bitmaps*: Table[Rune, Image]
+    name*: string # used to reference fontAtlas will be referenced in shader
+    characterUVs*: Table[Rune, array[4, Vec2f]]
+    characterDimensions*: Table[Rune, Vec2f]
+    fontAtlas*: Image
--- a/src/semicongine/core/imagetypes.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/core/imagetypes.nim	Sat May 27 13:43:46 2023 +0700
@@ -14,10 +14,9 @@
     filter*: VkFilter # TODO: replace with mag/minification
 
   Image* = ref ImageObject
-  TextureObject = object
+  Texture* = object
     image*: Image
     sampler*: Sampler
-  Texture* = ref TextureObject
 
 proc DefaultSampler*(): Sampler =
   Sampler(
@@ -29,7 +28,7 @@
 
 proc newImage*(width, height: uint32, imagedata: seq[Pixel] = @[]): Image =
   assert width > 0 and height > 0
-  assert uint32(imagedata.len) == width * height
+  assert uint32(imagedata.len) == width * height or imagedata.len == 0
 
   result = new Image
   result.imagedata = (if imagedata.len == 0: newSeq[Pixel](width * height) else: imagedata)
@@ -37,3 +36,16 @@
 
   result.width = width
   result.height = height
+
+proc `[]`*(image: Image, x, y: uint32): Pixel =
+  assert x < image.width
+  assert y < image.height
+
+  image[].imagedata[y * image.width + x]
+
+proc `[]=`*(image: var Image, x, y: uint32, value: Pixel) =
+  assert x < image.width
+  assert y < image.height
+
+  image[].imagedata[y * image.width + x] = value
+
--- a/src/semicongine/renderer.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/renderer.nim	Sat May 27 13:43:46 2023 +0700
@@ -17,6 +17,7 @@
 
 import ./scene
 import ./mesh
+import ./text
 
 type
   SceneData = object
@@ -163,7 +164,23 @@
       indexBufferOffset += size
     data.drawables[mesh] = drawable
 
-  # setup uniforms and textures
+  # extract textures
+  var sampler = DefaultSampler()
+  sampler.magnification = VK_FILTER_NEAREST
+  sampler.minification = VK_FILTER_NEAREST
+  # for mesh in allComponentsOfType[Mesh](scene.root):
+  for textbox in allEntitiesOfType[Textbox](scene.root):
+    if not (textbox.font.name in data.textures):
+      data.textures[textbox.font.name] = @[
+        renderer.device.uploadTexture(Texture(image: textbox.font.fontAtlas, sampler: sampler))
+      ]
+
+  for name, textures in scene.textures.pairs:
+    data.textures[name] = @[]
+    for texture in textures:
+      data.textures[name].add renderer.device.uploadTexture(texture)
+
+  # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
     for pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.mitems:
       var uniformBufferSize = 0'u64
@@ -177,10 +194,6 @@
             requireMappable=true,
             preferVRAM=true,
           )
-      for name, textures in scene.textures.pairs:
-        data.textures[name] = @[]
-        for texture in textures:
-          data.textures[name].add renderer.device.uploadTexture(texture)
           
       var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32(renderer.swapchain.inFlightFrames))]
       if samplers.len > 0:
--- a/src/semicongine/resources.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/resources.nim	Sat May 27 13:43:46 2023 +0700
@@ -2,6 +2,7 @@
 import std/strutils
 import std/strformat
 import std/os
+import std/unicode
 
 import ./core
 import ./resources/image
@@ -126,8 +127,9 @@
   else:
     raise newException(Exception, "Unsupported audio file type: " & path)
 
-proc loadFont*(path: string): Font =
-  loadResource_intern(path).readTrueType()
+proc loadFont*(path: string, name: string): Font =
+  let defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=+[{]};:,<.>/?".toRunes()
+  loadResource_intern(path).readTrueType(name, defaultCharset)
 
 proc loadMesh*(path: string): Entity =
   loadResource_intern(path).readglTF()[0].root
--- a/src/semicongine/resources/font.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/resources/font.nim	Sat May 27 13:43:46 2023 +0700
@@ -1,8 +1,10 @@
 import std/strformat
+import std/tables
 import std/streams
 import std/os
 import std/unicode
 
+import ../core/vector
 import ../core/imagetypes
 import ../core/fonttypes
 
@@ -18,24 +20,53 @@
 proc stbtt_GetCodepointBitmap(info: ptr stbtt_fontinfo, scale_x: cfloat, scale_y: cfloat, codepoint: cint, width: ptr cint, height: ptr cint, xoff: ptr cint, yoff: ptr cint): cstring {.importc, nodecl.}
 # proc free(p: pointer) {.importc.}
 
-proc readTrueType*(stream: Stream): Font =
+proc readTrueType*(stream: Stream, name: string, codePoints: seq[Rune]): Font =
   var
     indata = stream.readAll()
     fontinfo: stbtt_fontinfo
   if stbtt_InitFont(addr fontinfo, addr indata[0], 0) == 0:
     raise newException(Exception, "An error occured while loading PNG file")
+
+  let fontheight = stbtt_ScaleForPixelHeight(addr fontinfo, 100)
   var
-    width, height: cint
-    offsetX, offsetY: cint
-    data = stbtt_GetCodepointBitmap(addr fontinfo, 0, stbtt_ScaleForPixelHeight(addr fontinfo, 20), cint('a'), addr width, addr height, addr offsetX, addr offsetY)
-  echo width, "x", height
-  echo "offset: ", offsetX, "x", offsetY
-  for y in 0 ..< height:
-    for x in 0 ..< width:
-      if data[y * width + x] > char(128):
-        write stdout, '#'
-      else:
-        write stdout, ' '
-      write stdout, ' '
-    write stdout, '\n'
-  result
+    charOffset: Table[Rune, uint32]
+    offsetX: uint32
+    maxheight: uint32
+    bitmaps: Table[Rune, (cstring, cint, cint)]
+    baselines: Table[Rune, int]
+  for codePoint in codePoints:
+    var
+      width, height: cint
+      leftStart, baseline: cint
+      data = stbtt_GetCodepointBitmap(
+        addr fontinfo,
+        0, fontheight,
+        cint('a'),
+        addr width, addr height,
+        addr leftStart, addr baseline
+      )
+    bitmaps[codePoint] = (data, width, height)
+    maxheight = max(maxheight, uint32(height))
+    charOffset[codePoint] = offsetX
+    offsetX += uint32(width)
+    baselines[codePoint] = baseline
+
+  result.name = name
+  result.fontAtlas = newImage(offsetX, maxheight)
+
+  offsetX = 0
+  for codePoint in codePoints:
+    let d = bitmaps[codePoint][0]
+    let width = uint32(bitmaps[codePoint][1])
+    let height = uint32(bitmaps[codePoint][2])
+    for y in 0 ..< height:
+      for x in 0 ..< width:
+        result.fontAtlas[x + offsetX, y] = [255'u8, 255'u8, 255'u8, uint8(d[y * width + x])]
+    result.characterDimensions[codePoint] = newVec2f(float32(width), float32(height))
+    result.characterUVs[codePoint] = [
+      newVec2f(float32(offsetX) / float32(result.fontAtlas.width), 0),
+      newVec2f(float32(offsetX + width) / float32(result.fontAtlas.width), 0),
+      newVec2f(float32(offsetX) / float32(result.fontAtlas.width), 1),
+      newVec2f(float32(offsetX + width) / float32(result.fontAtlas.width), 1),
+    ]
+    offsetX += width
--- a/src/semicongine/resources/mesh.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Sat May 27 13:43:46 2023 +0700
@@ -1,4 +1,5 @@
 import std/strutils
+import std/options
 import std/json
 import std/logging
 import std/tables
@@ -12,7 +13,6 @@
 
 import ./image
 
-
 type
   glTFHeader = object
     magic: uint32
@@ -23,17 +23,17 @@
     binaryBufferData: seq[uint8]
   glTFMaterial = object
     color: Vec4f
-    colorTexture: Texture
+    colorTexture: Option[Texture]
     colorTextureIndex: uint32
     metallic: float32
     roughness: float32
-    metallicRoughnessTexture: Texture
+    metallicRoughnessTexture: Option[Texture]
     metallicRoughnessTextureIndex: uint32
-    normalTexture: Texture
+    normalTexture: Option[Texture]
     normalTextureIndex: uint32
-    occlusionTexture: Texture
+    occlusionTexture: Option[Texture]
     occlusionTextureIndex: uint32
-    emissiveTexture: Texture
+    emissiveTexture: Option[Texture]
     emissiveTextureIndex: uint32
     emissiveFactor: Vec3f
 
@@ -272,7 +272,6 @@
     raise newException(Exception, "Unsupported feature: Load image of type " & imageType)
 
 proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: var seq[uint8]): Texture =
-  result = new Texture
   let textureNode = root["textures"][textureIndex]
   result.image = loadImage(root, textureNode["source"].getInt(), mainBuffer)
   result.sampler = DefaultSampler()
@@ -298,10 +297,10 @@
     result.color[2] = pbr["baseColorFactor"][2].getFloat()
     result.color[3] = pbr["baseColorFactor"][3].getFloat()
   if pbr.hasKey("baseColorTexture"):
-    result.colorTexture = loadTexture(root, pbr["baseColorTexture"]["index"].getInt(), mainBuffer)
+    result.colorTexture = some(loadTexture(root, pbr["baseColorTexture"]["index"].getInt(), mainBuffer))
     result.colorTextureIndex = pbr["baseColorTexture"].getOrDefault("texCoord").getInt(0).uint32
   if pbr.hasKey("metallicRoughnessTexture"):
-    result.metallicRoughnessTexture = loadTexture(root, pbr["metallicRoughnessTexture"]["index"].getInt(), mainBuffer)
+    result.metallicRoughnessTexture = some(loadTexture(root, pbr["metallicRoughnessTexture"]["index"].getInt(), mainBuffer))
     result.metallicRoughnessTextureIndex = pbr["metallicRoughnessTexture"].getOrDefault("texCoord").getInt().uint32
   if pbr.hasKey("metallicFactor"):
     result.metallic = pbr["metallicFactor"].getFloat()
@@ -309,13 +308,13 @@
     result.roughness= pbr["roughnessFactor"].getFloat()
 
   if materialNode.hasKey("normalTexture"):
-    result.normalTexture = loadTexture(root, materialNode["normalTexture"]["index"].getInt(), mainBuffer)
+    result.normalTexture = some(loadTexture(root, materialNode["normalTexture"]["index"].getInt(), mainBuffer))
     result.metallicRoughnessTextureIndex = materialNode["normalTexture"].getOrDefault("texCoord").getInt().uint32
   if materialNode.hasKey("occlusionTexture"):
-    result.occlusionTexture = loadTexture(root, materialNode["occlusionTexture"]["index"].getInt(), mainBuffer)
+    result.occlusionTexture = some(loadTexture(root, materialNode["occlusionTexture"]["index"].getInt(), mainBuffer))
     result.occlusionTextureIndex = materialNode["occlusionTexture"].getOrDefault("texCoord").getInt().uint32
   if materialNode.hasKey("emissiveTexture"):
-    result.emissiveTexture = loadTexture(root, materialNode["emissiveTexture"]["index"].getInt(), mainBuffer)
+    result.emissiveTexture = some(loadTexture(root, materialNode["emissiveTexture"]["index"].getInt(), mainBuffer))
     result.occlusionTextureIndex = materialNode["emissiveTexture"].getOrDefault("texCoord").getInt().uint32
   if materialNode.hasKey("roughnessFactor"):
     result.roughness = materialNode["roughnessFactor"].getFloat()
@@ -372,22 +371,22 @@
     for materialNode in data.structuredContent["materials"]:
       let m = loadMaterial(data.structuredContent, materialNode, data.binaryBufferData)
       color.add m.color
-      if not m.colorTexture.isNil:
-        colorTexture.add m.colorTexture
+      if not m.colorTexture.isSome:
+        colorTexture.add m.colorTexture.get
         colorTextureIndex.add m.colorTextureIndex
       metallic.add m.metallic
       roughness.add m.roughness
-      if not m.metallicRoughnessTexture.isNil:
-        metallicRoughnessTexture.add m.metallicRoughnessTexture
+      if not m.metallicRoughnessTexture.isSome:
+        metallicRoughnessTexture.add m.metallicRoughnessTexture.get
         metallicRoughnessTextureIndex.add m.metallicRoughnessTextureIndex
-      if not m.normalTexture.isNil:
-        normalTexture.add m.normalTexture
+      if not m.normalTexture.isSome:
+        normalTexture.add m.normalTexture.get
         normalTextureIndex.add m.normalTextureIndex
-      if not m.occlusionTexture.isNil:
-        occlusionTexture.add m.occlusionTexture
+      if not m.occlusionTexture.isSome:
+        occlusionTexture.add m.occlusionTexture.get
         occlusionTextureIndex.add m.occlusionTextureIndex
-      if not m.emissiveTexture.isNil:
-        emissiveTexture.add m.emissiveTexture
+      if not m.emissiveTexture.isSome:
+        emissiveTexture.add m.emissiveTexture.get
         emissiveTextureIndex.add m.emissiveTextureIndex
       emissiveFactor.add m.emissiveFactor
 
--- a/src/semicongine/scene.nim	Fri May 26 00:49:58 2023 +0700
+++ b/src/semicongine/scene.nim	Sat May 27 13:43:46 2023 +0700
@@ -135,6 +135,15 @@
     result.name = &"Entity[{$(cast[ByteAddress](result))}]"
   result.transform = Unit4
 
+iterator allEntitiesOfType*[T: Entity](root: Entity): T =
+  var queue = @[root]
+  while queue.len > 0:
+    let entity = queue.pop
+    if entity of T:
+      yield T(entity)
+    for i in countdown(entity.children.len - 1, 0):
+      queue.add entity.children[i]
+
 iterator allComponentsOfType*[T: Component](root: Entity): var T =
   var queue = @[root]
   while queue.len > 0:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/text.nim	Sat May 27 13:43:46 2023 +0700
@@ -0,0 +1,35 @@
+import ./scene
+import ./mesh
+import ./core/vector
+import ./core/matrix
+import ./core/fonttypes
+
+type
+  TextAlignment = enum
+    Left
+    Center
+    Right
+  Textbox* = ref object of Entity
+    columns*: uint32
+    rows*: uint32
+    text*: string
+    alignment*: TextAlignment
+    font*: Font
+    lettermesh*: Mesh
+
+func len*(textbox: Textbox): uint32 =
+  textbox.columns * textbox.rows
+
+proc newTextbox*(columns, rows: uint32, font: Font, text=""): Textbox =
+  result = Textbox(columns: columns, rows: rows, text: text, font: font)
+  result.lettermesh = newMesh(
+    positions = [newVec3f(0, 0), newVec3f(0, 1), newVec3f(1, 1), newVec3f(1, 0)],
+    indices = [[0'u16, 1'u16, 2'u16], [0'u16, 0'u16, 0'u16]],
+    uvs = [newVec2f(0, 0), newVec2f(0, 1), newVec2f(1, 1), newVec2f(1, 0)],
+    instanceCount = result.len,
+  )
+  var transforms = newSeq[Mat4](result.len)
+  for i in 0 ..< result.len:
+    transforms[i] = Unit4f32
+  setInstanceData(result.lettermesh, "transform", transforms)
+  result.components.add result.lettermesh