changeset 603:f494e291ff4a

add: correct mesh buffer data updates to GPU
author Sam <sam@basx.dev>
date Tue, 25 Apr 2023 18:23:57 +0700
parents 0d8858aa0671
children fdd80220b5ff
files src/semicongine/engine.nim src/semicongine/entity.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/vulkan/renderpass.nim src/semicongine/vulkan/shader.nim
diffstat 6 files changed, 105 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/engine.nim	Sat Apr 22 17:34:59 2023 +0700
+++ b/src/semicongine/engine.nim	Tue Apr 25 18:23:57 2023 +0700
@@ -1,3 +1,4 @@
+import std/sequtils
 import ./platform/window
 
 import ./vulkan/api
@@ -9,6 +10,7 @@
 import ./gpu_data
 import ./entity
 import ./renderer
+import ./mesh
 import ./events
 import ./config
 import ./math
@@ -86,13 +88,15 @@
 proc setRenderer*(engine: var Engine, renderPass: RenderPass) =
   engine.renderer = engine.device.initRenderer(renderPass)
 
-proc addScene*(engine: var Engine, entity: Entity, vertexInput: seq[ShaderAttribute]) =
-  engine.renderer.setupDrawableBuffers(entity, vertexInput)
+proc addScene*(engine: var Engine, scene: Entity, vertexInput: seq[ShaderAttribute], transformAttribute="") =
+  assert transformAttribute in map(vertexInput, proc(a: ShaderAttribute): string = a.name)
+  engine.renderer.setupDrawableBuffers(scene, vertexInput, transformAttribute=transformAttribute)
 
-proc renderScene*(engine: var Engine, entity: Entity) =
+proc renderScene*(engine: var Engine, scene: Entity) =
   assert engine.renderer.valid
   if engine.running:
-    engine.renderer.render(entity)
+    engine.renderer.refreshMeshData(scene)
+    engine.renderer.render(scene)
 
 proc updateInputs*(engine: var Engine) =
   if not engine.running:
@@ -141,7 +145,7 @@
 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, key: Key): auto = engine.input.mousePosition
+func mousePosition*(engine: Engine): auto = engine.input.mousePosition
 func eventsProcessed*(engine: Engine): auto = engine.input.eventsProcessed
 func framesRendered*(engine: Engine): auto = engine.renderer.framesRendered
 func gpuDevice*(engine: Engine): Device = engine.device
--- a/src/semicongine/entity.nim	Sat Apr 22 17:34:59 2023 +0700
+++ b/src/semicongine/entity.nim	Tue Apr 25 18:23:57 2023 +0700
@@ -31,6 +31,9 @@
 func hash*(entity: Entity): Hash =
   hash(cast[pointer](entity))
 
+func hash*(component: Component): Hash =
+  hash(cast[pointer](component))
+
 method `$`*(entity: Entity): string {.base.} = entity.name
 method `$`*(component: Component): string {.base.} =
   "Unknown Component"
--- a/src/semicongine/mesh.nim	Sat Apr 22 17:34:59 2023 +0700
+++ b/src/semicongine/mesh.nim	Tue Apr 25 18:23:57 2023 +0700
@@ -22,6 +22,7 @@
     indicesCount*: uint32
     instanceCount*: uint32
     data: Table[string, DataList]
+    changedAttributes: seq[string]
     case indexType*: MeshIndexType
       of None: discard
       of Tiny: tinyIndices: seq[array[3, uint8]]
@@ -86,6 +87,9 @@
 ): auto =
   newMesh(positions, newSeq[array[3, int]](), colors, instanceCount)
 
+func availableAttributes*(mesh: Mesh): seq[string] =
+  mesh.data.keys.toSeq
+
 func dataSize*(mesh: Mesh, attribute: string): uint32 =
   mesh.data[attribute].size
 
@@ -112,6 +116,10 @@
 func getRawData*(mesh: Mesh, attribute: string): (pointer, uint32) =
   mesh.data[attribute].getRawData()
 
+proc getMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): seq[T] =
+  assert attribute in mesh.data
+  get[T](mesh.data[attribute])
+
 proc setMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
   assert not (attribute in mesh.data)
   mesh.data[attribute] = DataList(thetype: getDataType[T]())
@@ -123,6 +131,22 @@
   mesh.data[attribute] = DataList(thetype: getDataType[T]())
   setValues(mesh.data[attribute], data)
 
+proc updateMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, i: uint32, value: T) =
+  assert attribute in mesh.data
+  mesh.changedAttributes.add attribute
+  setValue(mesh.data[attribute], i, value)
+
+proc updateInstanceData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
+  assert uint32(data.len) == mesh.instanceCount
+  assert attribute in mesh.data
+  mesh.changedAttributes.add attribute
+  setValues(mesh.data[attribute], data)
+
+func hasDataChanged*(mesh: Mesh, attribute: string): bool =
+  attribute in mesh.changedAttributes
+
+proc clearDataChanged*(mesh: var Mesh) =
+  mesh.changedAttributes = @[]
 
 func rect*(width=1'f32, height=1'f32, color="ffffff"): Mesh =
   result = new Mesh
--- a/src/semicongine/renderer.nim	Sat Apr 22 17:34:59 2023 +0700
+++ b/src/semicongine/renderer.nim	Tue Apr 25 18:23:57 2023 +0700
@@ -15,12 +15,17 @@
 import ./entity
 import ./mesh
 import ./gpu_data
+import ./math
 
 type
   SceneData = object
-    drawables*: seq[Drawable]
+    drawables*: OrderedTable[Mesh, Drawable]
     vertexBuffers*: Table[MemoryLocation, Buffer]
     indexBuffer*: Buffer
+    attributeLocation*: Table[string, MemoryLocation]
+    attributeBindingNumber*: Table[string, int]
+    transformAttribute: string # name of attribute that is used for per-instance mesh transformation
+    entityTransformationCache: Table[Mesh, Mat4] # remembers last transformation, avoid to send GPU-updates if no changes
   Renderer* = object
     device: Device
     surfaceFormat: VkSurfaceFormatKHR
@@ -42,12 +47,23 @@
     raise newException(Exception, "Unable to create swapchain")
   result.swapchain = swapchain.get()
 
-proc setupDrawableBuffers*(renderer: var Renderer, tree: Entity, inputs: seq[ShaderAttribute]) =
-  assert not (tree in renderer.scenedata)
+proc setupDrawableBuffers*(renderer: var Renderer, scene: Entity, inputs: seq[ShaderAttribute], transformAttribute="") =
+  assert not (scene in renderer.scenedata)
   var data = SceneData()
 
+  if transformattribute != "":
+    var hasTransformAttribute = false
+    for input in inputs:
+      if input.name == transformattribute:
+        assert input.perInstance == true, $input
+        assert getDataType[Mat4]() == input.thetype
+        hasTransformAttribute = true
+    assert hasTransformAttribute
+    data.transformAttribute = transformAttribute
+
+
   var allMeshes: seq[Mesh]
-  for mesh in allComponentsOfType[Mesh](tree):
+  for mesh in allComponentsOfType[Mesh](scene):
     allMeshes.add mesh
     for inputAttr in inputs:
       assert mesh.hasDataFor(inputAttr.name), &"{mesh} missing data for {inputAttr}"
@@ -73,9 +89,14 @@
     )
 
   # one vertex data buffer per memory location
-  var perLocationOffsets: Table[MemoryLocation, uint64]
-  var perLocationSizes: Table[MemoryLocation, uint64]
+  var
+    perLocationOffsets: Table[MemoryLocation, uint64]
+    perLocationSizes: Table[MemoryLocation, uint64]
+    bindingNumber = 0
   for attribute in inputs:
+    data.attributeLocation[attribute.name] = attribute.memoryLocation
+    data.attributeBindingNumber[attribute.name] = bindingNumber
+    inc bindingNumber
     # setup one buffer per attribute-location-type
     if not (attribute.memoryLocation in perLocationSizes):
       perLocationSizes[attribute.memoryLocation] = 0'u64
@@ -121,11 +142,41 @@
       var (pdata, size) = mesh.getRawIndexData()
       data.indexBuffer.setData(pdata, size, indexBufferOffset)
       indexBufferOffset += size
-    data.drawables.add drawable
+    data.drawables[mesh] = drawable
+
+  renderer.scenedata[scene] = data
+
+proc refreshMeshAttributeData(sceneData: var SceneData, mesh: Mesh, attribute: string) =
+  debug &"Refreshing data on mesh {mesh} for {attribute}"
+  var (pdata, size) = mesh.getRawData(attribute)
+  let memoryLocation = sceneData.attributeLocation[attribute]
+  let bindingNumber = sceneData.attributeBindingNumber[attribute]
+  sceneData.vertexBuffers[memoryLocation].setData(pdata, size, sceneData.drawables[mesh].bufferOffsets[bindingNumber][1])
+
+
+proc refreshMeshData*(renderer: var Renderer, scene: Entity) =
+  assert scene in renderer.scenedata
 
-  renderer.scenedata[tree] = data
+  for mesh in allComponentsOfType[Mesh](scene):
+    # if mesh transformation attribute is enabled, update the model matrix
+    if renderer.scenedata[scene].transformAttribute != "":
+      let transform = mesh.entity.getModelTransform()
+      if not (mesh in renderer.scenedata[scene].entityTransformationCache) or renderer.scenedata[scene].entityTransformationCache[mesh] != transform:
+        mesh.updateInstanceData(renderer.scenedata[scene].transformAttribute, @[transform])
+        renderer.scenedata[scene].entityTransformationCache[mesh] = transform
 
-proc render*(renderer: var Renderer, entity: Entity) =
+    # update any changed mesh attributes
+    for attribute in mesh.availableAttributes():
+      if mesh.hasDataChanged(attribute):
+        renderer.scenedata[scene].refreshMeshAttributeData(mesh, attribute)
+    var m = mesh
+    m.clearDataChanged()
+
+
+
+proc render*(renderer: var Renderer, scene: Entity) =
+  assert scene in renderer.scenedata
+
   var
     commandBufferResult = renderer.swapchain.nextFrame()
     commandBuffer: VkCommandBuffer
@@ -150,15 +201,15 @@
       var mpipeline = pipeline
       commandBuffer.vkCmdBindPipeline(subpass.pipelineBindPoint, mpipeline.vk)
       commandBuffer.vkCmdBindDescriptorSets(subpass.pipelineBindPoint, mpipeline.layout, 0, 1, addr(mpipeline.descriptorSets[renderer.swapchain.currentInFlight].vk), 0, nil)
-      mpipeline.updateUniforms(entity, renderer.swapchain.currentInFlight)
+      mpipeline.updateUniforms(scene, renderer.swapchain.currentInFlight)
 
       debug "Scene buffers:"
-      for (location, buffer) in renderer.scenedata[entity].vertexBuffers.pairs:
+      for (location, buffer) in renderer.scenedata[scene].vertexBuffers.pairs:
         debug "  ", location, ": ", buffer
-      debug "  Index buffer: ", renderer.scenedata[entity].indexBuffer
+      debug "  Index buffer: ", renderer.scenedata[scene].indexBuffer
 
-      for drawable in renderer.scenedata[entity].drawables:
-        commandBuffer.draw(drawable, vertexBuffers=renderer.scenedata[entity].vertexBuffers, indexBuffer=renderer.scenedata[entity].indexBuffer)
+      for drawable in renderer.scenedata[scene].drawables.values:
+        commandBuffer.draw(drawable, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer)
 
     if i < renderer.renderPass.subpasses.len - 1:
       commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
--- a/src/semicongine/vulkan/renderpass.nim	Sat Apr 22 17:34:59 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Tue Apr 25 18:23:57 2023 +0700
@@ -70,8 +70,8 @@
   device: Device,
   vertexCode: ShaderCode,
   fragmentCode: ShaderCode,
-  inFlightFrames: int = 2,
-  format = VK_FORMAT_UNDEFINED ,
+  inFlightFrames=2,
+  format=VK_FORMAT_UNDEFINED ,
   clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])
 ): RenderPass =
   assert device.vk.valid
--- a/src/semicongine/vulkan/shader.nim	Sat Apr 22 17:34:59 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Tue Apr 25 18:23:57 2023 +0700
@@ -93,7 +93,9 @@
   entrypoint=DEFAULT_SHADER_ENTRYPOINT ,
   main: seq[string]
 ): ShaderCode {.compileTime.} =
+
   var code = @[&"#version {version}", ""] &
+  # var code = @[&"#version {version}", "layout(row_major) uniform;", ""] &
     (if inputs.len > 0: inputs.glslInput() & @[""] else: @[]) &
     (if uniforms.len > 0: uniforms.glslUniforms() & @[""] else: @[]) &
     (if outputs.len > 0: outputs.glslOutput() & @[""] else: @[]) &