simpleGaussianSplatSupport.kt
src/jvmMain/kotlin/afield/graphics/scene/fabricated/simpleGaussianSplatSupport.kt · 107 lines
@file:Suppress("unused")
package afield.graphics.scene.fabricated
import afield.document.graph.Box
import afield.runtime.lifecycle.RunsAt
import afield.graphics.render.ModulePointer
import afield.graphics.render.Parameters
import afield.runtime.lifecycle.macroRegistration
import afield.runtime.macros.Insert
import afield.runtime.macros.MacroRegistry
import afield.runtime.macros.MacroRegistry.callRewrite
import afield.graphics.shader.macro_moduleHereWithDefaultShaders
import kotlinx.coroutines.DelicateCoroutinesApi
A Gaussian-splat shader pair seeded into a box as editable starter source, so the splat shader is
live Field code rather than source hidden inside GaussianSplatRenderer.
The two entry points are call-shaped macros, so their Kotlin bodies only throw and the work is in
the expansion: it writes the renderer's starter vertex and fragment source into the box's
vertex and fragment properties, compiles them, reflects their interface into generated
bindings, and rewrites the call to _moduleHere. What comes back is an ordinary graphics
module, with the reload actions and shader-error reporting every other shader box has, and
GaussianSplatRenderer.Cloud takes it as suppliedRenderModule.
There are two of them because multiview is a different vertex shader and not a flag.
gaussianSplatMultiviewModuleHere seeds the variant that requires GL_EXT_multiview and selects
a model-view and projection matrix per eye from gl_ViewIndex; use it when the cloud draws into
a RenderToTexture whose multiviewViewMask is non-zero, and the mono one otherwise. The
fragment shader is the same in both cases.
The seeding happens once. macro_moduleHereWithDefaultShaders writes the starter source only
where the box has no shader source of its own, so an edited shader survives re-running the macro.
fun Box.gaussianSplatModuleHere(p: Parameters.() -> Unit): ModulePointer {
A box-owned Gaussian-splat graphics module, initially seeded with Field's built-in mono shader.
This follows the same path as plyModelHere: the macro copies the starter vertex/fragment pair
into this box, compiles their reflected interface, and rewrites the call to _moduleHere. From
then on the shader is ordinary live Field source rather than source hidden inside the renderer.
fun Box.gaussianSplatModuleHere(p: Parameters.() -> Unit): ModulePointer {
throw MacroRegistry.MacroNotExpandedException(
"gaussianSplatModuleHere should not be callable directly, but should be expanded as a macro"
)
}
fun Box.gaussianSplatMultiviewModuleHere(p: Parameters.() -> Unit): ModulePointer {
The multiview counterpart to gaussianSplatModuleHere. Use this for a RenderToTexture whose
multiviewViewMask is non-zero; the seeded vertex shader exposes left/right model-view and
projection matrices and selects them with gl_ViewIndex.
fun Box.gaussianSplatMultiviewModuleHere(p: Parameters.() -> Unit): ModulePointer {
throw MacroRegistry.MacroNotExpandedException(
"gaussianSplatMultiviewModuleHere should not be callable directly, but should be expanded as a macro"
)
}
fun Box.macro_gaussianSplatModuleHere(args: List<String>): Insert?
The macro expansion behind gaussianSplatModuleHere: the ordinary graphics-module expansion,
with GaussianSplatRenderer's mono splat shader pair as the source to seed.
It leaves macro_moduleHereWithDefaultShaders's resolvesToCall at its default, so the call
this rewrites to is _moduleHere rather than a runtime name of its own. The rewriter calls it;
box code does not.
@OptIn(DelicateCoroutinesApi::class)
fun Box.macro_gaussianSplatModuleHere(args: List<String>): Insert? =
macro_moduleHereWithDefaultShaders(
args = args,
vertex = GaussianSplatRenderer.splatVertexShader,
fragment = GaussianSplatRenderer.splatFragmentShader
)
fun Box.macro_gaussianSplatMultiviewModuleHere(args: List<String>): Insert?
macro_gaussianSplatModuleHere with the multiview vertex shader, behind
gaussianSplatMultiviewModuleHere.
Only the vertex stage differs between the two: the fragment shader seeded here is the same
splatFragmentShader.
@OptIn(DelicateCoroutinesApi::class)
fun Box.macro_gaussianSplatMultiviewModuleHere(args: List<String>): Insert? =
macro_moduleHereWithDefaultShaders(
args = args,
vertex = GaussianSplatRenderer.splatMultiviewVertexShader,
fragment = GaussianSplatRenderer.splatFragmentShader
)
fun registerGaussianSplatModuleMacros()
Puts both splat macros into MacroRegistry.callRewrite, under the names box code writes.
Nothing calls this by name. @RunsAt(macroRegistration) is found by a classpath scan of
afield, and the macroRegistration phase fires whenever a new root is made.
@RunsAt(macroRegistration)
fun registerGaussianSplatModuleMacros() {
callRewrite["gaussianSplatModuleHere"] = { box, _, args ->
box.macro_gaussianSplatModuleHere(args)
}
callRewrite["gaussianSplatMultiviewModuleHere"] = { box, _, args ->
box.macro_gaussianSplatMultiviewModuleHere(args)
}
}