changeset 1423:3b8a736c45a7

did: put almost all global state into a single struct
author sam <sam@basx.dev>
date Thu, 09 Jan 2025 23:03:47 +0700
parents 6f0c1b347403
children bb8a35d37896
files semicongine.nim semicongine/audio/mixer_module.nim semicongine/background_loader.nim semicongine/contrib/steam.nim semicongine/core.nim semicongine/core/types.nim semicongine/core/utils.nim semicongine/gltf.nim semicongine/input.nim semicongine/loaders.nim semicongine/rendering.nim semicongine/rendering/memory.nim semicongine/rendering/platform/linux.nim semicongine/rendering/renderer.nim semicongine/rendering/shaders.nim semicongine/rendering/swapchain.nim semicongine/rendering/vulkan/api.nim semicongine/rendering/vulkan_wrappers.nim semicongine/storage.nim semicongine/text.nim semicongine/text/font.nim tests/test_audio.nim tests/test_gltf.nim tests/test_rendering.nim tests/test_storage.nim tests/test_text.nim
diffstat 26 files changed, 968 insertions(+), 921 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -16,6 +16,24 @@
 import ./semicongine/rendering
 export rendering
 
+import ./semicongine/rendering/renderer
+export renderer
+
+import ./semicongine/rendering/swapchain
+export swapchain
+
+import ./semicongine/rendering/renderpasses
+export renderpasses
+
+import ./semicongine/rendering/shaders
+export shaders
+
+import ./semicongine/rendering/memory
+export memory
+
+import ./semicongine/rendering/vulkan_wrappers
+export vulkan_wrappers
+
 import ./semicongine/storage
 import ./semicongine/input
 export storage
@@ -25,6 +43,9 @@
 export audio
 
 # texture packing is required for font atlas
+import ./semicongine/text/font
+export font
+
 import ./semicongine/text
 export text
 
@@ -54,3 +75,10 @@
   engine_obj_internal.mixer[] = initMixer()
   engine_obj_internal.audiothread.createThread(audioWorker, engine_obj_internal.mixer)
   engine_obj_internal.initialized = true
+
+  engine_obj_internal.rawLoader = initBackgroundLoader(loadBytes)
+  engine_obj_internal.jsonLoader = initBackgroundLoader(loadJson)
+  engine_obj_internal.configLoader = initBackgroundLoader(loadConfig)
+  engine_obj_internal.grayImageLoader = initBackgroundLoader(loadImage[Gray])
+  engine_obj_internal.imageLoader = initBackgroundLoader(loadImage[BGRA])
+  engine_obj_internal.audioLoader = initBackgroundLoader(loadAudio)
--- a/semicongine/audio/mixer_module.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/audio/mixer_module.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -26,57 +26,46 @@
   result.tracks[""] = Track(level: 1)
   result.lock.initLock()
 
-proc setupDevice(mixer: var Mixer) =
-  # call this inside audio thread
-  var bufferaddresses: seq[ptr SoundData]
-  for i in 0 ..< NBUFFERS:
-    mixer.buffers.add newSeq[Sample](BUFFERSAMPLECOUNT)
-  for i in 0 ..< mixer.buffers.len:
-    bufferaddresses.add (addr mixer.buffers[i])
-  mixer.device = OpenSoundDevice(AUDIO_SAMPLE_RATE, bufferaddresses)
-
 # TODO: this should probably be in the load-code-stuff
 # proc LoadSound*(mixer: var Mixer, name: string, resource: string) =
 # assert not (name in mixer.sounds)
 # mixer.sounds[name] = LoadAudio(resource)
 
-proc addSound*(mixer: var Mixer, name: string, sound: SoundData) =
-  if name in mixer.sounds:
+proc addSound*(name: string, sound: SoundData) =
+  if name in engine().mixer.sounds:
     warn "sound with name '", name, "' was already loaded, overwriting"
-  mixer.sounds[name] = sound
+  engine().mixer.sounds[name] = sound
 
-proc addTrack*(mixer: var Mixer, name: string, level: AudioLevel = 1'f) =
-  if name in mixer.tracks:
+proc addTrack*(name: string, level: AudioLevel = 1'f) =
+  if name in engine().mixer.tracks:
     warn "track with name '", name, "' was already loaded, overwriting"
-  mixer.lock.withLock:
-    mixer.tracks[name] = Track(level: level)
+  engine().mixer.lock.withLock:
+    engine().mixer.tracks[name] = Track(level: level)
 
 proc play*(
-    mixer: var Mixer,
     soundName: string,
     track = "",
     stopOtherSounds = false,
     loop = false,
     levelLeft, levelRight: AudioLevel,
 ): uint64 =
-  assert track in mixer.tracks, &"Track '{track}' does not exists"
-  assert soundName in mixer.sounds, soundName & " not loaded"
-  mixer.lock.withLock:
+  assert track in engine().mixer.tracks, &"Track '{track}' does not exists"
+  assert soundName in engine().mixer.sounds, soundName & " not loaded"
+  engine().mixer.lock.withLock:
     if stopOtherSounds:
-      mixer.tracks[track].playing.clear()
-    mixer.tracks[track].playing[mixer.playbackCounter] = Playback(
-      sound: mixer.sounds[soundName],
+      engine().mixer.tracks[track].playing.clear()
+    engine().mixer.tracks[track].playing[engine().mixer.playbackCounter] = Playback(
+      sound: engine().mixer.sounds[soundName],
       position: 0,
       loop: loop,
       levelLeft: levelLeft,
       levelRight: levelRight,
       paused: false,
     )
-  result = mixer.playbackCounter
-  inc mixer.playbackCounter
+  result = engine().mixer.playbackCounter
+  inc engine().mixer.playbackCounter
 
 proc play*(
-    mixer: var Mixer,
     soundName: string,
     track = "",
     stopOtherSounds = false,
@@ -84,7 +73,6 @@
     level: AudioLevel = 1'f,
 ): uint64 =
   play(
-    mixer = mixer,
     soundName = soundName,
     track = track,
     stopOtherSounds = stopOtherSounds,
@@ -93,105 +81,104 @@
     levelRight = level,
   )
 
-proc stop*(mixer: var Mixer) =
-  mixer.lock.withLock:
-    for track in mixer.tracks.mvalues:
+proc stop*() =
+  engine().mixer.lock.withLock:
+    for track in engine().mixer.tracks.mvalues:
       track.playing.clear()
 
-proc getLevel*(mixer: var Mixer): AudioLevel =
-  mixer.level
+proc getLevel*(): AudioLevel =
+  engine().mixer.level
 
-proc getLevel*(mixer: var Mixer, track: string): AudioLevel =
-  mixer.tracks[track].level
+proc getLevel*(track: string): AudioLevel =
+  engine().mixer.tracks[track].level
 
-proc getLevel*(mixer: var Mixer, playbackId: uint64): (AudioLevel, AudioLevel) =
-  for track in mixer.tracks.mvalues:
+proc getLevel*(playbackId: uint64): (AudioLevel, AudioLevel) =
+  for track in engine().mixer.tracks.mvalues:
     if playbackId in track.playing:
       return (track.playing[playbackId].levelLeft, track.playing[playbackId].levelRight)
 
-proc setLevel*(mixer: var Mixer, level: AudioLevel) =
-  mixer.level = level
+proc setLevel*(level: AudioLevel) =
+  engine().mixer.level = level
 
-proc setLevel*(mixer: var Mixer, track: string, level: AudioLevel) =
-  mixer.lock.withLock:
-    mixer.tracks[track].level = level
+proc setLevel*(track: string, level: AudioLevel) =
+  engine().mixer.lock.withLock:
+    engine().mixer.tracks[track].level = level
 
-proc setLevel*(
-    mixer: var Mixer, playbackId: uint64, levelLeft, levelRight: AudioLevel
-) =
-  mixer.lock.withLock:
-    for track in mixer.tracks.mvalues:
+proc setLevel*(playbackId: uint64, levelLeft, levelRight: AudioLevel) =
+  engine().mixer.lock.withLock:
+    for track in engine().mixer.tracks.mvalues:
       if playbackId in track.playing:
         track.playing[playbackId].levelLeft = levelLeft
         track.playing[playbackId].levelRight = levelRight
 
-proc setLevel*(mixer: var Mixer, playbackId: uint64, level: AudioLevel) =
-  setLevel(mixer, playbackId, level, level)
+proc setLevel*(playbackId: uint64, level: AudioLevel) =
+  setLevel(playbackId, level, level)
 
-proc stop*(mixer: var Mixer, track: string) =
-  assert track in mixer.tracks
-  mixer.lock.withLock:
-    mixer.tracks[track].playing.clear()
+proc stop*(track: string) =
+  assert track in engine().mixer.tracks
+  engine().mixer.lock.withLock:
+    engine().mixer.tracks[track].playing.clear()
 
-proc stop*(mixer: var Mixer, playbackId: uint64) =
-  mixer.lock.withLock:
-    for track in mixer.tracks.mvalues:
+proc stop*(playbackId: uint64) =
+  engine().mixer.lock.withLock:
+    for track in engine().mixer.tracks.mvalues:
       if playbackId in track.playing:
         track.playing.del(playbackId)
         break
 
-proc pause*(mixer: var Mixer, value: bool) =
-  mixer.lock.withLock:
-    for track in mixer.tracks.mvalues:
+proc pause*(value: bool) =
+  engine().mixer.lock.withLock:
+    for track in engine().mixer.tracks.mvalues:
       for playback in track.playing.mvalues:
         playback.paused = value
 
-proc pause*(mixer: var Mixer, track: string, value: bool) =
-  mixer.lock.withLock:
-    for playback in mixer.tracks[track].playing.mvalues:
+proc pause*(track: string, value: bool) =
+  engine().mixer.lock.withLock:
+    for playback in engine().mixer.tracks[track].playing.mvalues:
       playback.paused = value
 
-proc pause*(mixer: var Mixer, playbackId: uint64, value: bool) =
-  mixer.lock.withLock:
-    for track in mixer.tracks.mvalues:
+proc pause*(playbackId: uint64, value: bool) =
+  engine().mixer.lock.withLock:
+    for track in engine().mixer.tracks.mvalues:
       if playbackId in track.playing:
         track.playing[playbackId].paused = value
 
-proc pause*(mixer: var Mixer) =
-  mixer.pause(true)
+proc pause*() =
+  pause(true)
 
-proc pause*(mixer: var Mixer, track: string) =
-  mixer.pause(track, true)
+proc pause*(track: string) =
+  pause(track, true)
 
-proc pause*(mixer: var Mixer, playbackId: uint64) =
-  mixer.pause(playbackId, true)
+proc pause*(playbackId: uint64) =
+  pause(playbackId, true)
 
-proc unpause*(mixer: var Mixer) =
-  mixer.pause(false)
+proc unpause*() =
+  pause(false)
 
-proc unpause*(mixer: var Mixer, track: string) =
-  mixer.pause(track, false)
+proc unpause*(track: string) =
+  pause(track, false)
 
-proc unpause*(mixer: var Mixer, playbackId: uint64) =
-  mixer.pause(playbackId, false)
+proc unpause*(playbackId: uint64) =
+  pause(playbackId, false)
 
-proc fadeTo*(mixer: var Mixer, track: string, level: AudioLevel, time: float) =
-  mixer.tracks[track].targetLevel = level
-  mixer.tracks[track].fadeTime = time
-  mixer.tracks[track].fadeStep = level.float - mixer.tracks[track].level.float / time
+proc fadeTo*(track: string, level: AudioLevel, time: float) =
+  engine().mixer.tracks[track].targetLevel = level
+  engine().mixer.tracks[track].fadeTime = time
+  engine().mixer.tracks[track].fadeStep =
+    level.float - engine().mixer.tracks[track].level.float / time
 
-proc isPlaying*(mixer: var Mixer): bool =
-  mixer.lock.withLock:
-    for track in mixer.tracks.mvalues:
+proc isPlaying*(): bool =
+  engine().mixer.lock.withLock:
+    for track in engine().mixer.tracks.mvalues:
       for playback in track.playing.values:
         if not playback.paused:
           return true
   return false
 
-proc isPlaying*(mixer: var Mixer, track: string): bool =
-  mixer.lock.withLock:
-    if mixer.tracks.contains(track):
-      for playback in mixer.tracks[track].playing.values:
+proc isPlaying*(track: string): bool =
+  engine().mixer.lock.withLock:
+    if engine().mixer.tracks.contains(track):
+      for playback in engine().mixer.tracks[track].playing.values:
         if not playback.paused:
           return true
     return false
@@ -289,6 +276,15 @@
 
     ]#
 
+proc setupDevice(mixer: var Mixer) =
+  # call this inside audio thread
+  var bufferaddresses: seq[ptr SoundData]
+  for i in 0 ..< NBUFFERS:
+    mixer.buffers.add newSeq[Sample](BUFFERSAMPLECOUNT)
+  for i in 0 ..< mixer.buffers.len:
+    bufferaddresses.add (addr mixer.buffers[i])
+  mixer.device = OpenSoundDevice(AUDIO_SAMPLE_RATE, bufferaddresses)
+
 proc destroy(mixer: var Mixer) =
   mixer.lock.deinitLock()
   mixer.device.CloseSoundDevice()
--- a/semicongine/background_loader.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/background_loader.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,23 +1,7 @@
 import std/syncio
 import std/tables
 
-type
-  LoaderThreadArgs[T] = (
-    ptr Channel[(string, string)],
-    ptr Channel[LoaderResponse[T]],
-    proc(f, p: string): T {.gcsafe.},
-  )
-  LoaderResponse[T] = object
-    path: string
-    package: string
-    data: T
-    error: string
-
-  BackgroundLoader[T] = object
-    loadRequestCn: Channel[(string, string)] # used for sending load requests
-    responseCn: Channel[LoaderResponse[T]] # used for sending back loaded data
-    worker: Thread[LoaderThreadArgs[T]] # does the actual loading from the disk
-    responseTable: Table[string, LoaderResponse[T]] # stores results
+import ./core
 
 proc loader[T](args: LoaderThreadArgs[T]) {.thread.} =
   while true:
--- a/semicongine/contrib/steam.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/contrib/steam.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,20 +1,10 @@
+{.hint[GlobalVar]: off.}
+
 import std/dynlib
 import std/logging
 import std/strutils
 
-var
-  steam_api: LibHandle
-  steam_is_loaded = false
-
-when defined(linux):
-  steam_api = "libsteam_api.so".loadLib()
-elif defined(windows):
-  steam_api = "steam_api".loadLib()
-  # TODO: maybe should get some error reporting on windows too?
-
-# required to store reference, when calling certain APIs
-type SteamUserStatsRef = ptr object
-var userStats: SteamUserStatsRef
+import ../core
 
 # load function pointers for steam API
 var
@@ -28,45 +18,62 @@
   StoreStats: proc(self: SteamUserStatsRef): bool {.stdcall.}
     # needs to be called in order for achievments to be saved
     # dynlib-helper function
-proc loadFunc[T](nimFunc: var T, dllFuncName: string) =
+
+proc loadFunc[T](steam_api: LibHandle, nimFunc: var T, dllFuncName: string) =
   nimFunc = cast[T](steam_api.checkedSymAddr(dllFuncName))
 
-if steam_api != nil:
-  loadFunc(Init, "SteamAPI_InitFlat")
-  loadFunc(Shutdown, "SteamAPI_Shutdown")
-  loadFunc(SteamUserStats, "SteamAPI_SteamUserStats_v012")
-  loadFunc(RequestCurrentStats, "SteamAPI_ISteamUserStats_RequestCurrentStats")
-  loadFunc(ClearAchievement, "SteamAPI_ISteamUserStats_ClearAchievement")
-  loadFunc(SetAchievement, "SteamAPI_ISteamUserStats_SetAchievement")
-  loadFunc(StoreStats, "SteamAPI_ISteamUserStats_StoreStats")
-
 # nice wrappers for steam API
 
 proc SteamRequestCurrentStats*(): bool =
-  RequestCurrentStats(userStats)
+  RequestCurrentStats(engine().userStats)
 
 proc SteamClearAchievement*(name: string): bool =
-  userStats.ClearAchievement(name.cstring)
+  engine().userStats.ClearAchievement(name.cstring)
 
 proc SteamSetAchievement*(name: string): bool =
-  userStats.SetAchievement(name.cstring)
+  engine().userStats.SetAchievement(name.cstring)
 
 proc SteamStoreStats*(): bool =
-  userStats.StoreStats()
+  engine().userStats.StoreStats()
 
 proc SteamShutdown*() =
   Shutdown()
 
 # helper funcs
+proc loadSteamLib() =
+  if engine().steam_api == nil:
+    when defined(linux):
+      engine().steam_api = "libsteam_api.so".loadLib()
+    elif defined(windows):
+      engine().steam_api = "steam_api".loadLib()
+
 proc SteamAvailable*(): bool =
-  steam_api != nil and steam_is_loaded
+  loadSteamLib()
+  engine().steam_api != nil and engine().steam_is_loaded
 
 # first function that should be called
 proc TrySteamInit*() =
-  if steam_api != nil and not steam_is_loaded:
+  loadSteamLib()
+  if engine().steam_api != nil and not engine().steam_is_loaded:
+    loadFunc(engine().steam_api, Init, "SteamAPI_InitFlat")
+    loadFunc(engine().steam_api, Shutdown, "SteamAPI_Shutdown")
+    loadFunc(engine().steam_api, SteamUserStats, "SteamAPI_SteamUserStats_v012")
+    loadFunc(
+      engine().steam_api,
+      RequestCurrentStats,
+      "SteamAPI_ISteamUserStats_RequestCurrentStats",
+    )
+    loadFunc(
+      engine().steam_api, ClearAchievement, "SteamAPI_ISteamUserStats_ClearAchievement"
+    )
+    loadFunc(
+      engine().steam_api, SetAchievement, "SteamAPI_ISteamUserStats_SetAchievement"
+    )
+    loadFunc(engine().steam_api, StoreStats, "SteamAPI_ISteamUserStats_StoreStats")
+
     var msg: array[1024, char]
     let success = Init(addr msg) == 0
     warn join(@msg, "")
     if success:
-      userStats = SteamUserStats()
-      steam_is_loaded = SteamRequestCurrentStats()
+      engine().userStats = SteamUserStats()
+      engine().steam_is_loaded = SteamRequestCurrentStats()
--- a/semicongine/core.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/core.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -27,5 +27,6 @@
 var engine_obj_internal*: Engine
 
 proc engine*(): Engine =
+  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
--- a/semicongine/core/types.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/core/types.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,3 +1,8 @@
+import std/json
+
+import ../thirdparty/parsetoml
+import ../thirdparty/db_connector/db_sqlite
+
 const
   INFLIGHTFRAMES* = 2'u32
   MAX_DESCRIPTORSETS* = 4
@@ -360,7 +365,6 @@
 const int[4] i_x = int[](0, 0, 2, 2);
 const int[4] i_y = int[](1, 3, 3, 1);
 const float epsilon = 0.0000001;
-// const float epsilon = 0.1;
 
 void main() {
   int vertexI = indices[gl_VertexIndex];
@@ -370,7 +374,9 @@
     0
   );
   // the epsilon-offset is necessary, as otherwise characters with the same Z might overlap, despite transparency
-  gl_Position = vec4(vertexPos + position - vec3(0, 0, clamp(0, 1, gl_InstanceIndex * epsilon)), 1.0);
+  gl_Position = vec4(vertexPos + position, 1.0);
+  gl_Position.z -= gl_InstanceIndex * epsilon;
+  gl_Position.z = fract(abs(gl_Position.z));
   vec2 uv = vec2(glyphquads.uv[glyphIndex][i_x[vertexI]], glyphquads.uv[glyphIndex][i_y[vertexI]]);
   fragmentUv = uv;
   fragmentColor = color;
@@ -427,12 +433,70 @@
     glyphIndex*: GPUArray[uint16, VertexBufferMapped]
     texts*: seq[Text]
 
+  # === background loader thread ===
+  LoaderThreadArgs*[T] = (
+    ptr Channel[(string, string)],
+    ptr Channel[LoaderResponse[T]],
+    proc(f, p: string): T {.gcsafe.},
+  )
+  LoaderResponse*[T] = object
+    path*: string
+    package*: string
+    data*: T
+    error*: string
+
+  BackgroundLoader*[T] = object
+    loadRequestCn*: Channel[(string, string)] # used for sending load requests
+    responseCn*: Channel[LoaderResponse[T]] # used for sending back loaded data
+    worker*: Thread[LoaderThreadArgs[T]] # does the actual loading from the disk
+    responseTable*: Table[string, LoaderResponse[T]] # stores results
+
+  # === input ===
+  Input* = object
+    keyIsDown*: set[Key]
+    keyWasPressed*: set[Key]
+    keyWasReleased*: set[Key]
+    mouseIsDown*: set[MouseButton]
+    mouseWasPressed*: set[MouseButton]
+    mouseWasReleased*: set[MouseButton]
+    mousePosition*: Vec2i
+    mouseMove*: Vec2i
+    mouseWheel*: float32
+    windowWasResized*: bool = true
+    windowIsMinimized*: bool = false
+    lockMouse*: bool = false
+    hasFocus*: bool = false
+
+  ActionMap* = object
+    keyActions*: Table[string, set[Key]]
+    mouseActions*: Table[string, set[MouseButton]]
+
+  # === storage ===
+  StorageType* = enum
+    SystemStorage
+    UserStorage # ? level storage type ?
+
+  # === steam ===
+  SteamUserStatsRef* = ptr object
+
   # === global engine object ===
   EngineObj = object
     initialized*: bool
     vulkan*: VulkanObject
     mixer*: ptr Mixer
     audiothread*: Thread[ptr Mixer]
+    input*: Input
+    actionMap*: ActionMap
+    db*: Table[StorageType, DbConn]
+    rawLoader*: ptr BackgroundLoader[seq[byte]]
+    jsonLoader*: ptr BackgroundLoader[JsonNode]
+    configLoader*: ptr BackgroundLoader[TomlValueRef]
+    grayImageLoader*: ptr BackgroundLoader[Image[Gray]]
+    imageLoader*: ptr BackgroundLoader[Image[BGRA]]
+    audioLoader*: ptr BackgroundLoader[SoundData]
+    userStats*: SteamUserStatsRef
+    steam_api*: LibHandle
+    steam_is_loaded*: bool
 
   Engine* = ref EngineObj
 
@@ -449,6 +513,7 @@
 proc `=copy`(dest: var Track, source: Track) {.error.}
 proc `=copy`(dest: var Mixer, source: Mixer) {.error.}
 proc `=copy`[S, T](dest: var ImageObject[S, T], source: ImageObject[S, T]) {.error.}
+proc `=copy`(dest: var Input, source: Input) {.error.}
 proc `=copy`(dest: var EngineObj, source: EngineObj) {.error.}
 proc `=copy`[MaxGlyphs: static int](
   dest: var FontObj[MaxGlyphs], source: FontObj[MaxGlyphs]
--- a/semicongine/core/utils.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/core/utils.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -70,3 +70,11 @@
       if i >= high(IX):
         break
       inc(i)
+
+# usefull to calculate next position for correct memory alignment
+func alignedTo*[T: SomeInteger](value: T, alignment: T): T =
+  let remainder = value mod alignment
+  if remainder == 0:
+    return value
+  else:
+    return value + alignment - remainder
--- a/semicongine/gltf.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/gltf.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -6,7 +6,6 @@
 import std/typetraits
 
 import ./core
-import ./rendering
 import ./image
 
 type
--- a/semicongine/input.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/input.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -5,41 +5,23 @@
 import ./rendering
 import ./storage
 
-type Input = object
-  keyIsDown: set[Key]
-  keyWasPressed: set[Key]
-  keyWasReleased: set[Key]
-  mouseIsDown: set[MouseButton]
-  mouseWasPressed: set[MouseButton]
-  mouseWasReleased: set[MouseButton]
-  mousePosition: Vec2i
-  mouseMove: Vec2i
-  mouseWheel: float32
-  windowWasResized: bool = true
-  windowIsMinimized: bool = false
-  lockMouse: bool = false
-  hasFocus: bool = false
-
-# warning, shit is not thread safe
-var input = Input()
-
 proc updateInputs*(): bool =
   # reset input states
-  input.keyWasPressed = {}
-  input.keyWasReleased = {}
-  input.mouseWasPressed = {}
-  input.mouseWasReleased = {}
-  input.mouseWheel = 0
-  input.mouseMove = vec2i(0, 0)
-  input.windowWasResized = false
+  engine().input.keyWasPressed = {}
+  engine().input.keyWasReleased = {}
+  engine().input.mouseWasPressed = {}
+  engine().input.mouseWasReleased = {}
+  engine().input.mouseWheel = 0
+  engine().input.mouseMove = vec2i(0, 0)
+  engine().input.windowWasResized = false
 
   let newMousePos = getMousePosition(engine().vulkan.window)
-  input.mouseMove = newMousePos - input.mousePosition
-  if input.lockMouse and input.hasFocus:
-    input.mousePosition = engine().vulkan.window.size div 2
-    setMousePosition(engine().vulkan.window, input.mousePosition)
+  engine().input.mouseMove = newMousePos - engine().input.mousePosition
+  if engine().input.lockMouse and engine().input.hasFocus:
+    engine().input.mousePosition = engine().vulkan.window.size div 2
+    setMousePosition(engine().vulkan.window, engine().input.mousePosition)
   else:
-    input.mousePosition = newMousePos
+    engine().input.mousePosition = newMousePos
 
   var killed = false
   for event in engine().vulkan.window.pendingEvents():
@@ -47,136 +29,130 @@
     of Quit:
       killed = true
     of ResizedWindow:
-      input.windowWasResized = true
+      engine().input.windowWasResized = true
     of KeyPressed:
-      input.keyWasPressed.incl event.key
-      input.keyIsDown.incl event.key
+      engine().input.keyWasPressed.incl event.key
+      engine().input.keyIsDown.incl event.key
     of KeyReleased:
-      input.keyWasReleased.incl event.key
-      input.keyIsDown.excl event.key
+      engine().input.keyWasReleased.incl event.key
+      engine().input.keyIsDown.excl event.key
     of MousePressed:
-      input.mouseWasPressed.incl event.button
-      input.mouseIsDown.incl event.button
+      engine().input.mouseWasPressed.incl event.button
+      engine().input.mouseIsDown.incl event.button
     of MouseReleased:
-      input.mouseWasReleased.incl event.button
-      input.mouseIsDown.excl event.button
+      engine().input.mouseWasReleased.incl event.button
+      engine().input.mouseIsDown.excl event.button
     of MouseWheel:
-      input.mouseWheel = event.amount
+      engine().input.mouseWheel = event.amount
     of MinimizedWindow:
-      input.windowIsMinimized = true
+      engine().input.windowIsMinimized = true
     of RestoredWindow:
-      input.windowIsMinimized = false
+      engine().input.windowIsMinimized = false
     of GotFocus:
-      input.hasFocus = true
+      engine().input.hasFocus = true
     of LostFocus:
-      input.hasFocus = false
+      engine().input.hasFocus = false
 
   return not killed
 
 proc keyIsDown*(key: Key): bool =
-  key in input.keyIsDown
+  key in engine().input.keyIsDown
 
 proc keyWasPressed*(key: Key): bool =
-  key in input.keyWasPressed
+  key in engine().input.keyWasPressed
 
 proc keyWasPressed*(): bool =
-  input.keyWasPressed.len > 0
+  engine().input.keyWasPressed.len > 0
 
 proc keyWasReleased*(key: Key): bool =
-  key in input.keyWasReleased
+  key in engine().input.keyWasReleased
 
 proc mouseIsDown*(button: MouseButton): bool =
-  button in input.mouseIsDown
+  button in engine().input.mouseIsDown
 
 proc mouseWasPressed*(): bool =
-  input.mouseWasPressed.len > 0
+  engine().input.mouseWasPressed.len > 0
 
 proc mouseWasPressed*(button: MouseButton): bool =
-  button in input.mouseWasPressed
+  button in engine().input.mouseWasPressed
 
 proc mousePressedButtons*(): set[MouseButton] =
-  input.mouseWasPressed
+  engine().input.mouseWasPressed
 
 proc mouseWasReleased*(): bool =
-  input.mouseWasReleased.len > 0
+  engine().input.mouseWasReleased.len > 0
 
 proc mouseWasReleased*(button: MouseButton): bool =
-  button in input.mouseWasReleased
+  button in engine().input.mouseWasReleased
 
 proc mouseReleasedButtons*(): set[MouseButton] =
-  input.mouseWasReleased
+  engine().input.mouseWasReleased
 
 proc mousePositionPixel*(): Vec2i =
-  input.mousePosition
+  engine().input.mousePosition
 
 proc mousePosition*(): Vec2f =
   result =
-    input.mousePosition.f32 / engine().vulkan.window.size().f32 * 2.0'f32 - 1.0'f32
+    engine().input.mousePosition.f32 / engine().vulkan.window.size().f32 * 2.0'f32 -
+    1.0'f32
   result.y = result.y * -1
 
 proc mouseMove*(): Vec2i =
-  input.mouseMove
+  engine().input.mouseMove
 
 proc mouseWheel*(): float32 =
-  input.mouseWheel
+  engine().input.mouseWheel
 
 proc windowWasResized*(): auto =
-  input.windowWasResized
+  engine().input.windowWasResized
 
 proc windowIsMinimized*(): auto =
-  input.windowIsMinimized
+  engine().input.windowIsMinimized
 
 proc lockMouse*(value: bool) =
-  input.lockMouse = value
+  engine().input.lockMouse = value
 
 proc hasFocus*(): bool =
-  input.hasFocus
+  engine().input.hasFocus
 
 # actions as a slight abstraction over raw input
 
-type ActionMap = object
-  keyActions: Table[string, set[Key]]
-  mouseActions: Table[string, set[MouseButton]]
-
-# warning, shit is not thread safe
-var actionMap: ActionMap
-
 proc mapAction*[T: enum](action: T, key: Key) =
-  if not actionMap.keyActions.contains($action):
-    actionMap.keyActions[$action] = {}
-  actionMap.keyActions[$action].incl key
+  if not engine().actionMap.keyActions.contains($action):
+    engine().actionMap.keyActions[$action] = {}
+  engine().actionMap.keyActions[$action].incl key
 
 proc mapAction*[T: enum](action: T, button: MouseButton) =
-  if not actionMap.mouseActions.contains($action):
-    actionMap.mouseActions[$action] = {}
-  actionMap.mouseActions[$action].incl button
+  if not engine().actionMap.mouseActions.contains($action):
+    engine().actionMap.mouseActions[$action] = {}
+  engine().actionMap.mouseActions[$action].incl button
 
 proc mapAction*[T: enum](action: T, keys: openArray[Key | MouseButton]) =
   for key in keys:
     mapAction(action, key)
 
 proc unmapAction*[T: enum](action: T, key: Key) =
-  if actionMap.keyActions.contains($action):
-    actionMap.keyActions[$action].excl(key)
+  if engine().actionMap.keyActions.contains($action):
+    engine().actionMap.keyActions[$action].excl(key)
 
 proc unmapAction*[T: enum](action: T, button: MouseButton) =
-  if actionMap.mouseActions.contains($action):
-    actionMap.mouseActions[$action].excl(button)
+  if engine().actionMap.mouseActions.contains($action):
+    engine().actionMap.mouseActions[$action].excl(button)
 
 proc unmapAction*[T: enum](action: T) =
-  if actionMap.keyActions.contains($action):
-    actionMap.keyActions[$action] = {}
-  if actionMap.mouseActions.contains($action):
-    actionMap.mouseActions[$action] = {}
+  if engine().actionMap.keyActions.contains($action):
+    engine().actionMap.keyActions[$action] = {}
+  if engine().actionMap.mouseActions.contains($action):
+    engine().actionMap.mouseActions[$action] = {}
 
 proc saveCurrentActionMapping*() =
-  for name, keys in actionMap.keyActions.pairs:
+  for name, keys in engine().actionMap.keyActions.pairs:
     SystemStorage.store(name, keys, table = "input_mapping_key")
-  for name, buttons in actionMap.mouseActions.pairs:
+  for name, buttons in engine().actionMap.mouseActions.pairs:
     SystemStorage.store(name, buttons, table = "input_mapping_mouse")
 
 proc loadActionMapping*[T]() =
-  reset(actionMap)
+  reset(engine().actionMap)
   for name in SystemStorage.List(table = "input_mapping_key"):
     let action = parseEnum[T](name)
     let keys = SystemStorage.Load(name, set[Key](), table = "input_mapping_key")
@@ -184,43 +160,43 @@
       mapAction(action, key)
 
 proc actionDown*[T](action: T): bool =
-  if actionMap.keyActions.contains($action):
-    for key in actionMap.keyActions[$action]:
-      if key in input.keyIsDown:
+  if engine().actionMap.keyActions.contains($action):
+    for key in engine().actionMap.keyActions[$action]:
+      if key in engine().input.keyIsDown:
         return true
     return false
-  if actionMap.mouseActions.contains($action):
-    for button in actionMap.mouseActions[$action]:
-      if button in input.mouseIsDown:
+  if engine().actionMap.mouseActions.contains($action):
+    for button in engine().actionMap.mouseActions[$action]:
+      if button in engine().input.mouseIsDown:
         return true
     return false
 
 proc actionPressed*[T](action: T): bool =
-  if actionMap.keyActions.contains($action):
-    for key in actionMap.keyActions[$action]:
-      if key in input.keyWasPressed:
+  if engine().actionMap.keyActions.contains($action):
+    for key in engine().actionMap.keyActions[$action]:
+      if key in engine().input.keyWasPressed:
         return true
-  elif actionMap.mouseActions.contains($action):
-    for button in actionMap.mouseActions[$action]:
-      if button in input.mouseWasPressed:
+  elif engine().actionMap.mouseActions.contains($action):
+    for button in engine().actionMap.mouseActions[$action]:
+      if button in engine().input.mouseWasPressed:
         return true
 
 proc actionReleased*[T](action: T): bool =
-  if actionMap.keyActions.contains($action):
-    for key in actionMap.keyActions[$action]:
-      if key in input.keyWasReleased:
+  if engine().actionMap.keyActions.contains($action):
+    for key in engine().actionMap.keyActions[$action]:
+      if key in engine().input.keyWasReleased:
         return true
-  elif actionMap.mouseActions.contains($action):
-    for button in actionMap.mouseActions[$action]:
-      if button in input.mouseWasReleased:
+  elif engine().actionMap.mouseActions.contains($action):
+    for button in engine().actionMap.mouseActions[$action]:
+      if button in engine().input.mouseWasReleased:
         return true
 
 proc actionValue*[T](action: T): float32 =
-  if actionMap.keyActions.contains($action):
-    for key in actionMap.keyActions[$action]:
-      if key in input.keyIsDown:
+  if engine().actionMap.keyActions.contains($action):
+    for key in engine().actionMap.keyActions[$action]:
+      if key in engine().input.keyIsDown:
         return 1
-  elif actionMap.mouseActions.contains($action):
-    for button in actionMap.mouseActions[$action]:
-      if button in input.mouseIsDown:
+  elif engine().actionMap.mouseActions.contains($action):
+    for button in engine().actionMap.mouseActions[$action]:
+      if button in engine().input.mouseIsDown:
         return 1
--- a/semicongine/loaders.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/loaders.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -104,13 +104,6 @@
 type ResourceType =
   seq[byte] | JsonNode | TomlValueRef | Image[Gray] | Image[BGRA] | SoundData
 
-var rawLoader = initBackgroundLoader(loadBytes)
-var jsonLoader = initBackgroundLoader(loadJson)
-var configLoader = initBackgroundLoader(loadConfig)
-var grayImageLoader = initBackgroundLoader(loadImage[Gray])
-var imageLoader = initBackgroundLoader(loadImage[BGRA])
-var audioLoader = initBackgroundLoader(loadAudio)
-
 proc loadAsync*[T: ResourceType](path: string, package = DEFAULT_PACKAGE) =
   when T is seq[byte]:
     requestLoading(rawLoader[], path, package)
--- a/semicongine/rendering.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -26,23 +26,6 @@
 when defined(linux):
   include ./rendering/platform/linux
 
-proc `[]`*[T, S](a: GPUArray[T, S], i: SomeInteger): T =
-  a.data[i]
-
-proc len*[T, S](a: GPUArray[T, S]): int =
-  a.data.len
-
-proc `[]=`*[T, S](a: var GPUArray[T, S], i: SomeInteger, value: T) =
-  a.data[i] = value
-
-func getBufferType*[A, B](value: GPUValue[A, B]): BufferType {.compileTime.} =
-  B
-
-func getBufferType*[A, B](
-    value: openArray[GPUValue[A, B]]
-): BufferType {.compileTime.} =
-  B
-
 import ./rendering/vulkan_wrappers
 import ./rendering/swapchain
 
@@ -149,7 +132,7 @@
   # get physical device and graphics queue family
   result.physicalDevice = getBestPhysicalDevice(result.instance)
   result.graphicsQueueFamily =
-    getQueueFamily(result.physicalDevice, VK_QUEUE_GRAPHICS_BIT)
+    getQueueFamily(result.physicalDevice, result.surface, VK_QUEUE_GRAPHICS_BIT)
 
   let
     priority = cfloat(1)
@@ -184,18 +167,6 @@
   result.graphicsQueue =
     svkGetDeviceQueue(result.device, result.graphicsQueueFamily, VK_QUEUE_GRAPHICS_BIT)
 
-proc clearSwapchain*() =
-  assert engine().vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  destroySwapchain(engine().vulkan.swapchain)
-  engine().vulkan.swapchain = nil
-
-proc setupSwapchain*(
-    renderPass: RenderPass, vSync: bool = false, tripleBuffering: bool = true
-) =
-  assert engine().vulkan.swapchain == nil, "Swapchain has already been initialized yet"
-  engine().vulkan.swapchain =
-    initSwapchain(renderPass, vSync = vSync, tripleBuffering = tripleBuffering)
-
 proc destroyVulkan*() =
   if engine().vulkan.swapchain != nil:
     clearSwapchain()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/rendering/memory.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -0,0 +1,423 @@
+import std/typetraits
+import std/sequtils
+
+import ../core
+import ./vulkan_wrappers
+
+proc `[]`*[T, S](a: GPUArray[T, S], i: SomeInteger): T =
+  a.data[i]
+
+proc len*[T, S](a: GPUArray[T, S]): int =
+  a.data.len
+
+proc `[]=`*[T, S](a: var GPUArray[T, S], i: SomeInteger, value: T) =
+  a.data[i] = value
+
+func getBufferType*[A, B](value: GPUValue[A, B]): BufferType {.compileTime.} =
+  B
+
+func getBufferType*[A, B](
+    value: openArray[GPUValue[A, B]]
+): BufferType {.compileTime.} =
+  B
+
+proc initRenderData*(descriptorPoolLimit = 1024'u32): RenderData =
+  result = RenderData()
+  # allocate descriptor pools
+  var poolSizes = [
+    VkDescriptorPoolSize(
+      thetype: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+      descriptorCount: descriptorPoolLimit,
+    ),
+    VkDescriptorPoolSize(
+      thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: descriptorPoolLimit
+    ),
+    VkDescriptorPoolSize(
+      thetype: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount: descriptorPoolLimit
+    ),
+  ]
+  var poolInfo = VkDescriptorPoolCreateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+    poolSizeCount: poolSizes.len.uint32,
+    pPoolSizes: poolSizes.ToCPointer,
+    maxSets: descriptorPoolLimit,
+  )
+  checkVkResult vkCreateDescriptorPool(
+    engine().vulkan.device, addr(poolInfo), nil, addr(result.descriptorPool)
+  )
+
+proc destroyRenderData*(renderData: RenderData) =
+  vkDestroyDescriptorPool(engine().vulkan.device, renderData.descriptorPool, nil)
+
+  for buffers in renderData.buffers:
+    for buffer in buffers:
+      vkDestroyBuffer(engine().vulkan.device, buffer.vk, nil)
+
+  for imageView in renderData.imageViews:
+    vkDestroyImageView(engine().vulkan.device, imageView, nil)
+
+  for sampler in renderData.samplers:
+    vkDestroySampler(engine().vulkan.device, sampler, nil)
+
+  for image in renderData.images:
+    vkDestroyImage(engine().vulkan.device, image, nil)
+
+  for memoryBlocks in renderData.memory.litems:
+    for memory in memoryBlocks:
+      vkFreeMemory(engine().vulkan.device, memory.vk, nil)
+
+func pointerAddOffset[T: SomeInteger](p: pointer, offset: T): pointer =
+  cast[pointer](cast[T](p) + offset)
+
+const BUFFER_USAGE: array[BufferType, seq[VkBufferUsageFlagBits]] = [
+  VertexBuffer: @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  VertexBufferMapped: @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
+  IndexBuffer: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  IndexBufferMapped: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
+  UniformBuffer: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  UniformBufferMapped: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
+  StorageBuffer: @[VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  StorageBufferMapped: @[VK_BUFFER_USAGE_STORAGE_BUFFER_BIT],
+]
+
+template bufferType(gpuData: GPUData): untyped =
+  typeof(gpuData).TBuffer
+
+func needsMapping(bType: BufferType): bool =
+  bType in
+    [VertexBufferMapped, IndexBufferMapped, UniformBufferMapped, StorageBufferMapped]
+template needsMapping(gpuData: GPUData): untyped =
+  gpuData.bufferType.needsMapping
+
+template size*(gpuArray: GPUArray, count = 0'u64): uint64 =
+  (if count == 0: gpuArray.data.len.uint64 else: count).uint64 *
+    sizeof(elementType(gpuArray.data)).uint64
+
+template size*(gpuValue: GPUValue): uint64 =
+  sizeof(gpuValue.data).uint64
+
+func size*(image: ImageObject): uint64 =
+  image.data.len.uint64 * sizeof(elementType(image.data)).uint64
+
+template rawPointer(gpuArray: GPUArray): pointer =
+  addr(gpuArray.data[0])
+
+template rawPointer(gpuValue: GPUValue): pointer =
+  addr(gpuValue.data)
+
+proc isMappable(memoryTypeIndex: uint32): bool =
+  var physicalProperties: VkPhysicalDeviceMemoryProperties
+  vkGetPhysicalDeviceMemoryProperties(
+    engine().vulkan.physicalDevice, addr(physicalProperties)
+  )
+  let flags = toEnums(physicalProperties.memoryTypes[memoryTypeIndex].propertyFlags)
+  return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags
+
+proc initDescriptorSet*(
+    renderData: RenderData,
+    layout: VkDescriptorSetLayout,
+    descriptorSet: DescriptorSetData,
+) =
+  # santization checks
+  for theName, value in descriptorSet.data.fieldPairs:
+    when typeof(value) is GPUValue:
+      assert value.buffer.vk.Valid,
+        "Invalid buffer, did you call 'assignBuffers' for this buffer?"
+    elif typeof(value) is ImageObject:
+      assert value.vk.Valid
+      assert value.imageview.Valid
+      assert value.sampler.Valid
+    elif typeof(value) is array:
+      when elementType(value) is ImageObject:
+        for t in value.litems:
+          assert t.vk.Valid
+          assert t.imageview.Valid
+          assert t.sampler.Valid
+      elif elementType(value) is GPUValue:
+        for t in value.litems:
+          assert t.buffer.vk.Valid
+      else:
+        {.error: "Unsupported descriptor set field: '" & theName & "'".}
+    else:
+      {.error: "Unsupported descriptor set field: '" & theName & "'".}
+
+  # allocate
+  var layouts = newSeqWith(descriptorSet.vk.len, layout)
+  var allocInfo = VkDescriptorSetAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+    descriptorPool: renderData.descriptorPool,
+    descriptorSetCount: uint32(layouts.len),
+    pSetLayouts: layouts.ToCPointer,
+  )
+  checkVkResult vkAllocateDescriptorSets(
+    engine().vulkan.device, addr(allocInfo), descriptorSet.vk.ToCPointer
+  )
+
+  # allocate seq with high cap to prevent realocation while adding to set
+  # (which invalidates pointers that are passed to the vulkan api call)
+  var descriptorSetWrites = newSeqOfCap[VkWriteDescriptorSet](1024)
+  var imageWrites = newSeqOfCap[VkDescriptorImageInfo](1024)
+  var bufferWrites = newSeqOfCap[VkDescriptorBufferInfo](1024)
+
+  for theFieldname, fieldvalue in fieldPairs(descriptorSet.data):
+    const descriptorType = getDescriptorType[typeof(fieldvalue)]()
+    const descriptorCount = getDescriptorCount[typeof(fieldvalue)]()
+    const descriptorBindingNumber =
+      getBindingNumber[typeof(descriptorSet.data)](theFieldname)
+    for i in 0 ..< descriptorSet.vk.len:
+      when typeof(fieldvalue) is GPUValue:
+        bufferWrites.add VkDescriptorBufferInfo(
+          buffer: fieldvalue.buffer.vk,
+          offset: fieldvalue.offset,
+          range: fieldvalue.size,
+        )
+        descriptorSetWrites.add VkWriteDescriptorSet(
+          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+          dstSet: descriptorSet.vk[i],
+          dstBinding: descriptorBindingNumber,
+          dstArrayElement: 0,
+          descriptorType: descriptorType,
+          descriptorCount: descriptorCount,
+          pImageInfo: nil,
+          pBufferInfo: addr(bufferWrites[^1]),
+        )
+      elif typeof(fieldvalue) is ImageObject:
+        imageWrites.add VkDescriptorImageInfo(
+          sampler: fieldvalue.sampler,
+          imageView: fieldvalue.imageView,
+          imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+        )
+        descriptorSetWrites.add VkWriteDescriptorSet(
+          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+          dstSet: descriptorSet.vk[i],
+          dstBinding: descriptorBindingNumber,
+          dstArrayElement: 0,
+          descriptorType: descriptorType,
+          descriptorCount: descriptorCount,
+          pImageInfo: addr(imageWrites[^1]),
+          pBufferInfo: nil,
+        )
+      elif typeof(fieldvalue) is array:
+        when elementType(fieldvalue) is ImageObject:
+          for image in fieldvalue.litems:
+            imageWrites.add VkDescriptorImageInfo(
+              sampler: image.sampler,
+              imageView: image.imageView,
+              imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+            )
+          descriptorSetWrites.add VkWriteDescriptorSet(
+            sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+            dstSet: descriptorSet.vk[i],
+            dstBinding: descriptorBindingNumber,
+            dstArrayElement: 0,
+            descriptorType: descriptorType,
+            descriptorCount: descriptorCount,
+            pImageInfo: addr(imageWrites[^descriptorCount.int]),
+            pBufferInfo: nil,
+          )
+        elif elementType(fieldvalue) is GPUValue:
+          for entry in fieldvalue.litems:
+            bufferWrites.add VkDescriptorBufferInfo(
+              buffer: entry.buffer.vk, offset: entry.offset, range: entry.size
+            )
+          descriptorSetWrites.add VkWriteDescriptorSet(
+            sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+            dstSet: descriptorSet.vk[i],
+            dstBinding: descriptorBindingNumber,
+            dstArrayElement: 0,
+            descriptorType: descriptorType,
+            descriptorCount: descriptorCount,
+            pImageInfo: nil,
+            pBufferInfo: addr(bufferWrites[^descriptorCount.int]),
+          )
+        else:
+          {.
+            error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldvalue))
+          .}
+      else:
+        {.
+          error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldvalue))
+        .}
+
+  vkUpdateDescriptorSets(
+    device = engine().vulkan.device,
+    descriptorWriteCount = descriptorSetWrites.len.uint32,
+    pDescriptorWrites = descriptorSetWrites.ToCPointer,
+    descriptorCopyCount = 0,
+    pDescriptorCopies = nil,
+  )
+
+proc allocateNewMemoryBlock*(size: uint64, mType: uint32): MemoryBlock =
+  result = MemoryBlock(
+    vk: svkAllocateMemory(size, mType), size: size, rawPointer: nil, offsetNextFree: 0
+  )
+  if mType.isMappable():
+    checkVkResult vkMapMemory(
+      device = engine().vulkan.device,
+      memory = result.vk,
+      offset = 0'u64,
+      size = result.size,
+      flags = VkMemoryMapFlags(0),
+      ppData = addr(result.rawPointer),
+    )
+
+proc flushBuffer*(buffer: Buffer) =
+  var flushRegion = VkMappedMemoryRange(
+    sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+    memory: buffer.memory,
+    offset: buffer.memoryOffset,
+    size: buffer.size,
+  )
+  checkVkResult vkFlushMappedMemoryRanges(engine().vulkan.device, 1, addr(flushRegion))
+
+proc flushAllMemory*(renderData: RenderData) =
+  var flushRegions = newSeq[VkMappedMemoryRange]()
+  for memoryBlocks in renderData.memory.litems:
+    for memoryBlock in memoryBlocks:
+      if memoryBlock.rawPointer != nil and memoryBlock.offsetNextFree > 0:
+        flushRegions.add VkMappedMemoryRange(
+          sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+          memory: memoryBlock.vk,
+          size: alignedTo(
+            memoryBlock.offsetNextFree,
+            svkGetPhysicalDeviceProperties().limits.nonCoherentAtomSize,
+          ),
+        )
+  if flushRegions.len > 0:
+    checkVkResult vkFlushMappedMemoryRanges(
+      engine().vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer()
+    )
+
+proc allocateNewBuffer(
+    renderData: var RenderData, size: uint64, bufferType: BufferType
+): Buffer =
+  result = Buffer(
+    vk: svkCreateBuffer(size, BUFFER_USAGE[bufferType]),
+    size: size,
+    rawPointer: nil,
+    offsetNextFree: 0,
+  )
+  let memoryRequirements = svkGetBufferMemoryRequirements(result.vk)
+  let memoryType = bestMemory(
+    mappable = bufferType.needsMapping, filter = memoryRequirements.memoryTypes
+  )
+
+  # check if there is an existing allocated memory block that is large enough to be used
+  var selectedBlockI = -1
+  for i in 0 ..< renderData.memory[memoryType].len:
+    if renderData.memory[memoryType][i].size -
+        alignedTo(
+          renderData.memory[memoryType][i].offsetNextFree, memoryRequirements.alignment
+        ) >= memoryRequirements.size:
+      selectedBlockI = i
+      break
+  # otherwise, allocate a new block of memory and use that
+  if selectedBlockI < 0:
+    selectedBlockI = renderData.memory[memoryType].len
+    renderData.memory[memoryType].add allocateNewMemoryBlock(
+      size = max(memoryRequirements.size, MEMORY_BLOCK_ALLOCATION_SIZE),
+      mType = memoryType,
+    )
+
+  template selectedBlock(): untyped =
+    renderData.memory[memoryType][selectedBlockI]
+
+  # let selectedBlock =
+  renderData.memory[memoryType][selectedBlockI].offsetNextFree =
+    alignedTo(selectedBlock.offsetNextFree, memoryRequirements.alignment)
+  checkVkResult vkBindBufferMemory(
+    engine().vulkan.device, result.vk, selectedBlock.vk, selectedBlock.offsetNextFree
+  )
+  result.memory = selectedBlock.vk
+  result.memoryOffset = selectedBlock.offsetNextFree
+  result.rawPointer =
+    selectedBlock.rawPointer.pointerAddOffset(selectedBlock.offsetNextFree)
+  renderData.memory[memoryType][selectedBlockI].offsetNextFree += memoryRequirements.size
+
+proc updateGPUBuffer*(gpuData: GPUData, count = 0'u64, flush = false) =
+  if gpuData.size() == 0:
+    return
+
+  when needsMapping(gpuData):
+    when gpuData is GPUArray:
+      copyMem(
+        pointerAddOffset(gpuData.buffer.rawPointer, gpuData.offset),
+        gpuData.rawPointer,
+        gpuData.size(count),
+      )
+    else:
+      copyMem(
+        pointerAddOffset(gpuData.buffer.rawPointer, gpuData.offset),
+        gpuData.rawPointer,
+        gpuData.size(),
+      )
+    if flush:
+      flushBuffer(gpuData.buffer)
+  else:
+    withStagingBuffer((gpuData.buffer.vk, gpuData.offset), gpuData.size, stagingPtr):
+      when gpuData is GPUArray:
+        copyMem(stagingPtr, gpuData.rawPointer, gpuData.size(count))
+      else:
+        copyMem(stagingPtr, gpuData.rawPointer, gpuData.size())
+
+proc updateAllGPUBuffers*[T](value: T, flush = false) =
+  for name, fieldvalue in value.fieldPairs():
+    when typeof(fieldvalue) is GPUData:
+      updateGPUBuffer(fieldvalue, flush = flush)
+    when typeof(fieldvalue) is array:
+      when elementType(fieldvalue) is GPUData:
+        for entry in fieldvalue.litems:
+          updateGPUBuffer(entry, flush = flush)
+
+proc allocateGPUData(
+    renderdata: var RenderData, bufferType: BufferType, size: uint64
+): (Buffer, uint64) =
+  # find buffer that has space
+  var selectedBufferI = -1
+
+  for i in 0 ..< renderData.buffers[bufferType].len:
+    let buffer = renderData.buffers[bufferType][i]
+    if buffer.size - alignedTo(buffer.offsetNextFree, BUFFER_ALIGNMENT) >= size:
+      selectedBufferI = i
+
+  # otherwise create new buffer
+  if selectedBufferI < 0:
+    selectedBufferI = renderdata.buffers[bufferType].len
+    renderdata.buffers[bufferType].add renderdata.allocateNewBuffer(
+      size = max(size, BUFFER_ALLOCATION_SIZE), bufferType = bufferType
+    )
+
+  # assigne value
+  let selectedBuffer = renderdata.buffers[bufferType][selectedBufferI]
+  renderdata.buffers[bufferType][selectedBufferI].offsetNextFree =
+    alignedTo(selectedBuffer.offsetNextFree, BUFFER_ALIGNMENT)
+
+  result[0] = selectedBuffer
+  result[1] = renderdata.buffers[bufferType][selectedBufferI].offsetNextFree
+  renderdata.buffers[bufferType][selectedBufferI].offsetNextFree += size
+
+proc assignBuffers*[T](renderdata: var RenderData, data: var T, uploadData = true) =
+  for name, value in fieldPairs(data):
+    when typeof(value) is GPUData:
+      (value.buffer, value.offset) =
+        allocateGPUData(renderdata, value.bufferType, value.size)
+    elif typeof(value) is DescriptorSetData:
+      assignBuffers(renderdata, value.data, uploadData = uploadData)
+    elif typeof(value) is array:
+      when elementType(value) is GPUValue:
+        for v in value.mitems:
+          (v.buffer, v.offset) = allocateGPUData(renderdata, v.bufferType, v.size)
+
+  if uploadData:
+    updateAllGPUBuffers(data, flush = true)
+
+proc assignBuffers*(
+    renderdata: var RenderData, descriptorSet: var DescriptorSetData, uploadData = true
+) =
+  assignBuffers(renderdata, descriptorSet.data, uploadData = uploadData)
+
+proc asGPUArray*[T](data: sink openArray[T], bufferType: static BufferType): auto =
+  GPUArray[T, bufferType](data: @data)
+
+proc asGPUValue*[T](data: sink T, bufferType: static BufferType): auto =
+  GPUValue[T, bufferType](data: data)
--- a/semicongine/rendering/platform/linux.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering/platform/linux.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -104,7 +104,7 @@
   x11.Button3: MouseButton.Mouse3,
 }.toTable
 
-var deleteMessage*: Atom
+var deleteMessage* {.hint[GlobalVar]: off.}: Atom # one internal use, not serious
 
 template checkXlibResult(call: untyped) =
   let value = call
--- a/semicongine/rendering/renderer.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering/renderer.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,441 +1,10 @@
 import std/typetraits
-import std/sequtils
 import std/macros
 import std/logging
 
 import ../core
 import ./vulkan_wrappers
-
-func pointerAddOffset[T: SomeInteger](p: pointer, offset: T): pointer =
-  cast[pointer](cast[T](p) + offset)
-
-const BUFFER_USAGE: array[BufferType, seq[VkBufferUsageFlagBits]] = [
-  VertexBuffer: @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
-  VertexBufferMapped: @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
-  IndexBuffer: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
-  IndexBufferMapped: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
-  UniformBuffer: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
-  UniformBufferMapped: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
-  StorageBuffer: @[VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
-  StorageBufferMapped: @[VK_BUFFER_USAGE_STORAGE_BUFFER_BIT],
-]
-
-proc getVkFormat(grayscale: bool, usage: openArray[VkImageUsageFlagBits]): VkFormat =
-  let formats =
-    if grayscale:
-      [VK_FORMAT_R8_SRGB, VK_FORMAT_R8_UNORM]
-    else:
-      [VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM]
-
-  var formatProperties =
-    VkImageFormatProperties2(sType: VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2)
-  for format in formats:
-    var formatInfo = VkPhysicalDeviceImageFormatInfo2(
-      sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
-      format: format,
-      thetype: VK_IMAGE_TYPE_2D,
-      tiling: VK_IMAGE_TILING_OPTIMAL,
-      usage: usage.toBits,
-    )
-    let formatCheck = vkGetPhysicalDeviceImageFormatProperties2(
-      engine().vulkan.physicalDevice, addr formatInfo, addr formatProperties
-    )
-    if formatCheck == VK_SUCCESS: # found suitable format
-      return format
-    elif formatCheck == VK_ERROR_FORMAT_NOT_SUPPORTED: # nope, try to find other format
-      continue
-    else: # raise error
-      checkVkResult formatCheck
-
-  assert false, "Unable to find format for textures"
-
-func alignedTo[T: SomeInteger](value: T, alignment: T): T =
-  let remainder = value mod alignment
-  if remainder == 0:
-    return value
-  else:
-    return value + alignment - remainder
-
-template bufferType(gpuData: GPUData): untyped =
-  typeof(gpuData).TBuffer
-
-func needsMapping(bType: BufferType): bool =
-  bType in
-    [VertexBufferMapped, IndexBufferMapped, UniformBufferMapped, StorageBufferMapped]
-template needsMapping(gpuData: GPUData): untyped =
-  gpuData.bufferType.needsMapping
-
-template size(gpuArray: GPUArray, count = 0'u64): uint64 =
-  (if count == 0: gpuArray.data.len.uint64 else: count).uint64 *
-    sizeof(elementType(gpuArray.data)).uint64
-
-template size(gpuValue: GPUValue): uint64 =
-  sizeof(gpuValue.data).uint64
-
-func size(image: ImageObject): uint64 =
-  image.data.len.uint64 * sizeof(elementType(image.data)).uint64
-
-template rawPointer(gpuArray: GPUArray): pointer =
-  addr(gpuArray.data[0])
-
-template rawPointer(gpuValue: GPUValue): pointer =
-  addr(gpuValue.data)
-
-proc isMappable(memoryTypeIndex: uint32): bool =
-  var physicalProperties: VkPhysicalDeviceMemoryProperties
-  vkGetPhysicalDeviceMemoryProperties(
-    engine().vulkan.physicalDevice, addr(physicalProperties)
-  )
-  let flags = toEnums(physicalProperties.memoryTypes[memoryTypeIndex].propertyFlags)
-  return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags
-
-proc initDescriptorSet*(
-    renderData: RenderData,
-    layout: VkDescriptorSetLayout,
-    descriptorSet: DescriptorSetData,
-) =
-  # santization checks
-  for theName, value in descriptorSet.data.fieldPairs:
-    when typeof(value) is GPUValue:
-      assert value.buffer.vk.Valid,
-        "Invalid buffer, did you call 'assignBuffers' for this buffer?"
-    elif typeof(value) is ImageObject:
-      assert value.vk.Valid
-      assert value.imageview.Valid
-      assert value.sampler.Valid
-    elif typeof(value) is array:
-      when elementType(value) is ImageObject:
-        for t in value.litems:
-          assert t.vk.Valid
-          assert t.imageview.Valid
-          assert t.sampler.Valid
-      elif elementType(value) is GPUValue:
-        for t in value.litems:
-          assert t.buffer.vk.Valid
-      else:
-        {.error: "Unsupported descriptor set field: '" & theName & "'".}
-    else:
-      {.error: "Unsupported descriptor set field: '" & theName & "'".}
-
-  # allocate
-  var layouts = newSeqWith(descriptorSet.vk.len, layout)
-  var allocInfo = VkDescriptorSetAllocateInfo(
-    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
-    descriptorPool: renderData.descriptorPool,
-    descriptorSetCount: uint32(layouts.len),
-    pSetLayouts: layouts.ToCPointer,
-  )
-  checkVkResult vkAllocateDescriptorSets(
-    engine().vulkan.device, addr(allocInfo), descriptorSet.vk.ToCPointer
-  )
-
-  # allocate seq with high cap to prevent realocation while adding to set
-  # (which invalidates pointers that are passed to the vulkan api call)
-  var descriptorSetWrites = newSeqOfCap[VkWriteDescriptorSet](1024)
-  var imageWrites = newSeqOfCap[VkDescriptorImageInfo](1024)
-  var bufferWrites = newSeqOfCap[VkDescriptorBufferInfo](1024)
-
-  for theFieldname, fieldvalue in fieldPairs(descriptorSet.data):
-    const descriptorType = getDescriptorType[typeof(fieldvalue)]()
-    const descriptorCount = getDescriptorCount[typeof(fieldvalue)]()
-    const descriptorBindingNumber =
-      getBindingNumber[typeof(descriptorSet.data)](theFieldname)
-    for i in 0 ..< descriptorSet.vk.len:
-      when typeof(fieldvalue) is GPUValue:
-        bufferWrites.add VkDescriptorBufferInfo(
-          buffer: fieldvalue.buffer.vk,
-          offset: fieldvalue.offset,
-          range: fieldvalue.size,
-        )
-        descriptorSetWrites.add VkWriteDescriptorSet(
-          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
-          dstSet: descriptorSet.vk[i],
-          dstBinding: descriptorBindingNumber,
-          dstArrayElement: 0,
-          descriptorType: descriptorType,
-          descriptorCount: descriptorCount,
-          pImageInfo: nil,
-          pBufferInfo: addr(bufferWrites[^1]),
-        )
-      elif typeof(fieldvalue) is ImageObject:
-        imageWrites.add VkDescriptorImageInfo(
-          sampler: fieldvalue.sampler,
-          imageView: fieldvalue.imageView,
-          imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-        )
-        descriptorSetWrites.add VkWriteDescriptorSet(
-          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
-          dstSet: descriptorSet.vk[i],
-          dstBinding: descriptorBindingNumber,
-          dstArrayElement: 0,
-          descriptorType: descriptorType,
-          descriptorCount: descriptorCount,
-          pImageInfo: addr(imageWrites[^1]),
-          pBufferInfo: nil,
-        )
-      elif typeof(fieldvalue) is array:
-        when elementType(fieldvalue) is ImageObject:
-          for image in fieldvalue.litems:
-            imageWrites.add VkDescriptorImageInfo(
-              sampler: image.sampler,
-              imageView: image.imageView,
-              imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-            )
-          descriptorSetWrites.add VkWriteDescriptorSet(
-            sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
-            dstSet: descriptorSet.vk[i],
-            dstBinding: descriptorBindingNumber,
-            dstArrayElement: 0,
-            descriptorType: descriptorType,
-            descriptorCount: descriptorCount,
-            pImageInfo: addr(imageWrites[^descriptorCount.int]),
-            pBufferInfo: nil,
-          )
-        elif elementType(fieldvalue) is GPUValue:
-          for entry in fieldvalue.litems:
-            bufferWrites.add VkDescriptorBufferInfo(
-              buffer: entry.buffer.vk, offset: entry.offset, range: entry.size
-            )
-          descriptorSetWrites.add VkWriteDescriptorSet(
-            sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
-            dstSet: descriptorSet.vk[i],
-            dstBinding: descriptorBindingNumber,
-            dstArrayElement: 0,
-            descriptorType: descriptorType,
-            descriptorCount: descriptorCount,
-            pImageInfo: nil,
-            pBufferInfo: addr(bufferWrites[^descriptorCount.int]),
-          )
-        else:
-          {.
-            error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldvalue))
-          .}
-      else:
-        {.
-          error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldvalue))
-        .}
-
-  vkUpdateDescriptorSets(
-    device = engine().vulkan.device,
-    descriptorWriteCount = descriptorSetWrites.len.uint32,
-    pDescriptorWrites = descriptorSetWrites.ToCPointer,
-    descriptorCopyCount = 0,
-    pDescriptorCopies = nil,
-  )
-
-proc allocateNewMemoryBlock(size: uint64, mType: uint32): MemoryBlock =
-  result = MemoryBlock(
-    vk: svkAllocateMemory(size, mType), size: size, rawPointer: nil, offsetNextFree: 0
-  )
-  if mType.isMappable():
-    checkVkResult vkMapMemory(
-      device = engine().vulkan.device,
-      memory = result.vk,
-      offset = 0'u64,
-      size = result.size,
-      flags = VkMemoryMapFlags(0),
-      ppData = addr(result.rawPointer),
-    )
-
-proc flushBuffer*(buffer: Buffer) =
-  var flushRegion = VkMappedMemoryRange(
-    sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
-    memory: buffer.memory,
-    offset: buffer.memoryOffset,
-    size: buffer.size,
-  )
-  checkVkResult vkFlushMappedMemoryRanges(engine().vulkan.device, 1, addr(flushRegion))
-
-proc flushAllMemory*(renderData: RenderData) =
-  var flushRegions = newSeq[VkMappedMemoryRange]()
-  for memoryBlocks in renderData.memory.litems:
-    for memoryBlock in memoryBlocks:
-      if memoryBlock.rawPointer != nil and memoryBlock.offsetNextFree > 0:
-        flushRegions.add VkMappedMemoryRange(
-          sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
-          memory: memoryBlock.vk,
-          size: alignedTo(
-            memoryBlock.offsetNextFree,
-            svkGetPhysicalDeviceProperties().limits.nonCoherentAtomSize,
-          ),
-        )
-  if flushRegions.len > 0:
-    checkVkResult vkFlushMappedMemoryRanges(
-      engine().vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer()
-    )
-
-proc allocateNewBuffer(
-    renderData: var RenderData, size: uint64, bufferType: BufferType
-): Buffer =
-  result = Buffer(
-    vk: svkCreateBuffer(size, BUFFER_USAGE[bufferType]),
-    size: size,
-    rawPointer: nil,
-    offsetNextFree: 0,
-  )
-  let memoryRequirements = svkGetBufferMemoryRequirements(result.vk)
-  let memoryType = bestMemory(
-    mappable = bufferType.needsMapping, filter = memoryRequirements.memoryTypes
-  )
-
-  # check if there is an existing allocated memory block that is large enough to be used
-  var selectedBlockI = -1
-  for i in 0 ..< renderData.memory[memoryType].len:
-    if renderData.memory[memoryType][i].size -
-        alignedTo(
-          renderData.memory[memoryType][i].offsetNextFree, memoryRequirements.alignment
-        ) >= memoryRequirements.size:
-      selectedBlockI = i
-      break
-  # otherwise, allocate a new block of memory and use that
-  if selectedBlockI < 0:
-    selectedBlockI = renderData.memory[memoryType].len
-    renderData.memory[memoryType].add allocateNewMemoryBlock(
-      size = max(memoryRequirements.size, MEMORY_BLOCK_ALLOCATION_SIZE),
-      mType = memoryType,
-    )
-
-  template selectedBlock(): untyped =
-    renderData.memory[memoryType][selectedBlockI]
-
-  # let selectedBlock =
-  renderData.memory[memoryType][selectedBlockI].offsetNextFree =
-    alignedTo(selectedBlock.offsetNextFree, memoryRequirements.alignment)
-  checkVkResult vkBindBufferMemory(
-    engine().vulkan.device, result.vk, selectedBlock.vk, selectedBlock.offsetNextFree
-  )
-  result.memory = selectedBlock.vk
-  result.memoryOffset = selectedBlock.offsetNextFree
-  result.rawPointer =
-    selectedBlock.rawPointer.pointerAddOffset(selectedBlock.offsetNextFree)
-  renderData.memory[memoryType][selectedBlockI].offsetNextFree += memoryRequirements.size
-
-proc updateGPUBuffer*(gpuData: GPUData, count = 0'u64, flush = false) =
-  if gpuData.size() == 0:
-    return
-
-  when needsMapping(gpuData):
-    when gpuData is GPUArray:
-      copyMem(
-        pointerAddOffset(gpuData.buffer.rawPointer, gpuData.offset),
-        gpuData.rawPointer,
-        gpuData.size(count),
-      )
-    else:
-      copyMem(
-        pointerAddOffset(gpuData.buffer.rawPointer, gpuData.offset),
-        gpuData.rawPointer,
-        gpuData.size(),
-      )
-    if flush:
-      flushBuffer(gpuData.buffer)
-  else:
-    withStagingBuffer((gpuData.buffer.vk, gpuData.offset), gpuData.size, stagingPtr):
-      when gpuData is GPUArray:
-        copyMem(stagingPtr, gpuData.rawPointer, gpuData.size(count))
-      else:
-        copyMem(stagingPtr, gpuData.rawPointer, gpuData.size())
-
-proc updateAllGPUBuffers*[T](value: T, flush = false) =
-  for name, fieldvalue in value.fieldPairs():
-    when typeof(fieldvalue) is GPUData:
-      updateGPUBuffer(fieldvalue, flush = flush)
-    when typeof(fieldvalue) is array:
-      when elementType(fieldvalue) is GPUData:
-        for entry in fieldvalue.litems:
-          updateGPUBuffer(entry, flush = flush)
-
-proc allocateGPUData(
-    renderdata: var RenderData, bufferType: BufferType, size: uint64
-): (Buffer, uint64) =
-  # find buffer that has space
-  var selectedBufferI = -1
-
-  for i in 0 ..< renderData.buffers[bufferType].len:
-    let buffer = renderData.buffers[bufferType][i]
-    if buffer.size - alignedTo(buffer.offsetNextFree, BUFFER_ALIGNMENT) >= size:
-      selectedBufferI = i
-
-  # otherwise create new buffer
-  if selectedBufferI < 0:
-    selectedBufferI = renderdata.buffers[bufferType].len
-    renderdata.buffers[bufferType].add renderdata.allocateNewBuffer(
-      size = max(size, BUFFER_ALLOCATION_SIZE), bufferType = bufferType
-    )
-
-  # assigne value
-  let selectedBuffer = renderdata.buffers[bufferType][selectedBufferI]
-  renderdata.buffers[bufferType][selectedBufferI].offsetNextFree =
-    alignedTo(selectedBuffer.offsetNextFree, BUFFER_ALIGNMENT)
-
-  result[0] = selectedBuffer
-  result[1] = renderdata.buffers[bufferType][selectedBufferI].offsetNextFree
-  renderdata.buffers[bufferType][selectedBufferI].offsetNextFree += size
-
-proc assignBuffers*[T](renderdata: var RenderData, data: var T, uploadData = true) =
-  for name, value in fieldPairs(data):
-    when typeof(value) is GPUData:
-      (value.buffer, value.offset) =
-        allocateGPUData(renderdata, value.bufferType, value.size)
-    elif typeof(value) is DescriptorSetData:
-      assignBuffers(renderdata, value.data, uploadData = uploadData)
-    elif typeof(value) is array:
-      when elementType(value) is GPUValue:
-        for v in value.mitems:
-          (v.buffer, v.offset) = allocateGPUData(renderdata, v.bufferType, v.size)
-
-  if uploadData:
-    updateAllGPUBuffers(data, flush = true)
-
-proc assignBuffers*(
-    renderdata: var RenderData, descriptorSet: var DescriptorSetData, uploadData = true
-) =
-  assignBuffers(renderdata, descriptorSet.data, uploadData = uploadData)
-
-proc initRenderData*(descriptorPoolLimit = 1024'u32): RenderData =
-  result = RenderData()
-  # allocate descriptor pools
-  var poolSizes = [
-    VkDescriptorPoolSize(
-      thetype: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
-      descriptorCount: descriptorPoolLimit,
-    ),
-    VkDescriptorPoolSize(
-      thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: descriptorPoolLimit
-    ),
-    VkDescriptorPoolSize(
-      thetype: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount: descriptorPoolLimit
-    ),
-  ]
-  var poolInfo = VkDescriptorPoolCreateInfo(
-    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
-    poolSizeCount: poolSizes.len.uint32,
-    pPoolSizes: poolSizes.ToCPointer,
-    maxSets: descriptorPoolLimit,
-  )
-  checkVkResult vkCreateDescriptorPool(
-    engine().vulkan.device, addr(poolInfo), nil, addr(result.descriptorPool)
-  )
-
-proc destroyRenderData*(renderData: RenderData) =
-  vkDestroyDescriptorPool(engine().vulkan.device, renderData.descriptorPool, nil)
-
-  for buffers in renderData.buffers:
-    for buffer in buffers:
-      vkDestroyBuffer(engine().vulkan.device, buffer.vk, nil)
-
-  for imageView in renderData.imageViews:
-    vkDestroyImageView(engine().vulkan.device, imageView, nil)
-
-  for sampler in renderData.samplers:
-    vkDestroySampler(engine().vulkan.device, sampler, nil)
-
-  for image in renderData.images:
-    vkDestroyImage(engine().vulkan.device, image, nil)
-
-  for memoryBlocks in renderData.memory.litems:
-    for memory in memoryBlocks:
-      vkFreeMemory(engine().vulkan.device, memory.vk, nil)
+from ./memory import allocateNewMemoryBlock, size
 
 proc transitionImageLayout(
     image: VkImage, oldLayout, newLayout: VkImageLayout, nLayers: uint32
@@ -516,6 +85,35 @@
     engine().vulkan.device, addr(samplerInfo), nil, addr(result)
   )
 
+proc getVkFormat(grayscale: bool, usage: openArray[VkImageUsageFlagBits]): VkFormat =
+  let formats =
+    if grayscale:
+      [VK_FORMAT_R8_SRGB, VK_FORMAT_R8_UNORM]
+    else:
+      [VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM]
+
+  var formatProperties =
+    VkImageFormatProperties2(sType: VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2)
+  for format in formats:
+    var formatInfo = VkPhysicalDeviceImageFormatInfo2(
+      sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+      format: format,
+      thetype: VK_IMAGE_TYPE_2D,
+      tiling: VK_IMAGE_TILING_OPTIMAL,
+      usage: usage.toBits,
+    )
+    let formatCheck = vkGetPhysicalDeviceImageFormatProperties2(
+      engine().vulkan.physicalDevice, addr formatInfo, addr formatProperties
+    )
+    if formatCheck == VK_SUCCESS: # found suitable format
+      return format
+    elif formatCheck == VK_ERROR_FORMAT_NOT_SUPPORTED: # nope, try to find other format
+      continue
+    else: # raise error
+      checkVkResult formatCheck
+
+  assert false, "Unable to find format for textures"
+
 proc createVulkanImage(renderData: var RenderData, image: var ImageObject) =
   assert image.vk == VkImage(0), "Image has already been created"
   var imgUsage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
@@ -813,11 +411,5 @@
   )
   render(commandBuffer, pipeline, mesh, EMPTY(), fixedVertexCount, fixedInstanceCount)
 
-proc asGPUArray*[T](data: sink openArray[T], bufferType: static BufferType): auto =
-  GPUArray[T, bufferType](data: @data)
-
-proc asGPUValue*[T](data: sink T, bufferType: static BufferType): auto =
-  GPUValue[T, bufferType](data: data)
-
 proc asDescriptorSetData*[T](data: sink T): auto =
   DescriptorSetData[T](data: data)
--- a/semicongine/rendering/shaders.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering/shaders.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,4 +1,5 @@
 import std/macros
+import std/typetraits
 import std/hashes
 import std/strformat
 import std/strutils
@@ -268,12 +269,11 @@
     elif hasCustomPragma(value, DescriptorSet):
       let setIndex = value.getCustomPragmaVal(DescriptorSet)
       assert not sawDescriptorSets[setIndex],
-        TShader.name & ": Only one DescriptorSet per index is allowed per shader"
+        "{TShader}: Only one DescriptorSet per index is allowed per shader"
       assert typeof(value) is object,
-        TShader.name & "Descriptor field '" & fieldname & "' must be of type object"
+        "{TShader}: Descriptor field '" & fieldname & "' must be of type object"
       assert setIndex < MAX_DESCRIPTORSETS,
-        typetraits.name(TShader) & ": maximum " & $MAX_DESCRIPTORSETS &
-          " descriptor sets allowed"
+        "{TShader}: maximum " & $MAX_DESCRIPTORSETS & " descriptor sets allowed"
       sawDescriptorSets[setIndex] = true
 
       var descriptorBinding = 0
--- a/semicongine/rendering/swapchain.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering/swapchain.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -3,7 +3,7 @@
 import ../core
 import ./vulkan_wrappers
 
-proc initSwapchain*(
+proc initSwapchain(
     renderPass: RenderPass,
     vSync: bool = false,
     tripleBuffering: bool = true,
@@ -324,3 +324,21 @@
         swap(swapchain = engine().vulkan.swapchain, commandBuffer = `commandBufferName`)
   else:
     recreateSwapchain()
+
+proc clearSwapchain*() =
+  assert engine().vulkan.swapchain != nil, "Swapchain has not been initialized yet"
+  destroySwapchain(engine().vulkan.swapchain)
+  engine().vulkan.swapchain = nil
+
+proc setupSwapchain*(
+    renderPass: RenderPass, vSync: bool = false, tripleBuffering: bool = true
+) =
+  assert engine().vulkan.swapchain == nil, "Swapchain has already been initialized yet"
+  engine().vulkan.swapchain =
+    initSwapchain(renderPass, vSync = vSync, tripleBuffering = tripleBuffering)
+
+proc frameWidth*(): uint32 =
+  engine().vulkan.swapchain.width
+
+proc frameHeight*(): uint32 =
+  engine().vulkan.swapchain.height
--- a/semicongine/rendering/vulkan/api.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering/vulkan/api.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,3 +1,5 @@
+{.hint[GlobalVar]: off.} # just unavoidable
+
 type
   VkHandle* = distinct uint
   VkNonDispatchableHandle* = distinct uint
--- a/semicongine/rendering/vulkan_wrappers.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/rendering/vulkan_wrappers.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,5 +1,6 @@
 import std/strformat
 import std/strutils
+import std/typetraits
 
 import ../core
 
@@ -31,17 +32,18 @@
 
   assert score > 0, "Unable to find integrated or discrete GPU"
 
-proc svkGetPhysicalDeviceSurfaceSupportKHR*(queueFamily: uint32): bool =
+proc svkGetPhysicalDeviceSurfaceSupportKHR*(
+    pDevice: VkPhysicalDevice, surface: VkSurfaceKHR, queueFamily: uint32
+): bool =
   var presentation = VkBool32(false)
   checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(
-    engine().vulkan.physicalDevice,
-    queueFamily,
-    engine().vulkan.surface,
-    addr(presentation),
+    pDevice, queueFamily, surface, addr(presentation)
   )
   return bool(presentation)
 
-proc getQueueFamily*(pDevice: VkPhysicalDevice, qType: VkQueueFlagBits): uint32 =
+proc getQueueFamily*(
+    pDevice: VkPhysicalDevice, surface: VkSurfaceKHR, qType: VkQueueFlagBits
+): uint32 =
   var nQueuefamilies: uint32
   vkGetPhysicalDeviceQueueFamilyProperties(pDevice, addr nQueuefamilies, nil)
   var queuFamilies = newSeq[VkQueueFamilyProperties](nQueuefamilies)
@@ -51,7 +53,8 @@
   for i in 0'u32 ..< nQueuefamilies:
     if qType in toEnums(queuFamilies[i].queueFlags):
       # for graphics queues we always also want prsentation, they seem never to be separated in practice
-      if svkGetPhysicalDeviceSurfaceSupportKHR(i) or qType != VK_QUEUE_GRAPHICS_BIT:
+      if pDevice.svkGetPhysicalDeviceSurfaceSupportKHR(surface, i) or
+          qType != VK_QUEUE_GRAPHICS_BIT:
         return i
   assert false, &"Queue of type {qType} not found"
 
--- a/semicongine/storage.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/storage.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -11,12 +11,6 @@
 const STORAGE_NAME = Path("storage.db")
 const DEFAULT_KEY_VALUE_TABLE_NAME = "shelf"
 
-type StorageType* = enum
-  SystemStorage
-  UserStorage # ? level storage type ?
-
-var db: Table[StorageType, DbConn]
-
 proc path(storageType: StorageType): Path =
   case storageType
   of SystemStorage:
@@ -26,13 +20,13 @@
     Path(getDataDir()) / Path(AppName()) / STORAGE_NAME
 
 proc ensureExists(storageType: StorageType) =
-  if storageType in db:
+  if storageType in engine().db:
     return
-  db[storageType] = open(string(storageType.path), "", "", "")
+  engine().db[storageType] = open(string(storageType.path), "", "", "")
 
 proc ensureExists(storageType: StorageType, table: string) =
   storageType.ensureExists()
-  db[storageType].exec(
+  engine().db[storageType].exec(
     sql(
       &"""CREATE TABLE IF NOT EXISTS {table} (
     key TEXT NOT NULL UNIQUE,
@@ -48,7 +42,7 @@
     table = DEFAULT_KEY_VALUE_TABLE_NAME,
 ) =
   storageType.ensureExists(table)
-  db[storageType].exec(
+  engine().db[storageType].exec(
     sql(
       &"""INSERT INTO {table} VALUES(?, ?)
   ON CONFLICT(key) DO UPDATE SET value=excluded.value
@@ -65,8 +59,9 @@
     table = DEFAULT_KEY_VALUE_TABLE_NAME,
 ): T =
   storageType.ensureExists(table)
-  let dbResult =
-    db[storageType].getValue(sql(&"""SELECT value FROM {table} WHERE key = ? """), key)
+  let dbResult = engine().db[storageType].getValue(
+    sql(&"""SELECT value FROM {table} WHERE key = ? """), key
+  )
   if dbResult == "":
     return default
   return to[T](dbResult)
@@ -75,7 +70,7 @@
     storageType: StorageType, table = DEFAULT_KEY_VALUE_TABLE_NAME
 ): seq[string] =
   storageType.ensureExists(table)
-  for row in db[storageType].fastRows(sql(&"""SELECT key FROM {table}""")):
+  for row in engine().db[storageType].fastRows(sql(&"""SELECT key FROM {table}""")):
     result.add row[0]
 
 proc purge*(storageType: StorageType) =
--- a/semicongine/text.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/text.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -10,6 +10,7 @@
 import ./rendering
 import ./image
 import ./rendering/renderer
+import ./rendering/memory
 
 proc initTextBuffer*[MaxGlyphs: static int](
     font: Font[MaxGlyphs],
--- a/semicongine/text/font.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/semicongine/text/font.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -1,5 +1,6 @@
 import std/os
 import std/strutils
+import std/strformat
 import std/sequtils
 import std/unicode
 import std/streams
@@ -9,6 +10,7 @@
 import ../core
 import ../resources
 import ../rendering/renderer
+import ../rendering/memory
 import ../contrib/algorithms/texture_packing
 
 {.emit: "#define STBTT_STATIC".}
--- a/tests/test_audio.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/tests/test_audio.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -5,22 +5,22 @@
 import ../semicongine
 
 proc test1() =
-  mixer[].addSound("test1", sineSoundData(1000, 2, 44100))
-  mixer[].addSound("test2", sineSoundData(500, 2, 44100))
+  addSound("test1", sineSoundData(1000, 2, 44100))
+  addSound("test2", sineSoundData(500, 2, 44100))
 
-  let s1 = mixer[].play("test1", loop = true)
-  let s2 = mixer[].play("test2", loop = true)
+  let s1 = play("test1", loop = true)
+  let s2 = play("test2", loop = true)
 
   let t0 = now()
-  mixer[].setLevel(0.5)
+  setLevel(0.5)
   while true:
     let runtime = (now() - t0).inMilliseconds()
     if runtime > 1500:
-      mixer[].setLevel(0.2)
+      setLevel(0.2)
     if runtime > 3000:
-      mixer[].stop(s2)
+      stop(s2)
     if runtime > 6000:
-      mixer[].stop("")
+      stop("")
     if runtime > 8000:
       break
 
@@ -47,32 +47,32 @@
       bbShort, a, f, c2Short, d2Short, c2Short, bbShort, a, f, f, c, f, f, f, c, f, f,
     )
 
-  mixer[].addSound("frerejaques", frerejaquesData)
-  discard mixer[].play("frerejaques")
+  addSound("frerejaques", frerejaquesData)
+  discard play("frerejaques")
 
-  while mixer[].isPlaying():
+  while isPlaying():
     sleep(1)
 
 proc test3() =
-  mixer[].addSound("toccata et fugue", loadAudio("toccata_et_fugue.ogg"))
-  mixer[].addSound("ping", sineSoundData(500, 0.05, 44100))
-  mixer[].addTrack("effects")
-  discard mixer[].play("toccata et fugue")
+  addSound("toccata et fugue", loadAudio("toccata_et_fugue.ogg"))
+  addSound("ping", sineSoundData(500, 0.05, 44100))
+  addTrack("effects")
+  discard play("toccata et fugue")
 
 when isMainModule:
+  initEngine("Test audio")
   test1()
-  mixer[].stop()
+  stop()
   test2()
-  mixer[].stop()
+  stop()
   test3()
 
-  while mixer[].isPlaying():
+  while isPlaying():
     # on windows we re-open stdin and this will not work
     when defined(linux):
-      discard
-        mixer[].play("ping", track = "effects", stopOtherSounds = true, level = 0.5)
+      discard play("ping", track = "effects", stopOtherSounds = true, level = 0.5)
       echo "Press q and enter to exit"
       if stdin.readLine() == "q":
-        mixer[].stop()
+        stop()
     elif defined(windows):
       sleep(1000)
--- a/tests/test_gltf.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/tests/test_gltf.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -6,7 +6,7 @@
 
 import ../semicongine
 
-proc test_gltf(time: float32) =
+proc test_gltf(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -134,8 +134,7 @@
       renderdata.assignBuffers(primitive[0])
   renderdata.assignBuffers(descriptors)
 
-  var pipeline =
-    createPipeline(Shader(), renderPass = vulkan.swapchain.renderPass, cullMode = [])
+  var pipeline = createPipeline(Shader(), renderPass = renderPass, cullMode = [])
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], descriptors)
 
   renderdata.flushAllMemory()
@@ -202,11 +201,11 @@
 
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -220,13 +219,13 @@
             )
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
 when isMainModule:
   var time = 1000'f32
-  initVulkan()
+  initEngine("Test glTF")
 
   var renderpass = createDirectPresentationRenderPass(
     depthBuffer = true, samples = VK_SAMPLE_COUNT_4_BIT
@@ -236,10 +235,10 @@
   # showSystemCursor(false)
 
   # tests a simple triangle with minimalistic shader and vertex format
-  test_gltf(time)
+  test_gltf(time, renderpass)
 
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
-  vkDestroyRenderPass(vulkan.device, renderpass.vk, nil)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
+  destroyRenderPass(renderpass)
   clearSwapchain()
 
   destroyVulkan()
--- a/tests/test_rendering.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/tests/test_rendering.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -8,7 +8,7 @@
 
 import ../semicongine
 
-proc test_01_triangle(time: float32) =
+proc test_01_triangle(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -43,17 +43,17 @@
   assignBuffers(renderdata, mesh)
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(Shader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(Shader(), renderPass = renderPass)
 
   var start = getMonoTime()
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -67,11 +67,11 @@
           )
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_02_triangle_quad_instanced(time: float32) =
+proc test_02_triangle_quad_instanced(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -136,17 +136,17 @@
   assignBuffers(renderdata, instancesB)
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(SomeShader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(SomeShader(), renderPass = renderPass)
 
   var start = getMonoTime()
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -176,11 +176,11 @@
           )
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_03_simple_descriptorset(time: float32) =
+proc test_03_simple_descriptorset(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -251,7 +251,7 @@
   uploadImages(renderdata, uniforms2)
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(QuadShader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(QuadShader(), renderPass = renderPass)
 
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms1)
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms2)
@@ -260,11 +260,11 @@
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -275,11 +275,11 @@
           render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_04_multiple_descriptorsets(time: float32) =
+proc test_04_multiple_descriptorsets(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -390,7 +390,7 @@
   uploadImages(renderdata, mainset)
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(QuadShader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(QuadShader(), renderPass = renderPass)
 
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], constset)
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[1], mainset)
@@ -404,11 +404,11 @@
       bindDescriptorSet(commandbuffer, mainset, 1, pipeline)
 
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -427,11 +427,11 @@
     renderdata.flushAllMemory()
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_05_cube(time: float32) =
+proc test_05_cube(time: float32, renderPass: RenderPass) =
   type
     UniformData = object
       mvp: Mat4
@@ -535,7 +535,7 @@
 
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(CubeShader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(CubeShader(), renderPass = renderPass)
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms1)
 
   var tStart = getMonoTime()
@@ -555,11 +555,11 @@
 
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -574,11 +574,11 @@
       sleep((waitTime / 1000).int)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_06_different_draw_modes(time: float32) =
+proc test_06_different_draw_modes(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -622,36 +622,32 @@
 
   var pipeline1 = createPipeline(
     Shader(),
-    renderPass = vulkan.swapchain.renderPass,
+    renderPass = renderPass,
     polygonMode = VK_POLYGON_MODE_LINE,
     lineWidth = 20'f32,
   )
   var pipeline2 = createPipeline(
-    Shader(),
-    renderPass = vulkan.swapchain.renderPass,
-    polygonMode = VK_POLYGON_MODE_POINT,
+    Shader(), renderPass = renderPass, polygonMode = VK_POLYGON_MODE_POINT
   )
   var pipeline3 = createPipeline(
     Shader(),
-    renderPass = vulkan.swapchain.renderPass,
+    renderPass = renderPass,
     topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
     lineWidth = 5,
   )
   var pipeline4 = createPipeline(
-    Shader(),
-    renderPass = vulkan.swapchain.renderPass,
-    topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
+    Shader(), renderPass = renderPass, topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST
   )
 
   var start = getMonoTime()
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline1):
@@ -664,14 +660,14 @@
           render(commandbuffer = commandbuffer, pipeline = pipeline4, mesh = lines)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline1)
   destroyPipeline(pipeline2)
   destroyPipeline(pipeline3)
   destroyPipeline(pipeline4)
   destroyRenderData(renderdata)
 
-proc test_08_texture_array(time: float32) =
+proc test_08_texture_array(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -724,7 +720,7 @@
   assignBuffers(renderdata, mesh)
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(Shader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(Shader(), renderPass = renderPass)
   var uniforms1 = asDescriptorSetData(
     Uniforms(textures: loadImageArray[BGRA](["art.png", "art1.png"]))
   )
@@ -735,11 +731,11 @@
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -747,11 +743,11 @@
           render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_07_png_texture(time: float32) =
+proc test_07_png_texture(time: float32, renderPass: RenderPass) =
   var renderdata = initRenderData()
 
   type
@@ -801,7 +797,7 @@
   assignBuffers(renderdata, mesh)
   renderdata.flushAllMemory()
 
-  var pipeline = createPipeline(Shader(), renderPass = vulkan.swapchain.renderPass)
+  var pipeline = createPipeline(Shader(), renderPass = renderPass)
   var uniforms1 = asDescriptorSetData(Uniforms(texture1: loadImage[BGRA]("art.png")))
   uploadImages(renderdata, uniforms1)
   initDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms1)
@@ -810,11 +806,11 @@
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -822,7 +818,7 @@
           render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
@@ -898,11 +894,8 @@
   )
   var uniforms1 = asDescriptorSetData(
     Uniforms(
-      frameTexture: Image[BGRA](
-        width: vulkan.swapchain.width,
-        height: vulkan.swapchain.height,
-        isRenderTarget: true,
-      )
+      frameTexture:
+        Image[BGRA](width: frameWidth(), height: frameHeight(), isRenderTarget: true)
     )
   )
   assignBuffers(renderdata, mesh)
@@ -923,8 +916,8 @@
     depthMemory: VkDeviceMemory
   if offscreenRP.depthBuffer:
     depthImage = svkCreate2DImage(
-      width = vulkan.swapchain.width,
-      height = vulkan.swapchain.height,
+      width = frameWidth(),
+      height = frameHeight(),
       format = DEPTH_FORMAT,
       usage = [VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT],
       samples = offscreenRP.samples,
@@ -933,7 +926,7 @@
     depthMemory = svkAllocateMemory(
       requirements.size, bestMemory(mappable = false, filter = requirements.memoryTypes)
     )
-    checkVkResult vkBindImageMemory(vulkan.device, depthImage, depthMemory, 0)
+    checkVkResult vkBindImageMemory(engine().vulkan.device, depthImage, depthMemory, 0)
     depthImageView = svkCreate2DImageView(
       image = depthImage, format = DEPTH_FORMAT, aspect = VK_IMAGE_ASPECT_DEPTH_BIT
     )
@@ -945,8 +938,8 @@
     msaaMemory: VkDeviceMemory
   if offscreenRP.samples != VK_SAMPLE_COUNT_1_BIT:
     msaaImage = svkCreate2DImage(
-      width = vulkan.swapchain.width,
-      height = vulkan.swapchain.height,
+      width = frameWidth(),
+      height = frameHeight(),
       format = SURFACE_FORMAT,
       usage = [VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT],
       samples = offscreenRP.samples,
@@ -955,7 +948,7 @@
     msaaMemory = svkAllocateMemory(
       requirements.size, bestMemory(mappable = false, filter = requirements.memoryTypes)
     )
-    checkVkResult vkBindImageMemory(vulkan.device, msaaImage, msaaMemory, 0)
+    checkVkResult vkBindImageMemory(engine().vulkan.device, msaaImage, msaaMemory, 0)
     msaaImageView = svkCreate2DImageView(image = msaaImage, format = SURFACE_FORMAT)
 
   var attachments: seq[VkImageView]
@@ -970,9 +963,8 @@
         @[msaaImageView, depthImageView, uniforms1.data.frameTexture.imageview]
     else:
       attachments = @[msaaImageView, uniforms1.data.frameTexture.imageview]
-  var offscreenFB = svkCreateFramebuffer(
-    offscreenRP.vk, vulkan.swapchain.width, vulkan.swapchain.height, attachments
-  )
+  var offscreenFB =
+    svkCreateFramebuffer(offscreenRP.vk, frameWidth(), frameHeight(), attachments)
 
   var start = getMonoTime()
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
@@ -981,8 +973,8 @@
         offscreenRP,
         offscreenFB,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, drawPipeline):
@@ -992,8 +984,8 @@
         presentRP,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, presentPipeline):
@@ -1001,28 +993,27 @@
           render(commandbuffer = commandbuffer, pipeline = presentPipeline, mesh = quad)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(presentPipeline)
   destroyPipeline(drawPipeline)
   destroyRenderData(renderdata)
   if depthImage.Valid:
-    vkDestroyImageView(vulkan.device, depthImageView, nil)
-    vkDestroyImage(vulkan.device, depthImage, nil)
-    vkFreeMemory(vulkan.device, depthMemory, nil)
+    vkDestroyImageView(engine().vulkan.device, depthImageView, nil)
+    vkDestroyImage(engine().vulkan.device, depthImage, nil)
+    vkFreeMemory(engine().vulkan.device, depthMemory, nil)
   if msaaImage.Valid:
-    vkDestroyImageView(vulkan.device, msaaImageView, nil)
-    vkDestroyImage(vulkan.device, msaaImage, nil)
-    vkFreeMemory(vulkan.device, msaaMemory, nil)
+    vkDestroyImageView(engine().vulkan.device, msaaImageView, nil)
+    vkDestroyImage(engine().vulkan.device, msaaImage, nil)
+    vkFreeMemory(engine().vulkan.device, msaaMemory, nil)
   destroyRenderPass(offscreenRP)
   destroyRenderPass(presentRP)
-  vkDestroyFramebuffer(vulkan.device, offscreenFB, nil)
+  vkDestroyFramebuffer(engine().vulkan.device, offscreenFB, nil)
   clearSwapchain()
 
 when isMainModule:
   var time = 1'f32
-  initVulkan()
+  initEngine("Test rendering")
 
-  var mainRenderpass: RenderPass
   var renderPasses = [
     (depthBuffer: false, samples: VK_SAMPLE_COUNT_1_BIT),
     (depthBuffer: false, samples: VK_SAMPLE_COUNT_4_BIT),
@@ -1037,28 +1028,28 @@
     setupSwapchain(renderpass = renderpass)
 
     # tests a simple triangle with minimalistic shader and vertex format
-    test_01_triangle(time)
+    test_01_triangle(time, renderpass)
 
     # tests instanced triangles and quads, mixing meshes and instances
-    test_02_triangle_quad_instanced(time)
+    test_02_triangle_quad_instanced(time, renderpass)
 
     # teste descriptor sets
-    test_03_simple_descriptorset(time)
+    test_03_simple_descriptorset(time, renderpass)
 
     # tests multiple descriptor sets and arrays
-    test_04_multiple_descriptorsets(time)
+    test_04_multiple_descriptorsets(time, renderpass)
 
     # rotating cube
-    test_05_cube(time)
+    test_05_cube(time, renderpass)
 
     # different draw modes (lines, points, and topologies)
-    test_06_different_draw_modes(time)
+    test_06_different_draw_modes(time, renderpass)
 
-    test_07_png_texture(time)
+    test_07_png_texture(time, renderpass)
 
-    test_08_texture_array(time)
+    test_08_texture_array(time, renderpass)
 
-    checkVkResult vkDeviceWaitIdle(vulkan.device)
+    checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
     destroyRenderPass(renderpass)
     clearSwapchain()
 
--- a/tests/test_storage.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/tests/test_storage.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -20,6 +20,7 @@
     assert storage.load(key, 0) == i
 
 proc main() =
+  initEngine("Test storage")
   SystemStorage.purge()
   echo "SystemStorage: Testing simple store/load"
   SystemStorage.testSimple()
--- a/tests/test_text.nim	Thu Jan 09 01:03:06 2025 +0700
+++ b/tests/test_text.nim	Thu Jan 09 23:03:47 2025 +0700
@@ -15,12 +15,10 @@
 const FONTNAME = "Overhaul.ttf"
 # const FONTNAME = "DejaVuSans.ttf"
 
-proc test_01_static_label(time: float32) =
+proc test_01_static_label(time: float32, renderPass: RenderPass) =
   var font = loadFont[MAX_CODEPOINTS](FONTNAME, lineHeightPixels = 200)
   var renderdata = initRenderData()
-  var pipeline = createPipeline(
-    GlyphShader[MAX_CODEPOINTS](), renderPass = vulkan.swapchain.renderPass
-  )
+  var pipeline = createPipeline(GlyphShader[MAX_CODEPOINTS](), renderPass = renderPass)
   var textbuffer = font.initTextBuffer(1000, renderdata, baseScale = 0.1)
 
   font.upload(renderdata)
@@ -29,11 +27,11 @@
   # small drop-shadow
   discard textbuffer.add(
     "Hello semicongine!",
-    vec3(0.009, -0.009 * getAspectRatio(), 0.002),
+    vec3(0.009, -0.009 * getAspectRatio(), 0.2),
     color = vec4(0.02, 0.02, 0.02, 1),
     scale = 1.01,
   )
-  discard textbuffer.add("Hello semicongine!", vec3(0, 0, 0))
+  discard textbuffer.add("Hello semicongine!", vec3(0, 0, 0.1))
 
   var start = getMonoTime()
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
@@ -44,30 +42,28 @@
     withNextFrame(framebuffer, commandbuffer):
       font.bindTo(pipeline, commandbuffer)
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
           renderTextBuffer(commandbuffer, pipeline, textbuffer)
 
   # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_02_multi_counter(time: float32) =
+proc test_02_multi_counter(time: float32, renderPass: RenderPass) =
   var font1 = loadFont[MAX_CODEPOINTS]("Overhaul.ttf", lineHeightPixels = 40)
   var font2 = loadFont[MAX_CODEPOINTS]("Overhaul.ttf", lineHeightPixels = 160)
   var font3 = loadFont[MAX_CODEPOINTS]("DejaVuSans.ttf", lineHeightPixels = 160)
   var renderdata = initRenderData()
 
-  var pipeline = createPipeline(
-    GlyphShader[MAX_CODEPOINTS](), renderPass = vulkan.swapchain.renderPass
-  )
+  var pipeline = createPipeline(GlyphShader[MAX_CODEPOINTS](), renderPass = renderPass)
 
   font1.upload(renderdata)
   font2.upload(renderdata)
@@ -101,11 +97,11 @@
 
     withNextFrame(framebuffer, commandbuffer):
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
@@ -117,17 +113,15 @@
           renderTextBuffer(commandbuffer, pipeline, textbuffer3)
 
       # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_03_layouting(time: float32) =
+proc test_03_layouting(time: float32, renderPass: RenderPass) =
   var font = loadFont[MAX_CODEPOINTS]("DejaVuSans.ttf", lineHeightPixels = 160)
   var renderdata = initRenderData()
 
-  var pipeline = createPipeline(
-    GlyphShader[MAX_CODEPOINTS](), renderPass = vulkan.swapchain.renderPass
-  )
+  var pipeline = createPipeline(GlyphShader[MAX_CODEPOINTS](), renderPass = renderPass)
 
   font.upload(renderdata)
   font.addToPipeline(renderdata, pipeline)
@@ -159,28 +153,26 @@
     withNextFrame(framebuffer, commandbuffer):
       bindDescriptorSet(commandbuffer, font.descriptorSet, 3, pipeline)
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
           renderTextBuffer(commandbuffer, pipeline, textbuffer)
 
       # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
-proc test_04_lots_of_texts(time: float32) =
+proc test_04_lots_of_texts(time: float32, renderPass: RenderPass) =
   var font = loadFont[MAX_CODEPOINTS]("DejaVuSans.ttf", lineHeightPixels = 160)
   var renderdata = initRenderData()
 
-  var pipeline = createPipeline(
-    GlyphShader[MAX_CODEPOINTS](), renderPass = vulkan.swapchain.renderPass
-  )
+  var pipeline = createPipeline(GlyphShader[MAX_CODEPOINTS](), renderPass = renderPass)
 
   font.upload(renderdata)
   font.addToPipeline(renderdata, pipeline)
@@ -190,7 +182,7 @@
   for i in 0 ..< 1000:
     discard textbuffer.add(
       $i,
-      vec3(rand(-0.8 .. 0.8), rand(-0.8 .. 0.8), rand(-0.1 .. 0.1)),
+      vec3(rand(-0.8 .. 0.8), rand(-0.8 .. 0.8), rand(0.1 .. 0.2)),
       color =
         vec4(rand(0.5 .. 1.0), rand(0.5 .. 1.0), rand(0.5 .. 1.0), rand(0.5 .. 1.0)),
       scale = rand(0.5'f32 .. 1.5'f32),
@@ -207,37 +199,37 @@
         textbuffer.refresh()
       bindDescriptorSet(commandbuffer, font.descriptorSet, 3, pipeline)
       withRenderPass(
-        vulkan.swapchain.renderPass,
+        renderPass,
         framebuffer,
         commandbuffer,
-        vulkan.swapchain.width,
-        vulkan.swapchain.height,
+        frameWidth(),
+        frameHeight(),
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
           renderTextBuffer(commandbuffer, pipeline, textbuffer)
 
         # cleanup
-  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
   destroyPipeline(pipeline)
   destroyRenderData(renderdata)
 
 when isMainModule:
   var time = 3'f32
-  initVulkan()
+  initEngine("Test text")
 
-  for depthBuffer in [true, false]:
-    var renderpass = createDirectPresentationRenderPass(depthBuffer = depthBuffer)
-    setupSwapchain(renderpass = renderpass)
+  # for depthBuffer in [true, false]:
+  var renderpass = createDirectPresentationRenderPass(depthBuffer = true)
+  setupSwapchain(renderpass = renderpass)
 
-    # tests a simple triangle with minimalistic shader and vertex format
-    test_01_static_label(time)
-    test_02_multi_counter(time)
-    test_03_layouting(time)
-    test_04_lots_of_texts(time)
+  # tests a simple triangle with minimalistic shader and vertex format
+  test_01_static_label(time, renderpass)
+  test_02_multi_counter(time, renderpass)
+  test_03_layouting(time, renderpass)
+  test_04_lots_of_texts(time, renderpass)
 
-    checkVkResult vkDeviceWaitIdle(vulkan.device)
-    vkDestroyRenderPass(vulkan.device, renderpass.vk, nil)
-    clearSwapchain()
+  checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
+  destroyRenderPass(renderpass)
+  clearSwapchain()
 
   destroyVulkan()