changeset 1429:ab01c577d91c

fix: some more import-stuff
author sam <sam@basx.dev>
date Sat, 11 Jan 2025 15:34:21 +0700
parents d16964858790
children db3af8a0b86b
files semicongine.nim semicongine/audio.nim semicongine/contrib/algorithms/texture_packing.nim semicongine/core.nim semicongine/font.nim semicongine/fonts.nim semicongine/gltf.nim semicongine/image.nim semicongine/images.nim semicongine/loaders.nim semicongine/rendering.nim semicongine/text.nim tests/test_text.nim
diffstat 13 files changed, 497 insertions(+), 485 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -8,6 +8,8 @@
 from ./semicongine/audio import audioWorker
 from ./semicongine/background_loaders import initBackgroundLoader
 import ./semicongine/loaders
+import ./semicongine/images
+import ./semicongine/audio
 
 proc initEngine*(appName: string) =
   ## Required to be called before most features of the engine can be used
--- a/semicongine/audio.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/audio.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -5,11 +5,13 @@
 import std/monotimes
 import std/os
 import std/strformat
+import std/streams
+import std/strutils
 import std/tables
 import std/times
-import std/streams
 
 import ./core
+import ./resources
 
 const NBUFFERS = 32
 # it seems that some alsa hardware has a problem with smaller buffers than 512
@@ -422,3 +424,11 @@
       "Only support mono and stereo audio at the moment (1 or 2 channels), but found " &
         $channels,
     )
+
+proc loadAudio*(path: string, package = DEFAULT_PACKAGE): SoundData {.gcsafe.} =
+  if path.splitFile().ext.toLowerAscii == ".au":
+    loadResource_intern(path, package = package).readAU()
+  elif path.splitFile().ext.toLowerAscii == ".ogg":
+    loadResource_intern(path, package = package).readVorbis()
+  else:
+    raise newException(Exception, "Unsupported audio file type: " & path)
--- a/semicongine/contrib/algorithms/texture_packing.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/contrib/algorithms/texture_packing.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -2,7 +2,7 @@
 import std/strformat
 
 import ../../core
-import ../../image
+import ../../images
 
 type Rect = tuple[i: int, x, y, w, h: uint32]
 
--- a/semicongine/core.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/core.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -30,3 +30,7 @@
   assert engine_obj_internal != nil, "Engine has not been initialized yet"
   assert engine_obj_internal.initialized, "Engine has not been initialized yet"
   return engine_obj_internal
+
+proc getAspectRatio*(): float32 =
+  assert engine().vulkan.swapchain != nil, "Swapchain has not been initialized yet"
+  engine().vulkan.swapchain.width.float32 / engine().vulkan.swapchain.height.float32
--- a/semicongine/font.nim	Sat Jan 11 14:15:29 2025 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-import std/os
-import std/strutils
-import std/strformat
-import std/sequtils
-import std/unicode
-import std/streams
-import std/logging
-import std/tables
-
-import ./core
-import ./resources
-import ./rendering/renderer
-import ./rendering/memory
-import ./contrib/algorithms/texture_packing
-
-{.emit: "#define STBTT_STATIC".}
-{.emit: "#define STB_TRUETYPE_IMPLEMENTATION".}
-{.
-  emit:
-    "#include \"" & currentSourcePath.parentDir() & "/thirdparty/stb/stb_truetype.h\""
-.}
-
-const ASCII_CHARSET = PrintableChars.toSeq.toRunes
-
-type stbtt_fontinfo {.importc, incompleteStruct.} = object
-
-proc stbtt_InitFont(
-  info: ptr stbtt_fontinfo, data: ptr char, offset: cint
-): cint {.importc, nodecl.}
-
-proc stbtt_ScaleForPixelHeight(
-  info: ptr stbtt_fontinfo, pixels: cfloat
-): cfloat {.importc, nodecl.}
-
-proc stbtt_GetCodepointBitmap(
-  info: ptr stbtt_fontinfo,
-  scale_x: cfloat,
-  scale_y: cfloat,
-  codepoint: cint,
-  width, height, xoff, yoff: ptr cint,
-): cstring {.importc, nodecl.}
-
-proc stbtt_GetCodepointBox(
-  info: ptr stbtt_fontinfo, codepoint: cint, x0, y0, x1, y1: ptr cint
-): cint {.importc, nodecl.}
-
-proc stbtt_GetCodepointHMetrics(
-  info: ptr stbtt_fontinfo, codepoint: cint, advance, leftBearing: ptr cint
-) {.importc, nodecl.}
-
-proc stbtt_GetCodepointKernAdvance(
-  info: ptr stbtt_fontinfo, ch1, ch2: cint
-): cint {.importc, nodecl.}
-
-proc stbtt_FindGlyphIndex(
-  info: ptr stbtt_fontinfo, codepoint: cint
-): cint {.importc, nodecl.}
-
-proc stbtt_GetFontVMetrics(
-  info: ptr stbtt_fontinfo, ascent, descent, lineGap: ptr cint
-) {.importc, nodecl.}
-
-proc readTrueType[N: static int](
-    stream: Stream, name: string, codePoints: seq[Rune], lineHeightPixels: float32
-): Font[N] =
-  assert codePoints.len <= N,
-    "asked for " & $codePoints.len & " glyphs but shader is only configured for " & $N
-
-  result = Font[N]()
-
-  var
-    indata = stream.readAll()
-    fi: stbtt_fontinfo
-  if stbtt_InitFont(addr fi, indata.ToCPointer, 0) == 0:
-    raise newException(Exception, "An error occured while loading font file")
-
-  let
-    glyph2bitmapScale =
-      float32(stbtt_ScaleForPixelHeight(addr fi, cfloat(lineHeightPixels)))
-    glyph2QuadScale = glyph2bitmapScale / lineHeightPixels
-
-  # ensure all codepoints are available in the font
-  for codePoint in codePoints:
-    if stbtt_FindGlyphIndex(addr fi, cint(codePoint)) == 0:
-      warn &"Loading font {name}: Codepoint '{codePoint}' ({cint(codePoint)}) has no glyph"
-
-  var
-    offsetY: Table[Rune, cint]
-    offsetX: Table[Rune, cint]
-    bitmaps: seq[Image[Gray]]
-
-  # render all glyphs to bitmaps and store quad geometry info
-  for codePoint in codePoints:
-    offsetX[codePoint] = 0
-    offsetY[codePoint] = 0
-    var width, height: cint
-    let data = stbtt_GetCodepointBitmap(
-      addr fi,
-      glyph2bitmapScale,
-      glyph2bitmapScale,
-      cint(codePoint),
-      addr width,
-      addr height,
-      addr (offsetX[codePoint]),
-      addr (offsetY[codePoint]),
-    )
-
-    if width > 0 and height > 0:
-      var bitmap = newSeq[Gray](width * height)
-      for i in 0 ..< width * height:
-        bitmap[i] = vec1u8(data[i].uint8)
-      bitmaps.add Image[Gray](width: width.uint32, height: height.uint32, data: bitmap)
-    else:
-      bitmaps.add Image[Gray](width: 1, height: 1, data: @[vec1u8(0)])
-
-    nativeFree(data)
-
-  # generate glyph atlas from bitmaps
-  let packed = pack(bitmaps)
-  result.descriptorSet.data.fontAtlas = packed.atlas
-
-  # generate quad-information for use in shader
-  for i in 0 ..< codePoints.len:
-    let codePoint = codePoints[i]
-    var advanceUnscaled, leftBearingUnscaled: cint
-      # is in glyph-space, needs to be scaled to pixel-space
-    stbtt_GetCodepointHMetrics(
-      addr fi, cint(codePoint), addr advanceUnscaled, addr leftBearingUnscaled
-    )
-    result.leftBearing[codePoint] = leftBearingUnscaled.float32 * glyph2QuadScale
-    result.advance[codePoint] = advanceUnscaled.float32 * glyph2QuadScale
-
-    let
-      atlasW = float32(result.descriptorSet.data.fontAtlas.width)
-      atlasH = float32(result.descriptorSet.data.fontAtlas.height)
-      uv = vec2(packed.coords[i].x, packed.coords[i].y)
-      bitmapW = float32(bitmaps[i].width)
-      bitmapH = float32(bitmaps[i].height)
-      # divide by lineHeightPixels to get from pixel-space to quad-geometry-space
-      left =
-        result.leftBearing[codePoint] + offsetX[codePoint].float32 / lineHeightPixels
-      right = left + bitmapW / lineHeightPixels
-      top = -offsetY[codePoint].float32 / lineHeightPixels
-      bottom = top - bitmapH / lineHeightPixels
-
-    template glyphquads(): untyped =
-      result.descriptorSet.data.glyphquads.data
-
-    glyphquads.pos[i] = vec4(left, bottom, right, top)
-    glyphquads.uv[i] = vec4(
-      (uv.x + 0.5) / atlasW, # left
-      (uv.y + bitmapH - 0.5) / atlasH, # bottom
-      (uv.x + bitmapW - 0.5) / atlasW, # right
-      (uv.y + 0.5) / atlasH, # top
-    )
-    if i == 0:
-      result.fallbackCharacter = codePoint
-    result.descriptorGlyphIndex[codePoint] = i.uint16
-    result.descriptorGlyphIndexRev[i.uint16] = codePoint # only used for debugging atm
-
-    # kerning
-    for codePointAfter in codePoints:
-      result.kerning[(codePoint, codePointAfter)] =
-        float32(
-          stbtt_GetCodepointKernAdvance(addr fi, cint(codePoint), cint(codePointAfter))
-        ) * glyph2QuadScale
-
-  # line spacing
-  var ascent, descent, lineGap: cint
-  stbtt_GetFontVMetrics(addr fi, addr ascent, addr descent, addr lineGap)
-  result.lineAdvance = float32(ascent - descent + lineGap) * glyph2QuadScale
-  result.lineHeight = float32(ascent - descent) * glyph2QuadScale # should be 1
-  result.ascent = float32(ascent) * glyph2QuadScale
-  result.descent = float32(descent) * glyph2QuadScale
-
-  var x0, y0, x1, y1: cint
-  discard
-    stbtt_GetCodepointBox(addr fi, cint(Rune('x')), addr x0, addr y0, addr x1, addr y1)
-  result.xHeight = float32(y1 - y0) * glyph2QuadScale
-
-proc loadFont*[N: static int](
-    path: string,
-    lineHeightPixels = 80'f32,
-    additional_codepoints: openArray[Rune] = [],
-    charset = ASCII_CHARSET,
-    package = DEFAULT_PACKAGE,
-): Font[N] =
-  readTrueType[N](
-    loadResource_intern(path, package = package),
-    path.splitFile().name,
-    charset & additional_codepoints.toSeq,
-    lineHeightPixels,
-  )
-
-proc upload*(font: Font, renderdata: var RenderData) =
-  assert font.descriptorSet.vk.allIt(not it.Valid), "Font was alread uploaded"
-  assignBuffers(renderdata, font.descriptorSet)
-  uploadImages(renderdata, font.descriptorSet)
-
-proc addToPipeline*(font: Font, renderdata: RenderData, pipeline: Pipeline) =
-  initDescriptorSet(renderdata, pipeline.layout(3), font.descriptorSet)
-
-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:
-      remaining.add newSeq[Rune]()
-    else:
-      remaining[^1].add c
-  remaining.reverse()
-
-  var currentLine: seq[Rune]
-
-  while remaining.len > 0:
-    var currentWord = remaining.pop()
-    assert not (SPACE in currentWord)
-
-    if currentWord.len == 0:
-      currentLine.add SPACE
-    else:
-      assert currentWord[^1] != SPACE
-      # if this is the first word of the line and it is too long we need to
-      # split by character
-      if currentLine.len == 0 and (SPACE & currentWord).textWidth(font) > maxWidth:
-        var subWord = @[currentWord[0]]
-        for c in currentWord[1 .. ^1]:
-          if (subWord & c).textWidth(font) > maxWidth:
-            break
-          subWord.add c
-        result.add subWord & NEWLINE
-        remaining.add currentWord[subWord.len .. ^1]
-          # process rest of the word in next iteration
-      else:
-        if (currentLine & SPACE & currentWord).textWidth(font) <= maxWidth:
-          if currentLine.len == 0:
-            currentLine = currentWord
-          else:
-            currentLine = currentLine & SPACE & currentWord
-        else:
-          result.add currentLine & NEWLINE
-          remaining.add currentWord
-          currentLine = @[]
-  if currentLine.len > 0 and currentLine != @[SPACE]:
-    result.add currentLine
-
-  return result
-  ]#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/fonts.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -0,0 +1,252 @@
+import std/os
+import std/strutils
+import std/strformat
+import std/sequtils
+import std/unicode
+import std/streams
+import std/logging
+import std/tables
+
+import ./core
+import ./resources
+import ./rendering/renderer
+import ./rendering/memory
+import ./contrib/algorithms/texture_packing
+
+{.emit: "#define STBTT_STATIC".}
+{.emit: "#define STB_TRUETYPE_IMPLEMENTATION".}
+{.
+  emit:
+    "#include \"" & currentSourcePath.parentDir() & "/thirdparty/stb/stb_truetype.h\""
+.}
+
+const ASCII_CHARSET = PrintableChars.toSeq.toRunes
+
+type stbtt_fontinfo {.importc, incompleteStruct.} = object
+
+proc stbtt_InitFont(
+  info: ptr stbtt_fontinfo, data: ptr char, offset: cint
+): cint {.importc, nodecl.}
+
+proc stbtt_ScaleForPixelHeight(
+  info: ptr stbtt_fontinfo, pixels: cfloat
+): cfloat {.importc, nodecl.}
+
+proc stbtt_GetCodepointBitmap(
+  info: ptr stbtt_fontinfo,
+  scale_x: cfloat,
+  scale_y: cfloat,
+  codepoint: cint,
+  width, height, xoff, yoff: ptr cint,
+): cstring {.importc, nodecl.}
+
+proc stbtt_GetCodepointBox(
+  info: ptr stbtt_fontinfo, codepoint: cint, x0, y0, x1, y1: ptr cint
+): cint {.importc, nodecl.}
+
+proc stbtt_GetCodepointHMetrics(
+  info: ptr stbtt_fontinfo, codepoint: cint, advance, leftBearing: ptr cint
+) {.importc, nodecl.}
+
+proc stbtt_GetCodepointKernAdvance(
+  info: ptr stbtt_fontinfo, ch1, ch2: cint
+): cint {.importc, nodecl.}
+
+proc stbtt_FindGlyphIndex(
+  info: ptr stbtt_fontinfo, codepoint: cint
+): cint {.importc, nodecl.}
+
+proc stbtt_GetFontVMetrics(
+  info: ptr stbtt_fontinfo, ascent, descent, lineGap: ptr cint
+) {.importc, nodecl.}
+
+proc readTrueType[N: static int](
+    stream: Stream, name: string, codePoints: seq[Rune], lineHeightPixels: float32
+): Font[N] =
+  assert codePoints.len <= N,
+    "asked for " & $codePoints.len & " glyphs but shader is only configured for " & $N
+
+  result = Font[N]()
+
+  var
+    indata = stream.readAll()
+    fi: stbtt_fontinfo
+  if stbtt_InitFont(addr fi, indata.ToCPointer, 0) == 0:
+    raise newException(Exception, "An error occured while loading font file")
+
+  let
+    glyph2bitmapScale =
+      float32(stbtt_ScaleForPixelHeight(addr fi, cfloat(lineHeightPixels)))
+    glyph2QuadScale = glyph2bitmapScale / lineHeightPixels
+
+  # ensure all codepoints are available in the font
+  for codePoint in codePoints:
+    if stbtt_FindGlyphIndex(addr fi, cint(codePoint)) == 0:
+      warn &"Loading font {name}: Codepoint '{codePoint}' ({cint(codePoint)}) has no glyph"
+
+  var
+    offsetY: Table[Rune, cint]
+    offsetX: Table[Rune, cint]
+    bitmaps: seq[Image[Gray]]
+
+  # render all glyphs to bitmaps and store quad geometry info
+  for codePoint in codePoints:
+    offsetX[codePoint] = 0
+    offsetY[codePoint] = 0
+    var width, height: cint
+    let data = stbtt_GetCodepointBitmap(
+      addr fi,
+      glyph2bitmapScale,
+      glyph2bitmapScale,
+      cint(codePoint),
+      addr width,
+      addr height,
+      addr (offsetX[codePoint]),
+      addr (offsetY[codePoint]),
+    )
+
+    if width > 0 and height > 0:
+      var bitmap = newSeq[Gray](width * height)
+      for i in 0 ..< width * height:
+        bitmap[i] = vec1u8(data[i].uint8)
+      bitmaps.add Image[Gray](width: width.uint32, height: height.uint32, data: bitmap)
+    else:
+      bitmaps.add Image[Gray](width: 1, height: 1, data: @[vec1u8(0)])
+
+    nativeFree(data)
+
+  # generate glyph atlas from bitmaps
+  let packed = pack(bitmaps)
+  result.descriptorSet.data.fontAtlas = packed.atlas
+
+  # generate quad-information for use in shader
+  for i in 0 ..< codePoints.len:
+    let codePoint = codePoints[i]
+    var advanceUnscaled, leftBearingUnscaled: cint
+      # is in glyph-space, needs to be scaled to pixel-space
+    stbtt_GetCodepointHMetrics(
+      addr fi, cint(codePoint), addr advanceUnscaled, addr leftBearingUnscaled
+    )
+    result.leftBearing[codePoint] = leftBearingUnscaled.float32 * glyph2QuadScale
+    result.advance[codePoint] = advanceUnscaled.float32 * glyph2QuadScale
+
+    let
+      atlasW = float32(result.descriptorSet.data.fontAtlas.width)
+      atlasH = float32(result.descriptorSet.data.fontAtlas.height)
+      uv = vec2(packed.coords[i].x, packed.coords[i].y)
+      bitmapW = float32(bitmaps[i].width)
+      bitmapH = float32(bitmaps[i].height)
+      # divide by lineHeightPixels to get from pixel-space to quad-geometry-space
+      left =
+        result.leftBearing[codePoint] + offsetX[codePoint].float32 / lineHeightPixels
+      right = left + bitmapW / lineHeightPixels
+      top = -offsetY[codePoint].float32 / lineHeightPixels
+      bottom = top - bitmapH / lineHeightPixels
+
+    template glyphquads(): untyped =
+      result.descriptorSet.data.glyphquads.data
+
+    glyphquads.pos[i] = vec4(left, bottom, right, top)
+    glyphquads.uv[i] = vec4(
+      (uv.x + 0.5) / atlasW, # left
+      (uv.y + bitmapH - 0.5) / atlasH, # bottom
+      (uv.x + bitmapW - 0.5) / atlasW, # right
+      (uv.y + 0.5) / atlasH, # top
+    )
+    if i == 0:
+      result.fallbackCharacter = codePoint
+    result.descriptorGlyphIndex[codePoint] = i.uint16
+    result.descriptorGlyphIndexRev[i.uint16] = codePoint # only used for debugging atm
+
+    # kerning
+    for codePointAfter in codePoints:
+      result.kerning[(codePoint, codePointAfter)] =
+        float32(
+          stbtt_GetCodepointKernAdvance(addr fi, cint(codePoint), cint(codePointAfter))
+        ) * glyph2QuadScale
+
+  # line spacing
+  var ascent, descent, lineGap: cint
+  stbtt_GetFontVMetrics(addr fi, addr ascent, addr descent, addr lineGap)
+  result.lineAdvance = float32(ascent - descent + lineGap) * glyph2QuadScale
+  result.lineHeight = float32(ascent - descent) * glyph2QuadScale # should be 1
+  result.ascent = float32(ascent) * glyph2QuadScale
+  result.descent = float32(descent) * glyph2QuadScale
+
+  var x0, y0, x1, y1: cint
+  discard
+    stbtt_GetCodepointBox(addr fi, cint(Rune('x')), addr x0, addr y0, addr x1, addr y1)
+  result.xHeight = float32(y1 - y0) * glyph2QuadScale
+
+proc loadFont*[N: static int](
+    path: string,
+    lineHeightPixels = 80'f32,
+    additional_codepoints: openArray[Rune] = [],
+    charset = ASCII_CHARSET,
+    package = DEFAULT_PACKAGE,
+): Font[N] =
+  readTrueType[N](
+    loadResource_intern(path, package = package),
+    path.splitFile().name,
+    charset & additional_codepoints.toSeq,
+    lineHeightPixels,
+  )
+
+proc upload*(font: Font, renderdata: var RenderData) =
+  assert font.descriptorSet.vk.allIt(not it.Valid), "Font was alread uploaded"
+  assignBuffers(renderdata, font.descriptorSet)
+  uploadImages(renderdata, font.descriptorSet)
+
+proc addToPipeline*(font: Font, renderdata: RenderData, pipeline: Pipeline) =
+  initDescriptorSet(renderdata, pipeline.layout(3), font.descriptorSet)
+
+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:
+      remaining.add newSeq[Rune]()
+    else:
+      remaining[^1].add c
+  remaining.reverse()
+
+  var currentLine: seq[Rune]
+
+  while remaining.len > 0:
+    var currentWord = remaining.pop()
+    assert not (SPACE in currentWord)
+
+    if currentWord.len == 0:
+      currentLine.add SPACE
+    else:
+      assert currentWord[^1] != SPACE
+      # if this is the first word of the line and it is too long we need to
+      # split by character
+      if currentLine.len == 0 and (SPACE & currentWord).textWidth(font) > maxWidth:
+        var subWord = @[currentWord[0]]
+        for c in currentWord[1 .. ^1]:
+          if (subWord & c).textWidth(font) > maxWidth:
+            break
+          subWord.add c
+        result.add subWord & NEWLINE
+        remaining.add currentWord[subWord.len .. ^1]
+          # process rest of the word in next iteration
+      else:
+        if (currentLine & SPACE & currentWord).textWidth(font) <= maxWidth:
+          if currentLine.len == 0:
+            currentLine = currentWord
+          else:
+            currentLine = currentLine & SPACE & currentWord
+        else:
+          result.add currentLine & NEWLINE
+          remaining.add currentWord
+          currentLine = @[]
+  if currentLine.len > 0 and currentLine != @[SPACE]:
+    result.add currentLine
+
+  return result
+  ]#
--- a/semicongine/gltf.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/gltf.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -6,7 +6,8 @@
 import std/typetraits
 
 import ./core
-import ./image
+import ./images
+import ./resources
 
 type
   glTFHeader = object
@@ -190,7 +191,7 @@
 ): TMaterial =
   result = TMaterial()
 
-  let pbr {.warning[InheritFromException]: off.} = materialNode["pbrMetallicRoughness"]
+  let pbr {.hint[XDeclaredButNotUsed]: off.} = materialNode["pbrMetallicRoughness"]
   for name, value in fieldPairs(result):
     for gltfAttribute, mappedName in fieldPairs(mapping):
       when gltfAttribute != "" and name == mappedName:
@@ -364,3 +365,15 @@
         for nodeId in items(scene["nodes"]):
           nodes.add nodeId.getInt()
         result.scenes.add nodes
+
+proc loadMeshes*[TMesh, TMaterial](
+    path: string,
+    meshAttributesMapping: static MeshAttributeNames,
+    materialAttributesMapping: static MaterialAttributeNames,
+    package = DEFAULT_PACKAGE,
+): GltfData[TMesh, TMaterial] {.gcsafe.} =
+  ReadglTF[TMesh, TMaterial](
+    stream = loadResource_intern(path, package = package),
+    meshAttributesMapping = meshAttributesMapping,
+    materialAttributesMapping = materialAttributesMapping,
+  )
--- a/semicongine/image.nim	Sat Jan 11 14:15:29 2025 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-import std/os
-import std/typetraits
-import std/strformat
-
-import ./core
-
-{.emit: "#define STB_IMAGE_STATIC".}
-{.emit: "#define STB_IMAGE_IMPLEMENTATION".}
-{.
-  emit: "#include \"" & currentSourcePath.parentDir() & "/thirdparty/stb/stb_image.h\""
-.}
-
-proc stbi_load_from_memory(
-  buffer: ptr uint8,
-  len: cint,
-  x, y: ptr cint,
-  channels_in_file: ptr cint,
-  desired_channels: cint,
-): ptr uint8 {.importc, nodecl.}
-
-func `$`*[S, IsArray](img: ImageObject[S, IsArray]): string =
-  let pixelTypeName = S.name
-  if IsArray == false:
-    $img.width & "x" & $img.height & " " & pixelTypeName
-  else:
-    $img.width & "x" & $img.height & "[" & $img.nLayers & "] " & pixelTypeName
-
-func copy*[S, T](img: ImageObject[S, T]): ImageObject[S, T] =
-  for bf, rf in fields(img, result):
-    rf = bf
-
-# loads single layer image
-proc loadImageData*[T: PixelType](
-    pngData: string | seq[uint8]
-): tuple[width: uint32, height: uint32, data: seq[T]] =
-  when T is Gray:
-    let nChannels = 1.cint
-  elif T is BGRA:
-    let nChannels = 4.cint
-
-  var w, h, c: cint
-
-  let data = stbi_load_from_memory(
-    buffer = cast[ptr uint8](pngData.ToCPointer),
-    len = pngData.len.cint,
-    x = addr(w),
-    y = addr(h),
-    channels_in_file = addr(c),
-    desired_channels = nChannels,
-  )
-  if data == nil:
-    raise newException(Exception, "An error occured while loading PNG file")
-
-  let imagesize = w * h * nChannels
-  result = (width: w.uint32, height: h.uint32, data: newSeq[T](w * h))
-  copyMem(result.data.ToCPointer, data, imagesize)
-  nativeFree(data)
-
-  when T is BGRA: # convert to BGRA
-    for i in 0 ..< result.data.len:
-      swap(result.data[i][0], result.data[i][2])
-
-# TODO: static versions to check for existing of files during compilation
-proc addImage*[T: PixelType](imageArray: var ImageArray[T], image: sink Image[T]) =
-  assert image.width == imageArray.width,
-    &"Image needs to have same dimension as ImageArray to be added (array has {imageArray.width}x{imageArray.height} but image has {image.width}x{image.height})"
-  assert image.height == imageArray.height,
-    &"Image needs to have same dimension as ImageArray to be added (array has {imageArray.width}x{imageArray.height} but image has {image.width}x{image.height})"
-
-  inc imageArray.nLayers
-  imageArray.data.add image.data
-
-proc `[]`*(image: Image, x, y: uint32): auto =
-  assert x < image.width, &"{x} < {image.width} is not true"
-  assert y < image.height, &"{y} < {image.height} is not true"
-
-  image.data[y * image.width + x]
-
-proc `[]=`*[T](image: var Image[T], x, y: uint32, value: T) =
-  assert x < image.width
-  assert y < image.height
-
-  image.data[y * image.width + x] = value
-
-proc `[]`*(image: ImageArray, layer, x, y: uint32): auto =
-  assert layer < image.nLayers,
-    &"Tried to access image layer {layer}, but image has only {image.nLayers} layers"
-  assert x < image.width,
-    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
-  assert y < image.height,
-    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
-
-  image.data[layer * (image.width * image.height) + y * image.width + x]
-
-proc `[]=`*[T](image: var ImageArray[T], layer, x, y: uint32, value: T) =
-  assert layer < image.nLayers,
-    &"Tried to access image layer {layer}, but image has only {image.nLayers} layers"
-  assert x < image.width,
-    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
-  assert y < image.height,
-    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
-
-  image.data[layer * (image.width * image.height) + y * image.width + x] = value
-
-# stb_image.h has no encoding support, maybe check stb_image_write or similar
-#
-# proc lodepng_encode_memory(out_data: ptr cstring, outsize: ptr csize_t, image: cstring, w: cuint, h: cuint, colorType: cint, bitdepth: cuint): cuint {.importc.}
-#
-#[
-proc toPNG[T: PixelType](image: Image[T]): seq[uint8] =
-  when T is Gray:
-    let pngType = 0 # hardcoded in lodepng.h
-  else:
-    let pngType = 6 # hardcoded in lodepng.h
-  var
-    pngData: cstring
-    pngSize: csize_t
-  for y in 0 ..< image.height:
-    for x in 0 ..< image.width:
-      discard
-  let ret = lodepng_encode_memory(
-    addr pngData,
-    addr pngSize,
-    cast[cstring](image.imagedata.ToCPointer),
-    cuint(image.width),
-    cuint(image.height),
-    cint(pngType),
-    8,
-  )
-  assert ret == 0, &"There was an error with generating the PNG data for image {image}, result was: {ret}"
-  result = newSeq[uint8](pngSize)
-  for i in 0 ..< pngSize:
-    result[i] = uint8(pngData[i])
-  nativeFree(pngData)
-
-proc WritePNG*(image: Image, filename: string) =
-  let f = filename.open(mode = fmWrite)
-  let data = image.toPNG()
-  let written = f.writeBytes(data, 0, data.len)
-  assert written == data.len, &"There was an error while saving '{filename}': only {written} of {data.len} bytes were written"
-  f.close()
-]#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/images.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -0,0 +1,202 @@
+import std/os
+import std/typetraits
+import std/strformat
+import std/strutils
+import std/streams
+
+import ./core
+import ./resources
+
+{.emit: "#define STB_IMAGE_STATIC".}
+{.emit: "#define STB_IMAGE_IMPLEMENTATION".}
+{.
+  emit: "#include \"" & currentSourcePath.parentDir() & "/thirdparty/stb/stb_image.h\""
+.}
+
+proc stbi_load_from_memory(
+  buffer: ptr uint8,
+  len: cint,
+  x, y: ptr cint,
+  channels_in_file: ptr cint,
+  desired_channels: cint,
+): ptr uint8 {.importc, nodecl.}
+
+func `$`*[S, IsArray](img: ImageObject[S, IsArray]): string =
+  let pixelTypeName = S.name
+  if IsArray == false:
+    $img.width & "x" & $img.height & " " & pixelTypeName
+  else:
+    $img.width & "x" & $img.height & "[" & $img.nLayers & "] " & pixelTypeName
+
+func copy*[S, T](img: ImageObject[S, T]): ImageObject[S, T] =
+  for bf, rf in fields(img, result):
+    rf = bf
+
+# loads single layer image
+proc loadImageData*[T: PixelType](
+    pngData: string | seq[uint8]
+): tuple[width: uint32, height: uint32, data: seq[T]] =
+  when T is Gray:
+    let nChannels = 1.cint
+  elif T is BGRA:
+    let nChannels = 4.cint
+
+  var w, h, c: cint
+
+  let data = stbi_load_from_memory(
+    buffer = cast[ptr uint8](pngData.ToCPointer),
+    len = pngData.len.cint,
+    x = addr(w),
+    y = addr(h),
+    channels_in_file = addr(c),
+    desired_channels = nChannels,
+  )
+  if data == nil:
+    raise newException(Exception, "An error occured while loading PNG file")
+
+  let imagesize = w * h * nChannels
+  result = (width: w.uint32, height: h.uint32, data: newSeq[T](w * h))
+  copyMem(result.data.ToCPointer, data, imagesize)
+  nativeFree(data)
+
+  when T is BGRA: # convert to BGRA
+    for i in 0 ..< result.data.len:
+      swap(result.data[i][0], result.data[i][2])
+
+# TODO: static versions to check for existing of files during compilation
+proc addImage*[T: PixelType](imageArray: var ImageArray[T], image: sink Image[T]) =
+  assert image.width == imageArray.width,
+    &"Image needs to have same dimension as ImageArray to be added (array has {imageArray.width}x{imageArray.height} but image has {image.width}x{image.height})"
+  assert image.height == imageArray.height,
+    &"Image needs to have same dimension as ImageArray to be added (array has {imageArray.width}x{imageArray.height} but image has {image.width}x{image.height})"
+
+  inc imageArray.nLayers
+  imageArray.data.add image.data
+
+proc `[]`*(image: Image, x, y: uint32): auto =
+  assert x < image.width, &"{x} < {image.width} is not true"
+  assert y < image.height, &"{y} < {image.height} is not true"
+
+  image.data[y * image.width + x]
+
+proc `[]=`*[T](image: var Image[T], x, y: uint32, value: T) =
+  assert x < image.width
+  assert y < image.height
+
+  image.data[y * image.width + x] = value
+
+proc `[]`*(image: ImageArray, layer, x, y: uint32): auto =
+  assert layer < image.nLayers,
+    &"Tried to access image layer {layer}, but image has only {image.nLayers} layers"
+  assert x < image.width,
+    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
+  assert y < image.height,
+    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
+
+  image.data[layer * (image.width * image.height) + y * image.width + x]
+
+proc `[]=`*[T](image: var ImageArray[T], layer, x, y: uint32, value: T) =
+  assert layer < image.nLayers,
+    &"Tried to access image layer {layer}, but image has only {image.nLayers} layers"
+  assert x < image.width,
+    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
+  assert y < image.height,
+    &"Tried to access pixel coordinate {(x, y)} but image has size {(image.width, image.height)}"
+
+  image.data[layer * (image.width * image.height) + y * image.width + x] = value
+
+# stb_image.h has no encoding support, maybe check stb_image_write or similar
+#
+# proc lodepng_encode_memory(out_data: ptr cstring, outsize: ptr csize_t, image: cstring, w: cuint, h: cuint, colorType: cint, bitdepth: cuint): cuint {.importc.}
+#
+#[
+proc toPNG[T: PixelType](image: Image[T]): seq[uint8] =
+  when T is Gray:
+    let pngType = 0 # hardcoded in lodepng.h
+  else:
+    let pngType = 6 # hardcoded in lodepng.h
+  var
+    pngData: cstring
+    pngSize: csize_t
+  for y in 0 ..< image.height:
+    for x in 0 ..< image.width:
+      discard
+  let ret = lodepng_encode_memory(
+    addr pngData,
+    addr pngSize,
+    cast[cstring](image.imagedata.ToCPointer),
+    cuint(image.width),
+    cuint(image.height),
+    cint(pngType),
+    8,
+  )
+  assert ret == 0, &"There was an error with generating the PNG data for image {image}, result was: {ret}"
+  result = newSeq[uint8](pngSize)
+  for i in 0 ..< pngSize:
+    result[i] = uint8(pngData[i])
+  nativeFree(pngData)
+
+proc WritePNG*(image: Image, filename: string) =
+  let f = filename.open(mode = fmWrite)
+  let data = image.toPNG()
+  let written = f.writeBytes(data, 0, data.len)
+  assert written == data.len, &"There was an error while saving '{filename}': only {written} of {data.len} bytes were written"
+  f.close()
+]#
+
+proc loadImage*[T: PixelType](
+    path: string, package = DEFAULT_PACKAGE
+): Image[T] {.gcsafe.} =
+  assert path.splitFile().ext.toLowerAscii == ".png",
+    "Unsupported image type: " & path.splitFile().ext.toLowerAscii
+
+  let (width, height, data) =
+    loadImageData[T](loadResource_intern(path, package = package).readAll())
+  result = Image[T](width: width, height: height, data: data)
+
+proc loadImageArray*[T: PixelType](
+    paths: openArray[string], package = DEFAULT_PACKAGE
+): ImageArray[T] {.gcsafe.} =
+  assert paths.len > 0, "Image array cannot contain 0 images"
+  for path in paths:
+    assert path.splitFile().ext.toLowerAscii == ".png",
+      "Unsupported image type: " & path.splitFile().ext.toLowerAscii
+
+  let (width, height, data) =
+    loadImageData[T](loadResource_intern(paths[0], package = package).readAll())
+  result =
+    ImageArray[T](width: width, height: height, data: data, nLayers: paths.len.uint32)
+  for path in paths[1 .. ^1]:
+    let (w, h, data) =
+      loadImageData[T](loadResource_intern(path, package = package).readAll())
+    assert w == result.width,
+      "New image layer has dimension {(w, y)} but image has dimension {(result.width, result.height)}"
+    assert h == result.height,
+      "New image layer has dimension {(w, y)} but image has dimension {(result.width, result.height)}"
+    result.data.add data
+
+proc loadImageArray*[T: PixelType](
+    path: string, tilesize: uint32, package = DEFAULT_PACKAGE
+): ImageArray[T] {.gcsafe.} =
+  assert path.splitFile().ext.toLowerAscii == ".png",
+    "Unsupported image type: " & path.splitFile().ext.toLowerAscii
+
+  let (width, height, data) =
+    loadImageData[T](loadResource_intern(path, package = package).readAll())
+  let tilesY = height div tilesize
+
+  result = ImageArray[T](width: tilesize, height: tilesize)
+  var tile = newSeq[T](tilesize * tilesize)
+
+  for ty in 0 ..< tilesY:
+    for tx in 0 ..< tilesY:
+      var hasNonTransparent = when T is BGRA: false else: true
+      let baseI = ty * tilesize * width + tx * tilesize
+      for y in 0 ..< tilesize:
+        for x in 0 ..< tilesize:
+          tile[y * tilesize + x] = data[baseI + y * width + x]
+          when T is BGRA:
+            hasNonTransparent = hasNonTransparent or tile[y * tilesize + x].a > 0
+      if hasNonTransparent:
+        result.data.add tile
+        result.nLayers.inc
--- a/semicongine/loaders.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/loaders.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -8,10 +8,13 @@
 import ./background_loaders
 import ./core
 import ./gltf
-import ./image
+import ./images
 import ./resources
 import ./thirdparty/parsetoml
 
+# necessary, so we don't need to import parsetoml extra when using this module
+export parsetoml
+
 proc loadBytes*(path, package: string): seq[byte] {.gcsafe.} =
   cast[seq[byte]](toSeq(path.loadResource_intern(package = package).readAll()))
 
@@ -21,83 +24,6 @@
 proc loadConfig*(path: string, package = DEFAULT_PACKAGE): TomlValueRef {.gcsafe.} =
   path.loadResource_intern(package = package).parseStream(filename = path)
 
-proc loadImage*[T: PixelType](
-    path: string, package = DEFAULT_PACKAGE
-): Image[T] {.gcsafe.} =
-  assert path.splitFile().ext.toLowerAscii == ".png",
-    "Unsupported image type: " & path.splitFile().ext.toLowerAscii
-
-  let (width, height, data) =
-    loadImageData[T](loadResource_intern(path, package = package).readAll())
-  result = Image[T](width: width, height: height, data: data)
-
-proc loadImageArray*[T: PixelType](
-    paths: openArray[string], package = DEFAULT_PACKAGE
-): ImageArray[T] {.gcsafe.} =
-  assert paths.len > 0, "Image array cannot contain 0 images"
-  for path in paths:
-    assert path.splitFile().ext.toLowerAscii == ".png",
-      "Unsupported image type: " & path.splitFile().ext.toLowerAscii
-
-  let (width, height, data) =
-    loadImageData[T](loadResource_intern(paths[0], package = package).readAll())
-  result =
-    ImageArray[T](width: width, height: height, data: data, nLayers: paths.len.uint32)
-  for path in paths[1 .. ^1]:
-    let (w, h, data) =
-      loadImageData[T](loadResource_intern(path, package = package).readAll())
-    assert w == result.width,
-      "New image layer has dimension {(w, y)} but image has dimension {(result.width, result.height)}"
-    assert h == result.height,
-      "New image layer has dimension {(w, y)} but image has dimension {(result.width, result.height)}"
-    result.data.add data
-
-proc loadImageArray*[T: PixelType](
-    path: string, tilesize: uint32, package = DEFAULT_PACKAGE
-): ImageArray[T] {.gcsafe.} =
-  assert path.splitFile().ext.toLowerAscii == ".png",
-    "Unsupported image type: " & path.splitFile().ext.toLowerAscii
-
-  let (width, height, data) =
-    loadImageData[T](loadResource_intern(path, package = package).readAll())
-  let tilesY = height div tilesize
-
-  result = ImageArray[T](width: tilesize, height: tilesize)
-  var tile = newSeq[T](tilesize * tilesize)
-
-  for ty in 0 ..< tilesY:
-    for tx in 0 ..< tilesY:
-      var hasNonTransparent = when T is BGRA: false else: true
-      let baseI = ty * tilesize * width + tx * tilesize
-      for y in 0 ..< tilesize:
-        for x in 0 ..< tilesize:
-          tile[y * tilesize + x] = data[baseI + y * width + x]
-          when T is BGRA:
-            hasNonTransparent = hasNonTransparent or tile[y * tilesize + x].a > 0
-      if hasNonTransparent:
-        result.data.add tile
-        result.nLayers.inc
-
-proc loadAudio*(path: string, package = DEFAULT_PACKAGE): SoundData {.gcsafe.} =
-  if path.splitFile().ext.toLowerAscii == ".au":
-    loadResource_intern(path, package = package).readAU()
-  elif path.splitFile().ext.toLowerAscii == ".ogg":
-    loadResource_intern(path, package = package).readVorbis()
-  else:
-    raise newException(Exception, "Unsupported audio file type: " & path)
-
-proc loadMeshes*[TMesh, TMaterial](
-    path: string,
-    meshAttributesMapping: static MeshAttributeNames,
-    materialAttributesMapping: static MaterialAttributeNames,
-    package = DEFAULT_PACKAGE,
-): GltfData[TMesh, TMaterial] {.gcsafe.} =
-  ReadglTF[TMesh, TMaterial](
-    stream = loadResource_intern(path, package = package),
-    meshAttributesMapping = meshAttributesMapping,
-    materialAttributesMapping = materialAttributesMapping,
-  )
-
 # background loaders
 
 type ResourceType =
--- a/semicongine/rendering.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/rendering.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -9,7 +9,7 @@
 
 import ./core
 
-import ./image
+import ./images
 
 # in this file:
 # - const defintions for rendering
@@ -200,10 +200,6 @@
     engine().vulkan.fullscreen_internal = enable
     engine().vulkan.window.setFullscreen(engine().vulkan.fullscreen_internal)
 
-proc getAspectRatio*(): float32 =
-  assert engine().vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  engine().vulkan.swapchain.width.float32 / engine().vulkan.swapchain.height.float32
-
 proc maxFramebufferSampleCount*(
     maxSamples = VK_SAMPLE_COUNT_8_BIT
 ): VkSampleCountFlagBits =
--- a/semicongine/text.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/semicongine/text.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -8,7 +8,7 @@
 
 import ./core
 import ./rendering
-import ./image
+import ./images
 import ./rendering/renderer
 import ./rendering/memory
 
@@ -190,8 +190,9 @@
   ## This should be called again after aspect ratio of window changes 
 
   let cap = if capacity == 0: text.len else: capacity
-  assert textbuffer.cursor + cap <= textbuffer.position.len,
-    &"Text is too big for TextBuffer ({textbuffer.position.len - textbuffer.cursor} left, but need {cap})"
+  let l = textbuffer.position.len
+  assert textbuffer.cursor + cap <= l,
+    &"Text is too big for TextBuffer ({l - textbuffer.cursor} left, but need {cap})"
 
   result =
     TextHandle(generation: textbuffer.generation, index: textbuffer.texts.len.uint32)
--- a/tests/test_text.nim	Sat Jan 11 14:15:29 2025 +0700
+++ b/tests/test_text.nim	Sat Jan 11 15:34:21 2025 +0700
@@ -13,7 +13,7 @@
 import ../semicongine/rendering
 import ../semicongine/text
 import ../semicongine/input
-import ../semicongine/font
+import ../semicongine/fonts
 
 const MAX_CODEPOINTS = 200
 const FONTNAME = "Overhaul.ttf"