PathTraceFrameOutput.kt
src/jvmMain/kotlin/afield/graphics/scene/fabricated/PathTraceFrameOutput.kt · 113 lines
package afield.graphics.scene.fabricated
import afield.graphics.vulkan.Target
import afield.graphics.vulkan.buffers.VulkanBuffer
import afield.graphics.vulkan.device.CommandBuffer
import afield.document.context.*
import afield.graphics.render.*
import afield.graphics.scene.*
import afield.graphics.shader.*
import afield.graphics.render.Module
import afield.media.reconstruction.*
import afield.graphics.vulkan.targets.RenderToTexture
import afield.math.linalg.Matrix4f
import org.lwjgl.vulkan.VK13.*
The path tracer's per-frame image set, for reconstruction rather than for display.
PathTracingRenderer normally accumulates samples into one progressively cleaner picture. A
denoiser wants the opposite: this frame's noisy radiance on its own, together with the buffers
that say what each pixel is looking at. enableFrameOutput on the tracer builds a
PathTraceFrameOutput, which converts the packed per-pixel records the tracer writes into
seven attachments: radiance, depth, motion, diffuse and specular albedo, normal and roughness,
and specular hit distance. inputs and guides hand those over in the shape
ReconstructionInputs and RayReconstructionGuides expect.
The images stay on the device and nothing is read back, so a consumer is a Target recorded
after the tracer's own render. Opaque materials only, which is why the tracer refuses the call
on a scene with glass in it.
class PathTraceFrameOutput internal constructor(val tracer: PathTracingRenderer) : Target, AutoCloseable
GPU-resident output of a single path-tracing dispatch, before progressive accumulation. Six vec4s per pixel (96 bytes). No readback or native library is needed to produce these images. The tracer owns this object; consumers borrow the images until the tracer closes. Jitter is projection displacement, positive right/down: primary rays sample pixel-jitter. Depth is RH perspective Vulkan [0,1], clear=1, using near/far. Motion is previous-current in input pixels without jitter; geometry is the tracer's immutable scene snapshot.
class PathTraceFrameOutput internal constructor(val tracer: PathTracingRenderer) : Target, AutoCloseable {
private val renderer=tracer.renderer
init
init { require(renderer.device.physicalDevice.vkPhysicalDeviceProperties.limits().maxColorAttachments()>=7) { "Path reconstruction needs seven color attachments" } }
val near=.01f
val far=10000f
var jitter=ReconstructionJitter(0f,0f)
internal val buffer
internal val buffer=VulkanBuffer(renderer,tracer.width.toLong()*tracer.height*96,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT or VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT or VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,needsDeviceAddress=true)
val images
val images=RenderToTexture(renderer) {
width=tracer.width;height=tracer.height;useDepth=false;colorAttachments=7;debugName="${tracer.name}-frame"
colorFormats=listOf(VK_FORMAT_R32G32B32A32_SFLOAT,VK_FORMAT_R32_SFLOAT,VK_FORMAT_R32G32_SFLOAT,
VK_FORMAT_R16G16B16A16_SFLOAT,VK_FORMAT_R16G16B16A16_SFLOAT,VK_FORMAT_R16G16B16A16_SFLOAT,VK_FORMAT_R32_SFLOAT)
}
val color get()=images.imageViewArray[0]
val depth get()=images.imageViewArray[1]
val motion get()=images.imageViewArray[2]
val diffuseAlbedo get()=images.imageViewArray[3]
val specularAlbedo get()=images.imageViewArray[4]
val normalRoughness get()=images.imageViewArray[5]
val specularHitDistance get()=images.imageViewArray[6]
val allocatedBytes get()
val allocatedBytes get()=buffer.requestedSize+images.colorImageRefs.sumOf { it?.memorySize ?: 0 }
fun inputs()=ReconstructionInputs(color,depth,motion)
fun guides()
fun guides()=RayReconstructionGuides(diffuseAlbedo,specularAlbedo,normalRoughness,tracer.camera.modelView(),
Matrix4f().setPerspective(tracer.camera.p.fov.toFloat(),tracer.width.toFloat()/tracer.height,near,far,true),specularHitDistance)
private val convert=Module(parameters(renderer) {
name="${tracer.name}-frame-convert";+images;depthTest=false;depthWrite=false;blend=false
vertex=VERTEX;fragment=FRAGMENT
})
private var closed=false
init
init {
check(convert.valid) { "Path frame conversion did not compile" }
val p=convert.pointer();val mesh=Mesh(renderer,p);mesh.resize(3,0,p)
mesh.push.setMembers(convert.fi.pushConstants.single().members)
mesh.push["pixels"]=buffer;mesh.push["width"]=tracer.width;mesh.push["height"]=tracer.height
convert.meshes["quad"]=mesh;images.modules["convert"]=p
}
override fun render(command: CommandBuffer,index: Int)
Record after tracer.render, before any reconstruction consumer.
override fun render(command: CommandBuffer,index: Int) {
check(!closed)
graphicsMemoryBarrier(command,buffer)
images.render(command,index)
}
override fun close()
override fun close() {
if(closed)return;closed=true
renderer.cleanup+=Runnable {
convert.cleanup();buffer.cleanup();images.imageViewArray.forEach { it.imageView.cleanup() }
images.colorImageRefs.forEach { it?.cleanup() };images.defaultSampler?.cleanup()
}
}
companion object
companion object {
private const val VERTEX="""#version 460
layout(location=0) out vec2 uv;
void main(){vec2 p=vec2((gl_VertexIndex<<1)&2,gl_VertexIndex&2);gl_Position=vec4(p*2-1,0,1);uv=vec2(p.x,1-p.y);}
"""
private const val FRAGMENT="""#version 460
#extension GL_EXT_buffer_reference : require
struct FramePixel {vec4 radiance;vec4 positionDepth;vec4 normalRoughness;vec4 diffuse;vec4 specular;vec4 motion;};
layout(buffer_reference,std430,buffer_reference_align=16) readonly buffer Pixels {FramePixel items[];};
layout(push_constant) uniform Resolve {Pixels pixels;int width;int height;} pc;
layout(location=0) in vec2 uv;
layout(location=0) out vec4 color;
layout(location=1) out float depth;
layout(location=2) out vec2 motion;
layout(location=3) out vec4 diffuse;
layout(location=4) out vec4 specular;
layout(location=5) out vec4 normalRoughness;
layout(location=6) out float specularDistance;
void main(){ivec2 p=clamp(ivec2(uv*vec2(pc.width,pc.height)),ivec2(0),ivec2(pc.width-1,pc.height-1));
FramePixel f=pc.pixels.items[p.y*pc.width+p.x];color=f.radiance;depth=f.positionDepth.w;
motion=f.motion.xy;diffuse=f.diffuse;specular=f.specular;normalRoughness=f.normalRoughness;specularDistance=f.motion.z;}
"""
}
}