changeset 1420:6f81a41603d9

did: start working on big restructuring
author sam <sam@basx.dev>
date Thu, 09 Jan 2025 01:00:58 +0700
parents b411735768fd
children ec7ace2e40d5
files .editorconfig semicongine.nim semicongine/audio.nim semicongine/audio/generators.nim semicongine/audio/mixer_module.nim semicongine/audio/platform/linux.nim semicongine/audio/platform/windows.nim semicongine/audio/resources.nim semicongine/build.nim semicongine/contrib/algorithms/texture_packing.nim semicongine/core.nim semicongine/core/buildconfig.nim semicongine/core/constants.nim semicongine/core/globals.nim semicongine/core/matrix.nim semicongine/core/types.nim semicongine/events.nim semicongine/gltf.nim semicongine/image.nim semicongine/input.nim semicongine/platform/linux/types.nim semicongine/platform/windows/types.nim semicongine/rendering.nim semicongine/rendering/platform/linux.nim semicongine/rendering/platform/windows.nim semicongine/rendering/renderer.nim semicongine/rendering/renderpasses.nim semicongine/rendering/shaders.nim semicongine/rendering/swapchain.nim semicongine/rendering/vulkan/api.nim semicongine/rendering/vulkan_wrappers.nim semicongine/resources.nim semicongine/text.nim semicongine/text/font.nim semicongine/thirdparty/parsetoml.nim
diffstat 35 files changed, 841 insertions(+), 771 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.editorconfig	Thu Jan 09 01:00:58 2025 +0700
@@ -0,0 +1,2 @@
+[*.nim]
+autocommit = false
--- a/semicongine.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -13,12 +13,8 @@
 import ./semicongine/image
 export image
 
-import ./semicongine/events
 import ./semicongine/rendering
-import ./semicongine/rendering/vulkan/api
-export events
 export rendering
-export api
 
 import ./semicongine/storage
 import ./semicongine/input
@@ -46,3 +42,15 @@
   export texture_packing
   export collision
   export noise
+
+#### Main engine object
+
+proc initEngine*(appName: string) =
+  engine_obj_internal = Engine()
+  engine_obj_internal.vulkan = initVulkan(appName)
+
+  # start audio
+  engine_obj_internal.mixer = createShared(Mixer)
+  engine_obj_internal.mixer[] = initMixer()
+  engine_obj_internal.audiothread.createThread(audioWorker, engine_obj_internal.mixer)
+  engine_obj_internal.initialized = true
--- a/semicongine/audio.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/audio.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -6,5 +6,3 @@
 
 import ./audio/resources
 export resources
-
-startMixerThread()
--- a/semicongine/audio/generators.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/audio/generators.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,6 +1,6 @@
 import std/math
 
-import ./mixer_module
+import ../core
 
 proc sinewave(f: float): proc(x: float): float =
   proc ret(x: float): float =
--- a/semicongine/audio/mixer_module.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/audio/mixer_module.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -5,10 +5,9 @@
 import std/monotimes
 import std/strformat
 import std/tables
-import std/logging
 import std/times
 
-import ../core/globals
+import ../core
 
 const NBUFFERS = 32
 # it seems that some alsa hardware has a problem with smaller buffers than 512
@@ -17,48 +16,12 @@
 else:
   const BUFFERSAMPLECOUNT = 256
 
-type
-  Level* = 0'f .. 1'f
-  Sample* = array[2, int16]
-  SoundData* = seq[Sample]
-
-  Playback = object
-    sound: SoundData
-    position: int
-    loop: bool
-    levelLeft: Level
-    levelRight: Level
-    paused: bool
-
-  Track = object
-    playing: Table[uint64, Playback]
-    level: Level
-    targetLevel: Level
-    fadeTime: float
-    fadeStep: float
-
-proc `=copy`(dest: var Playback, source: Playback) {.error.}
-proc `=copy`(dest: var Track, source: Track) {.error.}
-
 when defined(windows):
   include ./platform/windows
 when defined(linux):
   include ./platform/linux
 
-type Mixer* = object
-  playbackCounter: uint64
-  tracks: Table[string, Track]
-  sounds*: Table[string, SoundData]
-  level: Level
-  device: NativeSoundDevice
-  lock: Lock
-  buffers: seq[SoundData]
-  currentBuffer: int
-  lastUpdate: MonoTime
-
-proc `=copy`(dest: var Mixer, source: Mixer) {.error.}
-
-proc initMixer(): Mixer =
+proc initMixer*(): Mixer =
   result = Mixer(tracks: initTable[string, Track](), level: 1'f)
   result.tracks[""] = Track(level: 1)
   result.lock.initLock()
@@ -82,7 +45,7 @@
     warn "sound with name '", name, "' was already loaded, overwriting"
   mixer.sounds[name] = sound
 
-proc addTrack*(mixer: var Mixer, name: string, level: Level = 1'f) =
+proc addTrack*(mixer: var Mixer, name: string, level: AudioLevel = 1'f) =
   if name in mixer.tracks:
     warn "track with name '", name, "' was already loaded, overwriting"
   mixer.lock.withLock:
@@ -94,7 +57,7 @@
     track = "",
     stopOtherSounds = false,
     loop = false,
-    levelLeft, levelRight: Level,
+    levelLeft, levelRight: AudioLevel,
 ): uint64 =
   assert track in mixer.tracks, &"Track '{track}' does not exists"
   assert soundName in mixer.sounds, soundName & " not loaded"
@@ -118,7 +81,7 @@
     track = "",
     stopOtherSounds = false,
     loop = false,
-    level: Level = 1'f,
+    level: AudioLevel = 1'f,
 ): uint64 =
   play(
     mixer = mixer,
@@ -135,32 +98,34 @@
     for track in mixer.tracks.mvalues:
       track.playing.clear()
 
-proc getLevel*(mixer: var Mixer): Level =
+proc getLevel*(mixer: var Mixer): AudioLevel =
   mixer.level
 
-proc getLevel*(mixer: var Mixer, track: string): Level =
+proc getLevel*(mixer: var Mixer, track: string): AudioLevel =
   mixer.tracks[track].level
 
-proc getLevel*(mixer: var Mixer, playbackId: uint64): (Level, Level) =
+proc getLevel*(mixer: var Mixer, playbackId: uint64): (AudioLevel, AudioLevel) =
   for track in mixer.tracks.mvalues:
     if playbackId in track.playing:
       return (track.playing[playbackId].levelLeft, track.playing[playbackId].levelRight)
 
-proc setLevel*(mixer: var Mixer, level: Level) =
+proc setLevel*(mixer: var Mixer, level: AudioLevel) =
   mixer.level = level
 
-proc setLevel*(mixer: var Mixer, track: string, level: Level) =
+proc setLevel*(mixer: var Mixer, track: string, level: AudioLevel) =
   mixer.lock.withLock:
     mixer.tracks[track].level = level
 
-proc setLevel*(mixer: var Mixer, playbackId: uint64, levelLeft, levelRight: Level) =
+proc setLevel*(
+    mixer: var Mixer, playbackId: uint64, levelLeft, levelRight: AudioLevel
+) =
   mixer.lock.withLock:
     for track in 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: Level) =
+proc setLevel*(mixer: var Mixer, playbackId: uint64, level: AudioLevel) =
   setLevel(mixer, playbackId, level, level)
 
 proc stop*(mixer: var Mixer, track: string) =
@@ -210,7 +175,7 @@
 proc unpause*(mixer: var Mixer, playbackId: uint64) =
   mixer.pause(playbackId, false)
 
-proc fadeTo*(mixer: var Mixer, track: string, level: Level, time: float) =
+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
@@ -231,7 +196,7 @@
           return true
     return false
 
-func applyLevel(sample: Sample, levelLeft, levelRight: Level): Sample =
+func applyLevel(sample: Sample, levelLeft, levelRight: AudioLevel): Sample =
   [int16(float(sample[0]) * levelLeft), int16(float(sample[1]) * levelRight)]
 
 func clip(value: int32): int16 =
@@ -241,7 +206,7 @@
 func mix(a, b: Sample): Sample =
   [clip(int32(a[0]) + int32(b[0])), clip(int32(a[1]) + int32(b[1]))]
 
-proc updateSoundBuffer(mixer: var Mixer) =
+proc updateSoundBuffer*(mixer: var Mixer) =
   let t = getMonoTime()
 
   let dt = (t - mixer.lastUpdate).inNanoseconds.float64 / 1_000_000_000'f64
@@ -251,8 +216,9 @@
   for track in mixer.tracks.mvalues:
     if track.fadeTime > 0:
       track.fadeTime -= dt
-      track.level =
-        (track.level.float64 + track.fadeStep.float64 * dt).clamp(Level.low, Level.high)
+      track.level = (track.level.float64 + track.fadeStep.float64 * dt).clamp(
+        AudioLevel.low, AudioLevel.high
+      )
       if track.fadeTime <= 0:
         track.level = track.targetLevel
   # mix
@@ -327,11 +293,7 @@
   mixer.lock.deinitLock()
   mixer.device.CloseSoundDevice()
 
-var
-  mixer* = createShared(Mixer)
-  audiothread: Thread[void]
-
-proc audioWorker() {.thread.} =
+proc audioWorker*(mixer: ptr Mixer) {.thread.} =
   mixer[].setupDevice()
   onThreadDestruction(
     proc() =
@@ -340,14 +302,3 @@
   )
   while true:
     mixer[].updateSoundBuffer()
-
-# for thread priority (really necessary?)
-when defined(windows):
-  import ../thirdparty/winim/winim/inc/winbase
-when defined(linux):
-  import std/posix
-
-proc startMixerThread*() =
-  mixer[] = initMixer()
-  audiothread.createThread(audioWorker)
-  debug "Created audio thread"
--- a/semicongine/audio/platform/linux.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/audio/platform/linux.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,23 +1,3 @@
-# alsa API
-type
-  OpenMode* {.size: sizeof(culong).} = enum
-    SND_PCM_BLOCK = 0x00000000 # added by semicongine, for clarity
-    SND_PCM_NONBLOCK = 0x00000001
-
-  StreamMode* {.size: sizeof(cint).} = enum
-    SND_PCM_STREAM_PLAYBACK = 0
-
-  AccessMode* {.size: sizeof(cint).} = enum
-    SND_PCM_ACCESS_RW_INTERLEAVED = 3
-
-  PCMFormat* {.size: sizeof(cint).} = enum
-    SND_PCM_FORMAT_S16_LE = 2
-
-  snd_pcm_p* = ptr object
-  snd_pcm_hw_params_p* = ptr object
-  snd_pcm_uframes_t* = culong
-  snd_pcm_sframes_t* = clong
-
 {.pragma: alsafunc, importc, cdecl, dynlib: "libasound.so.2".}
 proc snd_pcm_open*(
   pcm_ref: ptr snd_pcm_p, name: cstring, streamMode: StreamMode, openmode: OpenMode
@@ -68,10 +48,6 @@
 
 # required for engine:
 
-type NativeSoundDevice* = object
-  handle: snd_pcm_p
-  buffers: seq[ptr SoundData]
-
 proc OpenSoundDevice*(
     sampleRate: uint32, buffers: seq[ptr SoundData]
 ): NativeSoundDevice =
--- a/semicongine/audio/platform/windows.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/audio/platform/windows.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -2,8 +2,6 @@
 
 import ../../thirdparty/winim/winim/inc/[mmsystem, windef]
 
-# import ../../thirdparty/winim/winim/inc/[windef, winuser, wincon, winbase]
-
 template CheckWinMMResult*(call: untyped) =
   let value = call
   if value < 0:
@@ -11,10 +9,6 @@
       Exception, "Windows multimedia error: " & astToStr(call) & " returned " & $value
     )
 
-type NativeSoundDevice* = object
-  handle: HWAVEOUT
-  buffers: seq[WAVEHDR]
-
 proc OpenSoundDevice*(
     sampleRate: uint32, buffers: seq[ptr SoundData]
 ): NativeSoundDevice =
--- a/semicongine/audio/resources.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/audio/resources.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -2,12 +2,8 @@
 import std/os
 import std/streams
 import std/strformat
-import std/strutils
 
 import ../core
-import ../resources
-
-import ./mixer_module
 
 type
   Encoding {.size: sizeof(uint32).} = enum
--- a/semicongine/build.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/build.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -4,7 +4,7 @@
 import std/os
 import std/strutils
 
-include ./core/globals
+include ./core/constants
 
 const BLENDER_CONVERT_SCRIPT = currentSourcePath().parentDir().parentDir().joinPath(
     "tools/blender_gltf_converter.py"
--- a/semicongine/contrib/algorithms/texture_packing.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/contrib/algorithms/texture_packing.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,6 +1,7 @@
 import std/algorithm
 import std/strformat
 
+import ../../core
 import ../../image
 
 type Rect = tuple[i: int, x, y, w, h: uint32]
--- a/semicongine/core.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/core.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,4 +1,6 @@
+import std/dynlib
 import std/hashes
+import std/locks
 import std/macros
 import std/math
 import std/monotimes
@@ -8,10 +10,22 @@
 import std/strformat
 import std/tables
 import std/times
+import std/unicode
 import std/typetraits
 
+import std/logging
+
+include ./rendering/vulkan/api
+
 include ./core/utils
 include ./core/buildconfig
 include ./core/vector
 include ./core/matrix
-include ./core/globals
+include ./core/constants
+include ./core/types
+
+var engine_obj_internal*: Engine
+
+proc engine*(): Engine =
+  assert engine_obj_internal.initialized, "Engine has not been initialized yet"
+  return engine_obj_internal
--- a/semicongine/core/buildconfig.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/core/buildconfig.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,5 +1,3 @@
-import std/logging
-
 const ENGINENAME = "semiconginev2"
 
 # checks required build options:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/core/constants.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -0,0 +1,2 @@
+const RESOURCEROOT* = "resources"
+const AUDIO_SAMPLE_RATE* = 44100
--- a/semicongine/core/globals.nim	Wed Jan 01 19:36:55 2025 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-const RESOURCEROOT* = "resources"
-const AUDIO_SAMPLE_RATE* = 44100
--- a/semicongine/core/matrix.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/core/matrix.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -545,7 +545,7 @@
     ]
   )
 
-func asMat3(m: Mat4): auto =
+func asMat3*(m: Mat4): auto =
   Mat3(
     data:
       [m[0, 0], m[0, 1], m[0, 2], m[1, 0], m[1, 1], m[1, 2], m[2, 0], m[2, 1], m[2, 2]]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/core/types.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -0,0 +1,459 @@
+const
+  INFLIGHTFRAMES* = 2'u32
+  MAX_DESCRIPTORSETS* = 4
+  BUFFER_ALIGNMENT* = 64'u64 # align offsets inside buffers along this alignment
+  # ca. 100mb per block, seems reasonable
+  MEMORY_BLOCK_ALLOCATION_SIZE* = 100_000_000'u64
+  # ca. 9mb per block, seems reasonable, can put 10 buffers into one memory block
+  BUFFER_ALLOCATION_SIZE* = 9_000_000'u64
+  SURFACE_FORMAT* = VK_FORMAT_B8G8R8A8_SRGB
+  DEPTH_FORMAT* = VK_FORMAT_D32_SFLOAT
+  PUSH_CONSTANT_SIZE* = 128
+
+# some declaration needed for platform-types
+type
+  AudioLevel* = 0'f .. 1'f
+  Sample* = array[2, int16]
+  SoundData* = seq[Sample]
+  DescriptorSetIndex* = range[0 .. MAX_DESCRIPTORSETS - 1]
+
+# custom pragmas to classify shader attributes
+template VertexAttribute*() {.pragma.}
+template InstanceAttribute*() {.pragma.}
+template PushConstant*() {.pragma.}
+template Pass*() {.pragma.}
+template PassFlat*() {.pragma.}
+template ShaderOutput*() {.pragma.}
+template DescriptorSet*(index: DescriptorSetIndex) {.pragma.}
+
+when defined(windows):
+  include ../platform/windows/types
+when defined(linux):
+  include ../platform/linux/types
+
+type
+  # === rendering ===
+  SupportedGPUType* =
+    float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
+    TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] |
+    TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] |
+    TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] |
+    TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] |
+    TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] |
+    TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[
+      float32
+    ] | TMat43[float64] | TMat4[float32] | TMat4[float64]
+
+  VulkanObject* = object # populated through InitVulkan proc
+    instance*: VkInstance
+    device*: VkDevice
+    physicalDevice*: VkPhysicalDevice
+    surface*: VkSurfaceKHR
+    window*: NativeWindow
+    graphicsQueueFamily*: uint32
+    graphicsQueue*: VkQueue
+    debugMessenger*: VkDebugUtilsMessengerEXT
+    # populated through the initSwapchain proc
+    swapchain*: Swapchain
+    # unclear as of yet
+    anisotropy*: float32 = 0 # needs to be enable during device creation
+    fullscreen_internal*: bool
+
+  RenderPass* = ref object
+    vk*: VkRenderPass
+    samples*: VkSampleCountFlagBits
+    depthBuffer*: bool
+
+  Swapchain* = ref object
+    # parameters to initSwapchain, required for swapchain recreation
+    renderPass*: RenderPass
+    vSync*: bool
+    tripleBuffering*: bool
+    # populated through initSwapchain proc
+    vk*: VkSwapchainKHR
+    width*: uint32
+    height*: uint32
+    framebuffers*: seq[VkFramebuffer]
+    framebufferViews*: seq[VkImageView]
+    currentFramebufferIndex*: uint32
+    commandBufferPool*: VkCommandPool
+    # depth buffer stuff, if enabled
+    depthImage*: VkImage
+    depthImageView*: VkImageView
+    depthMemory*: VkDeviceMemory
+    # MSAA stuff, if enabled
+    msaaImage*: VkImage
+    msaaImageView*: VkImageView
+    msaaMemory*: VkDeviceMemory
+    # frame-in-flight handling
+    currentFiF*: range[0 .. (INFLIGHTFRAMES - 1).int]
+    queueFinishedFence*: array[INFLIGHTFRAMES.int, VkFence]
+    imageAvailableSemaphore*: array[INFLIGHTFRAMES.int, VkSemaphore]
+    renderFinishedSemaphore*: array[INFLIGHTFRAMES.int, VkSemaphore]
+    commandBuffers*: array[INFLIGHTFRAMES.int, VkCommandBuffer]
+    oldSwapchain*: Swapchain
+    oldSwapchainCounter*: int # swaps until old swapchain will be destroyed
+
+  # shader related types
+  DescriptorSetData*[T: object] = object
+    data*: T
+    vk*: array[INFLIGHTFRAMES.int, VkDescriptorSet]
+
+  Pipeline*[TShader] = object
+    vk*: VkPipeline
+    vertexShaderModule*: VkShaderModule
+    fragmentShaderModule*: VkShaderModule
+    layout*: VkPipelineLayout
+    descriptorSetLayouts*: array[MAX_DESCRIPTORSETS, VkDescriptorSetLayout]
+
+  # memory/buffer related types
+  BufferType* = enum
+    VertexBuffer
+    VertexBufferMapped
+    IndexBuffer
+    IndexBufferMapped
+    UniformBuffer
+    UniformBufferMapped
+    StorageBuffer
+    StorageBufferMapped
+
+  MemoryBlock* = object
+    vk*: VkDeviceMemory
+    size*: uint64
+    rawPointer*: pointer # if not nil, this is mapped memory
+    offsetNextFree*: uint64
+
+  Buffer* = object
+    vk*: VkBuffer
+    size*: uint64
+    rawPointer*: pointer # if not nil, buffer is using mapped memory
+    offsetNextFree*: uint64
+    memoryOffset*: uint64
+    memory*: VkDeviceMemory
+
+  GPUArray*[T: SupportedGPUType, TBuffer: static BufferType] = object
+    # TODO: when using mapped buffer memory, directly write values to mapped location
+    # instead of using data as buffer
+    data*: seq[T]
+    buffer*: Buffer
+    offset*: uint64
+
+  GPUValue*[T: object, TBuffer: static BufferType] = object
+    data*: T
+    buffer*: Buffer
+    offset*: uint64
+
+  GPUData* = GPUArray | GPUValue
+
+  RenderDataObject = object
+    descriptorPool*: VkDescriptorPool
+    memory*: array[VK_MAX_MEMORY_TYPES.int, seq[MemoryBlock]]
+    buffers*: array[BufferType, seq[Buffer]]
+    images*: seq[VkImage]
+    imageViews*: seq[VkImageView]
+    samplers*: seq[VkSampler]
+
+  RenderData* = ref RenderDataObject
+
+  # === audio ===
+  Playback* = object
+    sound*: SoundData
+    position*: int
+    loop*: bool
+    levelLeft*: AudioLevel
+    levelRight*: AudioLevel
+    paused*: bool
+
+  Track* = object
+    playing*: Table[uint64, Playback]
+    level*: AudioLevel
+    targetLevel*: AudioLevel
+    fadeTime*: float
+    fadeStep*: float
+
+  Mixer* = object
+    playbackCounter*: uint64
+    tracks*: Table[string, Track]
+    sounds*: Table[string, SoundData]
+    level*: AudioLevel
+    device*: NativeSoundDevice
+    lock*: Lock
+    buffers*: seq[SoundData]
+    currentBuffer*: int
+    lastUpdate*: MonoTime
+
+  # === input + window handling ===
+  EventType* = enum
+    Quit
+    ResizedWindow
+    MinimizedWindow
+    RestoredWindow
+    KeyPressed
+    KeyReleased
+    MousePressed
+    MouseReleased
+    MouseWheel
+    GotFocus
+    LostFocus
+
+  Key* {.size: sizeof(cint), pure.} = enum
+    UNKNOWN
+    Escape
+    F1
+    F2
+    F3
+    F4
+    F5
+    F6
+    F7
+    F8
+    F9
+    F10
+    F11
+    F12
+    NumberRowExtra1
+    `1`
+    `2`
+    `3`
+    `4`
+    `5`
+    `6`
+    `7`
+    `8`
+    `9`
+    `0`
+    NumberRowExtra2
+    NumberRowExtra3 # tilde, minus, plus
+    A
+    B
+    C
+    D
+    E
+    F
+    G
+    H
+    I
+    J
+    K
+    L
+    M
+    N
+    O
+    P
+    Q
+    R
+    S
+    T
+    U
+    V
+    W
+    X
+    Y
+    Z
+    Tab
+    CapsLock
+    ShiftL
+    ShiftR
+    CtrlL
+    CtrlR
+    SuperL
+    SuperR
+    AltL
+    AltR
+    Space
+    Enter
+    Backspace
+    LetterRow1Extra1
+    LetterRow1Extra2 # open bracket, close brackt, backslash
+    LetterRow2Extra1
+    LetterRow2Extra2
+    LetterRow2Extra3 # semicolon, quote
+    LetterRow3Extra1
+    LetterRow3Extra2
+    LetterRow3Extra3 # comma, period, slash
+    Up
+    Down
+    Left
+    Right
+    PageUp
+    PageDown
+    Home
+    End
+    Insert
+    Delete
+    PrintScreen
+    ScrollLock
+    Pause
+
+  MouseButton* {.size: sizeof(cint), pure.} = enum
+    UNKNOWN
+    Mouse1
+    Mouse2
+    Mouse3 # Left, middle, right
+
+  Event* = object
+    case eventType*: EventType
+    of KeyPressed, KeyReleased:
+      key*: Key
+    of MousePressed, MouseReleased:
+      button*: MouseButton
+    of MouseWheel:
+      amount*: float32
+    of GotFocus:
+      discard
+    of LostFocus:
+      discard
+    else:
+      discard
+
+  # === images ===
+  Gray* = TVec1[uint8]
+  BGRA* = TVec4[uint8]
+  PixelType* = Gray | BGRA
+
+  ImageObject*[T: PixelType, IsArray: static bool] = object
+    width*: uint32
+    height*: uint32
+    minInterpolation*: VkFilter = VK_FILTER_LINEAR
+    magInterpolation*: VkFilter = VK_FILTER_LINEAR
+    wrapU*: VkSamplerAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT
+    wrapV*: VkSamplerAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT
+    data*: seq[T]
+    vk*: VkImage
+    imageview*: VkImageView
+    sampler*: VkSampler
+    isRenderTarget*: bool = false
+    samples*: VkSampleCountFlagBits = VK_SAMPLE_COUNT_1_BIT
+    when IsArray:
+      nLayers*: uint32
+
+  Image*[T: PixelType] = ImageObject[T, false]
+  ImageArray*[T: PixelType] = ImageObject[T, true]
+
+  # === fonts ===
+  GlyphQuad*[MaxGlyphs: static int] = object
+    # vertex offsets to glyph center: [left, bottom, right, top]
+    pos*: array[MaxGlyphs, Vec4f]
+    uv*: array[MaxGlyphs, Vec4f] # [left, bottom, right, top]
+
+  TextRendering* = object
+    aspectRatio*: float32
+
+  GlyphDescriptorSet*[MaxGlyphs: static int] = object
+    fontAtlas*: Image[Gray]
+    glyphquads*: GPUValue[GlyphQuad[MaxGlyphs], StorageBuffer]
+
+  GlyphShader*[MaxGlyphs: static int] = object
+    position {.InstanceAttribute.}: Vec3f
+    color {.InstanceAttribute.}: Vec4f
+    scale {.InstanceAttribute.}: float32
+    glyphIndex {.InstanceAttribute.}: uint16
+    textRendering {.PushConstant.}: TextRendering
+
+    fragmentUv {.Pass.}: Vec2f
+    fragmentColor {.PassFlat.}: Vec4f
+    outColor {.ShaderOutput.}: Vec4f
+    glyphData {.DescriptorSet: 3.}: GlyphDescriptorSet[MaxGlyphs]
+    vertexCode* =
+      """
+const int[6] indices = int[](0, 1, 2, 2, 3, 0);
+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];
+  vec3 vertexPos = vec3(
+    glyphquads.pos[glyphIndex][i_x[vertexI]] * scale / textRendering.aspectRatio,
+    glyphquads.pos[glyphIndex][i_y[vertexI]] * scale,
+    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);
+  vec2 uv = vec2(glyphquads.uv[glyphIndex][i_x[vertexI]], glyphquads.uv[glyphIndex][i_y[vertexI]]);
+  fragmentUv = uv;
+  fragmentColor = color;
+}  """
+    fragmentCode* =
+      """void main() {
+    float a = texture(fontAtlas, fragmentUv).r;
+    outColor = vec4(fragmentColor.rgb, fragmentColor.a * a);
+}"""
+
+  FontObj*[MaxGlyphs: static int] = object
+    advance*: Table[Rune, float32]
+    kerning*: Table[(Rune, Rune), float32]
+    leftBearing*: Table[Rune, float32]
+    lineAdvance*: float32
+    lineHeight*: float32 # like lineAdvance - lineGap
+    ascent*: float32 # from baseline to highest glyph
+    descent*: float32 # from baseline to lowest glyph
+    xHeight*: float32 # from baseline to height of lowercase x
+    descriptorSet*: DescriptorSetData[GlyphDescriptorSet[MaxGlyphs]]
+    descriptorGlyphIndex*: Table[Rune, uint16]
+    descriptorGlyphIndexRev*: Table[uint16, Rune] # only used for debugging atm
+    fallbackCharacter*: Rune
+
+  Font*[MaxGlyphs: static int] = ref FontObj[MaxGlyphs]
+
+  TextHandle* = object
+    index*: uint32
+    generation*: uint32
+
+  TextAlignment* = enum
+    Left
+    Center
+    Right
+
+  Text* = object
+    bufferOffset*: int
+    text*: seq[Rune]
+    position*: Vec3f = vec3()
+    alignment*: TextAlignment = TextAlignment.Left
+    anchor*: Vec2f = vec2()
+    scale*: float32 = 0
+    color*: Vec4f = vec4(1, 1, 1, 1)
+    capacity*: int
+
+  TextBuffer*[MaxGlyphs: static int] = object
+    cursor*: int
+    generation*: uint32
+    font*: Font[MaxGlyphs]
+    baseScale*: float32
+    position*: GPUArray[Vec3f, VertexBufferMapped]
+    color*: GPUArray[Vec4f, VertexBufferMapped]
+    scale*: GPUArray[float32, VertexBufferMapped]
+    glyphIndex*: GPUArray[uint16, VertexBufferMapped]
+    texts*: seq[Text]
+
+  # === global engine object ===
+  EngineObj = object
+    initialized*: bool
+    vulkan*: VulkanObject
+    mixer*: ptr Mixer
+    audiothread*: Thread[ptr Mixer]
+
+  Engine* = ref EngineObj
+
+# prevent object copies
+
+proc `=copy`(dest: var VulkanObject, source: VulkanObject) {.error.}
+proc `=copy`(dest: var RenderDataObject, source: RenderDataObject) {.error.}
+proc `=copy`[T, S](dest: var GPUValue[T, S], source: GPUValue[T, S]) {.error.}
+proc `=copy`[T, S](dest: var GPUArray[T, S], source: GPUArray[T, S]) {.error.}
+proc `=copy`(dest: var MemoryBlock, source: MemoryBlock) {.error.}
+proc `=copy`[T](dest: var Pipeline[T], source: Pipeline[T]) {.error.}
+proc `=copy`[T](dest: var DescriptorSetData[T], source: DescriptorSetData[T]) {.error.}
+proc `=copy`(dest: var Playback, source: Playback) {.error.}
+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 EngineObj, source: EngineObj) {.error.}
+proc `=copy`[MaxGlyphs: static int](
+  dest: var FontObj[MaxGlyphs], source: FontObj[MaxGlyphs]
+) {.error.}
+
+proc `=copy`[MaxGlyphs: static int](
+  dest: var TextBuffer[MaxGlyphs], source: TextBuffer[MaxGlyphs]
+) {.error.}
--- a/semicongine/events.nim	Wed Jan 01 19:36:55 2025 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-type
-  EventType* = enum
-    Quit
-    ResizedWindow
-    MinimizedWindow
-    RestoredWindow
-    KeyPressed
-    KeyReleased
-    MousePressed
-    MouseReleased
-    MouseWheel
-    GotFocus
-    LostFocus
-
-  Key* {.size: sizeof(cint), pure.} = enum
-    UNKNOWN
-    Escape
-    F1
-    F2
-    F3
-    F4
-    F5
-    F6
-    F7
-    F8
-    F9
-    F10
-    F11
-    F12
-    NumberRowExtra1
-    `1`
-    `2`
-    `3`
-    `4`
-    `5`
-    `6`
-    `7`
-    `8`
-    `9`
-    `0`
-    NumberRowExtra2
-    NumberRowExtra3 # tilde, minus, plus
-    A
-    B
-    C
-    D
-    E
-    F
-    G
-    H
-    I
-    J
-    K
-    L
-    M
-    N
-    O
-    P
-    Q
-    R
-    S
-    T
-    U
-    V
-    W
-    X
-    Y
-    Z
-    Tab
-    CapsLock
-    ShiftL
-    ShiftR
-    CtrlL
-    CtrlR
-    SuperL
-    SuperR
-    AltL
-    AltR
-    Space
-    Enter
-    Backspace
-    LetterRow1Extra1
-    LetterRow1Extra2 # open bracket, close brackt, backslash
-    LetterRow2Extra1
-    LetterRow2Extra2
-    LetterRow2Extra3 # semicolon, quote
-    LetterRow3Extra1
-    LetterRow3Extra2
-    LetterRow3Extra3 # comma, period, slash
-    Up
-    Down
-    Left
-    Right
-    PageUp
-    PageDown
-    Home
-    End
-    Insert
-    Delete
-    PrintScreen
-    ScrollLock
-    Pause
-
-  MouseButton* {.size: sizeof(cint), pure.} = enum
-    UNKNOWN
-    Mouse1
-    Mouse2
-    Mouse3 # Left, middle, right
-
-  Event* = object
-    case eventType*: EventType
-    of KeyPressed, KeyReleased:
-      key*: Key
-    of MousePressed, MouseReleased:
-      button*: MouseButton
-    of MouseWheel:
-      amount*: float32
-    of GotFocus:
-      discard
-    of LostFocus:
-      discard
-    else:
-      discard
--- a/semicongine/gltf.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/gltf.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -7,9 +7,7 @@
 
 import ./core
 import ./rendering
-import ./rendering/vulkan/api
 import ./image
-import ./resources
 
 type
   GltfNode* = object
--- a/semicongine/image.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/image.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,12 +1,8 @@
 import std/os
 import std/typetraits
-import std/streams
-import std/strutils
 import std/strformat
 
 import ./core
-import ./resources
-import ./rendering/vulkan/api
 
 {.emit: "#define STB_IMAGE_STATIC".}
 {.emit: "#define STB_IMAGE_IMPLEMENTATION".}
@@ -22,35 +18,9 @@
   desired_channels: cint,
 ): ptr uint8 {.importc, nodecl.}
 
-type
-  Gray* = TVec1[uint8]
-  BGRA* = TVec4[uint8]
-  PixelType* = Gray | BGRA
-
-  ImageObject*[T: PixelType, IsArray: static bool] = object
-    width*: uint32
-    height*: uint32
-    minInterpolation*: VkFilter = VK_FILTER_LINEAR
-    magInterpolation*: VkFilter = VK_FILTER_LINEAR
-    wrapU*: VkSamplerAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT
-    wrapV*: VkSamplerAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT
-    data*: seq[T]
-    vk*: VkImage
-    imageview*: VkImageView
-    sampler*: VkSampler
-    isRenderTarget*: bool = false
-    samples*: VkSampleCountFlagBits = VK_SAMPLE_COUNT_1_BIT
-    when IsArray:
-      nLayers*: uint32
-
-  Image*[T: PixelType] = ImageObject[T, false]
-  ImageArray*[T: PixelType] = ImageObject[T, true]
-
 template nLayers*(image: Image): untyped =
   1'u32
 
-proc `=copy`[S, T](dest: var ImageObject[S, T], source: ImageObject[S, T]) {.error.}
-
 func `$`*[S, IsArray](img: ImageObject[S, IsArray]): string =
   let pixelTypeName = S.name
   if IsArray == false:
--- a/semicongine/input.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/input.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -2,7 +2,6 @@
 import std/tables
 
 import ./core
-import ./events
 import ./rendering
 import ./storage
 
@@ -34,16 +33,16 @@
   input.mouseMove = vec2i(0, 0)
   input.windowWasResized = false
 
-  let newMousePos = getMousePosition(vulkan.window)
+  let newMousePos = getMousePosition(engine().vulkan.window)
   input.mouseMove = newMousePos - input.mousePosition
   if input.lockMouse and input.hasFocus:
-    input.mousePosition = vulkan.window.size div 2
-    setMousePosition(vulkan.window, input.mousePosition)
+    input.mousePosition = engine().vulkan.window.size div 2
+    setMousePosition(engine().vulkan.window, input.mousePosition)
   else:
     input.mousePosition = newMousePos
 
   var killed = false
-  for event in vulkan.window.pendingEvents():
+  for event in engine().vulkan.window.pendingEvents():
     case event.eventType
     of Quit:
       killed = true
@@ -111,7 +110,8 @@
   input.mousePosition
 
 proc mousePosition*(): Vec2f =
-  result = input.mousePosition.f32 / vulkan.window.size().f32 * 2.0'f32 - 1.0'f32
+  result =
+    input.mousePosition.f32 / engine().vulkan.window.size().f32 * 2.0'f32 - 1.0'f32
   result.y = result.y * -1
 
 proc mouseMove*(): Vec2i =
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/platform/linux/types.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -0,0 +1,31 @@
+import ../../thirdparty/x11/xlib
+import ../../thirdparty/x11/x as x11
+
+type NativeWindow* = object
+  display*: ptr xlib.Display
+  window*: x11.Window
+  emptyCursor*: Cursor
+
+# alsa API
+type
+  OpenMode* {.size: sizeof(culong).} = enum
+    SND_PCM_BLOCK = 0x00000000 # added by semicongine, for clarity
+    SND_PCM_NONBLOCK = 0x00000001
+
+  StreamMode* {.size: sizeof(cint).} = enum
+    SND_PCM_STREAM_PLAYBACK = 0
+
+  AccessMode* {.size: sizeof(cint).} = enum
+    SND_PCM_ACCESS_RW_INTERLEAVED = 3
+
+  PCMFormat* {.size: sizeof(cint).} = enum
+    SND_PCM_FORMAT_S16_LE = 2
+
+  snd_pcm_p* = ptr object
+  snd_pcm_hw_params_p* = ptr object
+  snd_pcm_uframes_t* = culong
+  snd_pcm_sframes_t* = clong
+
+type NativeSoundDevice* = object
+  handle*: snd_pcm_p
+  buffers*: seq[ptr SoundData]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/platform/windows/types.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -0,0 +1,10 @@
+import ../../thirdparty/winim/winim/inc/[windef, winuser, wincon, winbase]
+
+type NativeWindow* = object
+  hinstance*: HINSTANCE
+  hwnd*: HWND
+  g_wpPrev: WINDOWPLACEMENT
+
+type NativeSoundDevice* = object
+  handle*: HWAVEOUT
+  buffers*: seq[WAVEHDR]
--- a/semicongine/rendering.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,5 +1,4 @@
 import std/logging
-import std/enumerate
 import std/hashes
 import std/macros
 import std/os
@@ -8,7 +7,7 @@
 import std/strutils
 import std/typetraits
 
-import ./rendering/vulkan/api
+import ./core
 
 import ./image
 
@@ -19,28 +18,6 @@
 # - some utils code that is used in mutiple rendering files
 # - inclusion of all rendering files
 
-# const definitions
-const INFLIGHTFRAMES* = 2'u32
-const BUFFER_ALIGNMENT = 64'u64 # align offsets inside buffers along this alignment
-const MEMORY_BLOCK_ALLOCATION_SIZE = 100_000_000'u64
-  # ca. 100mb per block, seems reasonable
-const BUFFER_ALLOCATION_SIZE = 9_000_000'u64
-  # ca. 9mb per block, seems reasonable, can put 10 buffers into one memory block
-const MAX_DESCRIPTORSETS = 4
-const SURFACE_FORMAT* = VK_FORMAT_B8G8R8A8_SRGB
-const DEPTH_FORMAT* = VK_FORMAT_D32_SFLOAT
-const PUSH_CONSTANT_SIZE = 128
-
-# custom pragmas to classify shader attributes
-type DescriptorSetIndex = range[0 .. MAX_DESCRIPTORSETS - 1]
-template VertexAttribute*() {.pragma.}
-template InstanceAttribute*() {.pragma.}
-template PushConstant*() {.pragma.}
-template Pass*() {.pragma.}
-template PassFlat*() {.pragma.}
-template ShaderOutput*() {.pragma.}
-template DescriptorSet*(index: DescriptorSetIndex) {.pragma.}
-
 # there is a big, bad global vulkan object
 # believe me, this makes everything much, much easier
 
@@ -49,140 +26,6 @@
 when defined(linux):
   include ./rendering/platform/linux
 
-type
-  # type aliases
-  SupportedGPUType =
-    float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
-    TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] |
-    TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] |
-    TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] |
-    TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] |
-    TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] |
-    TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[
-      float32
-    ] | TMat43[float64] | TMat4[float32] | TMat4[float64]
-
-  VulkanGlobals* = object # populated through InitVulkan proc
-    instance*: VkInstance
-    device*: VkDevice
-    physicalDevice*: VkPhysicalDevice
-    surface: VkSurfaceKHR
-    window*: NativeWindow
-    graphicsQueueFamily*: uint32
-    graphicsQueue*: VkQueue
-    debugMessenger: VkDebugUtilsMessengerEXT
-    # populated through the initSwapchain proc
-    swapchain*: Swapchain
-    # unclear as of yet
-    anisotropy*: float32 = 0 # needs to be enable during device creation
-
-  RenderPass* = ref object
-    vk*: VkRenderPass
-    samples*: VkSampleCountFlagBits
-    depthBuffer*: bool
-
-  Swapchain* = ref object
-    # parameters to initSwapchain, required for swapchain recreation
-    renderPass*: RenderPass
-    vSync*: bool
-    tripleBuffering*: bool
-    # populated through initSwapchain proc
-    vk: VkSwapchainKHR
-    width*: uint32
-    height*: uint32
-    framebuffers: seq[VkFramebuffer]
-    framebufferViews: seq[VkImageView]
-    currentFramebufferIndex: uint32
-    commandBufferPool: VkCommandPool
-    # depth buffer stuff, if enabled
-    depthImage: VkImage
-    depthImageView*: VkImageView
-    depthMemory: VkDeviceMemory
-    # MSAA stuff, if enabled
-    msaaImage: VkImage
-    msaaImageView*: VkImageView
-    msaaMemory: VkDeviceMemory
-    # frame-in-flight handling
-    currentFiF: range[0 .. (INFLIGHTFRAMES - 1).int]
-    queueFinishedFence*: array[INFLIGHTFRAMES.int, VkFence]
-    imageAvailableSemaphore*: array[INFLIGHTFRAMES.int, VkSemaphore]
-    renderFinishedSemaphore*: array[INFLIGHTFRAMES.int, VkSemaphore]
-    commandBuffers: array[INFLIGHTFRAMES.int, VkCommandBuffer]
-    oldSwapchain: Swapchain
-    oldSwapchainCounter: int # swaps until old swapchain will be destroyed
-
-  # shader related types
-  DescriptorSetData*[T: object] = object
-    data*: T
-    vk*: array[INFLIGHTFRAMES.int, VkDescriptorSet]
-
-  Pipeline*[TShader] = object
-    vk: VkPipeline
-    vertexShaderModule: VkShaderModule
-    fragmentShaderModule: VkShaderModule
-    layout: VkPipelineLayout
-    descriptorSetLayouts*: array[MAX_DESCRIPTORSETS, VkDescriptorSetLayout]
-
-  # memory/buffer related types
-  BufferType* = enum
-    VertexBuffer
-    VertexBufferMapped
-    IndexBuffer
-    IndexBufferMapped
-    UniformBuffer
-    UniformBufferMapped
-    StorageBuffer
-    StorageBufferMapped
-
-  MemoryBlock* = object
-    vk: VkDeviceMemory
-    size: uint64
-    rawPointer: pointer # if not nil, this is mapped memory
-    offsetNextFree: uint64
-
-  Buffer* = object
-    vk: VkBuffer
-    size: uint64
-    rawPointer: pointer # if not nil, buffer is using mapped memory
-    offsetNextFree: uint64
-    memoryOffset: uint64
-    memory: VkDeviceMemory
-
-  GPUArray*[T: SupportedGPUType, TBuffer: static BufferType] = object
-    # TODO: when using mapped buffer memory, directly write values to mapped location
-    # instead of using data as buffer
-    data*: seq[T]
-    buffer*: Buffer
-    offset*: uint64
-
-  GPUValue*[T: object, TBuffer: static BufferType] = object
-    data*: T
-    buffer*: Buffer
-    offset: uint64
-
-  GPUData* = GPUArray | GPUValue
-
-  RenderDataObject = object
-    descriptorPool: VkDescriptorPool
-    memory: array[VK_MAX_MEMORY_TYPES.int, seq[MemoryBlock]]
-    buffers: array[BufferType, seq[Buffer]]
-    images: seq[VkImage]
-    imageViews: seq[VkImageView]
-    samplers: seq[VkSampler]
-
-  RenderData* = ref RenderDataObject
-
-var vulkan* = VulkanGlobals()
-var fullscreen_internal: bool
-
-proc `=copy`(dest: var VulkanGlobals, source: VulkanGlobals) {.error.}
-proc `=copy`(dest: var RenderDataObject, source: RenderDataObject) {.error.}
-proc `=copy`[T, S](dest: var GPUValue[T, S], source: GPUValue[T, S]) {.error.}
-proc `=copy`[T, S](dest: var GPUArray[T, S], source: GPUArray[T, S]) {.error.}
-proc `=copy`(dest: var MemoryBlock, source: MemoryBlock) {.error.}
-proc `=copy`[T](dest: var Pipeline[T], source: Pipeline[T]) {.error.}
-proc `=copy`[T](dest: var DescriptorSetData[T], source: DescriptorSetData[T]) {.error.}
-
 proc `[]`*[T, S](a: GPUArray[T, S], i: SomeInteger): T =
   a.data[i]
 
@@ -200,59 +43,8 @@
 ): BufferType {.compileTime.} =
   B
 
-func getDescriptorType[T](): VkDescriptorType {.compileTIme.} =
-  when T is ImageObject:
-    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
-  elif T is GPUValue:
-    when getBufferType(default(T)) in [UniformBuffer, UniformBufferMapped]:
-      VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
-    elif getBufferType(default(T)) in [StorageBuffer, StorageBufferMapped]:
-      VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
-    else:
-      {.error: "Unsupported descriptor type: " & $T.}
-  elif T is array:
-    when elementType(default(T)) is ImageObject:
-      VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
-    elif elementType(default(T)) is GPUValue:
-      when getBufferType(default(elementType(default(T)))) in
-          [UniformBuffer, UniformBufferMapped]:
-        VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
-      elif getBufferType(default(elementType(default(T)))) in
-          [StorageBuffer, StorageBufferMapped]:
-        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
-      else:
-        {.error: "Unsupported descriptor type: " & $T.}
-    else:
-      {.error: "Unsupported descriptor type: " & $T.}
-  else:
-    {.error: "Unsupported descriptor type: " & $T.}
-
-func getDescriptorCount[T](): uint32 {.compileTIme.} =
-  when T is array:
-    len(T)
-  else:
-    1
-
-func getBindingNumber[T](field: static string): uint32 {.compileTime.} =
-  var c = 0'u32
-  var found = false
-  for name, value in fieldPairs(default(T)):
-    when name == field:
-      result = c
-      found = true
-    else:
-      inc c
-  assert found, "Field '" & field & "' of descriptor '" & $T & "' not found"
-
-proc currentFiF*(): int =
-  assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  vulkan.swapchain.currentFiF
-
-include ./rendering/vulkan_wrappers
-include ./rendering/renderpasses
-include ./rendering/swapchain
-include ./rendering/shaders
-include ./rendering/renderer
+import ./rendering/vulkan_wrappers
+import ./rendering/swapchain
 
 proc debugCallback(
     messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
@@ -279,7 +71,7 @@
     raise newException(Exception, errorMsg)
   return false
 
-proc initVulkan*(appName: string = "semicongine app") =
+proc initVulkan*(appName: string = "semicongine app"): VulkanObject =
   # instance creation
 
   # enagle all kind of debug stuff
@@ -321,15 +113,15 @@
       enabledExtensionCount: requiredExtensions.len.uint32,
       ppEnabledExtensionNames: instanceExtensionsC,
     )
-  checkVkResult vkCreateInstance(addr(createinfo), nil, addr(vulkan.instance))
-  loadVulkan(vulkan.instance)
+  checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result.instance))
+  loadVulkan(result.instance)
 
   # load extensions
   #
   for extension in requiredExtensions:
-    loadExtension(vulkan.instance, $extension)
-  vulkan.window = createWindow(appName)
-  vulkan.surface = createNativeSurface(vulkan.instance, vulkan.window)
+    loadExtension(result.instance, $extension)
+  result.window = createWindow(appName)
+  result.surface = createNativeSurface(result.instance, result.window)
 
   # logical device creation
 
@@ -340,7 +132,7 @@
   # var deviceExtensions  = @["VK_KHR_swapchain", "VK_KHR_uniform_buffer_standard_layout"]
   var deviceExtensions = @["VK_KHR_swapchain"]
   for extension in deviceExtensions:
-    loadExtension(vulkan.instance, extension)
+    loadExtension(result.instance, extension)
 
   when not defined(release):
     var debugMessengerCreateInfo = VkDebugUtilsMessengerCreateInfoEXT(
@@ -351,19 +143,19 @@
       pUserData: nil,
     )
     checkVkResult vkCreateDebugUtilsMessengerEXT(
-      vulkan.instance, addr(debugMessengerCreateInfo), nil, addr(vulkan.debugMessenger)
+      result.instance, addr(debugMessengerCreateInfo), nil, addr(result.debugMessenger)
     )
 
   # get physical device and graphics queue family
-  vulkan.physicalDevice = getBestPhysicalDevice(vulkan.instance)
-  vulkan.graphicsQueueFamily =
-    getQueueFamily(vulkan.physicalDevice, VK_QUEUE_GRAPHICS_BIT)
+  result.physicalDevice = getBestPhysicalDevice(result.instance)
+  result.graphicsQueueFamily =
+    getQueueFamily(result.physicalDevice, VK_QUEUE_GRAPHICS_BIT)
 
   let
     priority = cfloat(1)
     queueInfo = VkDeviceQueueCreateInfo(
       sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
-      queueFamilyIndex: vulkan.graphicsQueueFamily,
+      queueFamilyIndex: result.graphicsQueueFamily,
       queueCount: 1,
       pQueuePriorities: addr(priority),
     )
@@ -384,49 +176,51 @@
     pEnabledFeatures: addr(enabledFeatures),
   )
   checkVkResult vkCreateDevice(
-    physicalDevice = vulkan.physicalDevice,
+    physicalDevice = result.physicalDevice,
     pCreateInfo = addr createDeviceInfo,
     pAllocator = nil,
-    pDevice = addr vulkan.device,
+    pDevice = addr result.device,
   )
-  vulkan.graphicsQueue =
-    svkGetDeviceQueue(vulkan.device, vulkan.graphicsQueueFamily, VK_QUEUE_GRAPHICS_BIT)
+  result.graphicsQueue =
+    svkGetDeviceQueue(result.device, result.graphicsQueueFamily, VK_QUEUE_GRAPHICS_BIT)
 
 proc clearSwapchain*() =
-  assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  destroySwapchain(vulkan.swapchain)
-  vulkan.swapchain = nil
+  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 vulkan.swapchain == nil, "Swapchain has already been initialized yet"
-  vulkan.swapchain =
+  assert engine().vulkan.swapchain == nil, "Swapchain has already been initialized yet"
+  engine().vulkan.swapchain =
     initSwapchain(renderPass, vSync = vSync, tripleBuffering = tripleBuffering)
 
 proc destroyVulkan*() =
-  if vulkan.swapchain != nil:
+  if engine().vulkan.swapchain != nil:
     clearSwapchain()
-  vkDestroyDevice(vulkan.device, nil)
-  vkDestroySurfaceKHR(vulkan.instance, vulkan.surface, nil)
-  if vulkan.debugMessenger.Valid:
-    vkDestroyDebugUtilsMessengerEXT(vulkan.instance, vulkan.debugMessenger, nil)
-  vkDestroyInstance(vulkan.instance, nil)
+  vkDestroyDevice(engine().vulkan.device, nil)
+  vkDestroySurfaceKHR(engine().vulkan.instance, engine().vulkan.surface, nil)
+  if engine().vulkan.debugMessenger.Valid:
+    vkDestroyDebugUtilsMessengerEXT(
+      engine().vulkan.instance, engine().vulkan.debugMessenger, nil
+    )
+  vkDestroyInstance(engine().vulkan.instance, nil)
 
 proc showSystemCursor*(value: bool) =
-  vulkan.window.showSystemCursor(value)
+  engine().vulkan.window.showSystemCursor(value)
 
 proc fullscreen*(): bool =
-  fullscreen_internal
+  engine().vulkan.fullscreen_internal
 
 proc setFullscreen*(enable: bool) =
-  if enable != fullscreen_internal:
-    fullscreen_internal = enable
-    vulkan.window.setFullscreen(fullscreen_internal)
+  if enable != engine().vulkan.fullscreen_internal:
+    engine().vulkan.fullscreen_internal = enable
+    engine().vulkan.window.setFullscreen(engine().vulkan.fullscreen_internal)
 
 proc getAspectRatio*(): float32 =
-  assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  vulkan.swapchain.width.float32 / vulkan.swapchain.height.float32
+  assert engine().vulkan.swapchain != nil, "Swapchain has not been initialized yet"
+  engine().vulkan.swapchain.width.float32 / engine().vulkan.swapchain.height.float32
 
 proc maxFramebufferSampleCount*(
     maxSamples = VK_SAMPLE_COUNT_8_BIT
--- a/semicongine/rendering/platform/linux.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/platform/linux.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -6,9 +6,6 @@
 import ../../thirdparty/x11/x as x11
 import ../../thirdparty/x11/xkblib
 
-import ../../core
-import ../../events
-
 const REQUIRED_PLATFORM_EXTENSIONS = @["VK_KHR_xlib_surface"]
 
 # got values (keycodes) from xev
@@ -109,11 +106,6 @@
 
 var deleteMessage*: Atom
 
-type NativeWindow* = object
-  display*: ptr xlib.Display
-  window*: x11.Window
-  emptyCursor: Cursor
-
 template checkXlibResult(call: untyped) =
   let value = call
   if value == 0:
@@ -303,8 +295,8 @@
 proc createNativeSurface(instance: VkInstance, window: NativeWindow): VkSurfaceKHR =
   var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
-    dpy: cast[ptr api.Display](window.display),
-    window: cast[api.Window](window.window),
+    dpy: cast[ptr core.Display](window.display),
+    window: cast[core.Window](window.window),
   )
   checkVkResult vkCreateXlibSurfaceKHR(
     instance, addr(surfaceCreateInfo), nil, addr(result)
--- a/semicongine/rendering/platform/windows.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/platform/windows.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,13 +1,9 @@
 import std/tables
 import std/options
 
-import ../../core
-
 import ../../thirdparty/winim/winim/inc/[windef, winuser, wincon, winbase]
 import ../../thirdparty/winim/winim/[winstr, utils]
 
-import ../../events
-
 const REQUIRED_PLATFORM_EXTENSIONS = @["VK_KHR_win32_surface"]
 
 const KeyTypeMap* = {
@@ -98,11 +94,6 @@
   VK_DELETE: Key.Delete,
 }.toTable
 
-type NativeWindow* = object
-  hinstance*: HINSTANCE
-  hwnd*: HWND
-  g_wpPrev: WINDOWPLACEMENT
-
 # sorry, have to use module-global variable to capture windows events
 var currentEvents: seq[Event]
 
--- a/semicongine/rendering/renderer.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/renderer.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,3 +1,11 @@
+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)
 
@@ -30,7 +38,7 @@
       usage: usage.toBits,
     )
     let formatCheck = vkGetPhysicalDeviceImageFormatProperties2(
-      vulkan.physicalDevice, addr formatInfo, addr formatProperties
+      engine().vulkan.physicalDevice, addr formatInfo, addr formatProperties
     )
     if formatCheck == VK_SUCCESS: # found suitable format
       return format
@@ -75,7 +83,9 @@
 
 proc isMappable(memoryTypeIndex: uint32): bool =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
-  vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
+  vkGetPhysicalDeviceMemoryProperties(
+    engine().vulkan.physicalDevice, addr(physicalProperties)
+  )
   let flags = toEnums(physicalProperties.memoryTypes[memoryTypeIndex].propertyFlags)
   return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags
 
@@ -116,7 +126,7 @@
     pSetLayouts: layouts.ToCPointer,
   )
   checkVkResult vkAllocateDescriptorSets(
-    vulkan.device, addr(allocInfo), descriptorSet.vk.ToCPointer
+    engine().vulkan.device, addr(allocInfo), descriptorSet.vk.ToCPointer
   )
 
   # allocate seq with high cap to prevent realocation while adding to set
@@ -206,7 +216,7 @@
         .}
 
   vkUpdateDescriptorSets(
-    device = vulkan.device,
+    device = engine().vulkan.device,
     descriptorWriteCount = descriptorSetWrites.len.uint32,
     pDescriptorWrites = descriptorSetWrites.ToCPointer,
     descriptorCopyCount = 0,
@@ -219,7 +229,7 @@
   )
   if mType.isMappable():
     checkVkResult vkMapMemory(
-      device = vulkan.device,
+      device = engine().vulkan.device,
       memory = result.vk,
       offset = 0'u64,
       size = result.size,
@@ -234,7 +244,7 @@
     offset: buffer.memoryOffset,
     size: buffer.size,
   )
-  checkVkResult vkFlushMappedMemoryRanges(vulkan.device, 1, addr(flushRegion))
+  checkVkResult vkFlushMappedMemoryRanges(engine().vulkan.device, 1, addr(flushRegion))
 
 proc flushAllMemory*(renderData: RenderData) =
   var flushRegions = newSeq[VkMappedMemoryRange]()
@@ -251,7 +261,7 @@
         )
   if flushRegions.len > 0:
     checkVkResult vkFlushMappedMemoryRanges(
-      vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer()
+      engine().vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer()
     )
 
 proc allocateNewBuffer(
@@ -292,7 +302,7 @@
   renderData.memory[memoryType][selectedBlockI].offsetNextFree =
     alignedTo(selectedBlock.offsetNextFree, memoryRequirements.alignment)
   checkVkResult vkBindBufferMemory(
-    vulkan.device, result.vk, selectedBlock.vk, selectedBlock.offsetNextFree
+    engine().vulkan.device, result.vk, selectedBlock.vk, selectedBlock.offsetNextFree
   )
   result.memory = selectedBlock.vk
   result.memoryOffset = selectedBlock.offsetNextFree
@@ -404,28 +414,28 @@
     maxSets: descriptorPoolLimit,
   )
   checkVkResult vkCreateDescriptorPool(
-    vulkan.device, addr(poolInfo), nil, addr(result.descriptorPool)
+    engine().vulkan.device, addr(poolInfo), nil, addr(result.descriptorPool)
   )
 
 proc destroyRenderData*(renderData: RenderData) =
-  vkDestroyDescriptorPool(vulkan.device, renderData.descriptorPool, nil)
+  vkDestroyDescriptorPool(engine().vulkan.device, renderData.descriptorPool, nil)
 
   for buffers in renderData.buffers:
     for buffer in buffers:
-      vkDestroyBuffer(vulkan.device, buffer.vk, nil)
+      vkDestroyBuffer(engine().vulkan.device, buffer.vk, nil)
 
   for imageView in renderData.imageViews:
-    vkDestroyImageView(vulkan.device, imageView, nil)
+    vkDestroyImageView(engine().vulkan.device, imageView, nil)
 
   for sampler in renderData.samplers:
-    vkDestroySampler(vulkan.device, sampler, nil)
+    vkDestroySampler(engine().vulkan.device, sampler, nil)
 
   for image in renderData.images:
-    vkDestroyImage(vulkan.device, image, nil)
+    vkDestroyImage(engine().vulkan.device, image, nil)
 
   for memoryBlocks in renderData.memory.litems:
     for memory in memoryBlocks:
-      vkFreeMemory(vulkan.device, memory.vk, nil)
+      vkFreeMemory(engine().vulkan.device, memory.vk, nil)
 
 proc transitionImageLayout(
     image: VkImage, oldLayout, newLayout: VkImageLayout, nLayers: uint32
@@ -491,8 +501,8 @@
     addressModeU: addressModeU,
     addressModeV: addressModeV,
     addressModeW: VK_SAMPLER_ADDRESS_MODE_REPEAT,
-    anisotropyEnable: vulkan.anisotropy > 0,
-    maxAnisotropy: vulkan.anisotropy,
+    anisotropyEnable: engine().vulkan.anisotropy > 0,
+    maxAnisotropy: engine().vulkan.anisotropy,
     borderColor: VK_BORDER_COLOR_INT_OPAQUE_BLACK,
     unnormalizedCoordinates: VK_FALSE,
     compareEnable: VK_FALSE,
@@ -502,7 +512,9 @@
     minLod: 0,
     maxLod: 0,
   )
-  checkVkResult vkCreateSampler(vulkan.device, addr(samplerInfo), nil, addr(result))
+  checkVkResult vkCreateSampler(
+    engine().vulkan.device, addr(samplerInfo), nil, addr(result)
+  )
 
 proc createVulkanImage(renderData: var RenderData, image: var ImageObject) =
   assert image.vk == VkImage(0), "Image has already been created"
@@ -549,7 +561,7 @@
     alignedTo(selectedBlock.offsetNextFree, memoryRequirements.alignment)
 
   checkVkResult vkBindImageMemory(
-    vulkan.device,
+    engine().vulkan.device,
     image.vk,
     selectedBlock.vk,
     renderData.memory[memoryType][selectedBlockI].offsetNextFree,
--- a/semicongine/rendering/renderpasses.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/renderpasses.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,7 +1,9 @@
+import ../core
+import ./vulkan_wrappers
+
 proc createDirectPresentationRenderPass*(
     depthBuffer: bool, samples = VK_SAMPLE_COUNT_1_BIT
 ): RenderPass =
-  assert vulkan.instance.Valid, "Vulkan not initialized"
   result = RenderPass(depthBuffer: depthBuffer, samples: samples)
 
   var attachments =
@@ -98,8 +100,6 @@
 proc createIndirectPresentationRenderPass*(
     depthBuffer: bool, samples = VK_SAMPLE_COUNT_1_BIT
 ): (RenderPass, RenderPass) =
-  assert vulkan.instance.Valid, "Vulkan not initialized"
-
   result[0] = RenderPass(depthBuffer: depthBuffer, samples: samples)
   result[1] = RenderPass(depthBuffer: false, samples: VK_SAMPLE_COUNT_1_BIT)
 
@@ -300,4 +300,4 @@
   vkCmdEndRenderPass(commandbuffer)
 
 proc destroyRenderPass*(renderPass: RenderPass) =
-  vkDestroyRenderPass(vulkan.device, renderpass.vk, nil)
+  vkDestroyRenderPass(engine().vulkan.device, renderpass.vk, nil)
--- a/semicongine/rendering/shaders.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/shaders.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,3 +1,13 @@
+import std/macros
+import std/hashes
+import std/strformat
+import std/strutils
+import std/enumerate
+import std/os
+
+import ../core
+import ./vulkan_wrappers
+
 func glslType[T: SupportedGPUType | ImageObject](value: T): string =
   when T is float32:
     "float"
@@ -419,7 +429,7 @@
     codeSize: csize_t(vertexBinary.len * sizeof(uint32)),
     pCode: vertexBinary.ToCPointer,
   )
-  checkVkResult vulkan.device.vkCreateShaderModule(
+  checkVkResult engine().vulkan.device.vkCreateShaderModule(
     addr(createInfoVertex), nil, addr(result[0])
   )
   var createInfoFragment = VkShaderModuleCreateInfo(
@@ -427,7 +437,7 @@
     codeSize: csize_t(fragmentBinary.len * sizeof(uint32)),
     pCode: fragmentBinary.ToCPointer,
   )
-  checkVkResult vulkan.device.vkCreateShaderModule(
+  checkVkResult engine().vulkan.device.vkCreateShaderModule(
     addr(createInfoFragment), nil, addr(result[1])
   )
 
@@ -467,7 +477,7 @@
         pBindings: layoutbindings.ToCPointer,
       )
       checkVkResult vkCreateDescriptorSetLayout(
-        vulkan.device,
+        engine().vulkan.device,
         addr(layoutCreateInfo),
         nil,
         addr(result[value.getCustomPragmaVal(DescriptorSet)]),
@@ -481,7 +491,7 @@
         pBindings: nil,
       )
       checkVkResult vkCreateDescriptorSetLayout(
-        vulkan.device, addr(layoutCreateInfo), nil, addr(result[i])
+        engine().vulkan.device, addr(layoutCreateInfo), nil, addr(result[i])
       )
 
 proc createPipeline*[TShader](
@@ -513,7 +523,7 @@
     pPushConstantRanges: addr(pushConstant),
   )
   checkVkResult vkCreatePipelineLayout(
-    vulkan.device, addr(pipelineLayoutInfo), nil, addr(result.layout)
+    engine().vulkan.device, addr(pipelineLayoutInfo), nil, addr(result.layout)
   )
 
   let stages = [
@@ -659,7 +669,12 @@
     basePipelineIndex: -1,
   )
   checkVkResult vkCreateGraphicsPipelines(
-    vulkan.device, VkPipelineCache(0), 1, addr(createInfo), nil, addr(result.vk)
+    engine().vulkan.device,
+    VkPipelineCache(0),
+    1,
+    addr(createInfo),
+    nil,
+    addr(result.vk),
   )
 
 func layout*(pipeline: Pipeline, level: int): VkDescriptorSetLayout =
@@ -674,9 +689,9 @@
 
 proc destroyPipeline*(pipeline: Pipeline) =
   for descriptorSetLayout in pipeline.descriptorSetLayouts:
-    vkDestroyDescriptorSetLayout(vulkan.device, descriptorSetLayout, nil)
+    vkDestroyDescriptorSetLayout(engine().vulkan.device, descriptorSetLayout, nil)
 
-  vkDestroyShaderModule(vulkan.device, pipeline.vertexShaderModule, nil)
-  vkDestroyShaderModule(vulkan.device, pipeline.fragmentShaderModule, nil)
-  vkDestroyPipelineLayout(vulkan.device, pipeline.layout, nil)
-  vkDestroyPipeline(vulkan.device, pipeline.vk, nil)
+  vkDestroyShaderModule(engine().vulkan.device, pipeline.vertexShaderModule, nil)
+  vkDestroyShaderModule(engine().vulkan.device, pipeline.fragmentShaderModule, nil)
+  vkDestroyPipelineLayout(engine().vulkan.device, pipeline.layout, nil)
+  vkDestroyPipeline(engine().vulkan.device, pipeline.vk, nil)
--- a/semicongine/rendering/swapchain.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/swapchain.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,14 +1,17 @@
-proc initSwapchain(
+import std/options
+
+import ../core
+import ./vulkan_wrappers
+
+proc initSwapchain*(
     renderPass: RenderPass,
     vSync: bool = false,
     tripleBuffering: bool = true,
     oldSwapchain: Swapchain = nil,
 ): Swapchain =
-  assert vulkan.instance.Valid, "Vulkan not initialized"
-
   var capabilities: VkSurfaceCapabilitiesKHR
   checkVkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
-    vulkan.physicalDevice, vulkan.surface, addr(capabilities)
+    engine().vulkan.physicalDevice, engine().vulkan.surface, addr(capabilities)
   )
   let
     width = capabilities.currentExtent.width
@@ -28,7 +31,7 @@
     VK_PRESENT_MODE_MAILBOX_KHR in svkGetPhysicalDeviceSurfacePresentModesKHR()
   var swapchainCreateInfo = VkSwapchainCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
-    surface: vulkan.surface,
+    surface: engine().vulkan.surface,
     minImageCount: minFramebufferCount,
     imageFormat: SURFACE_FORMAT,
     imageColorSpace: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
@@ -62,7 +65,7 @@
   )
 
   if vkCreateSwapchainKHR(
-    vulkan.device, addr(swapchainCreateInfo), nil, addr(swapchain.vk)
+    engine().vulkan.device, addr(swapchainCreateInfo), nil, addr(swapchain.vk)
   ) != VK_SUCCESS:
     return nil
 
@@ -83,7 +86,7 @@
       requirements.size, bestMemory(mappable = false, filter = requirements.memoryTypes)
     )
     checkVkResult vkBindImageMemory(
-      vulkan.device, swapchain.depthImage, swapchain.depthMemory, 0
+      engine().vulkan.device, swapchain.depthImage, swapchain.depthMemory, 0
     )
     swapchain.depthImageView = svkCreate2DImageView(
       image = swapchain.depthImage,
@@ -106,7 +109,7 @@
       requirements.size, bestMemory(mappable = false, filter = requirements.memoryTypes)
     )
     checkVkResult vkBindImageMemory(
-      vulkan.device, swapchain.msaaImage, swapchain.msaaMemory, 0
+      engine().vulkan.device, swapchain.msaaImage, swapchain.msaaMemory, 0
     )
     swapchain.msaaImageView =
       svkCreate2DImageView(image = swapchain.msaaImage, format = SURFACE_FORMAT)
@@ -114,11 +117,14 @@
   # create framebuffers
   var actualNFramebuffers: uint32
   checkVkResult vkGetSwapchainImagesKHR(
-    vulkan.device, swapchain.vk, addr(actualNFramebuffers), nil
+    engine().vulkan.device, swapchain.vk, addr(actualNFramebuffers), nil
   )
   var framebuffers = newSeq[VkImage](actualNFramebuffers)
   checkVkResult vkGetSwapchainImagesKHR(
-    vulkan.device, swapchain.vk, addr(actualNFramebuffers), framebuffers.ToCPointer
+    engine().vulkan.device,
+    swapchain.vk,
+    addr(actualNFramebuffers),
+    framebuffers.ToCPointer,
   )
 
   for framebuffer in framebuffers:
@@ -157,10 +163,13 @@
   var commandPoolCreateInfo = VkCommandPoolCreateInfo(
     sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
     flags: toBits [VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT],
-    queueFamilyIndex: vulkan.graphicsQueueFamily,
+    queueFamilyIndex: engine().vulkan.graphicsQueueFamily,
   )
   checkVkResult vkCreateCommandPool(
-    vulkan.device, addr(commandPoolCreateInfo), nil, addr(swapchain.commandBufferPool)
+    engine().vulkan.device,
+    addr(commandPoolCreateInfo),
+    nil,
+    addr(swapchain.commandBufferPool),
   )
   var allocInfo = VkCommandBufferAllocateInfo(
     sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
@@ -169,7 +178,7 @@
     commandBufferCount: INFLIGHTFRAMES,
   )
   checkVkResult vkAllocateCommandBuffers(
-    vulkan.device, addr(allocInfo), swapchain.commandBuffers.ToCPointer
+    engine().vulkan.device, addr(allocInfo), swapchain.commandBuffers.ToCPointer
   )
 
   return swapchain
@@ -179,40 +188,40 @@
     destroySwapchain(swapchain.oldSwapchain)
 
   if swapchain.msaaImage.Valid:
-    vkDestroyImageView(vulkan.device, swapchain.msaaImageView, nil)
-    vkDestroyImage(vulkan.device, swapchain.msaaImage, nil)
-    vkFreeMemory(vulkan.device, swapchain.msaaMemory, nil)
+    vkDestroyImageView(engine().vulkan.device, swapchain.msaaImageView, nil)
+    vkDestroyImage(engine().vulkan.device, swapchain.msaaImage, nil)
+    vkFreeMemory(engine().vulkan.device, swapchain.msaaMemory, nil)
 
   if swapchain.depthImage.Valid:
-    vkDestroyImageView(vulkan.device, swapchain.depthImageView, nil)
-    vkDestroyImage(vulkan.device, swapchain.depthImage, nil)
-    vkFreeMemory(vulkan.device, swapchain.depthMemory, nil)
+    vkDestroyImageView(engine().vulkan.device, swapchain.depthImageView, nil)
+    vkDestroyImage(engine().vulkan.device, swapchain.depthImage, nil)
+    vkFreeMemory(engine().vulkan.device, swapchain.depthMemory, nil)
 
   for fence in swapchain.queueFinishedFence:
-    vkDestroyFence(vulkan.device, fence, nil)
+    vkDestroyFence(engine().vulkan.device, fence, nil)
 
   for semaphore in swapchain.imageAvailableSemaphore:
-    vkDestroySemaphore(vulkan.device, semaphore, nil)
+    vkDestroySemaphore(engine().vulkan.device, semaphore, nil)
 
   for semaphore in swapchain.renderFinishedSemaphore:
-    vkDestroySemaphore(vulkan.device, semaphore, nil)
+    vkDestroySemaphore(engine().vulkan.device, semaphore, nil)
 
   for imageView in swapchain.framebufferViews:
-    vkDestroyImageView(vulkan.device, imageView, nil)
+    vkDestroyImageView(engine().vulkan.device, imageView, nil)
 
   for framebuffer in swapchain.framebuffers:
-    vkDestroyFramebuffer(vulkan.device, framebuffer, nil)
+    vkDestroyFramebuffer(engine().vulkan.device, framebuffer, nil)
 
-  vkDestroyCommandPool(vulkan.device, swapchain.commandBufferPool, nil)
+  vkDestroyCommandPool(engine().vulkan.device, swapchain.commandBufferPool, nil)
 
-  vkDestroySwapchainKHR(vulkan.device, swapchain.vk, nil)
+  vkDestroySwapchainKHR(engine().vulkan.device, swapchain.vk, nil)
 
 proc tryAcquireNextImage(swapchain: Swapchain): Option[VkFramebuffer] =
   if not swapchain.queueFinishedFence[swapchain.currentFiF].await(100_000_000):
     return none(VkFramebuffer)
 
   let nextImageResult = vkAcquireNextImageKHR(
-    vulkan.device,
+    engine().vulkan.device,
     swapchain.vk,
     high(uint64),
     swapchain.imageAvailableSemaphore[swapchain.currentFiF],
@@ -240,7 +249,7 @@
       pSignalSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentFiF]),
     )
   checkVkResult vkQueueSubmit(
-    queue = vulkan.graphicsQueue,
+    queue = engine().vulkan.graphicsQueue,
     submitCount = 1,
     pSubmits = addr(submitInfo),
     fence = swapchain.queueFinishedFence[swapchain.currentFiF],
@@ -255,7 +264,8 @@
     pImageIndices: addr(swapchain.currentFramebufferIndex),
     pResults: nil,
   )
-  let presentResult = vkQueuePresentKHR(vulkan.graphicsQueue, addr(presentInfo))
+  let presentResult =
+    vkQueuePresentKHR(engine().vulkan.graphicsQueue, addr(presentInfo))
 
   if swapchain.oldSwapchain != nil:
     dec swapchain.oldSwapchainCounter
@@ -272,33 +282,32 @@
 # for re-creation with same settings, e.g. window resized
 proc recreateSwapchain*() =
   let newSwapchain = initSwapchain(
-    renderPass = vulkan.swapchain.renderPass,
-    vSync = vulkan.swapchain.vSync,
-    tripleBuffering = vulkan.swapchain.tripleBuffering,
-    oldSwapchain = vulkan.swapchain,
+    renderPass = engine().vulkan.swapchain.renderPass,
+    vSync = engine().vulkan.swapchain.vSync,
+    tripleBuffering = engine().vulkan.swapchain.tripleBuffering,
+    oldSwapchain = engine().vulkan.swapchain,
   )
   if newSwapchain != nil:
-    vulkan.swapchain = newSwapchain
+    engine().vulkan.swapchain = newSwapchain
 
 # for re-creation with different settings
 proc recreateSwapchain*(vSync: bool, tripleBuffering: bool) =
   let newSwapchain = initSwapchain(
-    renderPass = vulkan.swapchain.renderPass,
+    renderPass = engine().vulkan.swapchain.renderPass,
     vSync = vSync,
     tripleBuffering = tripleBuffering,
-    oldSwapchain = vulkan.swapchain,
+    oldSwapchain = engine().vulkan.swapchain,
   )
   if newSwapchain != nil:
-    vulkan.swapchain = newSwapchain
+    engine().vulkan.swapchain = newSwapchain
 
 template withNextFrame*(framebufferName, commandBufferName, body: untyped): untyped =
-  assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  var maybeFramebuffer = tryAcquireNextImage(vulkan.swapchain)
+  var maybeFramebuffer = tryAcquireNextImage(engine().vulkan.swapchain)
   if maybeFramebuffer.isSome:
     block:
       let `framebufferName` {.inject.} = maybeFramebuffer.get
       let `commandBufferName` {.inject.} =
-        vulkan.swapchain.commandBuffers[vulkan.swapchain.currentFiF]
+        engine().vulkan.swapchain.commandBuffers[engine().vulkan.swapchain.currentFiF]
       let beginInfo = VkCommandBufferBeginInfo(
         sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
         flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
@@ -311,6 +320,7 @@
       body
 
       checkVkResult vkEndCommandBuffer(`commandBufferName`)
-      discard swap(swapchain = vulkan.swapchain, commandBuffer = `commandBufferName`)
+      discard
+        swap(swapchain = engine().vulkan.swapchain, commandBuffer = `commandBufferName`)
   else:
     recreateSwapchain()
--- a/semicongine/rendering/vulkan/api.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/vulkan/api.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,10 +1,3 @@
-import std/dynlib
-import std/logging
-import std/macros
-import std/strutils
-import std/typetraits
-import std/tables
-
 type
   VkHandle* = distinct uint
   VkNonDispatchableHandle* = distinct uint
--- a/semicongine/rendering/vulkan_wrappers.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/rendering/vulkan_wrappers.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,4 +1,9 @@
-proc getBestPhysicalDevice(instance: VkInstance): VkPhysicalDevice =
+import std/strformat
+import std/strutils
+
+import ../core
+
+proc getBestPhysicalDevice*(instance: VkInstance): VkPhysicalDevice =
   var nDevices: uint32
   checkVkResult vkEnumeratePhysicalDevices(instance, addr(nDevices), nil)
   var devices = newSeq[VkPhysicalDevice](nDevices)
@@ -29,11 +34,14 @@
 proc svkGetPhysicalDeviceSurfaceSupportKHR*(queueFamily: uint32): bool =
   var presentation = VkBool32(false)
   checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(
-    vulkan.physicalDevice, queueFamily, vulkan.surface, addr(presentation)
+    engine().vulkan.physicalDevice,
+    queueFamily,
+    engine().vulkan.surface,
+    addr(presentation),
   )
   return bool(presentation)
 
-proc getQueueFamily(pDevice: VkPhysicalDevice, qType: VkQueueFlagBits): uint32 =
+proc getQueueFamily*(pDevice: VkPhysicalDevice, qType: VkQueueFlagBits): uint32 =
   var nQueuefamilies: uint32
   vkGetPhysicalDeviceQueueFamilyProperties(pDevice, addr nQueuefamilies, nil)
   var queuFamilies = newSeq[VkQueueFamilyProperties](nQueuefamilies)
@@ -59,11 +67,14 @@
 proc svkGetPhysicalDeviceSurfacePresentModesKHR*(): seq[VkPresentModeKHR] =
   var n_modes: uint32
   checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(
-    vulkan.physicalDevice, vulkan.surface, addr(n_modes), nil
+    engine().vulkan.physicalDevice, engine().vulkan.surface, addr(n_modes), nil
   )
   result = newSeq[VkPresentModeKHR](n_modes)
   checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(
-    vulkan.physicalDevice, vulkan.surface, addr(n_modes), result.ToCPointer
+    engine().vulkan.physicalDevice,
+    engine().vulkan.surface,
+    addr(n_modes),
+    result.ToCPointer,
   )
 
 proc hasValidationLayer*(): bool =
@@ -78,7 +89,7 @@
   return false
 
 proc svkGetPhysicalDeviceProperties*(): VkPhysicalDeviceProperties =
-  vkGetPhysicalDeviceProperties(vulkan.physicalDevice, addr(result))
+  vkGetPhysicalDeviceProperties(engine().vulkan.physicalDevice, addr(result))
 
 proc svkCreateBuffer*(size: uint64, usage: openArray[VkBufferUsageFlagBits]): VkBuffer =
   var createInfo = VkBufferCreateInfo(
@@ -89,7 +100,7 @@
     sharingMode: VK_SHARING_MODE_EXCLUSIVE,
   )
   checkVkResult vkCreateBuffer(
-    device = vulkan.device,
+    device = engine().vulkan.device,
     pCreateInfo = addr(createInfo),
     pAllocator = nil,
     pBuffer = addr(result),
@@ -102,7 +113,7 @@
     memoryTypeIndex: typeIndex,
   )
   checkVkResult vkAllocateMemory(
-    vulkan.device, addr(memoryAllocationInfo), nil, addr(result)
+    engine().vulkan.device, addr(memoryAllocationInfo), nil, addr(result)
   )
 
 proc svkCreate2DImage*(
@@ -114,7 +125,7 @@
 ): VkImage =
   var imageProps: VkImageFormatProperties
   checkVkResult vkGetPhysicalDeviceImageFormatProperties(
-    vulkan.physicalDevice,
+    engine().vulkan.physicalDevice,
     format,
     VK_IMAGE_TYPE_2D,
     VK_IMAGE_TILING_OPTIMAL,
@@ -136,14 +147,14 @@
     sharingMode: VK_SHARING_MODE_EXCLUSIVE,
     samples: samples,
   )
-  checkVkResult vkCreateImage(vulkan.device, addr imageInfo, nil, addr(result))
+  checkVkResult vkCreateImage(engine().vulkan.device, addr imageInfo, nil, addr(result))
 
 proc svkCreate2DImageView*(
     image: VkImage,
     format: VkFormat,
     aspect = VK_IMAGE_ASPECT_COLOR_BIT,
     nLayers = 1'u32,
-    isArray = false
+    isArray = false,
 ): VkImageView =
   var createInfo = VkImageViewCreateInfo(
     sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
@@ -164,7 +175,9 @@
       layerCount: nLayers,
     ),
   )
-  checkVkResult vkCreateImageView(vulkan.device, addr(createInfo), nil, addr(result))
+  checkVkResult vkCreateImageView(
+    engine().vulkan.device, addr(createInfo), nil, addr(result)
+  )
 
 proc svkCreateFramebuffer*(
     renderpass: VkRenderPass, width, height: uint32, attachments: openArray[VkImageView]
@@ -179,14 +192,14 @@
     layers: 1,
   )
   checkVkResult vkCreateFramebuffer(
-    vulkan.device, addr(framebufferInfo), nil, addr(result)
+    engine().vulkan.device, addr(framebufferInfo), nil, addr(result)
   )
 
 proc svkGetBufferMemoryRequirements*(
     buffer: VkBuffer
 ): tuple[size: uint64, alignment: uint64, memoryTypes: seq[uint32]] =
   var reqs: VkMemoryRequirements
-  vkGetBufferMemoryRequirements(vulkan.device, buffer, addr(reqs))
+  vkGetBufferMemoryRequirements(engine().vulkan.device, buffer, addr(reqs))
   result.size = reqs.size
   result.alignment = reqs.alignment
   for i in 0'u32 ..< VK_MAX_MEMORY_TYPES:
@@ -197,7 +210,7 @@
     image: VkImage
 ): tuple[size: uint64, alignment: uint64, memoryTypes: seq[uint32]] =
   var reqs: VkMemoryRequirements
-  vkGetImageMemoryRequirements(vulkan.device, image, addr(reqs))
+  vkGetImageMemoryRequirements(engine().vulkan.device, image, addr(reqs))
   result.size = reqs.size
   result.alignment = reqs.alignment
   for i in 0'u32 ..< VK_MAX_MEMORY_TYPES:
@@ -213,24 +226,29 @@
       else:
         VkFenceCreateFlags(0),
   )
-  checkVkResult vkCreateFence(vulkan.device, addr(fenceInfo), nil, addr(result))
+  checkVkResult vkCreateFence(
+    engine().vulkan.device, addr(fenceInfo), nil, addr(result)
+  )
 
 proc svkCreateSemaphore*(): VkSemaphore =
   var semaphoreInfo =
     VkSemaphoreCreateInfo(sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO)
-  checkVkResult vkCreateSemaphore(vulkan.device, addr(semaphoreInfo), nil, addr(result))
+  checkVkResult vkCreateSemaphore(
+    engine().vulkan.device, addr(semaphoreInfo), nil, addr(result)
+  )
 
 proc await*(fence: VkFence, timeout = high(uint64)): bool =
-  let waitResult = vkWaitForFences(vulkan.device, 1, addr(fence), false, timeout)
+  let waitResult =
+    vkWaitForFences(engine().vulkan.device, 1, addr(fence), false, timeout)
   if waitResult == VK_TIMEOUT:
     return false
   checkVkResult waitResult
   return true
 
 proc svkResetFences*(fence: VkFence) =
-  checkVkResult vkResetFences(vulkan.device, 1, addr(fence))
+  checkVkResult vkResetFences(engine().vulkan.device, 1, addr(fence))
 
-proc svkCmdBindDescriptorSets(
+proc svkCmdBindDescriptorSets*(
     commandBuffer: VkCommandBuffer,
     descriptorSets: openArray[VkDescriptorSet],
     layout: VkPipelineLayout,
@@ -246,7 +264,7 @@
     pDynamicOffsets = nil,
   )
 
-proc svkCmdBindDescriptorSet(
+proc svkCmdBindDescriptorSet*(
     commandBuffer: VkCommandBuffer,
     descriptorSet: VkDescriptorSet,
     index: DescriptorSetIndex,
@@ -263,7 +281,7 @@
     pDynamicOffsets = nil,
   )
 
-proc svkCreateRenderPass(
+proc svkCreateRenderPass*(
     attachments: openArray[VkAttachmentDescription],
     colorAttachments: openArray[VkAttachmentReference],
     depthAttachments: openArray[VkAttachmentReference],
@@ -292,11 +310,15 @@
     dependencyCount: uint32(dependencies.len),
     pDependencies: dependencies.ToCPointer,
   )
-  checkVkResult vkCreateRenderPass(vulkan.device, addr(createInfo), nil, addr(result))
+  checkVkResult vkCreateRenderPass(
+    engine().vulkan.device, addr(createInfo), nil, addr(result)
+  )
 
 proc bestMemory*(mappable: bool, filter: seq[uint32] = @[]): uint32 =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
-  vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
+  vkGetPhysicalDeviceMemoryProperties(
+    engine().vulkan.physicalDevice, addr(physicalProperties)
+  )
 
   var maxScore: float = -1
   var maxIndex: uint32 = 0
@@ -329,10 +351,10 @@
       createInfo = VkCommandPoolCreateInfo(
         sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
         flags: VkCommandPoolCreateFlags(0),
-        queueFamilyIndex: vulkan.graphicsQueueFamily,
+        queueFamilyIndex: engine().vulkan.graphicsQueueFamily,
       )
     checkVkResult vkCreateCommandPool(
-      vulkan.device, addr createInfo, nil, addr(commandBufferPool)
+      engine().vulkan.device, addr createInfo, nil, addr(commandBufferPool)
     )
     var
       `cmd` {.inject.}: VkCommandBuffer
@@ -342,7 +364,9 @@
         level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
         commandBufferCount: 1,
       )
-    checkVkResult vulkan.device.vkAllocateCommandBuffers(addr allocInfo, addr(`cmd`))
+    checkVkResult engine().vulkan.device.vkAllocateCommandBuffers(
+      addr allocInfo, addr(`cmd`)
+    )
     var beginInfo = VkCommandBufferBeginInfo(
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
       flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
@@ -359,10 +383,12 @@
     )
 
     var fence = svkCreateFence()
-    checkVkResult vkQueueSubmit(vulkan.graphicsQueue, 1, addr(submitInfo), fence)
+    checkVkResult vkQueueSubmit(
+      engine().vulkan.graphicsQueue, 1, addr(submitInfo), fence
+    )
     discard fence.await()
-    vkDestroyFence(vulkan.device, fence, nil)
-    vkDestroyCommandPool(vulkan.device, commandBufferPool, nil)
+    vkDestroyFence(engine().vulkan.device, fence, nil)
+    vkDestroyCommandPool(engine().vulkan.device, commandBufferPool, nil)
 
 template withStagingBuffer*[T: (VkBuffer, uint64) | (VkImage, uint32, uint32, uint32)](
     target: T, bufferSize: uint64, dataPointer, body: untyped
@@ -373,14 +399,16 @@
   let memoryType = bestMemory(mappable = true, filter = memoryRequirements.memoryTypes)
   let stagingMemory = svkAllocateMemory(memoryRequirements.size, memoryType)
   checkVkResult vkMapMemory(
-    device = vulkan.device,
+    device = engine().vulkan.device,
     memory = stagingMemory,
     offset = 0'u64,
     size = VK_WHOLE_SIZE,
     flags = VkMemoryMapFlags(0),
     ppData = addr(`dataPointer`),
   )
-  checkVkResult vkBindBufferMemory(vulkan.device, stagingBuffer, stagingMemory, 0)
+  checkVkResult vkBindBufferMemory(
+    engine().vulkan.device, stagingBuffer, stagingMemory, 0
+  )
 
   block:
     # usually: write data to dataPointer in body
@@ -391,7 +419,7 @@
     memory: stagingMemory,
     size: VK_WHOLE_SIZE,
   )
-  checkVkResult vkFlushMappedMemoryRanges(vulkan.device, 1, addr(stagingRange))
+  checkVkResult vkFlushMappedMemoryRanges(engine().vulkan.device, 1, addr(stagingRange))
 
   withSingleUseCommandBuffer(commandBuffer):
     when T is (VkBuffer, uint64):
@@ -443,5 +471,53 @@
         pRegions = addr(region),
       )
 
-  vkDestroyBuffer(vulkan.device, stagingBuffer, nil)
-  vkFreeMemory(vulkan.device, stagingMemory, nil)
+  vkDestroyBuffer(engine().vulkan.device, stagingBuffer, nil)
+  vkFreeMemory(engine().vulkan.device, stagingMemory, nil)
+
+func getDescriptorType*[T](): VkDescriptorType {.compileTIme.} =
+  when T is ImageObject:
+    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
+  elif T is GPUValue:
+    when getBufferType(default(T)) in [UniformBuffer, UniformBufferMapped]:
+      VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
+    elif getBufferType(default(T)) in [StorageBuffer, StorageBufferMapped]:
+      VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
+    else:
+      {.error: "Unsupported descriptor type: " & $T.}
+  elif T is array:
+    when elementType(default(T)) is ImageObject:
+      VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
+    elif elementType(default(T)) is GPUValue:
+      when getBufferType(default(elementType(default(T)))) in
+          [UniformBuffer, UniformBufferMapped]:
+        VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
+      elif getBufferType(default(elementType(default(T)))) in
+          [StorageBuffer, StorageBufferMapped]:
+        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
+      else:
+        {.error: "Unsupported descriptor type: " & $T.}
+    else:
+      {.error: "Unsupported descriptor type: " & $T.}
+  else:
+    {.error: "Unsupported descriptor type: " & $T.}
+
+func getDescriptorCount*[T](): uint32 {.compileTIme.} =
+  when T is array:
+    len(T)
+  else:
+    1
+
+func getBindingNumber*[T](field: static string): uint32 {.compileTime.} =
+  var c = 0'u32
+  var found = false
+  for name, value in fieldPairs(default(T)):
+    when name == field:
+      result = c
+      found = true
+    else:
+      inc c
+  assert found, "Field '" & field & "' of descriptor '" & $T & "' not found"
+
+proc currentFiF*(): int =
+  assert engine().vulkan.swapchain != nil, "Swapchain has not been initialized yet"
+  engine().vulkan.swapchain.currentFiF
--- a/semicongine/resources.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/resources.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,7 +1,6 @@
 import std/algorithm
 import std/dirs
 import std/json
-import std/parsecfg
 import std/os
 import std/sequtils
 import std/sets
--- a/semicongine/text.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/text.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -11,116 +11,10 @@
 import ./core
 import ./resources
 import ./rendering
-import ./rendering/vulkan/api
 import ./image
 import ./contrib/algorithms/texture_packing
-
-type
-  GlyphQuad[MaxGlyphs: static int] = object
-    pos: array[MaxGlyphs, Vec4f]
-      # vertex offsets to glyph center: [left, bottom, right, top]
-    uv: array[MaxGlyphs, Vec4f] # [left, bottom, right, top]
-
-  TextRendering* = object
-    aspectRatio*: float32
-
-  GlyphDescriptorSet*[MaxGlyphs: static int] = object
-    fontAtlas*: Image[Gray]
-    glyphquads*: GPUValue[GlyphQuad[MaxGlyphs], StorageBuffer]
-
-  GlyphShader*[MaxGlyphs: static int] = object
-    position {.InstanceAttribute.}: Vec3f
-    color {.InstanceAttribute.}: Vec4f
-    scale {.InstanceAttribute.}: float32
-    glyphIndex {.InstanceAttribute.}: uint16
-    textRendering {.PushConstant.}: TextRendering
-
-    fragmentUv {.Pass.}: Vec2f
-    fragmentColor {.PassFlat.}: Vec4f
-    outColor {.ShaderOutput.}: Vec4f
-    glyphData {.DescriptorSet: 3.}: GlyphDescriptorSet[MaxGlyphs]
-    vertexCode* =
-      """
-const int[6] indices = int[](0, 1, 2, 2, 3, 0);
-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];
-  vec3 vertexPos = vec3(
-    glyphquads.pos[glyphIndex][i_x[vertexI]] * scale / textRendering.aspectRatio,
-    glyphquads.pos[glyphIndex][i_y[vertexI]] * scale,
-    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);
-  vec2 uv = vec2(glyphquads.uv[glyphIndex][i_x[vertexI]], glyphquads.uv[glyphIndex][i_y[vertexI]]);
-  fragmentUv = uv;
-  fragmentColor = color;
-}  """
-    fragmentCode* =
-      """void main() {
-    float a = texture(fontAtlas, fragmentUv).r;
-    outColor = vec4(fragmentColor.rgb, fragmentColor.a * a);
-}"""
-
-  FontObj*[MaxGlyphs: static int] = object
-    advance*: Table[Rune, float32]
-    kerning*: Table[(Rune, Rune), float32]
-    leftBearing*: Table[Rune, float32]
-    lineAdvance*: float32
-    lineHeight*: float32 # like lineAdvance - lineGap
-    ascent*: float32 # from baseline to highest glyph
-    descent*: float32 # from baseline to lowest glyph
-    xHeight*: float32 # from baseline to height of lowercase x
-    descriptorSet*: DescriptorSetData[GlyphDescriptorSet[MaxGlyphs]]
-    descriptorGlyphIndex: Table[Rune, uint16]
-    descriptorGlyphIndexRev*: Table[uint16, Rune] # only used for debugging atm
-    fallbackCharacter: Rune
-
-  Font*[MaxGlyphs: static int] = ref FontObj[MaxGlyphs]
-
-  TextHandle* = object
-    index: uint32
-    generation: uint32
-
-  TextAlignment* = enum
-    Left
-    Center
-    Right
-
-  Text = object
-    bufferOffset: int
-    text: seq[Rune]
-    position: Vec3f = vec3()
-    alignment: TextAlignment = Left
-    anchor: Vec2f = vec2()
-    scale: float32 = 0
-    color: Vec4f = vec4(1, 1, 1, 1)
-    capacity: int
-
-  TextBuffer*[MaxGlyphs: static int] = object
-    cursor: int
-    generation: uint32
-    font*: Font[MaxGlyphs]
-    baseScale*: float32
-    position*: GPUArray[Vec3f, VertexBufferMapped]
-    color*: GPUArray[Vec4f, VertexBufferMapped]
-    scale*: GPUArray[float32, VertexBufferMapped]
-    glyphIndex*: GPUArray[uint16, VertexBufferMapped]
-    texts: seq[Text]
-
-proc `=copy`[MaxGlyphs: static int](
-  dest: var FontObj[MaxGlyphs], source: FontObj[MaxGlyphs]
-) {.error.}
-
-proc `=copy`[MaxGlyphs: static int](
-  dest: var TextBuffer[MaxGlyphs], source: TextBuffer[MaxGlyphs]
-) {.error.}
-
-include ./text/font
+import ./rendering/renderer
+import ./text/font
 
 proc initTextBuffer*[MaxGlyphs: static int](
     font: Font[MaxGlyphs],
--- a/semicongine/text/font.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/text/font.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -1,3 +1,16 @@
+import std/os
+import std/strutils
+import std/sequtils
+import std/unicode
+import std/streams
+import std/logging
+import std/tables
+
+import ../core
+import ../resources
+import ../rendering/renderer
+import ../contrib/algorithms/texture_packing
+
 {.emit: "#define STBTT_STATIC".}
 {.emit: "#define STB_TRUETYPE_IMPLEMENTATION".}
 {.
--- a/semicongine/thirdparty/parsetoml.nim	Wed Jan 01 19:36:55 2025 +0700
+++ b/semicongine/thirdparty/parsetoml.nim	Thu Jan 09 01:00:58 2025 +0700
@@ -795,7 +795,6 @@
       raise newTomlError(state, "leading zero not allowed")
     else:
       raise newTomlError(state, "illegal character")
-    break
 
 proc parseFloat(state: var ParserState, intPart: int, forcedSign: Sign): TomlValueRef =
   var
@@ -935,7 +934,6 @@
               else:
                 curSum,
           )
-        break
     of '+', '-':
       forcedSign = if nextChar == '+': Pos else: Neg
       continue