MeshMesh.kt
src/jvmMain/kotlin/afield/graphics/render/MeshMesh.kt · 214 lines
package afield.graphics.render
import afield.document.context.fromContext
import org.lwjgl.vulkan.EXTMeshShader.VK_SHADER_STAGE_MESH_BIT_EXT
import org.lwjgl.vulkan.EXTMeshShader.VK_SHADER_STAGE_TASK_BIT_EXT
import org.lwjgl.vulkan.EXTMeshShader.vkCmdDrawMeshTasksEXT
import org.lwjgl.vulkan.EXTMeshShader.vkCmdDrawMeshTasksIndirectEXT
import afield.graphics.vulkan.buffers.FrameBufferGroup
import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.buffers.MissingRefException
import afield.graphics.vulkan.buffers.Structure
import afield.graphics.vulkan.buffers.bindPushConstant
import afield.graphics.vulkan.buffers.getBoundPushConstant
import afield.graphics.vulkan.buffers.getPushConstant
import afield.graphics.vulkan.buffers.setPushConstant
import afield.graphics.vulkan.device.CommandBuffer
import afield.graphics.vulkan.util.MapOfSequences
import afield.app.diagnostics.logging.lprintln
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.VK10
import java.nio.ByteBuffer
import kotlin.reflect.KProperty
The MeshTask interface for issuing mesh-shader work, and MeshMesh, a task whose push constants and uniforms are bound to Kotlin properties by name.
meshTask wraps a lambda as a MeshTask.
interface MeshTask
fun compute(inside: MeshModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer)
}
fun meshTask(c: (inside: MeshModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) -> Unit): MeshTask {
fun meshTask(c: (inside: MeshModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) -> Unit): MeshTask {
return object : MeshTask {
override fun compute(
inside: MeshModule,
cmd: CommandBuffer,
pushConstantBuffer: ByteBuffer
) {
c(inside, cmd, pushConstantBuffer)
}
}
}
class MeshMesh(var renderer: Renderer = fromContext()) : MeshTask
As names go, this one is pretty bad
class MeshMesh(var renderer: Renderer = fromContext()) : MeshTask {
var indirect: FrameBufferGroup? = null
GPU-written VkDrawMeshTasksIndirectCommandEXT; null keeps direct x/y/z dispatch.
var indirectOffset: Long = 0L
Byte offset of the three uint group counts, aligned to four bytes.
fun prepareForDraw(command: CommandBuffer)
Transfers must be prepared outside dynamic rendering, like a conventional mesh's buffers.
fun prepareForDraw(command: CommandBuffer) {
indirect?.let { group ->
require(indirectOffset >= 0 && indirectOffset % 4L == 0L)
group.getGPUAddress(command)
require(indirectOffset + 12L <= group.getLastWritten().requestedSize)
}
}
class FromUniform
operator fun getValue(inside: MeshModulePointer, property: KProperty<*>): Structure
operator fun getValue(inside: MeshModulePointer, property: KProperty<*>): Structure {
return ModuleRebuild.access(inside, property.name, inside.current.liveUniformStructures[property.name]
?: throw IllegalArgumentException(" can't find '${property.name}' in this module, options are ${inside.current.liveUniformStructures.keys}"))
}
}
class BoundFromStructure<T>
operator fun setValue(inside: MeshMesh, property: KProperty<*>, any: () -> T) {
operator fun setValue(inside: MeshMesh, property: KProperty<*>, any: () -> T) {
if (inside.push.offsets.isEmpty()) inside.initializePushConstantsFromModule(inside.inside)
inside.push.bindPushConstant(property.name, any) {
throw IllegalArgumentException(" can't find ${property.name} in a mesh in this module ${inside.inside.current.parameters.name} as a push_constant")
}
}
operator fun getValue(inside: MeshMesh, property: KProperty<*>): () -> T {
operator fun getValue(inside: MeshMesh, property: KProperty<*>): () -> T {
if (inside.push.offsets.isEmpty()) inside.initializePushConstantsFromModule(inside.inside)
return inside.push.getBoundPushConstant(property.name) {
throw IllegalArgumentException(" can't find ${property.name} in a mesh in this module ${inside.inside.current.parameters.name} as a push_constant or a UBO structure")
}
}
}
constructor(renderer: Renderer = fromContext(), pointer: MeshModulePointer = fromContext()) : this(renderer)
constructor(renderer: Renderer = fromContext(), pointer: MeshModulePointer = fromContext()) : this(renderer) {
this.inside = pointer
}
lateinit var inside: MeshModulePointer
var update = MapOfSequences<String>()
var push = Structure()
var x = 1;
var y = 1;
var z = 1;
internal fun initializePushConstantsFromModule(inside: MeshModulePointer)
internal fun initializePushConstantsFromModule(inside: MeshModulePointer) {
for (pc in inside.current.fi.pushConstants) {
push.setMembers(pc.members)
}
for (pc in inside.current.mi.pushConstants) {
push.setMembers(pc.members)
}
for (pc in inside.current.ti.pushConstants) {
push.setMembers(pc.members)
}
}
class FromStructure<T>
operator fun setValue(inside: MeshMesh, property: KProperty<*>, any: T)
operator fun setValue(inside: MeshMesh, property: KProperty<*>, any: T) {
if (inside.push.offsets.isEmpty()) inside.initializePushConstantsFromModule(inside.inside)
inside.push.setPushConstant(property.name, any) {
throw IllegalArgumentException(" can't find ${property.name} in a mesh in this module ${inside.inside.current.parameters.name} as a push_constant")
}
}
operator fun getValue(inside: MeshMesh, property: KProperty<*>): T
operator fun getValue(inside: MeshMesh, property: KProperty<*>): T {
if (inside.push.offsets.isEmpty()) inside.initializePushConstantsFromModule(inside.inside)
return inside.push.getPushConstant(property.name) {
// else if (property.name in inside.inside.current.liveUniformStructures) {
// return inside.inside.current.liveUniformStructures[property.name] as T
// }
throw IllegalArgumentException(" can't find ${property.name} in a mesh in this module ${inside.inside.current.parameters.name} as a push_constant or a UBO structure")
}
}
}
override fun compute(inside: MeshModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer)
body — 80 lines, from line 134
override fun compute(inside: MeshModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) {
if (inside != this.inside.current) lprintln("warning,graphics", "MeshMesh built in a different module ${inside}!=${this.inside.current}")
update.update()
MemoryStack.stackPush().use { _ ->
fun pushConstantsForCurrentDispatch(): Boolean {
if (pushConstantBuffer.limit() <= 0) return true
for (pc in inside.fi.pushConstants) {
push.setMembers(pc.members)
}
for (pc in inside.mi.pushConstants) {
push.setMembers(pc.members)
}
for (pc in inside.ti.pushConstants) {
push.setMembers(pc.members)
}
try {
pushConstantBuffer.clear()
push.writeToBuffer(pushConstantBuffer, cmd) {
throw MissingRefException(afield.graphics.vulkan.buffers.unassignedMemberMessage(it))
}
} catch (e: Exception) {
inside.parameters.errorHandler(e.message ?: "push constant error", e)
lprintln("error,graphics", "push constant error, not dispatching mesh task : ${e.message}")
return false
}
pushConstantBuffer.clear()
VK10.vkCmdPushConstants(
cmd.vkCommandBuffer,
inside.pipelineLayout,
VK_SHADER_STAGE_TASK_BIT_EXT or VK_SHADER_STAGE_MESH_BIT_EXT or VK10.VK_SHADER_STAGE_FRAGMENT_BIT,
0,
pushConstantBuffer
)
return true
}
fun dispatchForCurrentWriter(dispatchX: Int?, dispatchY: Int?, dispatchZ: Int?) {
indirect?.let {
vkCmdDrawMeshTasksIndirectEXT(cmd.vkCommandBuffer, it.getLastWritten().buffer, indirectOffset, 1, 12)
return
}
val xx = dispatchX ?: x
val yy = dispatchY ?: y
val zz = dispatchZ ?: z
if (xx * yy * zz > 0) {
vkCmdDrawMeshTasksEXT(cmd.vkCommandBuffer, xx, yy, zz)
} else {
lprintln("warning,graphics", "MeshMesh has no work to do $xx $yy $zz")
}
}
try {
forEachFanOutDispatch(
renderer = renderer,
push = push,
onNoFanOut = {
if (pushConstantsForCurrentDispatch()) {
dispatchForCurrentWriter(null, null, null)
}
},
onWriter = { writer ->
if (pushConstantsForCurrentDispatch()) {
dispatchForCurrentWriter(
dispatchX = writer.publication.dispatchX,
dispatchY = writer.publication.dispatchY,
dispatchZ = writer.publication.dispatchZ
)
}
}
)
} catch (e: Exception) {
inside.parameters.errorHandler(e.message ?: "fan-out dispatch error", e)
lprintln("error,graphics", "fan-out dispatch error, not dispatching mesh task : ${e.message}")
}
}
}
}