RayTracingModule.kt
src/jvmMain/kotlin/afield/graphics/render/RayTracingModule.kt · 972 lines
The ray-tracing counterpart of Module: a Vulkan ray-tracing pipeline built from raygen, miss, hit-group and callable shaders, with its shader binding table and bound resources.
RayTracingGroups lists the stages; RayTracingGroups.fromParameters builds it from the
single-stage sources on Parameters. A RayTracingModulePointer lets a box swap a recompiled
module in place, as ModulePointer does for rasterising modules. Work is recorded as
RayTracingTasks: RayTracingDispatch traces a width × height × depth grid of rays, and
rayTracingTask wraps an arbitrary command-buffer callback. rayTracingHostReadBarrier
makes ray-tracing shader writes visible to a host read.
package and imports — 48 lines, from line 13
package afield.graphics.render
import afield.graphics.scene.main
import afield.document.context.fromContext
import afield.graphics.shader.Introspected
import afield.graphics.shader.Introspection
import afield.graphics.shader.ShaderIncludeResolver
import afield.graphics.shader.ShaderKind
import afield.graphics.shader.shaderInterfaceTransition
import afield.app.diagnostics.Named
import afield.graphics.vulkan.Target
import afield.graphics.vulkan.buffers.BufferParameters
import afield.graphics.vulkan.buffers.MissingRefException
import afield.graphics.vulkan.buffers.Structure
import afield.graphics.vulkan.buffers.VulkanBuffer
import afield.graphics.vulkan.buffers.bindPushConstant
import afield.graphics.vulkan.buffers.buffer
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.device.DescriptorSetLayout
import afield.graphics.vulkan.device.UpdatesDescriptorSet
import afield.graphics.vulkan.device.beginLabel
import afield.graphics.vulkan.device.endLabel
import afield.graphics.vulkan.device.nameObject
import afield.graphics.texture.HasImageViewArray
import afield.graphics.texture.TextureSampler
import afield.graphics.vulkan.util.AutoDisposer
import afield.graphics.vulkan.util.FIELD_PROBE_BUFFER_NAME
import afield.graphics.vulkan.util.vkCheck
import afield.app.diagnostics.logging.lprintln
import afield.math.linalg.f
import afield.math.linalg.Rectanglef
import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil
import org.lwjgl.vulkan.KHRAccelerationStructure.*
import org.lwjgl.vulkan.KHRRayTracingPipeline.*
import org.lwjgl.vulkan.KHRSynchronization2.VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR
import org.lwjgl.vulkan.VK10.*
import org.lwjgl.vulkan.VK12.VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
import org.lwjgl.vulkan.VK13.*
import org.lwjgl.vulkan.*
import java.nio.ByteBuffer
import kotlin.math.min
import kotlin.reflect.KProperty
data class RayTracingShaderStage
One ray-tracing shader: its name, stage kind and GLSL source.
data class RayTracingShaderStage(
val name: String,
val kind: ShaderKind,
val source: String,
val introspection: Introspection? = null,
val sourceName: String = "$name.glsl",
val includeResolver: ShaderIncludeResolver? = null
)
data class RayTracingHitGroup
data class RayTracingHitGroup(
val name: String,
val closestHit: RayTracingShaderStage? = null,
val anyHit: RayTracingShaderStage? = null,
val intersection: RayTracingShaderStage? = null
)
data class RayTracingGroups
data class RayTracingGroups(
var raygen: RayTracingShaderStage? = null,
val miss: MutableList<RayTracingShaderStage> = mutableListOf(),
val hit: MutableList<RayTracingHitGroup> = mutableListOf(),
val callable: MutableList<RayTracingShaderStage> = mutableListOf()
) {
companion object
fun fromParameters(parameters: Parameters): RayTracingGroups
body — 75 lines, from line 86
fun fromParameters(parameters: Parameters): RayTracingGroups {
val groups = RayTracingGroups()
if (parameters.raygen.isNotBlank()) {
groups.raygen = RayTracingShaderStage(
"raygen",
ShaderKind.RAYGEN_SHADER,
parameters.raygen,
parameters.raygenIntrospection,
parameters.raygenSourceName,
parameters.raygenIncludeResolver
)
}
if (parameters.miss.isNotBlank()) {
groups.miss += RayTracingShaderStage(
"miss",
ShaderKind.MISS_SHADER,
parameters.miss,
parameters.missIntrospection,
parameters.missSourceName,
parameters.missIncludeResolver
)
}
val hasHitGroup =
parameters.closesthit.isNotBlank() ||
parameters.anyhit.isNotBlank() ||
parameters.intersection.isNotBlank()
if (hasHitGroup) {
groups.hit += RayTracingHitGroup(
name = "hit",
closestHit = parameters.closesthit.takeIf { it.isNotBlank() }?.let {
RayTracingShaderStage(
"closesthit",
ShaderKind.CLOSEST_HIT_SHADER,
it,
parameters.closesthitIntrospection,
parameters.closesthitSourceName,
parameters.closesthitIncludeResolver
)
},
anyHit = parameters.anyhit.takeIf { it.isNotBlank() }?.let {
RayTracingShaderStage(
"anyhit",
ShaderKind.ANY_HIT_SHADER,
it,
parameters.anyhitIntrospection,
parameters.anyhitSourceName,
parameters.anyhitIncludeResolver
)
},
intersection = parameters.intersection.takeIf { it.isNotBlank() }?.let {
RayTracingShaderStage(
"intersection",
ShaderKind.INTERSECTION_SHADER,
it,
parameters.intersectionIntrospection,
parameters.intersectionSourceName,
parameters.intersectionIncludeResolver
)
}
)
}
if (parameters.callable.isNotBlank()) {
groups.callable += RayTracingShaderStage(
"callable",
ShaderKind.CALLABLE_SHADER,
parameters.callable,
parameters.callableIntrospection,
parameters.callableSourceName,
parameters.callableIncludeResolver
)
}
return groups
}
}
}
class RayTracingModulePointer(var current: RayTracingModule) : Renderable
class RayTracingModulePointer(var current: RayTracingModule) : Renderable {
var lastReloadReport: ModuleReloadReport?
What the last successful reload did to the interface and which values it could not carry.
var lastReloadReport: ModuleReloadReport? = null
internal set
override fun render(command: CommandBuffer, bounds: Rectanglef)
override fun render(command: CommandBuffer, bounds: Rectanglef) {
if (current.valid && current.enabled) current.render(command, bounds)
}
fun reload
body — 41 lines, from line 171
fun reload(
p: Parameters,
forceReresolve: Boolean = true,
onError: (String, Throwable?) -> Unit = p.errorHandler
): Boolean {
if (current.valid && p == current.parameters && !forceReresolve) return false
p.errorHandler = onError
p.ssboResolver = reloadWrap(p.ssboResolver) { oldSsboResolver, it ->
val prev = current.uniforms[it.name]
val prevSrc = prev?.source
if (prevSrc != null && prevSrc.checkCompatibility(it) != false) oldSsboResolver(prevSrc)
else oldSsboResolver(it)
}
p.textureResolver = reloadWrap(p.textureResolver) { oldTextureResolver, it ->
if (forceReresolve) {
oldTextureResolver(it) ?: current.textureUniform(it.name) ?: oldTextureResolver(it)
} else {
current.textureUniform(it.name) ?: oldTextureResolver(it)
}
}
val m = RayTracingModule(p)
if (m.valid) {
val previous = current
m.copyTasksFrom(previous)
m.enabled = previous.enabled
val transition = runCatching { shaderInterfaceTransition(previous.stages(), m.stages()) }.getOrNull()
lastReloadReport = ModuleReloadReport(
transition,
ReloadCarryOver.finishAndNotify(this, transition, previous.uniforms.values, m.uniforms, previous.liveUniformStructures, m.liveUniformStructures)
)
current = m
// Retired through the renderer's deferred queue, as in ModulePointer.reload.
previous.cleanup()
return true
}
m.cleanup()
return false
}
operator fun plusAssign(task: Pair<String, RayTracingTask>)
operator fun plusAssign(task: Pair<String, RayTracingTask>) {
current.tasks[task.first] = task.second
(task.second as? RayTracingDispatch)?.inside = this
}
operator fun plusAssign(task: Named<RayTracingTask>)
operator fun plusAssign(task: Named<RayTracingTask>) {
current.tasks[task.name] = task.value
(task.value as? RayTracingDispatch)?.inside = this
}
operator fun plusAssign(m: Pair<String, Target>)
@JvmName("plusAssign_target")
operator fun plusAssign(m: Pair<String, Target>) {
current.after[m.first] = m.second
}
fun enable() { current.enable() }
fun disable() { current.disable() }
override fun toString(): String
override fun toString(): String = if (!current.valid) "Invalid RayTracingModule" else "RayTracingModule"
}
class RayTracingFromUniform
operator fun getValue(inside: RayTracingModulePointer, property: KProperty<*>): Structure
operator fun getValue(inside: RayTracingModulePointer, property: KProperty<*>): Structure {
return inside.current.liveUniformStructures[property.name]
?: throw IllegalArgumentException("can't find '${property.name}' in this ray tracing module, options are ${inside.current.liveUniformStructures.keys}")
}
}
fun RayTracingModule.pointer() = RayTracingModulePointer(this)
class RayTracingModule(val parameters: Parameters) : Renderable
class RayTracingModule(val parameters: Parameters) : Renderable {
var valid
Volatile: cleanup() clears it on a box thread while render workers read it.
@Volatile
var valid = false
var enabled = true
val tasks = linkedMapOf<String, RayTracingTask>()
val before = linkedMapOf<String, Target>()
val after = linkedMapOf<String, Target>()
val groups: RayTracingGroups
val groups: RayTracingGroups = parameters.rayTracingGroups ?: RayTracingGroups.fromParameters(parameters)
val stageRecords = mutableListOf<StageRecord>()
internal fun stages(): List<Pair<String, Introspection>>
The module's stages, named (raygen, miss#2, ...), for shaderInterfaceTransition.
internal fun stages(): List<Pair<String, Introspection>> {
val seen = mutableMapOf<String, Int>()
return stageRecords.map { r ->
val n = seen.merge(r.stage.name, 1, Int::plus)!!
(if (n == 1) r.stage.name else "${r.stage.name}#$n") to r.introspection
}
}
var uniforms: MutableMap<String, UpdatesDescriptorSet> = mutableMapOf()
var liveUniformStructures: MutableMap<String, Structure> = mutableMapOf()
var pipelineLayout: Long = 0L
var pipeline: Long = 0L
private val pipelineDisposeToken = AutoDisposer.register(this, "RayTracingPipeline(${parameters.name})", owner = parameters.renderer.device) { }
internal var sbt: ShaderBindingTable? = null
private var descriptorLayouts: Map<Int, DescriptorSetLayout.SimpleDescriptorSetLayout> = emptyMap()
val pushConstantSize: Int
init
body — 261 lines, from line 276
init {
var computedPushConstantSize = 0
try {
require(parameters.renderer.device.supportsRayTracing) {
"Device does not support VK_KHR_ray_tracing_pipeline."
}
val raygen = groups.raygen ?: throw IllegalStateException("Ray tracing pipeline needs a raygen shader.")
addStage(raygen)
groups.miss.forEach(::addStage)
groups.hit.forEach { group ->
group.closestHit?.let(::addStage)
group.anyHit?.let(::addStage)
group.intersection?.let(::addStage)
}
groups.callable.forEach(::addStage)
if (stageRecords.isEmpty()) throw IllegalStateException("Ray tracing pipeline has no shader stages.")
computedPushConstantSize = stageRecords.flatMap { it.introspection.pushConstants }.maxOfOrNull { it.size } ?: 0
buildResources()
createPipeline(computedPushConstantSize)
sbt = ShaderBindingTable(parameters.renderer, pipeline, groupCount(), groupRegions())
pipelineDisposeToken.updateCleanup(snapshotPipelineCleanup())
valid = true
} catch (e: Exception) {
parameters.errorHandler("Ray tracing module error: ${e.message}", e)
}
pushConstantSize = computedPushConstantSize
}
private fun addStage(stage: RayTracingShaderStage) {
val introspection = stage.introspection ?: Introspection(
stage.source,
stage.kind,
errorHandler = parameters.errorHandler,
sourceName = stage.sourceName,
includeResolver = stage.includeResolver
)
if (!introspection.valid) throw IllegalStateException("Invalid ${stage.name} shader")
val shaderModule = createShaderModule(introspection.code.bytecode()!!)
parameters.renderer.device.nameObject(
shaderModule,
VK_OBJECT_TYPE_SHADER_MODULE,
"${parameters.name} ${stage.name}"
)
stageRecords += StageRecord(stage, introspection, shaderModule, stage.kind.toVulkan())
}
private fun buildResources() {
val bindingUses = linkedMapOf<BindingKey, BindingUse>()
fun note(bindable: Introspected.Bindable, descriptorType: Int, stage: Int, count: Int = 1) {
val key = BindingKey(bindable.set, bindable.binding)
val use = bindingUses.getOrPut(key) { BindingUse(descriptorType, 0, 1) }
require(use.descriptorType == descriptorType) {
"binding ${bindable.binding} in set ${bindable.set} resolves to multiple descriptor types"
}
use.stageFlags = use.stageFlags or stage
use.descriptorCount = maxOf(use.descriptorCount, count)
}
stageRecords.forEach { record ->
val i = record.introspection
i.ubos.forEach { note(it, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, record.stageFlag) }
i.ssbo.forEach { note(it, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, record.stageFlag) }
i.textures.forEach { note(it, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, record.stageFlag, maxOf(1, it.arrayCount)) }
i.images.forEach { note(it, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, record.stageFlag) }
i.accelerationStructures.forEach { note(it, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, record.stageFlag) }
i.ubos.forEach { liveUniformStructures[it.name] = Structure().setMembers(it.members).reportingTo(parameters) }
i.ssbo.forEach { liveUniformStructures[it.name] = Structure().setMembers(it.members).reportingTo(parameters) }
}
val layoutMap = mutableMapOf<Int, DescriptorSetLayout.SimpleDescriptorSetLayout>()
bindingUses.entries
.groupBy { it.key.set }
.forEach { (set, entries) ->
val sorted = entries.sortedBy { it.key.binding }
layoutMap[set] = DescriptorSetLayout.SimpleDescriptorSetLayout(
parameters.renderer.device,
descriptorType = sorted.map { it.value.descriptorType },
set = set,
binding = sorted.map { it.key.binding },
stage = sorted.fold(0) { acc, entry -> acc or entry.value.stageFlags },
descriptorCount = sorted.map { it.value.descriptorCount },
pushDescriptor = true
)
}
descriptorLayouts = layoutMap
stageRecords.flatMap { it.introspection.ubos }.forEach { ubo ->
val bp = buffer(ubo, ubo.name) {
size = ubo.size.toLong()
source = ubo
uniform()
hosted()
}
val buf = ReloadCarryOver.resolve(bp, parameters.uboResolver) ?: throw IllegalArgumentException("can't link UBO ${ubo.name}")
uniforms[ubo.name] = Module.Uniform(ubo.name, ubo, source = buf, buf.toBuffer(parameters.renderer), VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
}
stageRecords.flatMap { it.introspection.ssbo }.forEach { ssbo ->
var sz = ssbo.size.toLong()
if (sz == 0L) sz = parameters.ssboSizer(ssbo)?.toLong() ?: 0L
val bp = buffer(ssbo, ssbo.name) {
size = sz
source = ssbo
storage()
hosted()
}
val buf = ReloadCarryOver.resolve(bp, parameters.ssboResolver)
?: throw IllegalArgumentException("can't link SSBO buffer ${ssbo.name}, resolver returned null")
uniforms[ssbo.name] = Module.Uniform(ssbo.name, ssbo, source = buf, buf.toBuffer(parameters.renderer), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
}
stageRecords.flatMap { it.introspection.textures }.forEach { texture ->
val (resolved, sampler) = parameters.textureResolver(texture)
?: throw IllegalArgumentException("can't link texture ${texture.name}")
val views = resolved.imageViewArray
require(views.isNotEmpty()) { "Texture ${texture.name} resolved to empty image view array" }
uniforms[texture.name] = if (texture.arrayCount == 0) {
Module.TexturePointer(texture.name, texture, source = null, resolved, sampler)
} else {
require(views.size == texture.arrayCount) {
"can't link texture resource ${texture.name} of length ${views.size} but the shader expects ${texture.arrayCount}"
}
Module.TextureArrayPointer(texture.name, texture, source = null, resolved, sampler)
}
}
stageRecords.flatMap { it.introspection.images }.forEach { image ->
val resolved = parameters.imageResolver(image)
?: throw IllegalArgumentException("can't link storage image ${image.name}")
uniforms[image.name] = Module.ImagePointer(image.name, image, source = null, resolved)
}
stageRecords.flatMap { it.introspection.accelerationStructures }.forEach { accel ->
val resolved = parameters.accelerationStructureResolver(accel)
?: throw IllegalArgumentException("can't link acceleration structure ${accel.name}")
uniforms[accel.name] = AccelerationStructurePointer(accel.name, accel, resolved)
}
liveUniformStructures.forEach { (name, structure) ->
(uniforms[name] as? Module.Uniform)?.let { structure.proxying = it.buffer }
}
}
private fun createPipeline(pushConstantSize: Int) {
MemoryStack.stackPush().use { stack ->
val shaderStages = VkPipelineShaderStageCreateInfo.calloc(stageRecords.size, stack)
stageRecords.forEachIndexed { index, record ->
shaderStages[index]
.sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO)
.stage(record.stageFlag)
.pName(stack.UTF8("main"))
.module(record.shaderModule)
}
val shaderGroups = VkRayTracingShaderGroupCreateInfoKHR.calloc(groupCount(), stack)
var groupIndex = 0
fun generalGroup(stageIndex: Int) {
shaderGroups[groupIndex]
.sType(VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR)
.type(VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR)
.generalShader(stageIndex)
.closestHitShader(VK_SHADER_UNUSED_KHR)
.anyHitShader(VK_SHADER_UNUSED_KHR)
.intersectionShader(VK_SHADER_UNUSED_KHR)
groupIndex++
}
generalGroup(indexOf(groups.raygen!!))
groups.miss.forEach { generalGroup(indexOf(it)) }
groups.hit.forEach { group ->
shaderGroups[groupIndex]
.sType(VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR)
.type(if (group.intersection == null) VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR else VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR)
.generalShader(VK_SHADER_UNUSED_KHR)
.closestHitShader(group.closestHit?.let(::indexOf) ?: VK_SHADER_UNUSED_KHR)
.anyHitShader(group.anyHit?.let(::indexOf) ?: VK_SHADER_UNUSED_KHR)
.intersectionShader(group.intersection?.let(::indexOf) ?: VK_SHADER_UNUSED_KHR)
groupIndex++
}
groups.callable.forEach { generalGroup(indexOf(it)) }
val highestSet = descriptorLayouts.keys.maxOrNull() ?: -1
val ppLayout = if (highestSet >= 0) stack.callocLong(highestSet + 1) else null
if (highestSet >= 0) {
for (set in 0..highestSet) {
val layout = descriptorLayouts[set]
?: throw IllegalArgumentException("Pipeline layout is missing descriptor set $set. Make sets contiguous starting at set=0.")
ppLayout!!.put(set, layout.vkDescriptorLayout)
}
}
var pushRanges: VkPushConstantRange.Buffer? = null
if (pushConstantSize > 0) {
pushRanges = VkPushConstantRange.calloc(1, stack)
.stageFlags(rayTracingStageFlags())
.offset(0)
.size(pushConstantSize)
}
val layoutInfo = VkPipelineLayoutCreateInfo.calloc(stack)
.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO)
ppLayout?.let { layoutInfo.pSetLayouts(it) }
pushRanges?.let { layoutInfo.pPushConstantRanges(it) }
val out = stack.mallocLong(1)
vkCheck(
vkCreatePipelineLayout(parameters.renderer.device.vkDevice, layoutInfo, null, out),
"Failed to create ray tracing pipeline layout"
)
pipelineLayout = out[0]
parameters.renderer.device.nameObject(
pipelineLayout,
VK_OBJECT_TYPE_PIPELINE_LAYOUT,
"${parameters.name} RayTracingPipelineLayout"
)
val maxDepth = min(
parameters.maxRayRecursionDepth.coerceAtLeast(1),
parameters.renderer.device.rayTracingLimits?.maxRayRecursionDepth ?: parameters.maxRayRecursionDepth.coerceAtLeast(1)
)
val createInfo = VkRayTracingPipelineCreateInfoKHR.calloc(1, stack)
.sType(VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR)
.flags(parameters.rayTracingPipelineFlags)
.pStages(shaderStages)
.pGroups(shaderGroups)
.maxPipelineRayRecursionDepth(maxDepth)
.layout(pipelineLayout)
if (parameters.allowClusterAccelerationStructure) {
require(parameters.renderer.device.supportsClusterAccelerationStructure) { "Cluster acceleration structures were not enabled" }
createInfo.pNext(org.lwjgl.vulkan.VkRayTracingPipelineClusterAccelerationStructureCreateInfoNV.calloc(stack)
.`sType$Default`().allowClusterAccelerationStructure(true).address())
}
vkCheck(
vkCreateRayTracingPipelinesKHR(
parameters.renderer.device.vkDevice,
0L,
parameters.renderer.pipelineCache.vkPipelineCache,
createInfo,
null,
out
),
"Error creating ray tracing pipeline"
)
pipeline = out[0]
parameters.renderer.device.nameObject(
pipeline,
VK_OBJECT_TYPE_PIPELINE,
"${parameters.name} RayTracingPipeline"
)
pipelineDisposeToken.updateCleanup(snapshotPipelineCleanup())
}
}
override fun render(command: CommandBuffer, bounds: Rectanglef)
body — 45 lines, from line 538
override fun render(command: CommandBuffer, bounds: Rectanglef) {
if (!valid || !enabled) return
MemoryStack.stackPush().use { stack ->
val labelColor = stack.floats(
parameters.labelColor.x.f,
parameters.labelColor.y.f,
parameters.labelColor.z.f,
parameters.labelColor.w.f
)
command.beginLabel(parameters.name, labelColor)
val cmd = command.vkCommandBuffer
val frameIndex = parameters.renderer.currentImage
val fieldProbe = parameters.fieldProbe?.takeIf { FIELD_PROBE_BUFFER_NAME in uniforms }
fieldProbe?.beforeShaderUse(parameters.renderer, frameIndex)
before.values.forEach { it.render(command, frameIndex) }
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline)
val pushConstantBuffer = if (pushConstantSize > 0) stack.calloc(pushConstantSize) else stack.malloc(0)
liveUniformStructures.forEach { (name, structure) ->
val target = (uniforms[name] as Module.Uniform).buffer
structure.proxying = target
structure.writeIfStale(target, command) {
throw MissingRefException(afield.graphics.vulkan.buffers.unassignedMemberMessage(it))
}
target.clean(false, command)
}
uniforms.values.forEach { upd ->
upd.pushDescriptorSet(command, pipelineLayout, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR)
}
tasks.values.forEach { it.trace(this, command, pushConstantBuffer) }
after.values.forEach { it.render(command, frameIndex) }
fieldProbe?.let {
rayTracingHostReadBarrier(command, it.bufferForImage(parameters.renderer, frameIndex))
it.afterShaderUse(parameters.renderer, frameIndex)
}
command.endLabel()
}
}
fun enable() { enabled = true }
fun disable() { enabled = false }
fun copyTasksFrom(old: RayTracingModule)
fun copyTasksFrom(old: RayTracingModule) {
old.tasks.entries.removeIf {
tasks[it.key] = it.value
(it.value as? RayTracingDispatch)?.inside = this.pointer()
true
}
before.putAll(old.before)
after.putAll(old.after)
}
fun textureUniform(name: String): Pair<HasImageViewArray, TextureSampler>?
fun textureUniform(name: String): Pair<HasImageViewArray, TextureSampler>? {
return when (val uniform = uniforms[name]) {
is Module.TexturePointer -> uniform.texture to uniform.sampler
is Module.TextureArrayPointer -> uniform.texture to uniform.sampler
else -> null
}
}
private fun snapshotPipelineCleanup(): () -> Unit {
val dev = parameters.renderer.device.vkDevice
val p = pipeline
val layout = pipelineLayout
val shaders = stageRecords.map { it.shaderModule }
val table = sbt
val sets = descriptorLayouts.values.toList()
return {
table?.cleanup()
if (p != 0L) vkDestroyPipeline(dev, p, null)
if (layout != 0L) vkDestroyPipelineLayout(dev, layout, null)
shaders.forEach { if (it != 0L) vkDestroyShaderModule(dev, it, null) }
sets.forEach { it.cleanup() }
}
}
fun cleanup()
Release this module's pipeline, shader binding table, shader modules and descriptor layouts. It stops tracing at once; while its renderer is alive the handles go through the retirement queue, as for Module.cleanup — a full reload retires the module it replaces while frames that recorded it may still be in flight — and at once after the renderer has closed.
body — 58 lines, from line 627
fun cleanup() {
if (!pipelineDisposeToken.disable()) return
valid = false
val renderer = parameters.renderer
if (renderer.isClosed) destroyNow()
else renderer.cleanup.add(Runnable { destroyNow() })
}
private fun destroyNow() {
cleanupPipelineObjects()
descriptorLayouts.values.forEach { it.cleanup() }
descriptorLayouts = emptyMap()
}
private fun cleanupPipelineObjects() {
sbt?.cleanup()
sbt = null
if (pipeline != 0L) {
vkDestroyPipeline(parameters.renderer.device.vkDevice, pipeline, null)
pipeline = 0L
}
if (pipelineLayout != 0L) {
vkDestroyPipelineLayout(parameters.renderer.device.vkDevice, pipelineLayout, null)
pipelineLayout = 0L
}
stageRecords.forEach {
if (it.shaderModule != 0L) vkDestroyShaderModule(parameters.renderer.device.vkDevice, it.shaderModule, null)
}
}
private fun createShaderModule(code: ByteBuffer): Long {
MemoryStack.stackPush().use { stack ->
val pCode = stack.malloc(code.limit()).put(code).flip()
val ci = VkShaderModuleCreateInfo.calloc(stack)
.sType(VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO)
.pCode(pCode)
val out = stack.mallocLong(1)
vkCheck(
vkCreateShaderModule(parameters.renderer.device.vkDevice, ci, null, out),
"Failed to create ray tracing shader module"
)
return out[0]
}
}
private fun indexOf(stage: RayTracingShaderStage): Int =
stageRecords.indexOfFirst { it.stage === stage }.takeIf { it >= 0 }
?: throw IllegalArgumentException("No shader stage '${stage.name}' in ray tracing module")
private fun groupCount(): Int = 1 + groups.miss.size + groups.hit.size + groups.callable.size
private fun groupRegions(): ShaderBindingTable.GroupRegions =
ShaderBindingTable.GroupRegions(
raygenCount = 1,
missCount = groups.miss.size,
hitCount = groups.hit.size,
callableCount = groups.callable.size
)
internal fun rayTracingStageFlags(): Int
internal fun rayTracingStageFlags(): Int = stageRecords.fold(0) { acc, record -> acc or record.stageFlag }
data class StageRecord
data class StageRecord(
val stage: RayTracingShaderStage,
val introspection: Introspection,
val shaderModule: Long,
val stageFlag: Int
)
private data class BindingKey(val set: Int, val binding: Int)
private data class BindingUse(val descriptorType: Int, var stageFlags: Int, var descriptorCount: Int)
class AccelerationStructurePointer
class AccelerationStructurePointer(
val name: String,
override val binding: Introspected.Bindable,
val accelerationStructure: HasAccelerationStructure
) : UpdatesDescriptorSet {
override var source: BufferParameters? = null
override fun pushDescriptorSet(command: CommandBuffer, pipelineLayout: Long, bindPoint: Int)
override fun pushDescriptorSet(command: CommandBuffer, pipelineLayout: Long, bindPoint: Int) {
MemoryStack.stackPush().use { stack ->
val handles = stack.longs(accelerationStructure.vkAccelerationStructure)
val accelInfo = VkWriteDescriptorSetAccelerationStructureKHR.calloc(stack)
.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR)
.pAccelerationStructures(handles)
val write = VkWriteDescriptorSet.calloc(1, stack)
.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET)
.pNext(accelInfo.address())
.dstBinding(binding.binding)
.dstArrayElement(0)
.descriptorType(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR)
.descriptorCount(1)
org.lwjgl.vulkan.KHRPushDescriptor.vkCmdPushDescriptorSetKHR(
command.vkCommandBuffer,
bindPoint,
pipelineLayout,
binding.set,
write
)
}
}
}
internal class ShaderBindingTable
internal class ShaderBindingTable(
val renderer: afield.graphics.vulkan.Renderer,
val pipeline: Long,
val groupCount: Int,
val regions: GroupRegions
) {
data class GroupRegions
data class GroupRegions(
val raygenCount: Int,
val missCount: Int,
val hitCount: Int,
val callableCount: Int
)
private val handleSize = renderer.device.rayTracingLimits!!.shaderGroupHandleSize
private val handleAlignment = renderer.device.rayTracingLimits!!.shaderGroupHandleAlignment
private val baseAlignment = renderer.device.rayTracingLimits!!.shaderGroupBaseAlignment
private val stride = alignUp(handleSize, handleAlignment)
private val raygenOffset = 0L
private val raygenSize = stride.toLong()
private val missOffset = alignUp(raygenOffset + raygenSize, baseAlignment)
private val missSize = regions.missCount * stride.toLong()
private val hitOffset = alignUp(missOffset + missSize, baseAlignment)
private val hitSize = regions.hitCount * stride.toLong()
private val callableOffset = alignUp(hitOffset + hitSize, baseAlignment)
private val callableSize = regions.callableCount * stride.toLong()
private val totalSize = alignUp(callableOffset + callableSize, baseAlignment).coerceAtLeast(raygenSize)
private val buffer = VulkanBuffer(
renderer,
totalSize,
VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR or VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT or VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
needsDeviceAddress = true
)
init
init {
val handles = MemoryUtil.memAlloc(groupCount * handleSize)
try {
vkCheck(
vkGetRayTracingShaderGroupHandlesKHR(
renderer.device.vkDevice,
pipeline,
0,
groupCount,
handles
),
"Failed to fetch ray tracing shader group handles"
)
buffer.mapAndWrite_bytes {
var group = 0
fun writeRegion(offset: Long, count: Int) {
for (i in 0 until count) {
val oldPosition = handles.position()
val oldLimit = handles.limit()
handles.position(group * handleSize)
handles.limit((group + 1) * handleSize)
this.position((offset + i * stride).toInt())
this.put(handles)
handles.position(oldPosition)
handles.limit(oldLimit)
group++
}
}
writeRegion(raygenOffset, regions.raygenCount)
writeRegion(missOffset, regions.missCount)
writeRegion(hitOffset, regions.hitCount)
writeRegion(callableOffset, regions.callableCount)
}
} finally {
MemoryUtil.memFree(handles)
}
}
fun trace(command: CommandBuffer, width: Int, height: Int, depth: Int)
fun trace(command: CommandBuffer, width: Int, height: Int, depth: Int) {
MemoryStack.stackPush().use { stack ->
val base = buffer.getGPUAddress(command)
val raygen = region(stack, base, raygenOffset, raygenSize, stride)
val miss = region(stack, base, missOffset, missSize, stride)
val hit = region(stack, base, hitOffset, hitSize, stride)
val callable = region(stack, base, callableOffset, callableSize, stride)
vkCmdTraceRaysKHR(command.vkCommandBuffer, raygen, miss, hit, callable, width, height, depth)
}
}
private fun region(
stack: MemoryStack,
base: Long,
offset: Long,
size: Long,
stride: Int
): VkStridedDeviceAddressRegionKHR {
val r = VkStridedDeviceAddressRegionKHR.calloc(stack)
if (size == 0L) return r
return r.deviceAddress(base + offset)
.stride(stride.toLong())
.size(size)
}
fun cleanup()
fun cleanup() {
buffer.cleanup()
}
}
}
interface RayTracingTask
fun trace(inside: RayTracingModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer)
fun rayTracingTask(c: (inside: RayTracingModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) -> Unit): RayTracingTask {
fun rayTracingTask(c: (inside: RayTracingModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) -> Unit): RayTracingTask {
return object : RayTracingTask {
override fun trace(inside: RayTracingModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) {
c(inside, cmd, pushConstantBuffer)
}
}
}
class RayTracingDispatch(var width: Int, var height: Int = 1, var depth: Int = 1) : RayTracingTask
constructor(pointer: RayTracingModulePointer = fromContext(), width: Int, height: Int = 1, depth: Int = 1) : this(width, height, depth)
constructor(pointer: RayTracingModulePointer = fromContext(), width: Int, height: Int = 1, depth: Int = 1) : this(width, height, depth) {
inside = pointer
}
lateinit var inside: RayTracingModulePointer
var push = Structure()
class BoundFromStructure<T>
operator fun setValue(inside: RayTracingDispatch, property: KProperty<*>, any: () -> T) {
operator fun setValue(inside: RayTracingDispatch, 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} as a ray tracing push_constant")
}
}
operator fun getValue(inside: RayTracingDispatch, property: KProperty<*>): () -> T {
operator fun getValue(inside: RayTracingDispatch, 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} as a ray tracing push_constant")
}
}
}
class FromStructure<T>
operator fun setValue(inside: RayTracingDispatch, property: KProperty<*>, any: T)
operator fun setValue(inside: RayTracingDispatch, 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} as a ray tracing push_constant")
}
}
operator fun getValue(inside: RayTracingDispatch, property: KProperty<*>): T
operator fun getValue(inside: RayTracingDispatch, property: KProperty<*>): T {
if (inside.push.offsets.isEmpty()) inside.initializePushConstantsFromModule(inside.inside)
return inside.push.getPushConstant(property.name) {
throw IllegalArgumentException("can't find ${property.name} as a ray tracing push_constant")
}
}
}
internal fun initializePushConstantsFromModule(inside: RayTracingModulePointer)
internal fun initializePushConstantsFromModule(inside: RayTracingModulePointer) {
inside.current.stageRecords.forEach { record ->
record.introspection.pushConstants.forEach { push.setMembers(it.members) }
}
}
override fun trace(inside: RayTracingModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer)
override fun trace(inside: RayTracingModule, cmd: CommandBuffer, pushConstantBuffer: ByteBuffer) {
if (inside != this.inside.current) {
lprintln("warning,graphics", "RayTracingDispatch built in a different module ${inside}!=${this.inside.current}")
}
if (width <= 0 || height <= 0 || depth <= 0) return
fun pushConstantsForCurrentDispatch(): Boolean {
if (pushConstantBuffer.limit() <= 0) return true
inside.stageRecords.forEach { record ->
record.introspection.pushConstants.forEach { push.setMembers(it.members) }
}
try {
pushConstantBuffer.clear()
push.writeToBuffer(pushConstantBuffer, cmd) {
throw MissingRefException(afield.graphics.vulkan.buffers.unassignedMemberMessage(it))
}
} catch (e: Exception) {
inside.parameters.errorHandler(e.message ?: "ray tracing push constant error", e)
lprintln("error,graphics", "push constant error, not tracing rays : ${e.message}")
return false
}
pushConstantBuffer.clear()
vkCmdPushConstants(
cmd.vkCommandBuffer,
inside.pipelineLayout,
inside.rayTracingStageFlags(),
0,
pushConstantBuffer
)
return true
}
if (pushConstantsForCurrentDispatch()) {
inside.sbt?.trace(cmd, width, height, depth)
}
}
}
fun rayTracingHostReadBarrier(cmd: CommandBuffer, buffer: VulkanBuffer? = null)
fun rayTracingHostReadBarrier(cmd: CommandBuffer, buffer: VulkanBuffer? = null) {
MemoryStack.stackPush().use { stack ->
val dep = VkDependencyInfo.calloc(stack).sType(VK_STRUCTURE_TYPE_DEPENDENCY_INFO)
if (buffer != null) {
val b = VkBufferMemoryBarrier2.calloc(1, stack)
b[0]
.sType(VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2)
.srcStageMask(VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR)
.srcAccessMask(VK_ACCESS_2_SHADER_WRITE_BIT)
.dstStageMask(VK_PIPELINE_STAGE_2_HOST_BIT)
.dstAccessMask(VK_ACCESS_2_HOST_READ_BIT)
.srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.buffer(buffer.buffer)
.offset(0L)
.size(buffer.requestedSize)
dep.pBufferMemoryBarriers(b)
} else {
val m = VkMemoryBarrier2.calloc(1, stack)
m[0]
.sType(VK_STRUCTURE_TYPE_MEMORY_BARRIER_2)
.srcStageMask(VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR)
.srcAccessMask(VK_ACCESS_2_SHADER_WRITE_BIT)
.dstStageMask(VK_PIPELINE_STAGE_2_HOST_BIT)
.dstAccessMask(VK_ACCESS_2_HOST_READ_BIT)
dep.pMemoryBarriers(m)
}
vkCmdPipelineBarrier2(cmd.vkCommandBuffer, dep)
}
}
private fun alignUp(value: Int, alignment: Int): Int {
if (alignment <= 1) return value
return ((value + alignment - 1) / alignment) * alignment
}
private fun alignUp(value: Long, alignment: Int): Long {
if (alignment <= 1) return value
return ((value + alignment - 1L) / alignment) * alignment
}