# HG changeset patch # User sam # Date 1736438627 -25200 # Node ID 3b8a736c45a70e36b5198a6c17d46e9f0cbad0de # Parent 6f0c1b3474037896cc93fac13a64f5063af7a144 did: put almost all global state into a single struct diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine.nim --- 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) diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/audio/mixer_module.nim --- 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() diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/background_loader.nim --- 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: diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/contrib/steam.nim --- 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() diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/core.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/core/types.nim --- 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] diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/core/utils.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/gltf.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/input.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/loaders.nim --- 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) diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering.nim --- 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() diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/memory.nim --- /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) diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/platform/linux.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/renderer.nim --- 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) diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/shaders.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/swapchain.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/vulkan/api.nim --- 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 diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/rendering/vulkan_wrappers.nim --- 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" diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/storage.nim --- 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) = diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/text.nim --- 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], diff -r 6f0c1b347403 -r 3b8a736c45a7 semicongine/text/font.nim --- 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".} diff -r 6f0c1b347403 -r 3b8a736c45a7 tests/test_audio.nim --- 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) diff -r 6f0c1b347403 -r 3b8a736c45a7 tests/test_gltf.nim --- 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() diff -r 6f0c1b347403 -r 3b8a736c45a7 tests/test_rendering.nim --- 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() diff -r 6f0c1b347403 -r 3b8a736c45a7 tests/test_storage.nim --- 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() diff -r 6f0c1b347403 -r 3b8a736c45a7 tests/test_text.nim --- 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()