groups.kt
src/jvmMain/kotlin/afield/graphics/render/groups.kt · 95 lines
package afield.graphics.render
import afield.graphics.vulkan.buffers.bindPushConstantAcrossGroup
import afield.graphics.vulkan.buffers.firstBoundPushConstant
import kotlin.reflect.KProperty
Mesh groups: lists of meshes that share one live push-constant binding.
MeshGroup, MeshMeshGroup and ComputeMeshGroup each carry a BoundFromStructure delegate, so
a property assigned on the group (a supplier () -> T) is bound to the push constant of the same
name in every member, and read back from the first member that has it. A name no member's module
declares as a push constant throws. bindPush is the looser form for meshes that are not in a
group: it runs one binding block against each target in turn.
class MeshGroup : ArrayList<Mesh>()
A list of Meshes whose push constants are bound together by property name.
class BoundFromStructure<T>
operator fun setValue(inside: MeshGroup, property: KProperty<*>, any: () -> T) {
operator fun setValue(inside: MeshGroup, property: KProperty<*>, any: () -> T) {
inside.bindPushConstantAcrossGroup(
property.name, any,
push = { it.push },
ensureInitialized = { if (it.push.offsets.isEmpty()) it.initializePushConstantsFromModule(it.inside) },
) { element ->
throw IllegalArgumentException(" can't find ${property.name} in a mesh group in this module ${element.inside.current.parameters.name} as a push_constant")
}
}
operator fun getValue(inside: MeshGroup, property: KProperty<*>): () -> T =
operator fun getValue(inside: MeshGroup, property: KProperty<*>): () -> T =
inside.firstBoundPushConstant(
property.name,
push = { it.push },
ensureInitialized = { if (it.push.offsets.isEmpty()) it.initializePushConstantsFromModule(it.inside) },
) {
throw IllegalArgumentException(" can't find ${property.name} in a mesh group in this module $inside as a push_constant or a UBO structure")
}
}
}
class MeshMeshGroup : ArrayList<MeshMesh>()
class BoundFromStructure<T>
operator fun setValue(inside: MeshMeshGroup, property: KProperty<*>, any: () -> T) {
operator fun setValue(inside: MeshMeshGroup, property: KProperty<*>, any: () -> T) {
inside.bindPushConstantAcrossGroup(
property.name, any,
push = { it.push },
ensureInitialized = { if (it.push.offsets.isEmpty()) it.initializePushConstantsFromModule(it.inside) },
) { element ->
throw IllegalArgumentException(" can't find ${property.name} in a mesh group in this module ${element.inside.current.parameters.name} as a push_constant")
}
}
operator fun getValue(inside: MeshMeshGroup, property: KProperty<*>): () -> T =
operator fun getValue(inside: MeshMeshGroup, property: KProperty<*>): () -> T =
inside.firstBoundPushConstant(
property.name,
push = { it.push },
ensureInitialized = { if (it.push.offsets.isEmpty()) it.initializePushConstantsFromModule(it.inside) },
) {
throw IllegalArgumentException(" can't find ${property.name} in a mesh group in this module $inside as a push_constant or a UBO structure")
}
}
}
class ComputeMeshGroup : ArrayList<ComputeMesh>()
class BoundFromStructure<T>
operator fun setValue(inside: ComputeMeshGroup, property: KProperty<*>, any: () -> T) {
operator fun setValue(inside: ComputeMeshGroup, property: KProperty<*>, any: () -> T) {
inside.bindPushConstantAcrossGroup(
property.name, any,
push = { it.push },
ensureInitialized = { if (it.push.offsets.isEmpty()) it.initializePushConstantsFromModule(it.inside) },
) { element ->
throw IllegalArgumentException(" can't find ${property.name} in a mesh group in this module ${element.inside.current.parameters.name} as a push_constant")
}
}
operator fun getValue(inside: ComputeMeshGroup, property: KProperty<*>): () -> T =
operator fun getValue(inside: ComputeMeshGroup, property: KProperty<*>): () -> T =
inside.firstBoundPushConstant(
property.name,
push = { it.push },
ensureInitialized = { if (it.push.offsets.isEmpty()) it.initializePushConstantsFromModule(it.inside) },
) {
throw IllegalArgumentException(" can't find ${property.name} in a mesh group in this module $inside as a push_constant or a UBO structure")
}
}
}
fun <T> bindPush(vararg targets: T, bindings: T.() -> Unit) {
Apply the same live push bindings to each mesh: bindPush(even, odd) { count = { n } }.
The receiver retains its concrete mesh type and generated shader properties. The block executes
once per target; suppliers assigned inside it remain live rather than being sampled here.
fun <T> bindPush(vararg targets: T, bindings: T.() -> Unit) {
targets.forEach { target -> target.bindings() }
}