Skip to content

GpuSchemaSmokeTest.kt

src/jvmMain/kotlin/afield/examples/four/GpuSchemaSmokeTest.kt · 96 lines

package afield.examples.four

import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.buffers.GpuStruct
import afield.graphics.vulkan.buffers.gpuSchema
import afield.graphics.vulkan.util.GpuProbe
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import afield.math.linalg.Vector3f
import afield.math.linalg.Vector4f
import kotlin.system.exitProcess

FIELD_TEST id: four.gpu-schema.readback requiresDisplay: true runner: jvm-main entrypoint: afield.examples.four.GpuSchemaSmokeTest tags: [smoke, four, gpu, compute, schema] timeoutSec: 180 expect: exitCode: 0 stdoutContains: - "GPU schema readback PASSED"

object GpuSchemaSmokeTest

Verifies nested CPU packing and array stride through real buffer-address GPU loads.

data class Material(val color: Vector4f, val roughness: Float, val metallic: Float)

    @GpuStruct
    data class Material(val color: Vector4f, val roughness: Float, val metallic: Float)

data class Particle(val position: Vector3f, val radius: Float, val material: Material, val flags: UInt)

    @GpuStruct
    data class Particle(val position: Vector3f, val radius: Float, val material: Material, val flags: UInt)

fun main(args: Array<String>)

body — 62 lines, from line 34
    @JvmStatic
    fun main(args: Array<String>) {
        Options.enableVR = false
        val window = Window("GPU schema readback")
        val renderer = Renderer(window)
        try {
            GpuProbe(renderer, "gpu-schema", pump = { window.pollEvents(); renderer.render() }).use { probe ->
                val schema = gpuSchema<Particle>()
                val values = (0 until 3).map { i ->
                    val n = (i * 16).toFloat()
                    Particle(Vector3f(n + 1, n + 2, n + 3), n + 4,
                        Material(Vector4f(n + 5, n + 6, n + 7, n + 8), n + 9, n + 10), (i + 11).toUInt())
                }
                val input = probe.fixture("particles", values.size * schema.strideBytes / 4)
                input.mapAndWrite_bytes { schema.writeArray(values, this) }
                val output = probe.fixture("readback", values.size * 11)
                probe.stage("schema-loads", """
                    #version 460
                    ${schema.glslDeclarations()}
                    layout(buffer_reference, std430) buffer Result { float values[]; };
                    layout(push_constant) uniform Push { ParticleArray particles; Result result; } pc;
                    layout(local_size_x = 1) in;
                    void main() {
                        uint i = gl_GlobalInvocationID.x;
                        Particle p = pc.particles.elements[i];
                        uint o = i * 11u;
                        pc.result.values[o] = p.position.x;
                        pc.result.values[o + 1u] = p.position.y;
                        pc.result.values[o + 2u] = p.position.z;
                        pc.result.values[o + 3u] = p.radius;
                        pc.result.values[o + 4u] = p.material.color.x;
                        pc.result.values[o + 5u] = p.material.color.y;
                        pc.result.values[o + 6u] = p.material.color.z;
                        pc.result.values[o + 7u] = p.material.color.w;
                        pc.result.values[o + 8u] = p.material.roughness;
                        pc.result.values[o + 9u] = p.material.metallic;
                        pc.result.values[o + 10u] = float(p.flags);
                    }
                """.trimIndent()).dispatch(groups = values.size) {
                    bind("particles") { input }
                    bind("result") { output }
                }
                probe.settle(output)
                val actual = probe.read(output)
                for (i in values.indices) {
                    val expected = (1..10).map { (i * 16 + it).toFloat() } + (i + 11).toFloat()
                    expected.forEachIndexed { lane, value ->
                        check(actual[i * 11 + lane] == value) {
                            "Particle $i lane $lane: expected $value, got ${actual[i * 11 + lane]}"
                        }
                    }
                }
                check(probe.verdict() == 0)
                println("GPU schema readback PASSED: 3 nested structs, 33 values, stride=${schema.strideBytes}")
            }
        } finally {
            renderer.cleanup()
            window.cleanup()
        }
        exitProcess(0)
    }
}