Skip to content

FabricateFrameGraph.kt

src/jvmMain/kotlin/afield/graphics/scene/fabricated/FabricateFrameGraph.kt · 142 lines

package afield.graphics.scene.fabricated

import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.Target
import afield.graphics.vulkan.targets.RenderTarget
import afield.graphics.vulkan.util.Window
import java.util.WeakHashMap

The renderer's off-screen passes as a named, ordered list, and the one place that order is written into a RenderTarget.

A StandardRenderer.Bundle holds its five built-in passes as five separate fields, which is a set rather than a sequence. FabricateFrameGraph takes a bundle and makes it a sequence: g-buffer, shadow map, ambient occlusion, lighting, transparent composite, then one entry for each effect currently in the bundle's FabricatePostStack. Every built-in name is a constant on the companion, and passNameFor supplies the two that arrive as post effects rather than as bundle fields, motion blur and anti-aliasing; anything else is named after its effect. So insertAfter("my-pass", PASS_LIGHTING, target) works without knowing how the bundle was built.

attachTo is where the order stops being advisory. A RenderTarget renders its preTargets before its own pass and in map order, so attaching removes and re-adds every pass by name, which leaves preTargets iterating in the graph's order. It also remembers, per target, which names this graph put there last time and drops the ones that have since gone, while a pass some other code added under a name this graph never used is left alone. Attaching a second time is therefore a resync rather than a duplicate.

The graph is a view. StandardRenderer.Bundle.frameGraph() builds a fresh one on each call, it does not own the targets it names, and it does not stop a box reaching past it to a RenderToTexture directly.

class FabricateFrameGraph private constructor

Ordered, inspectable pass contract for StandardRenderer.

It is thin: it does not hide the underlying RenderToTexture/Target objects or prevent direct pass manipulation. Instead it gives the fabricated renderer a stable ordered surface for built-in passes and user-inserted post/utility passes before syncing that order into a RenderTarget's preTargets map.

class FabricateFrameGraph private constructor(
    private val graph: FabricatePassGraph<Target>
) {

constructor(bundle: StandardRenderer.Bundle) : this

    constructor(bundle: StandardRenderer.Bundle) : this(
        FabricatePassGraph(
            listOf(
                PASS_GBUFFER to bundle.gBuffer,
                PASS_SHADOW to bundle.shadowMap,
                PASS_AO to bundle.ao,
                PASS_LIGHTING to bundle.lighting,
                PASS_TRANSPARENCY to bundle.transparentComposite
            ) + bundle.postStack.effects().map { effect -> passNameFor(effect) to effect.target }
        )
    )

    private val attachedNames = WeakHashMap<RenderTarget, MutableSet<String>>()

fun passNames(): List<String> = graph.names()

fun passes(): List<Pair<String, Target>> = graph.entries()

operator fun get(name: String): Target? = graph[name]

fun replace(name: String, target: Target): FabricateFrameGraph

    fun replace(name: String, target: Target): FabricateFrameGraph {
        graph.replace(name, target)
        return this
    }

fun append(name: String, target: Target): FabricateFrameGraph

    fun append(name: String, target: Target): FabricateFrameGraph {
        graph.addLast(name, target)
        return this
    }

fun insertBefore(name: String, before: String, target: Target): FabricateFrameGraph

    fun insertBefore(name: String, before: String, target: Target): FabricateFrameGraph {
        graph.insertBefore(name, before, target)
        return this
    }

fun insertAfter(name: String, after: String, target: Target): FabricateFrameGraph

    fun insertAfter(name: String, after: String, target: Target): FabricateFrameGraph {
        graph.insertAfter(name, after, target)
        return this
    }

fun remove(name: String): Target? = graph.remove(name)

fun attachTo(renderer: Renderer, windowIndex: Int = 0): RenderTarget

    fun attachTo(renderer: Renderer, windowIndex: Int = 0): RenderTarget =
        attachTo(renderer.renderTargetFor(windowIndex))

fun attachTo(renderer: Renderer, window: Window): RenderTarget

    fun attachTo(renderer: Renderer, window: Window): RenderTarget =
        attachTo(renderer, renderer.windowIndex(window))

fun attachTo(target: RenderTarget): RenderTarget

    fun attachTo(target: RenderTarget): RenderTarget {
        val currentNames = graph.names().toSet()
        val previouslyAttached = attachedNames.getOrPut(target) { linkedSetOf() }

        (previouslyAttached - currentNames).forEach { stale ->
            target.preTargets.remove(stale)
        }

        graph.entries().forEach { (name, pass) ->
            target.preTargets.remove(name)
            target.preTargets[name] = pass
        }

        previouslyAttached.clear()
        previouslyAttached.addAll(currentNames)
        return target
    }

internal fun detachFrom(target: RenderTarget)

Remove from target every pass this graph put there.

    internal fun detachFrom(target: RenderTarget) {
        attachedNames.remove(target)?.forEach { target.preTargets.remove(it) }
    }

companion object

const val PASS_GBUFFER = "std-gbuffer"

const val PASS_SHADOW = "std-shadow"

const val PASS_AO = "std-ao"

const val PASS_LIGHTING = "std-lighting"

const val PASS_TRANSPARENCY = "std-transparency"

const val PASS_MOTION_BLUR = "std-motion-blur"

const val PASS_ANTI_ALIAS = "std-aa"

val STANDARD_PASS_ORDER

        val STANDARD_PASS_ORDER = listOf(
            PASS_GBUFFER,
            PASS_SHADOW,
            PASS_AO,
            PASS_LIGHTING,
            PASS_TRANSPARENCY,
            PASS_MOTION_BLUR,
            PASS_ANTI_ALIAS
        )

fun passNameFor(effect: FabricatePostEffect): String

        fun passNameFor(effect: FabricatePostEffect): String =
            when (effect.kind) {
                FabricatePostEffect.Kind.MOTION_BLUR -> PASS_MOTION_BLUR
                FabricatePostEffect.Kind.FXAA -> PASS_ANTI_ALIAS
                else -> "std-post-${effect.name}"
            }
    }
}