# HG changeset patch # User sam # Date 1716403450 -25200 # Node ID ae54dbb08e53d0e6933fb319f0fcb9e6e467498e # Parent 073ce95ae5c7358e6b015ce5833615f5a0cb111a# Parent 0811ddd7326d51dc21f23340a6b8deacec0c25b1 merge into git mirror diff -r 073ce95ae5c7 -r ae54dbb08e53 .gitignore diff -r 073ce95ae5c7 -r ae54dbb08e53 .hgtags --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Thu May 23 01:44:10 2024 +0700 @@ -0,0 +1,1 @@ +2ba3f18e7cad4339c3404d2c49580bde83b285f6 hg diff -r 073ce95ae5c7 -r ae54dbb08e53 examples/E01_hello_triangle.nim --- a/examples/E01_hello_triangle.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/examples/E01_hello_triangle.nim Thu May 23 01:44:10 2024 +0700 @@ -5,6 +5,7 @@ # shader setup const shaderConfiguration = createShaderConfiguration( + name = "default shader", inputs = [ attr[Vec3f]("position"), attr[Vec4f]("color"), @@ -29,7 +30,7 @@ myengine.initRenderer({VERTEX_COLORED_MATERIAL: shaderConfiguration}, inFlightFrames = 2) myengine.loadScene(scene) -while myengine.updateInputs() == Running and not myengine.keyWasPressed(Escape): +while myengine.UpdateInputs() and not KeyWasPressed(Escape): transform[Vec3f](scene.meshes[0][], "position", scale(1.001, 1.001)) myengine.renderScene(scene) diff -r 073ce95ae5c7 -r ae54dbb08e53 examples/E02_squares.nim --- a/examples/E02_squares.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/examples/E02_squares.nim Thu May 23 01:44:10 2024 +0700 @@ -2,7 +2,7 @@ import std/tables import std/random -import ../src/semicongine +import ../semicongine when isMainModule: @@ -44,15 +44,16 @@ const shaderConfiguration = createShaderConfiguration( - inputs=[ + name = "default shader", + inputs = [ attr[Vec3f]("position"), - attr[Vec4f]("color", memoryPerformanceHint=PreferFastWrite), + attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite), attr[uint32]("index"), ], - intermediates=[attr[Vec4f]("outcolor")], - uniforms=[attr[float32]("time")], - outputs=[attr[Vec4f]("color")], - vertexCode=""" + intermediates = [attr[Vec4f]("outcolor")], + uniforms = [attr[float32]("time")], + outputs = [attr[Vec4f]("color")], + vertexCode = """ float pos_weight = index / 100.0; // add some gamma correction? float t = sin(Uniforms.time * 0.5) * 0.5 + 0.5; float v = min(1, max(0, pow(pos_weight - t, 2))); @@ -60,23 +61,28 @@ outcolor = vec4(color.r, color.g, v * 0.5, 1); gl_Position = vec4(position, 1.0); """, - fragmentCode="color = outcolor;", + fragmentCode = "color = outcolor;", ) + let matDef = MaterialType(name: "default", vertexAttributes: { + "position": Vec3F32, + "color": Vec4F32, + "index": UInt32, + }.toTable) var squaremesh = newMesh( - positions=vertices, - indices=indices, - colors=colors, - material=Material(name: "default") + positions = vertices, + indices = indices, + colors = colors, ) squaremesh[].initVertexAttribute("index", iValues.toSeq) + squaremesh.material = matDef.initMaterialData(name = "default") var myengine = initEngine("Squares") - myengine.initRenderer({"default": shaderConfiguration}.toTable) + myengine.initRenderer({matDef: shaderConfiguration}) var scene = Scene(name: "scene", meshes: @[squaremesh]) scene.addShaderGlobal("time", 0.0'f32) - myengine.addScene(scene) - while myengine.updateInputs() == Running and not myengine.keyWasPressed(Escape): + myengine.loadScene(scene) + while myengine.UpdateInputs() and not KeyWasPressed(Escape): scene.setShaderGlobal("time", getShaderGlobal[float32](scene, "time") + 0.0005'f) myengine.renderScene(scene) diff -r 073ce95ae5c7 -r ae54dbb08e53 examples/E03_hello_cube.nim --- a/examples/E03_hello_cube.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/examples/E03_hello_cube.nim Thu May 23 01:44:10 2024 +0700 @@ -1,17 +1,7 @@ -# -# TODO: Needs Depth-Buffer first! -# -# -# -# -# -# - - import std/tables import std/times -import ../src/semicongine +import ../semicongine const TopLeftFront = newVec3f(-0.5'f32, -0.5'f32, -0.5'f32) @@ -24,11 +14,11 @@ BottomLeftBack = newVec3f(0.5'f32, 0.5'f32, 0.5'f32) const cube_pos = @[ - TopLeftFront, TopRightFront, BottomRightFront, BottomLeftFront, # front - TopLeftBack, TopRightBack, BottomRightBack, BottomLeftBack, # back - TopLeftBack, TopLeftFront, BottomLeftFront, BottomLeftBack, # left - TopRightBack, TopRightFront, BottomRightFront, BottomRightBack, # right - TopLeftBack, TopRightBack, TopRightFront, TopLeftFront, # top + TopLeftFront, TopRightFront, BottomRightFront, BottomLeftFront, # front + TopLeftBack, TopRightBack, BottomRightBack, BottomLeftBack, # back + TopLeftBack, TopLeftFront, BottomLeftFront, BottomLeftBack, # left + TopRightBack, TopRightFront, BottomRightFront, BottomRightBack, # right + TopLeftBack, TopRightBack, TopRightFront, TopLeftFront, # top BottomLeftFront, BottomRightFront, BottomRightBack, BottomLeftBack, # bottom ] R = newVec4f(1, 0, 0, 1) @@ -54,34 +44,36 @@ const shaderConfiguration = createShaderConfiguration( - inputs=[ + name = "default shader", + inputs = [ attr[Vec3f]("position"), - attr[Vec4f]("color", memoryPerformanceHint=PreferFastWrite), + attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite), ], - intermediates=[attr[Vec4f]("outcolor")], - uniforms=[ + intermediates = [attr[Vec4f]("outcolor")], + uniforms = [ attr[Mat4]("projection"), attr[Mat4]("view"), attr[Mat4]("model"), ], - outputs=[attr[Vec4f]("color")], - vertexCode="""outcolor = color; gl_Position = (Uniforms.projection * Uniforms.view * Uniforms.model) * vec4(position, 1);""", - fragmentCode="color = outcolor;", + outputs = [attr[Vec4f]("color")], + vertexCode = """outcolor = color; gl_Position = (Uniforms.projection * Uniforms.view * Uniforms.model) * vec4(position, 1);""", + fragmentCode = "color = outcolor;", ) - myengine.initRenderer({"default": shaderConfiguration}.toTable) - var cube = Scene(name: "scene", meshes: @[newMesh(positions=cube_pos, indices=tris, colors=cube_color, material=Material(name: "default"))]) + var matDef = MaterialType(name: "default material", vertexAttributes: {"position": Vec3F32, "color": Vec4F32}.toTable) + var cube = Scene(name: "scene", meshes: @[newMesh(positions = cube_pos, indices = tris, colors = cube_color, material = matDef.initMaterialData(name = "default"))]) cube.addShaderGlobal("projection", Unit4f32) cube.addShaderGlobal("view", Unit4f32) cube.addShaderGlobal("model", Unit4f32) - myengine.addScene(cube) + myengine.initRenderer({matDef: shaderConfiguration}) + myengine.loadScene(cube) var t: float32 = cpuTime() - while myengine.updateInputs() == Running and not myengine.keyWasPressed(Escape): + while myengine.UpdateInputs() and not KeyWasPressed(Escape): setShaderGlobal(cube, "model", translate(0'f32, 0'f32, 10'f32) * rotate(t, Yf32)) setShaderGlobal(cube, "projection", perspective( float32(PI / 4), - float32(myengine.getWindow().size[0]) / float32(myengine.getWindow().size[0]), + float32(myengine.GetWindow().size[0]) / float32(myengine.GetWindow().size[1]), 0.1'f32, 100'f32 ) diff -r 073ce95ae5c7 -r ae54dbb08e53 examples/E04_input.nim --- a/examples/E04_input.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/examples/E04_input.nim Thu May 23 01:44:10 2024 +0700 @@ -3,7 +3,7 @@ import std/typetraits import std/math -import ../src/semicongine +import ../semicongine const arrow = @[ @@ -108,33 +108,36 @@ # define mesh objects var - material = Material(name: "default") + matDef = MaterialType(name: "default", vertexAttributes: { + "position": Vec3F32, + "color": Vec4F32, + }.toTable) cursormesh = newMesh( - positions=positions, - colors=arrow_colors, - material=material, + positions = positions, + colors = arrow_colors, + material = matDef.initMaterialData(), ) keyboardmesh = newMesh( - positions=keyvertexpos, - colors=keyvertexcolor, - indices=keymeshindices, - material=material, + positions = keyvertexpos, + colors = keyvertexcolor, + indices = keymeshindices, + material = matDef.initMaterialData(), ) backgroundmesh = newMesh( - positions= @[ + positions = @[ newVec3f(0'f32, 0'f32), newVec3f(1'f32, 0'f32), newVec3f(1'f32, 1'f32), newVec3f(0'f32, 1'f32), ], - colors= @[ + colors = @[ backgroundColor, backgroundColor, backgroundColor, backgroundColor, ], - indices= @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], - material=material, + indices = @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], + material = matDef.initMaterialData(), ) # define mesh objects @@ -148,51 +151,52 @@ # shaders const shaderConfiguration = createShaderConfiguration( - inputs=[ + name = "default shader", + inputs = [ attr[Vec3f]("position"), - attr[Vec4f]("color", memoryPerformanceHint=PreferFastWrite), - attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite, perInstance=true), + attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite), + attr[Mat4]("transform", memoryPerformanceHint = PreferFastWrite, perInstance = true), ], - intermediates=[attr[Vec4f]("outcolor")], - uniforms=[attr[Mat4]("projection")], - outputs=[attr[Vec4f]("color")], - vertexCode="""outcolor = color; gl_Position = vec4(position, 1) * (transform * Uniforms.projection);""", - fragmentCode="color = outcolor;", + intermediates = [attr[Vec4f]("outcolor")], + uniforms = [attr[Mat4]("projection")], + outputs = [attr[Vec4f]("color")], + vertexCode = """outcolor = color; gl_Position = vec4(position, 1) * (transform * Uniforms.projection);""", + fragmentCode = "color = outcolor;", ) # set up rendering - myengine.initRenderer({"default": shaderConfiguration}.toTable) + myengine.initRenderer({matDef: shaderConfiguration}) scene.addShaderGlobal("projection", Unit4f32) - myengine.addScene(scene) - myengine.hideSystemCursor() + myengine.loadScene(scene) + myengine.HideSystemCursor() # mainloop - while myengine.updateInputs() == Running: - if myengine.windowWasResized(): + while myengine.UpdateInputs(): + if WindowWasResized(): scene.setShaderGlobal("projection", ortho( - 0, float32(myengine.getWindow().size[0]), - 0, float32(myengine.getWindow().size[1]), + 0, float32(myengine.GetWindow().size[0]), + 0, float32(myengine.GetWindow().size[1]), 0, 1, ) ) let - winsize = myengine.getWindow().size + winsize = myengine.GetWindow().size center = translate(float32(winsize[0]) / 2'f32, float32(winsize[1]) / 2'f32, 0.1'f32) keyboardmesh.transform = keyboard_center * center backgroundmesh.transform = scale(float32(winsize[0]), float32(winsize[1]), 1'f32) - let mousePos = translate(myengine.mousePosition().x + 20, myengine.mousePosition().y + 20, 0'f32) + let mousePos = translate(MousePosition().x + 20, MousePosition().y + 20, 0'f32) cursormesh.transform = mousePos for (index, key) in enumerate(keyIndices): - if myengine.keyWasPressed(key): + if KeyWasPressed(key): let baseIndex = index * 4 keyboardmesh["color", baseIndex + 0] = activeColor keyboardmesh["color", baseIndex + 1] = activeColor keyboardmesh["color", baseIndex + 2] = activeColor keyboardmesh["color", baseIndex + 3] = activeColor - if myengine.keyWasReleased(key): + if KeyWasReleased(key): let baseIndex = index * 4 keyboardmesh["color", baseIndex + 0] = baseColor keyboardmesh["color", baseIndex + 1] = baseColor diff -r 073ce95ae5c7 -r ae54dbb08e53 examples/E10_pong.nim --- a/examples/E10_pong.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/examples/E10_pong.nim Thu May 23 01:44:10 2024 +0700 @@ -1,16 +1,19 @@ import std/times import std/tables -import ../src/semicongine +import ../semicongine let - barcolor = hexToColorAlpha("5A3F00").gamma(2.2).colorToHex() + barcolor = toRGBA("5A3F00").toSRGB().colorToHex() barSize = 0.1'f barWidth = 0.01'f - ballcolor = hexToColorAlpha("B17F08").gamma(2.2).colorToHex() + ballcolor = toRGBA("B17F08").toSRGB().colorToHex() ballSize = 0.01'f ballSpeed = 60'f - material = Material(name: "default") + matDef = MaterialType(name: "default", vertexAttributes: { + "position": Vec3F32, + "color": Vec4F32, + }.toTable) var level: Scene @@ -19,59 +22,60 @@ when isMainModule: var myengine = initEngine("Pong") - var player = rect(color=barcolor, width=barWidth, height=barSize) - player.material = material - var ball = circle(color=ballcolor) - ball.material = material + var player = rect(color = barcolor, width = barWidth, height = barSize) + player.material = matDef.initMaterialData(name = "player material") + var ball = circle(color = ballcolor) + ball.material = matDef.initMaterialData(name = "player material") level = Scene(name: "scene", meshes: @[ball, player]) const shaderConfiguration = createShaderConfiguration( - inputs=[ + name = "default shader", + inputs = [ attr[Vec3f]("position"), - attr[Vec4f]("color", memoryPerformanceHint=PreferFastWrite), - attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite, perInstance=true), + attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite), + attr[Mat4]("transform", memoryPerformanceHint = PreferFastWrite, perInstance = true), ], - intermediates=[attr[Vec4f]("outcolor")], - uniforms=[attr[Mat4]("projection")], - outputs=[attr[Vec4f]("color")], - vertexCode="""outcolor = color; gl_Position = vec4(position, 1) * (transform * Uniforms.projection);""", - fragmentCode="color = outcolor;", + intermediates = [attr[Vec4f]("outcolor")], + uniforms = [attr[Mat4]("projection")], + outputs = [attr[Vec4f]("color")], + vertexCode = """outcolor = color; gl_Position = vec4(position, 1) * (transform * Uniforms.projection);""", + fragmentCode = "color = outcolor;", ) # set up rendering - myengine.initRenderer({"default": shaderConfiguration}.toTable) + myengine.initRenderer({matDef: shaderConfiguration}) level.addShaderGlobal("projection", Unit4f32) - myengine.addScene(level) + myengine.loadScene(level) var - winsize = myengine.getWindow().size + winsize = myengine.GetWindow().size height = float32(winsize[1]) / float32(winsize[0]) width = 1'f currentTime = cpuTime() showSystemCursor = true fullscreen = false - while myengine.updateInputs() == Running and not myengine.keyIsDown(Escape): - if myengine.keyWasPressed(C): + while myengine.UpdateInputs() and not KeyIsDown(Escape): + if KeyWasPressed(C): if showSystemCursor: - myengine.hideSystemCursor() + myengine.HideSystemCursor() else: - myengine.showSystemCursor() + myengine.ShowSystemCursor() showSystemCursor = not showSystemCursor - if myengine.keyWasPressed(F): + if KeyWasPressed(F): fullscreen = not fullscreen - myengine.fullscreen(fullscreen) + myengine.Fullscreen = fullscreen let dt: float32 = cpuTime() - currentTime currentTime = cpuTime() - if myengine.windowWasResized(): - winsize = myengine.getWindow().size + if WindowWasResized(): + winsize = myengine.GetWindow().size height = float32(winsize[1]) / float32(winsize[0]) width = 1'f setShaderGlobal(level, "projection", ortho(0, width, 0, height, 0, 1)) - if myengine.keyIsDown(Down) and (player.transform.col(3).y + barSize/2) < height: + if KeyIsDown(Down) and (player.transform.col(3).y + barSize/2) < height: player.transform = player.transform * translate(0'f, 1'f * dt, 0'f) - if myengine.keyIsDown(Up) and (player.transform.col(3).y - barSize/2) > 0: + if KeyIsDown(Up) and (player.transform.col(3).y - barSize/2) > 0: player.transform = player.transform * translate(0'f, -1'f * dt, 0'f) # bounce level diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine.nim --- a/semicongine.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine.nim Thu May 23 01:44:10 2024 +0700 @@ -10,6 +10,7 @@ import semicongine/collision import semicongine/scene import semicongine/events +import semicongine/input import semicongine/material import semicongine/mesh import semicongine/noise @@ -29,6 +30,7 @@ export collision export scene export events +export input export material export mesh export noise diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/audio.nim --- a/semicongine/audio.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/audio.nim Thu May 23 01:44:10 2024 +0700 @@ -1,4 +1,5 @@ import std/monotimes +import std/strformat import std/times import std/tables import std/locks @@ -43,7 +44,7 @@ currentBuffer: int lastUpdate: MonoTime -proc initMixer*(): Mixer = +proc initMixer(): Mixer = result = Mixer( tracks: {"": Track(level: 1'f)}.toTable, level: 1'f, @@ -59,25 +60,25 @@ bufferaddresses.add (addr mixer.buffers[i]) mixer.device = openSoundDevice(AUDIO_SAMPLE_RATE, bufferaddresses) -proc loadSound*(mixer: var Mixer, name: string, resource: string) = +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: Sound) = +proc AddSound*(mixer: var Mixer, name: string, sound: Sound) = assert not (name in mixer.sounds) mixer.sounds[name] = sound -proc replaceSound*(mixer: var Mixer, name: string, sound: Sound) = +proc ReplaceSound*(mixer: var Mixer, name: string, sound: Sound) = assert (name in mixer.sounds) mixer.sounds[name] = sound -proc addTrack*(mixer: var Mixer, name: string, level: Level = 1'f) = +proc AddTrack*(mixer: var Mixer, name: string, level: Level = 1'f) = assert not (name in mixer.tracks) mixer.lock.withLock(): mixer.tracks[name] = Track(level: level) -proc play*(mixer: var Mixer, soundName: string, track = "", stopOtherSounds = false, loop = false, levelLeft, levelRight: Level): uint64 = - assert track in mixer.tracks +proc Play*(mixer: var Mixer, soundName: string, track = "", stopOtherSounds = false, loop = false, levelLeft, levelRight: Level): uint64 = + assert track in mixer.tracks, &"Track '{track}' does not exists" assert soundName in mixer.sounds, soundName & " not loaded" mixer.lock.withLock(): if stopOtherSounds: @@ -93,8 +94,8 @@ result = mixer.playbackCounter inc mixer.playbackCounter -proc play*(mixer: var Mixer, soundName: string, track = "", stopOtherSounds = false, loop = false, level: Level = 1'f): uint64 = - play( +proc Play*(mixer: var Mixer, soundName: string, track = "", stopOtherSounds = false, loop = false, level: Level = 1'f): uint64 = + Play( mixer = mixer, soundName = soundName, track = track, @@ -104,73 +105,73 @@ levelRight = level ) -proc stop*(mixer: var Mixer) = +proc Stop*(mixer: var Mixer) = mixer.lock.withLock(): for track in mixer.tracks.mvalues: track.playing.clear() -proc getLevel*(mixer: var Mixer): Level = mixer.level -proc getLevel*(mixer: var Mixer, track: string): Level = mixer.tracks[track].level -proc getLevel*(mixer: var Mixer, playbackId: uint64): (Level, Level) = +proc GetLevel*(mixer: var Mixer): Level = mixer.level +proc GetLevel*(mixer: var Mixer, track: string): Level = mixer.tracks[track].level +proc GetLevel*(mixer: var Mixer, playbackId: uint64): (Level, Level) = for track in mixer.tracks.mvalues: if playbackId in track.playing: return (track.playing[playbackId].levelLeft, track.playing[playbackId].levelRight) -proc setLevel*(mixer: var Mixer, level: Level) = mixer.level = level -proc setLevel*(mixer: var Mixer, track: string, level: Level) = +proc SetLevel*(mixer: var Mixer, level: Level) = mixer.level = level +proc SetLevel*(mixer: var Mixer, track: string, level: Level) = mixer.lock.withLock(): mixer.tracks[track].level = level -proc setLevel*(mixer: var Mixer, playbackId: uint64, levelLeft, levelRight: Level) = +proc SetLevel*(mixer: var Mixer, playbackId: uint64, levelLeft, levelRight: Level) = mixer.lock.withLock(): for track in mixer.tracks.mvalues: if playbackId in track.playing: track.playing[playbackId].levelLeft = levelLeft track.playing[playbackId].levelRight = levelRight -proc setLevel*(mixer: var Mixer, playbackId: uint64, level: Level) = - setLevel(mixer, playbackId, level, level) +proc SetLevel*(mixer: var Mixer, playbackId: uint64, level: Level) = + SetLevel(mixer, playbackId, level, level) -proc stop*(mixer: var Mixer, track: string) = +proc Stop*(mixer: var Mixer, track: string) = assert track in mixer.tracks mixer.lock.withLock(): mixer.tracks[track].playing.clear() -proc stop*(mixer: var Mixer, playbackId: uint64) = +proc Stop*(mixer: var Mixer, playbackId: uint64) = mixer.lock.withLock(): for track in mixer.tracks.mvalues: if playbackId in track.playing: track.playing.del(playbackId) break -proc pause*(mixer: var Mixer, value: bool) = +proc Pause*(mixer: var Mixer, value: bool) = mixer.lock.withLock(): for track in mixer.tracks.mvalues: for playback in track.playing.mvalues: playback.paused = value -proc pause*(mixer: var Mixer, track: string, value: bool) = +proc Pause*(mixer: var Mixer, track: string, value: bool) = mixer.lock.withLock(): for playback in mixer.tracks[track].playing.mvalues: playback.paused = value -proc pause*(mixer: var Mixer, playbackId: uint64, value: bool) = +proc Pause*(mixer: var Mixer, playbackId: uint64, value: bool) = mixer.lock.withLock(): for track in mixer.tracks.mvalues: if playbackId in track.playing: track.playing[playbackId].paused = value -proc pause*(mixer: var Mixer) = mixer.pause(true) -proc pause*(mixer: var Mixer, track: string) = mixer.pause(track, true) -proc pause*(mixer: var Mixer, playbackId: uint64) = mixer.pause(playbackId, true) -proc unpause*(mixer: var Mixer) = mixer.pause(false) -proc unpause*(mixer: var Mixer, track: string) = mixer.pause(track, false) -proc unpause*(mixer: var Mixer, playbackId: uint64) = mixer.pause(playbackId, false) +proc Pause*(mixer: var Mixer) = mixer.Pause(true) +proc Pause*(mixer: var Mixer, track: string) = mixer.Pause(track, true) +proc Pause*(mixer: var Mixer, playbackId: uint64) = mixer.Pause(playbackId, true) +proc Unpause*(mixer: var Mixer) = mixer.Pause(false) +proc Unpause*(mixer: var Mixer, track: string) = mixer.Pause(track, false) +proc Unpause*(mixer: var Mixer, playbackId: uint64) = mixer.Pause(playbackId, false) -proc fadeTo*(mixer: var Mixer, track: string, level: Level, time: float) = +proc FadeTo*(mixer: var Mixer, track: string, level: Level, 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 isPlaying*(mixer: var Mixer): bool = +proc IsPlaying*(mixer: var Mixer): bool = mixer.lock.withLock(): for track in mixer.tracks.mvalues: for playback in track.playing.values: @@ -178,7 +179,7 @@ return true return false -proc isPlaying*(mixer: var Mixer, track: string): bool = +proc IsPlaying*(mixer: var Mixer, track: string): bool = mixer.lock.withLock(): if mixer.tracks.contains(track): for playback in mixer.tracks[track].playing.values: @@ -240,7 +241,10 @@ mixer.currentBuffer = (mixer.currentBuffer + 1) mod mixer.buffers.len # DSP functions +# TODO: finish implementation, one day +#[ +# proc lowPassFilter(data: var SoundData, cutoff: int) = let alpha = float(cutoff) / AUDIO_SAMPLE_RATE var value = data[0] @@ -249,29 +253,31 @@ value[1] += int16(alpha * float(data[i][1] - value[1])) data[i] = value -proc downsample(data: var SoundData, n: int) = - let newLen = (data.len - 1) div n + 1 - for i in 0 ..< newLen: - data[i] = data[i * n] - data.setLen(newLen) + proc downsample(data: var SoundData, n: int) = + let newLen = (data.len - 1) div n + 1 + for i in 0 ..< newLen: + data[i] = data[i * n] + data.setLen(newLen) -proc upsample(data: var SoundData, m: int) = - data.setLen(data.len * m) - var i = data.len - 1 - while i < 0: - if i mod m == 0: - data[i] = data[i div m] - else: - data[i] = [0, 0] - i.dec + proc upsample(data: var SoundData, m: int) = + data.setLen(data.len * m) + var i = data.len - 1 + while i < 0: + if i mod m == 0: + data[i] = data[i div m] + else: + data[i] = [0, 0] + i.dec -proc slowdown(data: var SoundData, m, n: int) = - data.upsample(m) - # TODO - # data.lowPassFilter(m) - data.downsample(n) + proc slowdown(data: var SoundData, m, n: int) = + data.upsample(m) + # TODO + # data.lowPassFilter(m) + data.downsample(n) -proc destroy*(mixer: var Mixer) = + ]# + +proc destroy(mixer: var Mixer) = mixer.lock.deinitLock() mixer.device.closeSoundDevice() @@ -287,7 +293,7 @@ while true: mixer[].updateSoundBuffer() -proc startMixerThread*() = +proc StartMixerThread*() = mixer[] = initMixer() audiothread.createThread(audioWorker) debug "Created audio thread" diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/core/buildconfig.nim --- a/semicongine/core/buildconfig.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/core/buildconfig.nim Thu May 23 01:44:10 2024 +0700 @@ -29,7 +29,7 @@ # root of where settings files will be searched # must be relative (to the directory of the binary) -const DEBUG* = not defined(release) +const DEBUG* {.booldefine.} = not defined(release) const CONFIGROOT* {.strdefine.}: string = "." assert not isAbsolute(CONFIGROOT) diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/engine.nim --- a/semicongine/engine.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/engine.nim Thu May 23 01:44:10 2024 +0700 @@ -18,8 +18,8 @@ import ./scene import ./material import ./renderer -import ./events import ./audio +import ./input import ./text import ./panel @@ -32,38 +32,20 @@ Starting Running Shutdown - Input = object - keyIsDown: set[Key] - keyWasPressed: set[Key] - keyWasReleased: set[Key] - mouseIsDown: set[MouseButton] - mouseWasPressed: set[MouseButton] - mouseWasReleased: set[MouseButton] - mousePosition: Vec2f - mouseMove: Vec2f - eventsProcessed: uint64 - windowWasResized: bool - mouseWheel: float32 Engine* = object applicationName: string - debug: bool showFps: bool - state*: EngineState device: Device debugger: Debugger instance: Instance window: NativeWindow renderer: Option[Renderer] - input: Input - exitHandler: proc(engine: var Engine) - resizeHandler: proc(engine: var Engine) - eventHandler: proc(engine: var Engine, event: Event) fullscreen: bool lastNRenderTimes: array[COUNT_N_RENDERTIMES, int64] currentRenderTimeI: int = 0 # forward declarations -func getAspectRatio*(engine: Engine): float32 +func GetAspectRatio*(engine: Engine): float32 proc destroy*(engine: var Engine) = checkVkResult engine.device.vk.vkDeviceWaitIdle() @@ -80,11 +62,7 @@ proc initEngine*( applicationName = querySetting(projectName), - debug = DEBUG, showFps = DEBUG, - exitHandler: proc(engine: var Engine) = nil, - resizeHandler: proc(engine: var Engine) = nil, - eventHandler: proc(engine: var Engine, event: Event) = nil, vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0), vulkanLayers: openArray[string] = [], ): Engine = @@ -97,12 +75,7 @@ else: echo "Starting without Steam" - result.state = Starting - result.exitHandler = exitHandler - result.resizeHandler = resizeHandler - result.eventHandler = eventHandler result.applicationName = applicationName - result.debug = debug result.showFps = showFps result.window = createWindow(result.applicationName) @@ -110,7 +83,7 @@ layers = @vulkanLayers instanceExtensions: seq[string] - if result.debug: + if DEBUG: instanceExtensions.add "VK_EXT_debug_utils" layers.add "VK_LAYER_KHRONOS_validation" # This stuff might be usefull if we one day to smart GPU memory allocation, @@ -123,7 +96,7 @@ instanceExtensions = instanceExtensions, layers = layers.deduplicate(), ) - if result.debug: + if DEBUG: result.debugger = result.instance.createDebugMessenger() # create devices let selectedPhysicalDevice = result.instance.getPhysicalDevices().filterBestGraphics() @@ -132,7 +105,7 @@ enabledExtensions = @[], selectedPhysicalDevice.filterForGraphicsPresentationQueues() ) - startMixerThread() + StartMixerThread() proc initRenderer*( engine: var Engine, @@ -169,7 +142,7 @@ assert engine.renderer.isSome assert not scene.loaded checkVkResult engine.device.vk.vkDeviceWaitIdle() - scene.addShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.getAspectRatio) + scene.addShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.GetAspectRatio) engine.renderer.get.setupDrawableBuffers(scene) engine.renderer.get.updateMeshData(scene, forceAll = true) engine.renderer.get.updateUniformData(scene, forceAll = true) @@ -181,13 +154,12 @@ engine.renderer.get.destroy(scene) proc renderScene*(engine: var Engine, scene: var Scene) = - assert engine.state == Running assert engine.renderer.isSome, "Renderer has not yet been initialized, call 'engine.initRenderer' first" assert engine.renderer.get.hasScene(scene), &"Scene '{scene.name}' has not been loaded yet" let t0 = getMonoTime() engine.renderer.get.startNewFrame() - scene.setShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.getAspectRatio) + scene.setShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.GetAspectRatio) engine.renderer.get.updateMeshData(scene) engine.renderer.get.updateUniformData(scene) engine.renderer.get.render(scene) @@ -206,94 +178,26 @@ engine.window.setTitle(&"{engine.applicationName} ({min:.2}, {median:.2}, {max:.2})") -proc updateInputs*(engine: var Engine): EngineState = - assert engine.state in [Starting, Running] - - # reset input states - engine.input.keyWasPressed = {} - engine.input.keyWasReleased = {} - engine.input.mouseWasPressed = {} - engine.input.mouseWasReleased = {} - engine.input.mouseWheel = 0 - engine.input.mouseMove = newVec2f() - engine.input.windowWasResized = false - - if engine.state == Starting: - engine.input.windowWasResized = true - var mpos = engine.window.getMousePosition() - if mpos.isSome: - engine.input.mousePosition = mpos.get - - var killed = false - for event in engine.window.pendingEvents(): - inc engine.input.eventsProcessed - if engine.eventHandler != nil: - engine.eventHandler(engine, event) - case event.eventType: - of Quit: - killed = true - of ResizedWindow: - engine.input.windowWasResized = true - of KeyPressed: - engine.input.keyWasPressed.incl event.key - engine.input.keyIsDown.incl event.key - of KeyReleased: - engine.input.keyWasReleased.incl event.key - engine.input.keyIsDown.excl event.key - of MousePressed: - engine.input.mouseWasPressed.incl event.button - engine.input.mouseIsDown.incl event.button - of MouseReleased: - engine.input.mouseWasReleased.incl event.button - engine.input.mouseIsDown.excl event.button - of MouseMoved: - let newPos = newVec2(float32(event.x), float32(event.y)) - engine.input.mouseMove = newPos - engine.input.mousePosition - engine.input.mousePosition = newPos - of MouseWheel: - engine.input.mouseWheel = event.amount - if engine.state == Starting: - engine.state = Running - if killed: - engine.state = Shutdown - if engine.exitHandler != nil: - engine.exitHandler(engine) - if engine.input.windowWasResized and engine.resizeHandler != nil: - engine.resizeHandler(engine) - return engine.state - # wrappers for internal things -func keyIsDown*(engine: Engine, key: Key): auto = key in engine.input.keyIsDown -func keyWasPressed*(engine: Engine, key: Key): auto = key in engine.input.keyWasPressed -func keyWasPressed*(engine: Engine): auto = engine.input.keyWasPressed.len > 0 -func keyWasReleased*(engine: Engine, key: Key): auto = key in engine.input.keyWasReleased -func mouseIsDown*(engine: Engine, button: MouseButton): auto = button in engine.input.mouseIsDown -func mouseWasPressed*(engine: Engine, button: MouseButton): auto = button in engine.input.mouseWasPressed -func mouseWasReleased*(engine: Engine, button: MouseButton): auto = button in engine.input.mouseWasReleased -func mousePosition*(engine: Engine): auto = engine.input.mousePosition -func mousePositionNormalized*(engine: Engine): Vec2f = - result.x = (engine.input.mousePosition.x / float32(engine.window.size[0])) * 2.0 - 1.0 - result.y = (engine.input.mousePosition.y / float32(engine.window.size[1])) * 2.0 - 1.0 -func mouseMove*(engine: Engine): auto = engine.input.mouseMove -func mouseWheel*(engine: Engine): auto = engine.input.mouseWheel -func eventsProcessed*(engine: Engine): auto = engine.input.eventsProcessed -func gpuDevice*(engine: Engine): Device = engine.device -func getWindow*(engine: Engine): auto = engine.window -func getAspectRatio*(engine: Engine): float32 = engine.getWindow().size[0] / engine.getWindow().size[1] -func windowWasResized*(engine: Engine): auto = engine.input.windowWasResized -func showSystemCursor*(engine: Engine) = engine.window.showSystemCursor() -func hideSystemCursor*(engine: Engine) = engine.window.hideSystemCursor() -func fullscreen*(engine: Engine): bool = engine.fullscreen -proc `fullscreen=`*(engine: var Engine, enable: bool) = +func GpuDevice*(engine: Engine): Device = engine.device +func GetWindow*(engine: Engine): auto = engine.window +func GetAspectRatio*(engine: Engine): float32 = engine.GetWindow().size[0] / engine.GetWindow().size[1] +func ShowSystemCursor*(engine: Engine) = engine.window.showSystemCursor() +func HideSystemCursor*(engine: Engine) = engine.window.hideSystemCursor() +func Fullscreen*(engine: Engine): bool = engine.fullscreen +proc `Fullscreen=`*(engine: var Engine, enable: bool) = if enable != engine.fullscreen: engine.fullscreen = enable engine.window.fullscreen(engine.fullscreen) -func limits*(engine: Engine): VkPhysicalDeviceLimits = - engine.gpuDevice().physicalDevice.properties.limits +func Limits*(engine: Engine): VkPhysicalDeviceLimits = + engine.device.physicalDevice.properties.limits -proc processEvents*(engine: Engine, panel: var Panel) = - let hasMouseNow = panel.contains(engine.mousePositionNormalized, engine.getAspectRatio) +proc UpdateInputs*(engine: Engine): bool = + UpdateInputs(engine.window.pendingEvents()) + +proc ProcessEvents*(engine: Engine, panel: var Panel) = + let hasMouseNow = panel.contains(MousePositionNormalized(engine.window.size), engine.GetAspectRatio) # enter/leave events if hasMouseNow: @@ -307,9 +211,9 @@ # button events if hasMouseNow: - if engine.input.mouseWasPressed.len > 0: - if panel.onMouseDown != nil: panel.onMouseDown(panel, engine.input.mouseWasPressed) - if engine.input.mouseWasReleased.len > 0: - if panel.onMouseUp != nil: panel.onMouseUp(panel, engine.input.mouseWasReleased) + if MouseWasPressed(): + if panel.onMouseDown != nil: panel.onMouseDown(panel, MousePressedButtons()) + if MouseWasReleased(): + if panel.onMouseUp != nil: panel.onMouseUp(panel, MouseReleasedButtons()) panel.hasMouse = hasMouseNow diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/input.nim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/semicongine/input.nim Thu May 23 01:44:10 2024 +0700 @@ -0,0 +1,176 @@ +# Linux joystick: https://www.kernel.org/doc/Documentation/input/joystick-api.txt +# Windows joystick: https://learn.microsoft.com/en-us/windows/win32/xinput/getting-started-with-xinput + + +import std/tables +import std/strutils + +import ./core/vector +import ./events +import ./storage + +type + Input = object + keyIsDown: set[Key] + keyWasPressed: set[Key] + keyWasReleased: set[Key] + mouseIsDown: set[MouseButton] + mouseWasPressed: set[MouseButton] + mouseWasReleased: set[MouseButton] + mousePosition: Vec2f + mouseMove: Vec2f + mouseWheel: float32 + windowWasResized: bool = true + +# warning, shit is not thread safe +var input: Input + +proc UpdateInputs*(events: seq[Event]): bool = + # reset input states + input.keyWasPressed = {} + input.keyWasReleased = {} + input.mouseWasPressed = {} + input.mouseWasReleased = {} + input.mouseWheel = 0 + input.mouseMove = newVec2f() + input.windowWasResized = false + + var killed = false + for event in events: + case event.eventType: + of Quit: + killed = true + of ResizedWindow: + input.windowWasResized = true + of KeyPressed: + input.keyWasPressed.incl event.key + input.keyIsDown.incl event.key + of KeyReleased: + input.keyWasReleased.incl event.key + input.keyIsDown.excl event.key + of MousePressed: + input.mouseWasPressed.incl event.button + input.mouseIsDown.incl event.button + of MouseReleased: + input.mouseWasReleased.incl event.button + input.mouseIsDown.excl event.button + of MouseMoved: + let newPos = newVec2(float32(event.x), float32(event.y)) + input.mouseMove = newPos - input.mousePosition + input.mousePosition = newPos + of MouseWheel: + input.mouseWheel = event.amount + return not killed + +proc KeyIsDown*(key: Key): bool = key in input.keyIsDown +proc KeyWasPressed*(key: Key): bool = key in input.keyWasPressed +proc KeyWasPressed*(): bool = input.keyWasPressed.len > 0 +proc KeyWasReleased*(key: Key): bool = key in input.keyWasReleased +proc MouseIsDown*(button: MouseButton): bool = button in input.mouseIsDown +proc MouseWasPressed*(): bool = input.mouseWasPressed.len > 0 +proc MouseWasPressed*(button: MouseButton): bool = button in input.mouseWasPressed +proc MousePressedButtons*(): set[MouseButton] = input.mouseWasPressed +proc MouseWasReleased*(): bool = input.mouseWasReleased.len > 0 +proc MouseWasReleased*(button: MouseButton): bool = button in input.mouseWasReleased +proc MouseReleasedButtons*(): set[MouseButton] = input.mouseWasReleased +proc MousePosition*(): Vec2f = input.mousePosition +proc MousePositionNormalized*(size: (int, int)): Vec2f = + result.x = (input.mousePosition.x / float32(size[0])) * 2.0 - 1.0 + result.y = (input.mousePosition.y / float32(size[1])) * 2.0 - 1.0 +proc MouseMove*(): auto = input.mouseMove +proc MouseWheel*(): auto = input.mouseWheel +proc WindowWasResized*(): auto = input.windowWasResized + +# 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 + +proc MapAction*[T: enum](action: T, button: MouseButton) = + if not actionMap.mouseActions.contains($action): + actionMap.mouseActions[$action] = {} + 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) + +proc UnmapAction*[T: enum](action: T, button: MouseButton) = + if actionMap.mouseActions.contains($action): + 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] = {} + +proc SaveCurrentActionMapping*() = + for name, keys in actionMap.keyActions.pairs: + SystemStorage.store(name, keys, table = "input_mapping_key") + for name, buttons in actionMap.mouseActions.pairs: + SystemStorage.store(name, buttons, table = "input_mapping_mouse") + +proc LoadActionMapping*[T]() = + reset(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") + for key in keys: + 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: + return true + return false + if actionMap.mouseActions.contains($action): + for button in actionMap.mouseActions[$action]: + if button in 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: + return true + elif actionMap.mouseActions.contains($action): + for button in actionMap.mouseActions[$action]: + if button in 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: + return true + elif actionMap.mouseActions.contains($action): + for button in actionMap.mouseActions[$action]: + if button in 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: + return 1 + elif actionMap.mouseActions.contains($action): + for button in actionMap.mouseActions[$action]: + if button in input.mouseIsDown: + return 1 diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/mesh.nim --- a/semicongine/mesh.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/mesh.nim Thu May 23 01:44:10 2024 +0700 @@ -382,6 +382,13 @@ proc clearDirtyAttributes*(mesh: var MeshObject) = mesh.dirtyAttributes.reset +proc setShaderMaterialIndices*(mesh: var MeshObject, shadername: string, values: seq[uint16], attributeName = MATERIALINDEX_ATTRIBUTE) = + let indexAttribute = shadername & "_" & attributeName + assert values.len == mesh.instanceCount, &"Mesh {mesh}: While trying to set shader material indices for shader '{shadername}': indices have len {values.len}, but instance count is {mesh.instanceCount}" + mesh[indexAttribute] = values + +# MESH-TOOLS + proc transform*[T: GPUType](mesh: var MeshObject, attribute: string, transform: Mat4) = if mesh.vertexData.contains(attribute): for i in 0 ..< mesh.vertexData[attribute].len: @@ -393,17 +400,12 @@ raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") mesh.dirtyAttributes.add attribute -proc applyTransformToVertices*(mesh: var MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE) = - for i in 0 ..< mesh.vertexData[positionAttribute].len: - mesh.vertexData[positionAttribute][i] = mesh.transform * mesh.vertexData[positionAttribute][i, Vec3f] - mesh.transform = Unit4 - -func getCollisionPoints*(mesh: MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE): seq[Vec3f] = +func getMeshPoints*(mesh: MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE): seq[Vec3f] = for p in mesh[positionAttribute, Vec3f][]: result.add mesh.transform * p func getCollider*(mesh: MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE): Collider = - return mesh.getCollisionPoints(positionAttribute).calculateCollider(Points) + return mesh.getMeshPoints(positionAttribute).calculateCollider(Points) proc asNonIndexedMesh*(mesh: MeshObject): MeshObject = if mesh.indexType == None: diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/renderer.nim --- a/semicongine/renderer.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/renderer.nim Thu May 23 01:44:10 2024 +0700 @@ -25,17 +25,20 @@ const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment type + ShaderData = ref object + descriptorPool: DescriptorPool + descriptorSets: seq[DescriptorSet] # len = n swapchain images + uniformBuffers: seq[Buffer] + textures: Table[string, seq[VulkanTexture]] + SceneData = ref object - drawables*: seq[tuple[drawable: Drawable, mesh: Mesh]] - vertexBuffers*: Table[MemoryPerformanceHint, Buffer] - indexBuffer*: Buffer - uniformBuffers*: Table[VkPipeline, seq[Buffer]] # one per frame-in-flight - textures*: Table[VkPipeline, Table[string, seq[VulkanTexture]]] # per frame-in-flight - attributeLocation*: Table[string, MemoryPerformanceHint] - vertexBufferOffsets*: Table[(Mesh, string), uint64] - descriptorPools*: Table[VkPipeline, DescriptorPool] - descriptorSets*: Table[VkPipeline, seq[DescriptorSet]] + drawables: seq[tuple[drawable: Drawable, mesh: Mesh]] + vertexBuffers: Table[MemoryPerformanceHint, Buffer] + indexBuffer: Buffer + attributeLocation: Table[string, MemoryPerformanceHint] + vertexBufferOffsets: Table[(Mesh, string), uint64] materials: Table[MaterialType, seq[MaterialData]] + shaderData: Table[VkPipeline, ShaderData] Renderer* = object device: Device renderPass: RenderPass @@ -78,78 +81,23 @@ result.swapchain = swapchain.get() result.emptyTexture = device.uploadTexture(result.queue, EMPTY_TEXTURE) -func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = - var found: Table[string, ShaderAttribute] +func shadersForScene(renderer: Renderer, scene: Scene): seq[(MaterialType, ShaderPipeline)] = for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines: if scene.usesMaterial(materialType): - for input in shaderPipeline.inputs: - if found.contains(input.name): - assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}" - assert input.theType == found[input.name].theType, &"{input.name}: {input.theType} != {found[input.name].theType}" - assert input.arrayCount == found[input.name].arrayCount, &"{input.name}: {input.arrayCount} != {found[input.name].arrayCount}" - assert input.memoryPerformanceHint == found[input.name].memoryPerformanceHint, &"{input.name}: {input.memoryPerformanceHint} != {found[input.name].memoryPerformanceHint}" - else: - result.add input - found[input.name] = input - -proc materialCompatibleWithPipeline(scene: Scene, materialType: MaterialType, shaderPipeline: ShaderPipeline): (bool, string) = - for uniform in shaderPipeline.uniforms: - if scene.shaderGlobals.contains(uniform.name): - if scene.shaderGlobals[uniform.name].theType != uniform.theType: - return (true, &"shader uniform needs type {uniform.theType} but scene global is of type {scene.shaderGlobals[uniform.name].theType}") - else: - if not materialType.hasMatchingAttribute(uniform): - return (true, &"shader uniform '{uniform.name}' was not found in scene globals or scene materials") - for texture in shaderPipeline.samplers: - if scene.shaderGlobals.contains(texture.name): - if scene.shaderGlobals[texture.name].theType != texture.theType: - return (true, &"shader texture '{texture.name}' needs type {texture.theType} but scene global is of type {scene.shaderGlobals[texture.name].theType}") - else: - if not materialType.hasMatchingAttribute(texture): - return (true, &"Required texture for shader texture '{texture.name}' was not found in scene materials") - - return (false, "") + result.add (materialType, shaderPipeline) -proc meshCompatibleWithPipeline(scene: Scene, mesh: Mesh, shaderPipeline: ShaderPipeline): (bool, string) = - for input in shaderPipeline.inputs: - if input.name in [TRANSFORM_ATTRIB, MATERIALINDEX_ATTRIBUTE]: # will be populated automatically - assert input.perInstance == true, &"Currently the {input.name} attribute must be a per instance attribute" - continue - if not (input.name in mesh[].attributes): - return (true, &"Shader input '{input.name}' is not available for mesh") - if input.theType != mesh[].attributeType(input.name): - return (true, &"Shader input '{input.name}' expects type {input.theType}, but mesh has {mesh[].attributeType(input.name)}") - if not input.perInstance and not mesh[].vertexAttributes.contains(input.name): - return (true, &"Shader input '{input.name}' expected to be vertex attribute, but mesh has no such vertex attribute (available are: {mesh[].vertexAttributes})") - if input.perInstance and not mesh[].instanceAttributes.contains(input.name): - return (true, &"Shader input '{input.name}' expected to be per instance attribute, but mesh has no such instance attribute (available are: {mesh[].instanceAttributes})") - - let pipelineCompatability = scene.materialCompatibleWithPipeline(mesh.material.theType, shaderPipeline) - if pipelineCompatability[0]: - return (true, pipelineCompatability[1]) - return (false, "") - -proc checkSceneIntegrity(renderer: Renderer, scene: Scene) = - # TODO: this and the sub-functions can likely be simplified a ton - if scene.meshes.len == 0: - return - - var foundRenderableObject = false - var materialTypes: seq[MaterialType] - for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines: - materialTypes.add materialType - for mesh in scene.meshes: - if mesh.material.theType == materialType: - foundRenderableObject = true - let (error, message) = scene.meshCompatibleWithPipeline(mesh, shaderPipeline) - assert not error, &"Mesh '{mesh}' not compatible with assigned shaderPipeline ({materialType}) because: {message}" - - if not foundRenderableObject: - var matTypes: Table[string, MaterialType] - for mesh in scene.meshes: - if not matTypes.contains(mesh.material.name): - matTypes[mesh.material.name] = mesh.material.theType - assert false, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {matTypes}, registered shader-materialtypes: {materialTypes}" +func vertexInputsForScene(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = + var found: Table[string, ShaderAttribute] + for (materialType, shaderPipeline) in renderer.shadersForScene(scene): + for input in shaderPipeline.inputs: + if found.contains(input.name): + assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}" + assert input.theType == found[input.name].theType, &"{input.name}: {input.theType} != {found[input.name].theType}" + assert input.arrayCount == found[input.name].arrayCount, &"{input.name}: {input.arrayCount} != {found[input.name].arrayCount}" + assert input.memoryPerformanceHint == found[input.name].memoryPerformanceHint, &"{input.name}: {input.memoryPerformanceHint} != {found[input.name].memoryPerformanceHint}" + else: + result.add input + found[input.name] = input proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) = assert not (scene in renderer.scenedata) @@ -171,8 +119,6 @@ if not (MATERIALINDEX_ATTRIBUTE in mesh[].attributes): mesh[].initInstanceAttribute(MATERIALINDEX_ATTRIBUTE, uint16(scenedata.materials[mesh.material.theType].find(mesh.material))) - # renderer.checkSceneIntegrity(scene) - # create index buffer if necessary var indicesBufferSize = 0'u64 for mesh in scene.meshes: @@ -200,17 +146,21 @@ for hint in MemoryPerformanceHint: perLocationSizes[hint] = 0 - let inputs = renderer.inputs(scene) + let sceneVertexInputs = renderer.vertexInputsForScene(scene) + let sceneShaders = renderer.shadersForScene(scene) - for attribute in inputs: - scenedata.attributeLocation[attribute.name] = attribute.memoryPerformanceHint - # setup one buffer per attribute-location-type + for (materialType, shaderPipeline) in sceneShaders: + scenedata.shaderData[shaderPipeline.vk] = ShaderData() + + for vertexAttribute in sceneVertexInputs: + scenedata.attributeLocation[vertexAttribute.name] = vertexAttribute.memoryPerformanceHint + # setup one buffer per vertexAttribute-location-type for mesh in scene.meshes: # align size to VERTEX_ATTRIB_ALIGNMENT bytes (the important thing is the correct alignment of the offsets, but # we need to expand the buffer size as well, therefore considering alignment already here as well - if perLocationSizes[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0: - perLocationSizes[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationSizes[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT) - perLocationSizes[attribute.memoryPerformanceHint] += mesh[].attributeSize(attribute.name) + if perLocationSizes[vertexAttribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0: + perLocationSizes[vertexAttribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationSizes[vertexAttribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT) + perLocationSizes[vertexAttribute.memoryPerformanceHint] += mesh[].attributeSize(vertexAttribute.name) # create vertex buffers for memoryPerformanceHint, bufferSize in perLocationSizes.pairs: @@ -229,7 +179,7 @@ perLocationOffsets[hint] = 0 for mesh in scene.meshes: - for attribute in inputs: + for attribute in sceneVertexInputs: scenedata.vertexBufferOffsets[(mesh, attribute.name)] = perLocationOffsets[attribute.memoryPerformanceHint] if mesh[].attributes.contains(attribute.name): perLocationOffsets[attribute.memoryPerformanceHint] += mesh[].attributeSize(attribute.name) @@ -238,11 +188,10 @@ # fill offsets per shaderPipeline (as sequence corresponds to shader input binding) var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, uint64)]] - for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines: - if scene.usesMaterial(materialType): - offsets[shaderPipeline.vk] = newSeq[(string, MemoryPerformanceHint, uint64)]() - for attribute in shaderPipeline.inputs: - offsets[shaderPipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)]) + for (materialType, shaderPipeline) in sceneShaders: + offsets[shaderPipeline.vk] = newSeq[(string, MemoryPerformanceHint, uint64)]() + for attribute in shaderPipeline.inputs: + offsets[shaderPipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)]) # create drawables let indexed = mesh.indexType != MeshIndexType.None @@ -271,66 +220,63 @@ # setup uniforms and textures (anything descriptor) var uploadedTextures: Table[Texture, VulkanTexture] - for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines: - if scene.usesMaterial(materialType): - # gather textures - scenedata.textures[shaderPipeline.vk] = initTable[string, seq[VulkanTexture]]() - for texture in shaderPipeline.samplers: - scenedata.textures[shaderPipeline.vk][texture.name] = newSeq[VulkanTexture]() - if scene.shaderGlobals.contains(texture.name): - for textureValue in scene.shaderGlobals[texture.name][Texture][]: - if not uploadedTextures.contains(textureValue): - uploadedTextures[textureValue] = renderer.device.uploadTexture(renderer.queue, textureValue) - scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[textureValue] - else: - var foundTexture = false - for material in scene.getMaterials(materialType): - if material.hasMatchingAttribute(texture): - foundTexture = true - let value = material[texture.name, Texture][] - assert value.len == 1, &"Mesh material attribute '{texture.name}' has texture-array, but only single textures are allowed" - if not uploadedTextures.contains(value[0]): - uploadedTextures[value[0]] = renderer.device.uploadTexture(renderer.queue, value[0]) - scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[value[0]] - assert foundTexture, &"No texture found in shaderGlobals or materials for '{texture.name}'" - let nTextures = scenedata.textures[shaderPipeline.vk][texture.name].len.uint32 - assert (texture.arrayCount == 0 and nTextures == 1) or texture.arrayCount >= nTextures, &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}" - if texture.arrayCount < nTextures: - warn &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}" + for (materialType, shaderPipeline) in sceneShaders: + # gather textures + for textureAttribute in shaderPipeline.samplers: + scenedata.shaderData[shaderPipeline.vk].textures[textureAttribute.name] = newSeq[VulkanTexture]() + if scene.shaderGlobals.contains(textureAttribute.name): + for textureValue in scene.shaderGlobals[textureAttribute.name][Texture][]: + if not uploadedTextures.contains(textureValue): + uploadedTextures[textureValue] = renderer.device.uploadTexture(renderer.queue, textureValue) + scenedata.shaderData[shaderPipeline.vk].textures[textureAttribute.name].add uploadedTextures[textureValue] + else: + var foundTexture = false + for material in scene.getMaterials(materialType): + if material.hasMatchingAttribute(textureAttribute): + foundTexture = true + let value = material[textureAttribute.name, Texture][] + assert value.len == 1, &"Mesh material attribute '{textureAttribute.name}' has texture-array, but only single textures are allowed" + if not uploadedTextures.contains(value[0]): + uploadedTextures[value[0]] = renderer.device.uploadTexture(renderer.queue, value[0]) + scenedata.shaderData[shaderPipeline.vk].textures[textureAttribute.name].add uploadedTextures[value[0]] + assert foundTexture, &"No texture found in shaderGlobals or materials for '{textureAttribute.name}'" + let nTextures = scenedata.shaderData[shaderPipeline.vk].textures[textureAttribute.name].len.uint32 + assert (textureAttribute.arrayCount == 0 and nTextures == 1) or textureAttribute.arrayCount >= nTextures, &"Shader assigned to render '{materialType}' expected {textureAttribute.arrayCount} textures for '{textureAttribute.name}' but got {nTextures}" + if textureAttribute.arrayCount < nTextures: + warn &"Shader assigned to render '{materialType}' expected {textureAttribute.arrayCount} textures for '{textureAttribute.name}' but got {nTextures}" - # gather uniform sizes - var uniformBufferSize = 0'u64 - for uniform in shaderPipeline.uniforms: - uniformBufferSize += uniform.size - if uniformBufferSize > 0: - scenedata.uniformBuffers[shaderPipeline.vk] = newSeq[Buffer]() - for frame_i in 0 ..< renderer.swapchain.inFlightFrames: - scenedata.uniformBuffers[shaderPipeline.vk].add renderer.device.createBuffer( - size = uniformBufferSize, - usage = [VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT], - requireMappable = true, - preferVRAM = true, - ) + # gather uniform sizes + var uniformBufferSize = 0'u64 + for uniform in shaderPipeline.uniforms: + uniformBufferSize += uniform.size + if uniformBufferSize > 0: + for frame_i in 0 ..< renderer.swapchain.inFlightFrames: + scenedata.shaderData[shaderPipeline.vk].uniformBuffers.add renderer.device.createBuffer( + size = uniformBufferSize, + usage = [VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT], + requireMappable = true, + preferVRAM = true, + ) - # TODO: rework the whole descriptor/pool/layout stuff, a bit unclear - var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, renderer.swapchain.inFlightFrames.uint32)] - var nTextures = 0'u32 - for descriptor in shaderPipeline.descriptorSetLayout.descriptors: - if descriptor.thetype == ImageSampler: - nTextures += descriptor.count - if nTextures > 0: - poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nTextures * renderer.swapchain.inFlightFrames.uint32) - scenedata.descriptorPools[shaderPipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes) + # TODO: rework the whole descriptor/pool/layout stuff, a bit unclear + var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, renderer.swapchain.inFlightFrames.uint32)] + var nTextures = 0'u32 + for descriptor in shaderPipeline.descriptorSetLayout.descriptors: + if descriptor.thetype == ImageSampler: + nTextures += descriptor.count + if nTextures > 0: + poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nTextures * renderer.swapchain.inFlightFrames.uint32) + scenedata.shaderData[shaderPipeline.vk].descriptorPool = renderer.device.createDescriptorSetPool(poolsizes) - scenedata.descriptorSets[shaderPipeline.vk] = shaderPipeline.setupDescriptors( - scenedata.descriptorPools[shaderPipeline.vk], - scenedata.uniformBuffers.getOrDefault(shaderPipeline.vk, @[]), - scenedata.textures[shaderPipeline.vk], - inFlightFrames = renderer.swapchain.inFlightFrames, - emptyTexture = renderer.emptyTexture, - ) - for frame_i in 0 ..< renderer.swapchain.inFlightFrames: - scenedata.descriptorSets[shaderPipeline.vk][frame_i].writeDescriptorSet() + scenedata.shaderData[shaderPipeline.vk].descriptorSets = shaderPipeline.setupDescriptors( + scenedata.shaderData[shaderPipeline.vk].descriptorPool, + scenedata.shaderData[shaderPipeline.vk].uniformBuffers, + scenedata.shaderData[shaderPipeline.vk].textures, + inFlightFrames = renderer.swapchain.inFlightFrames, + emptyTexture = renderer.emptyTexture, + ) + for frame_i in 0 ..< renderer.swapchain.inFlightFrames: + scenedata.shaderData[shaderPipeline.vk].descriptorSets[frame_i].writeDescriptorSet() renderer.scenedata[scene] = scenedata @@ -382,19 +328,15 @@ debug &"Update uniforms because of dirty scene globals: {dirty}" # loop over all used shaders/pipelines - for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines: - if ( - scene.usesMaterial(materialType) and - renderer.scenedata[scene].uniformBuffers.hasKey(shaderPipeline.vk) and - renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk].len != 0 - ): + for (materialType, shaderPipeline) in renderer.shadersForScene(scene): + if renderer.scenedata[scene].shaderData[shaderPipeline.vk].uniformBuffers.len > 0: var dirtyMaterialAttribs: seq[string] for material in renderer.scenedata[scene].materials[materialType].mitems: dirtyMaterialAttribs.add material.dirtyAttributes material.clearDirtyAttributes() - assert renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk.valid + assert renderer.scenedata[scene].shaderData[shaderPipeline.vk].uniformBuffers[renderer.swapchain.currentInFlight].vk.valid if forceAll: - for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]: + for buffer in renderer.scenedata[scene].shaderData[shaderPipeline.vk].uniformBuffers: assert buffer.vk.valid var offset = 0'u64 @@ -422,7 +364,7 @@ # TODO: technically we would only need to update the uniform buffer of the current # frameInFlight (I think), but we don't track for which frame the shaderglobals are no longer dirty # therefore we have to update the uniform values in all buffers, of all inFlightframes (usually 2) - for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]: + for buffer in renderer.scenedata[scene].shaderData[shaderPipeline.vk].uniformBuffers: buffer.setData(renderer.queue, value.getPointer(), value.size, offset) offset += uniform.size scene.clearDirtyShaderGlobals() @@ -458,7 +400,15 @@ if scene.usesMaterial(materialType): debug &"Start shaderPipeline for '{materialType}'" renderer.currentFrameCommandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, shaderPipeline.vk) - renderer.currentFrameCommandBuffer.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, shaderPipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil) + renderer.currentFrameCommandBuffer.vkCmdBindDescriptorSets( + VK_PIPELINE_BIND_POINT_GRAPHICS, + shaderPipeline.layout, + 0, + 1, + addr(renderer.scenedata[scene].shaderData[shaderPipeline.vk].descriptorSets[renderer.swapchain.currentInFlight].vk), + 0, + nil + ) for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType): drawable.draw(renderer.currentFrameCommandBuffer, vertexBuffers = renderer.scenedata[scene].vertexBuffers, indexBuffer = renderer.scenedata[scene].indexBuffer, shaderPipeline.vk) @@ -482,52 +432,37 @@ proc destroy*(renderer: var Renderer, scene: Scene) = checkVkResult renderer.device.vk.vkDeviceWaitIdle() var scenedata = renderer.scenedata[scene] + for buffer in scenedata.vertexBuffers.mvalues: assert buffer.vk.valid buffer.destroy() + if scenedata.indexBuffer.vk.valid: assert scenedata.indexBuffer.vk.valid scenedata.indexBuffer.destroy() - for pipelineUniforms in scenedata.uniformBuffers.mvalues: - for buffer in pipelineUniforms.mitems: + + var destroyedTextures: seq[VkImage] + + for (vkPipeline, shaderData) in scenedata.shaderData.mpairs: + + for buffer in shaderData.uniformBuffers.mitems: assert buffer.vk.valid buffer.destroy() - var destroyedTextures: seq[VkImage] - for pipelineTextures in scenedata.textures.mvalues: - for textures in pipelineTextures.mvalues: + + for textures in shaderData.textures.mvalues: for texture in textures.mitems: if not destroyedTextures.contains(texture.image.vk): destroyedTextures.add texture.image.vk texture.destroy() - for descriptorPool in scenedata.descriptorPools.mvalues: - descriptorPool.destroy() + + shaderData.descriptorPool.destroy() + renderer.scenedata.del(scene) proc destroy*(renderer: var Renderer) = - checkVkResult renderer.device.vk.vkDeviceWaitIdle() - - for scenedata in renderer.scenedata.mvalues: - for buffer in scenedata.vertexBuffers.mvalues: - assert buffer.vk.valid - buffer.destroy() - if scenedata.indexBuffer.vk.valid: - assert scenedata.indexBuffer.vk.valid - scenedata.indexBuffer.destroy() - for pipelineUniforms in scenedata.uniformBuffers.mvalues: - for buffer in pipelineUniforms.mitems: - assert buffer.vk.valid - buffer.destroy() - var destroyedTextures: seq[VkImage] - for pipelineTextures in scenedata.textures.mvalues: - for textures in pipelineTextures.mvalues: - for texture in textures.mitems: - if not destroyedTextures.contains(texture.image.vk): - destroyedTextures.add texture.image.vk - texture.destroy() - for descriptorPool in scenedata.descriptorPools.mvalues: - descriptorPool.destroy() for scene in renderer.scenedata.keys.toSeq: - renderer.scenedata.del(scene) + renderer.destroy(scene) + assert renderer.scenedata.len == 0 renderer.emptyTexture.destroy() renderer.renderPass.destroy() renderer.commandBufferPool.destroy() diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/scene.nim --- a/semicongine/scene.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/scene.nim Thu May 23 01:44:10 2024 +0700 @@ -51,11 +51,11 @@ proc addShaderGlobal*[T](scene: var Scene, name: string, data: T) = scene.addShaderGlobalArray(name, [data]) -func getShaderGlobalArray*[T](scene: Scene, name: string): ref seq[T] = +proc getShaderGlobalArray*[T](scene: Scene, name: string): ref seq[T] = scene.shaderGlobals[name][T] -func getShaderGlobal*[T](scene: Scene, name: string): T = - scene.getShaderGlobalArray(name)[][0] +proc getShaderGlobal*[T](scene: Scene, name: string): T = + getShaderGlobalArray[T](scene, name)[][0] proc setShaderGlobalArray*[T](scene: var Scene, name: string, value: openArray[T]) = if scene.shaderGlobals[name, T][] == @value: diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/storage.nim --- a/semicongine/storage.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/storage.nim Thu May 23 01:44:10 2024 +0700 @@ -9,7 +9,7 @@ import ./core const STORAGE_NAME = Path("storage.db") -const KEY_VALUE_TABLE_NAME = "shelf" +const DEFAULT_KEY_VALUE_TABLE_NAME = "shelf" type StorageType* = enum @@ -27,29 +27,35 @@ string(Path(getDataDir()) / Path(AppName())).createDir() Path(getDataDir()) / Path(AppName()) / STORAGE_NAME -proc setup(storageType: StorageType) = +proc ensureExists(storageType: StorageType) = if storageType in db: return db[storageType] = open(string(storageType.path), "", "", "") - db[storageType].exec(sql(&"""CREATE TABLE IF NOT EXISTS {KEY_VALUE_TABLE_NAME} ( + +proc ensureExists(storageType: StorageType, table: string) = + storageType.ensureExists() + db[storageType].exec(sql(&"""CREATE TABLE IF NOT EXISTS {table} ( key TEXT NOT NULL UNIQUE, value TEXT NOT NULL )""")) -proc store*[T](storageType: StorageType, key: string, value: T) = - storageType.setup() - const KEY_VALUE_TABLE_NAME = "shelf" - db[storageType].exec(sql(&"""INSERT INTO {KEY_VALUE_TABLE_NAME} VALUES(?, ?) +proc store*[T](storageType: StorageType, key: string, value: T, table = DEFAULT_KEY_VALUE_TABLE_NAME) = + storageType.ensureExists(table) + db[storageType].exec(sql(&"""INSERT INTO {table} VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value """), key, $$value) -proc load*[T](storageType: StorageType, key: string, default: T): T = - storageType.setup() - const KEY_VALUE_TABLE_NAME = "shelf" - let dbResult = db[storageType].getValue(sql(&"""SELECT value FROM {KEY_VALUE_TABLE_NAME} WHERE key = ? """), key) +proc load*[T](storageType: StorageType, key: string, default: T, table = DEFAULT_KEY_VALUE_TABLE_NAME): T = + storageType.ensureExists(table) + let dbResult = db[storageType].getValue(sql(&"""SELECT value FROM {table} WHERE key = ? """), key) if dbResult == "": return default return to[T](dbResult) +proc list*[T](storageType: StorageType, table = DEFAULT_KEY_VALUE_TABLE_NAME): seq[string] = + storageType.ensureExists(table) + for row in db[storageType].fastRows(sql(&"""SELECT key FROM {table}""")): + result.add row[0] + proc purge*(storageType: StorageType) = storageType.path().string.removeFile() diff -r 073ce95ae5c7 -r ae54dbb08e53 semicongine/text.nim --- a/semicongine/text.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/semicongine/text.nim Thu May 23 01:44:10 2024 +0700 @@ -13,7 +13,7 @@ SPACE = Rune(' ') # font shader - MAX_TEXT_MATERIALS = 10 + MAX_TEXT_MATERIALS = 100 # need for every different font AND color SHADER_ATTRIB_PREFIX = "semicon_text_" POSITION_ATTRIB = SHADER_ATTRIB_PREFIX & "position" UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv" @@ -254,8 +254,7 @@ result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs, name = &"text-{instanceCounter}") result.mesh[].renameAttribute("position", POSITION_ATTRIB) result.mesh[].renameAttribute("uv", UV_ATTRIB) - result.mesh.material = initMaterialData( - theType = TEXT_MATERIAL_TYPE, + result.mesh.material = TEXT_MATERIAL_TYPE.initMaterialData( name = font.name & " text", attributes = {"fontAtlas": initDataList(@[font.fontAtlas]), "color": initDataList(@[color])}, ) diff -r 073ce95ae5c7 -r ae54dbb08e53 tests/test_collision.nim --- a/tests/test_collision.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/tests/test_collision.nim Thu May 23 01:44:10 2024 +0700 @@ -3,9 +3,9 @@ proc main() = var scene = Scene(name: "main") - scene.add rect(color="f00f") + scene.add rect(color = "f00f") scene.add rect() - scene.add circle(color="0f0f") + scene.add circle(color = "0f0f") scene.meshes[0].material = VERTEX_COLORED_MATERIAL.initMaterialData() scene.meshes[1].material = VERTEX_COLORED_MATERIAL.initMaterialData() scene.meshes[2].material = VERTEX_COLORED_MATERIAL.initMaterialData() @@ -15,16 +15,17 @@ const shaderConfiguration = createShaderConfiguration( - inputs=[ - attr[Mat4]("transform", memoryPerformanceHint=PreferFastRead, perInstance=true), - attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead), - attr[Vec4f]("color", memoryPerformanceHint=PreferFastRead), + name = "default shader", + inputs = [ + attr[Mat4]("transform", memoryPerformanceHint = PreferFastRead, perInstance = true), + attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead), + attr[Vec4f]("color", memoryPerformanceHint = PreferFastRead), ], - intermediates=[attr[Vec4f]("colorout")], - uniforms=[attr[Mat4]("perspective")], - outputs=[attr[Vec4f]("fragcolor")], - vertexCode="""gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); colorout = color;""", - fragmentCode="""fragcolor = colorout;""", + intermediates = [attr[Vec4f]("colorout")], + uniforms = [attr[Mat4]("perspective")], + outputs = [attr[Vec4f]("fragcolor")], + vertexCode = """gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); colorout = color;""", + fragmentCode = """fragcolor = colorout;""", ) var engine = initEngine("Test collisions") @@ -32,21 +33,21 @@ engine.initRenderer({VERTEX_COLORED_MATERIAL: shaderConfiguration}) engine.loadScene(scene) - while engine.updateInputs() == Running and not engine.keyIsDown(Escape): - if engine.windowWasResized(): + while engine.updateInputs() and not keyIsDown(Escape): + if windowWasResized(): var winSize = engine.getWindow().size - scene.setShaderGlobal("perspective", orthoWindowAspect(winSize[1] / winSize[0])) - if engine.keyIsDown(A): scene.meshes[0].transform = scene.meshes[0].transform * translate(-0.001, 0, 0) - if engine.keyIsDown(D): scene.meshes[0].transform = scene.meshes[0].transform * translate( 0.001, 0, 0) - if engine.keyIsDown(W): scene.meshes[0].transform = scene.meshes[0].transform * translate( 0, -0.001, 0) - if engine.keyIsDown(S): scene.meshes[0].transform = scene.meshes[0].transform * translate( 0, 0.001, 0) - if engine.keyIsDown(Q): scene.meshes[0].transform = scene.meshes[0].transform * rotate(-0.001, Z) - if engine.keyIsDown(Key.E): scene.meshes[0].transform = scene.meshes[0].transform * rotate( 0.001, Z) + scene.setShaderGlobal("perspective", orthoWindowAspect(winSize[0] / winSize[1])) + if keyIsDown(A): scene.meshes[0].transform = scene.meshes[0].transform * translate(-0.001, 0, 0) + if keyIsDown(D): scene.meshes[0].transform = scene.meshes[0].transform * translate(0.001, 0, 0) + if keyIsDown(W): scene.meshes[0].transform = scene.meshes[0].transform * translate(0, -0.001, 0) + if keyIsDown(S): scene.meshes[0].transform = scene.meshes[0].transform * translate(0, 0.001, 0) + if keyIsDown(Q): scene.meshes[0].transform = scene.meshes[0].transform * rotate(-0.001, Z) + if keyIsDown(Key.E): scene.meshes[0].transform = scene.meshes[0].transform * rotate(0.001, Z) - if engine.keyIsDown(Key.Z): scene.meshes[1].transform = scene.meshes[1].transform * rotate(-0.001, Z) - if engine.keyIsDown(Key.X): scene.meshes[1].transform = scene.meshes[1].transform * rotate( 0.001, Z) - if engine.keyIsDown(Key.C): scene.meshes[1].transform = scene.meshes[1].transform * translate(0, -0.001, 0) - if engine.keyIsDown(Key.V): scene.meshes[1].transform = scene.meshes[1].transform * translate(0, 0.001, 0) + if keyIsDown(Key.Z): scene.meshes[1].transform = scene.meshes[1].transform * rotate(-0.001, Z) + if keyIsDown(Key.X): scene.meshes[1].transform = scene.meshes[1].transform * rotate(0.001, Z) + if keyIsDown(Key.C): scene.meshes[1].transform = scene.meshes[1].transform * translate(0, -0.001, 0) + if keyIsDown(Key.V): scene.meshes[1].transform = scene.meshes[1].transform * translate(0, 0.001, 0) let hitbox = Collider(theType: Box, transform: scene.meshes[0].transform * translate(-0.5, -0.5)) let hitsphere = Collider(theType: Sphere, transform: scene.meshes[2].transform, radius: 0.5) echo intersects(hitbox, hitsphere) diff -r 073ce95ae5c7 -r ae54dbb08e53 tests/test_font.nim --- a/tests/test_font.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/tests/test_font.nim Thu May 23 01:44:10 2024 +0700 @@ -31,10 +31,10 @@ mixer[].loadSound("key", "key.ogg") mixer[].setLevel(0.5) - while engine.updateInputs() == Running and not engine.keyIsDown(Escape): + while engine.updateInputs() and not keyIsDown(Escape): var t = cpuTime() main_text.color = newVec4f(sin(t) * 0.5 + 0.5, 0.15, 0.15, 1) - if engine.windowWasResized(): + if windowWasResized(): var winSize = engine.getWindow().size # add character @@ -42,31 +42,31 @@ for c in [Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, Key.J, Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, Key.T, Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z]: - if engine.keyWasPressed(c): + if keyWasPressed(c): discard mixer[].play("key") - if engine.keyIsDown(ShiftL) or engine.keyIsDown(ShiftR): + if keyIsDown(ShiftL) or keyIsDown(ShiftR): main_text.text = main_text.text & ($c).toRunes else: main_text.text = main_text.text & ($c).toRunes[0].toLower() - if engine.keyWasPressed(Enter): + if keyWasPressed(Enter): discard mixer[].play("key") main_text.text = main_text.text & Rune('\n') - if engine.keyWasPressed(Space): + if keyWasPressed(Space): discard mixer[].play("key") main_text.text = main_text.text & Rune(' ') # remove character - if engine.keyWasPressed(Backspace) and main_text.text.len > 0: + if keyWasPressed(Backspace) and main_text.text.len > 0: discard mixer[].play("key") main_text.text = main_text.text[0 ..< ^1] # alignemtn with F-keys - if engine.keyWasPressed(F1): main_text.horizontalAlignment = Left - elif engine.keyWasPressed(F2): main_text.horizontalAlignment = Center - elif engine.keyWasPressed(F3): main_text.horizontalAlignment = Right - elif engine.keyWasPressed(F4): main_text.verticalAlignment = Top - elif engine.keyWasPressed(F5): main_text.verticalAlignment = Center - elif engine.keyWasPressed(F6): main_text.verticalAlignment = Bottom + if keyWasPressed(F1): main_text.horizontalAlignment = Left + elif keyWasPressed(F2): main_text.horizontalAlignment = Center + elif keyWasPressed(F3): main_text.horizontalAlignment = Right + elif keyWasPressed(F4): main_text.verticalAlignment = Top + elif keyWasPressed(F5): main_text.verticalAlignment = Center + elif keyWasPressed(F6): main_text.verticalAlignment = Bottom origin.refresh() main_text.text = main_text.text & Rune('_') diff -r 073ce95ae5c7 -r ae54dbb08e53 tests/test_materials.nim --- a/tests/test_materials.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/tests/test_materials.nim Thu May 23 01:44:10 2024 +0700 @@ -41,6 +41,7 @@ const shaderConfiguration1 = createShaderConfiguration( + name = "shader 1", inputs = [ attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead), attr[Vec2f]("uv", memoryPerformanceHint = PreferFastRead), @@ -67,7 +68,7 @@ }) engine.loadScene(scene) var t = cpuTime() - while engine.updateInputs() == Running and not engine.keyIsDown(Escape): + while engine.updateInputs() and not keyIsDown(Escape): var d = float32(cpuTime() - t) setShaderGlobalArray(scene, "test2", @[newVec4f(d), newVec4f(d * 2)]) engine.renderScene(scene) diff -r 073ce95ae5c7 -r ae54dbb08e53 tests/test_mesh.nim --- a/tests/test_mesh.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/tests/test_mesh.nim Thu May 23 01:44:10 2024 +0700 @@ -1,14 +1,25 @@ import std/strformat import semicongine +const + MeshMaterial* = MaterialType( + name: "colored single texture material", + vertexAttributes: { + "position": Vec3F32, + "texcoord_0": Vec2F32, + }.toTable, + attributes: {"baseTexture": TextureType, "color": Vec4F32}.toTable + ) + proc main() = var scenes = [ - Scene(name: "Donut", meshes: loadMeshes("donut.glb", COLORED_SINGLE_TEXTURE_MATERIAL)[0].toSeq), + Scene(name: "Donut", meshes: loadMeshes("donut.glb", MeshMaterial)[0].toSeq), ] var engine = initEngine("Test meshes") const shaderConfiguration = createShaderConfiguration( + name = "default shader", inputs = [ attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead), attr[uint16](MATERIALINDEX_ATTRIBUTE, memoryPerformanceHint = PreferFastRead, perInstance = true), @@ -28,14 +39,14 @@ ], samplers = [attr[Texture]("baseTexture", arrayCount = 4)], vertexCode = &""" - gl_Position = vec4(position, 1.0) * (transform * Uniforms.view * Uniforms.projection); + gl_Position = vec4(position, 1.0) * (transform * (Uniforms.view * Uniforms.projection)); vertexColor = Uniforms.color[{MATERIALINDEX_ATTRIBUTE}]; colorTexCoord = texcoord_0; materialIndexOut = {MATERIALINDEX_ATTRIBUTE}; """, fragmentCode = "color = texture(baseTexture[materialIndexOut], colorTexCoord) * vertexColor;" ) - engine.initRenderer({COLORED_SINGLE_TEXTURE_MATERIAL: shaderConfiguration}) + engine.initRenderer({MeshMaterial: shaderConfiguration}) for scene in scenes.mitems: scene.addShaderGlobal("projection", Unit4F32) @@ -48,30 +59,30 @@ azimut = 0'f32 currentScene = 0 - while engine.updateInputs() == Running and not engine.keyIsDown(Escape): - if engine.keyWasPressed(`1`): + while engine.updateInputs() and not keyIsDown(Escape): + if keyWasPressed(`1`): currentScene = 0 - elif engine.keyWasPressed(`2`): + elif keyWasPressed(`2`): currentScene = 1 - elif engine.keyWasPressed(`3`): + elif keyWasPressed(`3`): currentScene = 2 - elif engine.keyWasPressed(`4`): + elif keyWasPressed(`4`): currentScene = 3 - elif engine.keyWasPressed(`5`): + elif keyWasPressed(`5`): currentScene = 4 - elif engine.keyWasPressed(`6`): + elif keyWasPressed(`6`): currentScene = 5 - if engine.keyWasPressed(NumberRowExtra3): + if keyWasPressed(NumberRowExtra3): size = 0.3'f32 elevation = 0'f32 azimut = 0'f32 let ratio = engine.getWindow().size[0] / engine.getWindow().size[1] - size *= 1'f32 + engine.mouseWheel() * 0.05 - azimut += engine.mouseMove().x / 180'f32 - elevation -= engine.mouseMove().y / 180'f32 - scenes[currentScene].setShaderGlobal("projection", ortho(-ratio, ratio, -1, 1, -1, 1)) + size *= 1'f32 + mouseWheel() * 0.05 + azimut += mouseMove().x / 180'f32 + elevation -= mouseMove().y / 180'f32 + scenes[currentScene].setShaderGlobal("projection", perspective(PI / 2, ratio, -0.5, 1)) scenes[currentScene].setShaderGlobal( "view", scale(size, size, size) * rotate(elevation, newVec3f(1, 0, 0)) * rotate(azimut, Yf32) diff -r 073ce95ae5c7 -r ae54dbb08e53 tests/test_panel.nim --- a/tests/test_panel.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/tests/test_panel.nim Thu May 23 01:44:10 2024 +0700 @@ -64,23 +64,23 @@ scene.add origin engine.loadScene(scene) - while engine.updateInputs() == Running and not engine.keyIsDown(Escape): - if engine.keyWasPressed(F1): + while engine.updateInputs() and not keyIsDown(Escape): + if keyWasPressed(F1): button.horizontalAlignment = Left counterText.horizontalAlignment = Left - elif engine.keyWasPressed(F2): + elif keyWasPressed(F2): button.horizontalAlignment = Center counterText.horizontalAlignment = Center - elif engine.keyWasPressed(F3): + elif keyWasPressed(F3): button.horizontalAlignment = Right counterText.horizontalAlignment = Right - elif engine.keyWasPressed(F4): + elif keyWasPressed(F4): button.verticalAlignment = Top counterText.verticalAlignment = Top - elif engine.keyWasPressed(F5): + elif keyWasPressed(F5): button.verticalAlignment = Center counterText.verticalAlignment = Center - elif engine.keyWasPressed(F6): + elif keyWasPressed(F6): button.verticalAlignment = Bottom counterText.verticalAlignment = Bottom diff -r 073ce95ae5c7 -r ae54dbb08e53 tests/test_vulkan_wrapper.nim --- a/tests/test_vulkan_wrapper.nim Mon Apr 29 02:37:42 2024 -0700 +++ b/tests/test_vulkan_wrapper.nim Thu May 23 01:44:10 2024 +0700 @@ -11,7 +11,16 @@ wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, ) (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8]) - mat = SINGLE_TEXTURE_MATERIAL.initMaterialData( + Mat1Type = MaterialType( + name: "single texture material 1", + vertexAttributes: { + "color": Vec4F32, + "position": Vec3F32, + "uv": Vec2F32, + }.toTable, + attributes: {"baseTexture": TextureType}.toTable + ) + mat = Mat1Type.initMaterialData( name = "mat", attributes = { "baseTexture": initDataList(@[Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 5, height: 5, imagedata: @[ @@ -26,6 +35,7 @@ Mat2Type = MaterialType( name: "single texture material 2", vertexAttributes: { + "color": Vec4F32, "position": Vec3F32, "uv": Vec2F32, }.toTable, @@ -178,6 +188,7 @@ # INIT RENDERER: const shaderConfiguration1 = createShaderConfiguration( + name = "shader1", inputs = [ attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead), attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite), @@ -187,13 +198,12 @@ attr[Vec4f]("outcolor"), ], outputs = [attr[Vec4f]("color")], - samplers = [ - attr[Texture]("baseTexture") - ], + samplers = [attr[Texture]("baseTexture")], vertexCode = """gl_Position = vec4(position, 1.0) * transform; outcolor = color;""", fragmentCode = "color = texture(baseTexture, outcolor.xy) * 0.5 + outcolor * 0.5;", ) shaderConfiguration2 = createShaderConfiguration( + name = "shader2", inputs = [ attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead), attr[Mat4]("transform", perInstance = true), @@ -205,8 +215,8 @@ fragmentCode = "color = outcolor;", ) engine.initRenderer({ - SINGLE_TEXTURE_MATERIAL: shaderConfiguration1, - SINGLE_TEXTURE_MATERIAL: shaderConfiguration1, + Mat1Type: shaderConfiguration1, + Mat1Type: shaderConfiguration1, Mat2Type: shaderConfiguration1, SINGLE_COLOR_MATERIAL: shaderConfiguration2, }) @@ -229,7 +239,7 @@ for scene in scenes.mitems: echo "rendering scene ", scene.name for i in 0 ..< 1000: - if engine.updateInputs() != Running or engine.keyIsDown(Escape): + if not engine.updateInputs() or keyIsDown(Escape): engine.destroy() return engine.renderScene(scene)