ReconstructionSmoke.kt
src/jvmMain/kotlin/afield/media/reconstruction/ReconstructionSmoke.kt · 145 lines
package afield.media.reconstruction
import afield.document.graph.Box
import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.device.lightingCapabilityReport
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import org.json.JSONObject
import java.io.File
import kotlin.math.abs
import kotlin.system.exitProcess
object ReconstructionSmoke
Actual NGX evaluation with RT disabled. --clip records consecutive scene frames, not wall time. --fallback verifies ordinary rendering with FIELD_NGX_DISABLE=1, without initializing NGX.
@JvmStatic fun main(args: Array<String>)
body — 128 lines, from line 17
@JvmStatic fun main(args: Array<String>) {
if("--fallback" in args)System.setProperty("afield.ngx.disabled","true")
Options.enableVR=false;Options.enableHDR=false;Options.enableRayTracing=false;Options.doVulkanSDK()
val stress="--stress" in args
val folder=File(if(stress)"build/reconstruction/stress" else "build/reconstruction").also { it.mkdirs() }
val window=Window("Field · neural reconstruction");val renderer=Renderer(window)
val box=Box("reconstruction-check");box.setPropertyRaw("renderer",renderer,doResync=false);box.setPropertyRaw("window",window,doResync=false)
var success=false
try {
Box.withBoxContextNow(box) {
val report=renderer.device.lightingCapabilityReport()
report.put("ngx",NgxSupport.availability(renderer.device).fold({it},{it.toString()}))
report.put("sdkRevision",NgxSupport.SDK_REVISION)
report.put("enabledInstanceExtensions",renderer.instance.enabledExtensions.toList())
report.put("enabledDeviceExtensions",renderer.device.enabledExtensions.toList())
fun pump(n: Int=1) { val end=renderer.frameCounter+n;while(renderer.frameCounter<end){window.pollEvents();renderer.render()} }
val modes=when {
"--fallback" in args -> listOf(null)
stress -> listOf(null,ReconstructionMode.PERFORMANCE,ReconstructionMode.ULTRA_PERFORMANCE)
else -> listOf(null,ReconstructionMode.DLAA,ReconstructionMode.QUALITY,ReconstructionMode.PERFORMANCE)
}
if("--fallback" !in args)NgxSupport.availability(renderer.device).getOrThrow()
val output=ReconstructionSize(1920,1080)
val staticOutputs=mutableMapOf<String,FloatArray>()
for(mode in modes) {
val name=mode?.name?.lowercase() ?: "bypass"
val recommendation=mode?.let { DlssReconstruction.recommend(renderer,output,it) }
ReconstructionDemo(renderer,mode,output).use { demo ->
val warmup=maxOf(40,kotlin.math.ceil(8.0*output.width/demo.inputSize.width*output.height/demo.inputSize.height).toInt())
println("RECONSTRUCTION $name: ${demo.inputSize}, recommendation=$recommendation, warmup=$warmup")
demo.animate=false;demo.cameraMotion=false;pump(warmup)
val pixels=ReconstructionCapture.read(renderer,demo.output)
staticOutputs[name]=pixels
check(pixels.indices.filter { it%4!=3 }.all { pixels[it].isFinite() }) { "Nonfinite $name output" }
check(pixels.max()>0.25f && pixels.min()<0.1f) { "Empty $name output" }
ReconstructionCapture.save(File(folder,"$name.png"),pixels,output.width,output.height)
ReconstructionCapture.save(File(folder,"$name-input.png"),ReconstructionCapture.read(renderer,demo.color),demo.inputSize.width,demo.inputSize.height)
// Translation by +4 input pixels must map back by exactly -4, with no jitter delta.
demo.horizontalShiftPixels=4f;pump()
val depth=ReconstructionCapture.read(renderer,demo.depth)
val velocity=ReconstructionCapture.read(renderer,demo.motion)
val rawColor=ReconstructionCapture.read(renderer,demo.color)
var count=0;var maxError=0f
for(i in depth.indices)if(depth[i]<.999f){count++;maxError=maxOf(maxError,abs(velocity[2*i]+4),abs(velocity[2*i+1]))}
check(count>minOf(10000,demo.inputSize.width*demo.inputSize.height/10) && maxError<.002f) { "Motion sign/scale/jitter error $maxError across $count pixels" }
for((signal,values) in listOf("color" to rawColor,"depth" to depth,"motion" to velocity))
File(folder,"$name-$signal.f32").outputStream().use { stream ->
val bytes=java.nio.ByteBuffer.allocate(values.size*4).order(java.nio.ByteOrder.LITTLE_ENDIAN)
bytes.asFloatBuffer().put(values);stream.write(bytes.array())
}
val jitter=if(mode==null)ReconstructionJitter(0f,0f) else ReconstructionJitter.halton(warmup.toLong(),demo.inputSize,output)
File(folder,"$name-inputs.json").writeText(JSONObject().put("width",demo.inputSize.width).put("height",demo.inputSize.height)
.put("encoding","little-endian float32, row-major, top left").put("channels",JSONObject().put("color",4).put("depth",1).put("motion",2))
.put("jitterPixels",listOf(jitter.x,jitter.y)).put("frame",warmup).put("sceneTime",0).put("translationPixels",4).toString(2))
demo.horizontalShiftPixels=0f;demo.reset("translation check complete");demo.cameraMotion=true;demo.animate=true;pump(warmup)
val start=demo.reconstruction?.gpuMilliseconds?.size ?: 0;pump(100)
val timing=demo.reconstruction?.gpuMilliseconds?.drop(start)?.sorted().orEmpty()
val modeReport=JSONObject().put("inputWidth",demo.inputSize.width).put("inputHeight",demo.inputSize.height)
.put("outputWidth",output.width).put("outputHeight",output.height).put("motionMaxErrorPixels",maxError)
.put("warmupFrames",warmup)
.put("sdkAllocatedBytes",demo.reconstruction?.sdkAllocatedBytes ?: 0)
.put("outputAllocatedBytes",demo.reconstruction?.outputAllocatedBytes ?: 0)
if(timing.isNotEmpty())modeReport.put("gpuMs",JSONObject().put("count",timing.size).put("p50",timing[timing.size/2]).put("p95",timing[(timing.size*.95).toInt()]))
if(recommendation!=null)modeReport.put("sdkSizes",JSONObject()
.put("optimal",listOf(recommendation.optimal.width,recommendation.optimal.height))
.put("minimum",listOf(recommendation.minimum.width,recommendation.minimum.height))
.put("maximum",listOf(recommendation.maximum.width,recommendation.maximum.height)))
report.put(name,modeReport)
for(abstract in listOf(false,true)) {
demo.abstractScene=abstract;demo.time=0f;demo.reset("comparison clip restart");pump(40)
val scene=if(abstract)"abstract" else "geometry"
ReconstructionCapture.save(File(folder,"$name-$scene.png"),ReconstructionCapture.read(renderer,demo.output),output.width,output.height)
if("--clip" in args)repeat(120) { frame ->
// At the midpoint exercise an explicit discontinuity and immediate reset.
if(frame==60){demo.time+=4f;demo.reset("camera cut")}
pump();ReconstructionCapture.save(File(folder,"clips/$name-$scene/%04d.png".format(frame)),
ReconstructionCapture.read(renderer,demo.output),output.width,output.height)
if(stress && mode==ReconstructionMode.ULTRA_PERFORMANCE)ReconstructionCapture.save(
File(folder,"clips/$name-$scene-input/%04d.png".format(frame)),
ReconstructionCapture.read(renderer,demo.color),demo.inputSize.width,demo.inputSize.height)
}
}
val resets=demo.reconstruction?.resetCount ?: 0
demo.reset("live edit");pump();check(mode==null || demo.reconstruction!!.resetCount==resets+1)
check(demo.reload(fragment="#version 460\nthis is invalid").isFailure)
check(demo.reload(vertex=ReconstructionDemo::class.java.getResource("/afield/four/reconstruction/scene.vert")!!.readText()
.replace("vec4 settings;","vec4 settings; vec4 incompatible;")).isFailure)
pump();check(ReconstructionCapture.read(renderer,demo.output).any { it>.2f })
demo.reload().getOrThrow();pump()
}
pump(8) // exercise deferred resource retirement before creating the next mode
}
if("--fallback" !in args) {
// Independent spatial reference: four raster samples per output pixel, no NGX.
ReconstructionDemo(renderer,null,ReconstructionSize(3840,2160)).use { reference ->
reference.animate=false;reference.cameraMotion=false;pump(2)
val high=ReconstructionCapture.read(renderer,reference.output)
val low=FloatArray(output.width*output.height*4)
for(y in 0 until output.height)for(x in 0 until output.width)for(c in 0..3) {
var sum=0f;for(dy in 0..1)for(dx in 0..1)sum+=high[((y*2+dy)*3840+x*2+dx)*4+c]
low[(y*output.width+x)*4+c]=sum*.25f
}
fun mse(pixels: FloatArray): Double {
var error=0.0;for(i in low.indices)if(i%4!=3){val d=(low[i]-pixels[i]).toDouble();error+=d*d}
return error/(output.width*output.height*3)
}
val errors=JSONObject();staticOutputs.forEach { (name,pixels) -> errors.put(name,mse(pixels)) }
report.put("staticMseAgainst4spp",errors)
ReconstructionCapture.save(File(folder,"reference-4spp.png"),low,output.width,output.height)
if(!stress)check(mse(staticOutputs.getValue("dlaa"))<mse(staticOutputs.getValue("bypass"))) { "DLAA does not improve error against the independent spatial reference" }
}
pump(8)
}
// Recreate at a different size after full-resolution evaluation, then disable again.
if("--fallback" !in args)ReconstructionDemo(renderer,ReconstructionMode.DLAA,ReconstructionSize(960,540)).use { demo ->
pump(8);check(ReconstructionCapture.read(renderer,demo.output).size==960*540*4)
}
if(stress)ReconstructionDemo(renderer,ReconstructionMode.PERFORMANCE,inputSize=ReconstructionSize(1280,720)).use { demo ->
pump(8);check(ReconstructionCapture.read(renderer,demo.output).all { it.isFinite() })
report.put("nonOptimalInputCheck","Performance created at SDK optimal 960x540; evaluated at 1280x720")
}
pump(8);File(folder,if("--fallback" in args)"fallback.json" else "measurements.json").writeText(report.toString(2))
success=true
}
} finally { renderer.cleanup();window.cleanup() }
check(success);println("RECONSTRUCTION CHECK DONE");exitProcess(0)
}
}