Skip to content

GpuShaderLearningView.kt

src/jvmMain/kotlin/afield/graphics/shader/learning/GpuShaderLearningView.kt · 136 lines

package afield.graphics.shader.learning

import afield.document.context.*
import afield.graphics.render.*
import afield.graphics.render.Module
import afield.graphics.scene.*
import afield.graphics.shader.*
import afield.graphics.shader.fromViews
import afield.graphics.texture.TextureSampler
import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.Target
import afield.graphics.vulkan.device.CommandBuffer
import afield.graphics.vulkan.targets.RenderToTexture

An editable render-to-texture teacher next to a learner that trains on the GPU while it runs.

GpuShaderLearningView pairs a source RenderToTexture with GpuShaderTraining; with learning off and the learned-only view selected, the source stops running and the learned function stays.

class GpuShaderLearningView

An editable RTT teacher and its live GPU learner. Source lifetime remains with the caller. View 1 with learning=false stops executing the source entirely; its learned function remains. parameter is normalized to [-1,1], with the same value supplied to the teacher and network.

class GpuShaderLearningView(
    val renderer: Renderer,
    val source: RenderToTexture,
    val name: String = "gpu-learning",
    private val setSourceParameter: (Float) -> Unit,
) : Target, AutoCloseable {

val model = GpuShaderTraining(renderer, source.width, source.height)

var learning = true

var iterationsPerFrame

    var iterationsPerFrame = 8
        set(value) {
            require(value in 1..64)
            field = value
        }

var parameter = 0f

var view

    var view = 2
        set(value) {
            require(value in 0..2)
            field = value
        }

var sourceRenderCount

    var sourceRenderCount = 0L
        private set

val output

    val output = RenderToTexture(renderer) {
        width = source.width
        height = source.height
        useDepth = false
        format = org.lwjgl.vulkan.VK13.VK_FORMAT_R32G32B32A32_SFLOAT
    }
    private val sampler = TextureSampler(renderer, 1, false)
    private val display =
        Module(
            parameters(renderer) {
                this.name = "$name-comparison"
                +output
                depthTest = false
                depthWrite = false
                blend = false
                vertex =
                    """#version 460
            void main(){vec2 p=vec2((gl_VertexIndex<<1)&2,gl_VertexIndex&2);gl_Position=vec4(p*2-1,0,1);}
        """
                fragment =
                    """#version 460
            layout(set=0,binding=0) uniform sampler2D teacher;
            layout(set=0,binding=1) uniform sampler2D learned;
            layout(push_constant) uniform P {int view;} p;
            layout(location=0) out vec4 color;
            void main(){vec2 uv=gl_FragCoord.xy/vec2(textureSize(teacher,0));int panel=min(int(uv.x*3),2);
                if(p.view==2)uv.x=fract(uv.x*3);
                vec3 a=texture(teacher,uv).rgb,b=texture(learned,uv).rgb;
                color=vec4(p.view==0?a:p.view==1?b:panel==0?a:panel==1?b:abs(a-b)*8,1);}
        """
                textureResolver = {
                    fromViews(listOf(if (it.name == "teacher") source else model.output), it.name, sampler)
                }
            },
        )
    private val quad: Mesh
    private var closed = false

init

    init {
        check(display.valid)
        val p = display.pointer()
        quad = Mesh(renderer, p)
        quad.resize(3, 0, p)
        quad.push.setMembers(display.fi.pushConstants.single().members)
        display.meshes["quad"] = quad
        output.modules["quad"] = p
    }

fun install(): GpuShaderLearningView

    fun install(): GpuShaderLearningView {
        val old = renderer.renderList.preTargets[name]
        require(old == null || old === this || old is GpuShaderLearningView)
        if (old is GpuShaderLearningView && old !== this) old.close()
        renderer.renderList.preTargets[name] = this
        output.blitToScreen(name = name)
        return this
    }

override fun render(command: CommandBuffer, index: Int)

    override fun render(command: CommandBuffer, index: Int) {
        if (closed) return
        require(parameter.isFinite() && parameter in -1f..1f)
        if (learning || view != 1) {
            setSourceParameter(parameter)
            source.render(command, index)
            sourceRenderCount++
        }
        if (learning) model.train(command, source, parameter, iterationsPerFrame)
        model.evaluate(command, index, parameter)
        quad.push["view"] = view
        output.render(command, index)
    }

override fun close()

    override fun close() {
        if (closed) return
        closed = true
        if (renderer.renderList.preTargets.remove(name, this)) renderer.renderList.modules.remove(name)
        model.close()
        renderer.cleanup += Runnable {
            display.cleanup()
            sampler.cleanup()
            output.imageViewArray.forEach { it.imageView.cleanup() }
            output.colorImageRefs.forEach { it?.cleanup() }
            output.defaultSampler?.cleanup()
        }
    }
}