ClusterGeometrySmoke.kt
src/jvmMain/kotlin/afield/graphics/render/ClusterGeometrySmoke.kt · 100 lines
package afield.graphics.render
import afield.graphics.scene.main
import afield.document.graph.Box
import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.buffers.VulkanBuffer
import afield.graphics.vulkan.device.CommandBuffer
import afield.graphics.vulkan.device.Fence
import afield.media.reconstruction.ReconstructionCapture
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import org.json.JSONObject
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.*
import org.lwjgl.vulkan.VK13.*
import java.io.File
import kotlin.math.abs
import kotlin.system.exitProcess
object ClusterGeometrySmoke
@JvmStatic fun main(args: Array<String>)
body — 57 lines, from line 22
@JvmStatic fun main(args: Array<String>) {
Options.enableVR=false;Options.enableHDR=false;Options.enableRayTracing=true;Options.doVulkanSDK()
val folder=File("build/neural-tools/clusters").also { it.mkdirs() }
val window=Window("Field computed Mega Geometry");val renderer=Renderer(window)
val box=Box("cluster-check");box.setPropertyRaw("renderer",renderer,doResync=false)
try { Box.withBoxContextNow(box) {
fun pump(n: Int=1){val end=renderer.frameCounter+n;while(renderer.frameCounter<end){window.pollEvents();renderer.render()}}
val report=JSONObject().put("enabled",renderer.device.supportsClusterAccelerationStructure)
ClusterGeometryDemo(renderer).use { demo ->
demo.install();pump(32)
val a=ReconstructionCapture.read(renderer,demo.output)
check(a.all { it.isFinite() } && a.max()>.15f)
val status=readBuffer(renderer,demo.geometry.status).asIntBuffer();check((0 until status.remaining()).all { status[it]==0 })
// A GPU-instance owner cannot silently resize storage that earlier frames use.
val guardCommand=CommandBuffer(renderer.commandPool,true,false)
try {
check(runCatching { demo.tlas.buildTopLevel(guardCommand,demo.instances,2) }.exceptionOrNull()?.message?.contains("fixed")==true)
check(runCatching { demo.tlas.buildTopLevel(guardCommand,demo.instances,1,0) }.exceptionOrNull()?.message?.contains("fixed")==true)
check(runCatching { demo.tlas.buildTopLevel(guardCommand,listOf(RayTracingInstance(demo.geometry))) }.isFailure)
} finally { guardCommand.cleanup() }
val recordBytes=readBuffer(renderer,demo.geometry.records)
MemoryStack.stackPush().use { stack ->
val nativeRecord=VkClusterAccelerationStructureBuildTriangleClusterInfoNV.calloc(stack)
recordBytes.limit(64)
org.lwjgl.system.MemoryUtil.memByteBuffer(nativeRecord.address(),64).put(recordBytes)
check(nativeRecord.indexBufferStride().toInt()==4 && nativeRecord.vertexBufferStride().toInt()==16)
check(nativeRecord.triangleCount()==128 && nativeRecord.vertexCount()==81)
check(nativeRecord.indexBuffer()>0 && nativeRecord.vertexBuffer()>0)
}
ReconstructionCapture.save(File(folder,"computed.png"),a,demo.width,demo.height)
demo.animate=false;demo.time+=1;pump(4)
val b=ReconstructionCapture.read(renderer,demo.output)
check(a.indices.sumOf { abs(a[it]-b[it]).toDouble() }/a.size>.01)
val before=readBuffer(renderer,demo.indices)
demo.topology=1;pump(4)
val after=readBuffer(renderer,demo.indices);check(before!=after) { "GPU topology edit had no effect" }
demo.view=1;pump(4)
ReconstructionCapture.save(File(folder,"clusters.png"),ReconstructionCapture.read(renderer,demo.output),demo.width,demo.height)
demo.invalidCluster=0
for(mode in listOf(1,2,4)) {
demo.invalidMode=mode;pump(4)
check(readBuffer(renderer,demo.geometry.status).asIntBuffer()[0]==mode) { "Invalid input escaped guard: $mode" }
}
demo.invalidCluster=-1;demo.view=0;demo.animate=true;pump(32)
repeat(120){i->pump();if("--clip" in args)ReconstructionCapture.save(File(folder,"clip/%04d.png".format(i)),
ReconstructionCapture.read(renderer,demo.output),demo.width,demo.height)}
fun distribution(times: List<Double>): JSONObject {val t=times.takeLast(100).sorted();check(t.isNotEmpty())
return JSONObject().put("count",t.size).put("p50",t[t.size/2]).put("p95",t[(t.size*.95).toInt()])}
report.put("clusterCount",demo.count).put("triangles",demo.count*128).put("limits",demo.geometry.limits.toString())
.put("builderBytes",demo.geometry.allocatedBytes).put("buildMs",distribution(demo.geometry.gpuMilliseconds))
.put("wholePassMs",distribution(demo.gpuMilliseconds))
}
pump(32)
File(folder,"measurements.json").writeText(report.toString(2));println(report.toString(2))
} } finally { renderer.cleanup();window.cleanup() }
println("COMPUTED CLUSTERS CHECK DONE");exitProcess(0)
}
internal fun readBuffer(renderer: Renderer,source: VulkanBuffer): java.nio.ByteBuffer
Blocking test helper, never the frame path.
internal fun readBuffer(renderer: Renderer,source: VulkanBuffer): java.nio.ByteBuffer {
val staging=VulkanBuffer(renderer,source.requestedSize,VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT or VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
val command=CommandBuffer(renderer.commandPool,true,true);val fence=Fence(renderer.device,false)
try {
command.beginRecording()
MemoryStack.stackPush().use { s ->
fun barrier(src: Long,access: Long,dst: Long,dstAccess: Long) {
vkCmdPipelineBarrier2(command.vkCommandBuffer,VkDependencyInfo.calloc(s).`sType$Default`().pMemoryBarriers(
VkMemoryBarrier2.calloc(1,s).`sType$Default`().srcStageMask(src).srcAccessMask(access).dstStageMask(dst).dstAccessMask(dstAccess)))
}
barrier(VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,VK_ACCESS_2_MEMORY_WRITE_BIT,VK_PIPELINE_STAGE_2_TRANSFER_BIT,VK_ACCESS_2_TRANSFER_READ_BIT)
vkCmdCopyBuffer(command.vkCommandBuffer,source.buffer,staging.buffer,VkBufferCopy.calloc(1,s).size(source.requestedSize))
barrier(VK_PIPELINE_STAGE_2_TRANSFER_BIT,VK_ACCESS_2_TRANSFER_WRITE_BIT,VK_PIPELINE_STAGE_2_HOST_BIT,VK_ACCESS_2_HOST_READ_BIT)
}
command.endRecording();MemoryStack.stackPush().use { renderer.graphQueue.submit(it.pointers(command.vkCommandBuffer),null,null,null,fence) };fence.fenceWait()
return java.nio.ByteBuffer.allocate(source.requestedSize.toInt()).order(java.nio.ByteOrder.nativeOrder()).also { out ->staging.mapAndRead_bytes { out.put(this).flip() } }
} finally { fence.cleanup();command.cleanup();staging.cleanup() }
}
}