changeset 1021:73b572f82a1f

add: bases for a better input-system
author sam <sam@basx.dev>
date Thu, 09 May 2024 23:02:35 +0700
parents 355ef428b5f4
children f888ac4825b8
files semicongine.nim semicongine/engine.nim semicongine/input.nim tests/test_collision.nim tests/test_font.nim tests/test_materials.nim tests/test_mesh.nim tests/test_panel.nim tests/test_vulkan_wrapper.nim
diffstat 9 files changed, 202 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine.nim	Wed May 08 15:46:47 2024 +0700
+++ b/semicongine.nim	Thu May 09 23:02:35 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
--- a/semicongine/engine.nim	Wed May 08 15:46:47 2024 +0700
+++ b/semicongine/engine.nim	Thu May 09 23:02:35 2024 +0700
@@ -18,8 +18,8 @@
 import ./scene
 import ./material
 import ./renderer
-import ./events
 import ./audio
+import ./input
 import ./text
 import ./panel
 
@@ -32,32 +32,15 @@
     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
@@ -82,9 +65,6 @@
   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,10 +77,6 @@
   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
@@ -181,7 +157,6 @@
   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()
@@ -206,81 +181,10 @@
       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
@@ -292,8 +196,11 @@
 func limits*(engine: Engine): VkPhysicalDeviceLimits =
   engine.gpuDevice().physicalDevice.properties.limits
 
+proc updateInputs*(engine: Engine): bool =
+  updateInputs(engine.window.pendingEvents())
+
 proc processEvents*(engine: Engine, panel: var Panel) =
-  let hasMouseNow = panel.contains(engine.mousePositionNormalized, engine.getAspectRatio)
+  let hasMouseNow = panel.contains(mousePositionNormalized(engine.window.size), engine.getAspectRatio)
 
   # enter/leave events
   if hasMouseNow:
@@ -307,9 +214,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 input.mouseWasPressed():
+      if panel.onMouseDown != nil: panel.onMouseDown(panel, input.mousePressedButtons())
+    if input.mouseWasReleased():
+      if panel.onMouseUp != nil: panel.onMouseUp(panel, input.mouseReleasedButtons())
 
   panel.hasMouse = hasMouseNow
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/input.nim	Thu May 09 23:02:35 2024 +0700
@@ -0,0 +1,101 @@
+# 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
+#
+# API to define actions that are connected to user inputs
+#
+# Example:
+#
+# type
+#   Action = enum
+#     Jump
+#     Left
+#     Right
+#
+# AddAction(Jump, SpaceDown, Pressed) # trigger action
+# AddAction(Left, Arrow, Down) # boolean action
+# AddAction(Left, Joystick_Left, Axis) # axis action
+#
+#
+#
+#
+# if Action(Jump).Triggered:
+#   accel_y = 1
+# if Action(Left).Active:
+#   accel_y = 1
+# if Action(Left).Value:
+#   accel_y = 1
+
+
+import ./core/vector
+import ./events
+
+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
+
+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
--- a/tests/test_collision.nim	Wed May 08 15:46:47 2024 +0700
+++ b/tests/test_collision.nim	Thu May 09 23:02:35 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)
--- a/tests/test_font.nim	Wed May 08 15:46:47 2024 +0700
+++ b/tests/test_font.nim	Thu May 09 23:02:35 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('_')
--- a/tests/test_materials.nim	Wed May 08 15:46:47 2024 +0700
+++ b/tests/test_materials.nim	Thu May 09 23:02:35 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)
--- a/tests/test_mesh.nim	Wed May 08 15:46:47 2024 +0700
+++ b/tests/test_mesh.nim	Thu May 09 23:02:35 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)
--- a/tests/test_panel.nim	Wed May 08 15:46:47 2024 +0700
+++ b/tests/test_panel.nim	Thu May 09 23:02:35 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
 
--- a/tests/test_vulkan_wrapper.nim	Wed May 08 15:46:47 2024 +0700
+++ b/tests/test_vulkan_wrapper.nim	Thu May 09 23:02:35 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)