|
| 1 | +package io.computenode.cyfra.samples.examples |
| 2 | + |
| 3 | +import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} |
| 4 | +import io.computenode.cyfra.core.GProgram.StaticDispatch |
| 5 | +import io.computenode.cyfra.core.layout.Layout |
| 6 | +import io.computenode.cyfra.dsl.{*, given} |
| 7 | +import io.computenode.cyfra.runtime.VkCyfraRuntime |
| 8 | + |
| 9 | +/** Test snippets from gpu-programs.md documentation |
| 10 | + */ |
| 11 | +object DocSnippets_GpuPrograms: |
| 12 | + |
| 13 | + // === Snippet 1: Basic DoubleLayout and doubleProgram === |
| 14 | + case class DoubleLayout(input: GBuffer[Float32], output: GBuffer[Float32]) derives Layout |
| 15 | + |
| 16 | + val doubleProgram: GProgram[Int, DoubleLayout] = GProgram.static[Int, DoubleLayout]( |
| 17 | + layout = size => DoubleLayout(input = GBuffer[Float32](size), output = GBuffer[Float32](size)), |
| 18 | + dispatchSize = size => size, |
| 19 | + ): layout => |
| 20 | + val idx = GIO.invocationId |
| 21 | + GIO.when(idx < 256): |
| 22 | + val value = layout.input.read(idx) |
| 23 | + layout.output.write(idx, value * 2.0f) |
| 24 | + |
| 25 | + @main |
| 26 | + def testDoubleProgram(): Unit = VkCyfraRuntime.using: |
| 27 | + val size = 256 |
| 28 | + val inputData = (0 until size).map(_.toFloat).toArray |
| 29 | + val results = Array.ofDim[Float](size) |
| 30 | + |
| 31 | + val region = GBufferRegion |
| 32 | + .allocate[DoubleLayout] |
| 33 | + .map: layout => |
| 34 | + doubleProgram.execute(size, layout) |
| 35 | + |
| 36 | + region.runUnsafe( |
| 37 | + init = DoubleLayout(input = GBuffer(inputData), output = GBuffer[Float32](size)), |
| 38 | + onDone = layout => layout.output.readArray(results), |
| 39 | + ) |
| 40 | + |
| 41 | + println(s"[testDoubleProgram] Results: ${results.take(5).mkString(", ")}...") |
| 42 | + val expected = inputData.map(_ * 2.0f) |
| 43 | + val correct = results.zip(expected).forall((r, e) => Math.abs(r - e) < 0.001f) |
| 44 | + println(s"[testDoubleProgram] Correct: $correct") |
| 45 | + |
| 46 | + // === Snippet 2: Parameterized Program with MulParams === |
| 47 | + case class MulParams(factor: Float32) extends GStruct[MulParams] |
| 48 | + |
| 49 | + case class MulLayout(input: GBuffer[Float32], output: GBuffer[Float32], params: GUniform[MulParams]) derives Layout |
| 50 | + |
| 51 | + val mulProgram: GProgram[Int, MulLayout] = GProgram.static[Int, MulLayout]( |
| 52 | + layout = size => MulLayout(input = GBuffer[Float32](size), output = GBuffer[Float32](size), params = GUniform[MulParams]()), |
| 53 | + dispatchSize = size => size, |
| 54 | + ): layout => |
| 55 | + val idx = GIO.invocationId |
| 56 | + GIO.when(idx < 256): |
| 57 | + val value = layout.input.read(idx) |
| 58 | + val factor = layout.params.read.factor |
| 59 | + layout.output.write(idx, value * factor) |
| 60 | + |
| 61 | + @main |
| 62 | + def testMulProgram(): Unit = VkCyfraRuntime.using: |
| 63 | + val size = 256 |
| 64 | + val inputData = (0 until size).map(_.toFloat).toArray |
| 65 | + val results = Array.ofDim[Float](size) |
| 66 | + |
| 67 | + val region = GBufferRegion |
| 68 | + .allocate[MulLayout] |
| 69 | + .map: layout => |
| 70 | + mulProgram.execute(size, layout) |
| 71 | + |
| 72 | + region.runUnsafe( |
| 73 | + init = MulLayout(input = GBuffer(inputData), output = GBuffer[Float32](size), params = GUniform(MulParams(3.0f))), |
| 74 | + onDone = layout => layout.output.readArray(results), |
| 75 | + ) |
| 76 | + |
| 77 | + println(s"[testMulProgram] Input: ${inputData.take(5).mkString(", ")}...") |
| 78 | + println(s"[testMulProgram] Output: ${results.take(5).mkString(", ")}...") |
| 79 | + val expected = inputData.map(_ * 3.0f) |
| 80 | + val correct = results.zip(expected).forall((r, e) => Math.abs(r - e) < 0.001f) |
| 81 | + println(s"[testMulProgram] Correct: $correct") |
| 82 | + |
| 83 | + // === Snippet 3: Running Multiple Programs === |
| 84 | + case class AddParams(addend: Float32) extends GStruct[AddParams] |
| 85 | + case class AddLayout(input: GBuffer[Float32], output: GBuffer[Float32], params: GUniform[AddParams]) derives Layout |
| 86 | + |
| 87 | + val addProgram: GProgram[Int, AddLayout] = GProgram.static[Int, AddLayout]( |
| 88 | + layout = size => AddLayout(GBuffer[Float32](size), GBuffer[Float32](size), GUniform[AddParams]()), |
| 89 | + dispatchSize = size => size, |
| 90 | + ): layout => |
| 91 | + val idx = GIO.invocationId |
| 92 | + GIO.when(idx < 256): |
| 93 | + val value = layout.input.read(idx) |
| 94 | + val addend = layout.params.read.addend |
| 95 | + layout.output.write(idx, value + addend) |
| 96 | + |
| 97 | + case class PipelineLayout(input: GBuffer[Float32], intermediate: GBuffer[Float32], output: GBuffer[Float32], addParams: GUniform[AddParams]) |
| 98 | + derives Layout |
| 99 | + |
| 100 | + @main |
| 101 | + def testMultiplePrograms(): Unit = VkCyfraRuntime.using: |
| 102 | + val size = 256 |
| 103 | + val inputData = (0 until size).map(_.toFloat).toArray |
| 104 | + val results = Array.ofDim[Float](size) |
| 105 | + |
| 106 | + val region = GBufferRegion |
| 107 | + .allocate[PipelineLayout] |
| 108 | + .map: layout => |
| 109 | + // Program 1: input -> intermediate (doubles values) |
| 110 | + val afterDouble = doubleProgram.execute(size, DoubleLayout(layout.input, layout.intermediate)) |
| 111 | + |
| 112 | + // Program 2: use intermediate from afterDouble as input, write to output |
| 113 | + addProgram.execute(size, AddLayout(afterDouble.output, layout.output, layout.addParams)) |
| 114 | + |
| 115 | + // Return the original layout so onDone can access it |
| 116 | + layout |
| 117 | + |
| 118 | + region.runUnsafe( |
| 119 | + init = PipelineLayout( |
| 120 | + input = GBuffer(inputData), |
| 121 | + intermediate = GBuffer[Float32](size), |
| 122 | + output = GBuffer[Float32](size), |
| 123 | + addParams = GUniform(AddParams(10.0f)), |
| 124 | + ), |
| 125 | + onDone = layout => layout.output.readArray(results), |
| 126 | + ) |
| 127 | + |
| 128 | + println(s"[testMultiplePrograms] Pipeline: input -> double -> add 10") |
| 129 | + println(s"[testMultiplePrograms] Input: ${inputData.take(5).mkString(", ")}...") |
| 130 | + println(s"[testMultiplePrograms] Output: ${results.take(5).mkString(", ")}...") |
| 131 | + val expected = inputData.map(x => x * 2.0f + 10.0f) |
| 132 | + val correct = results.zip(expected).forall((r, e) => Math.abs(r - e) < 0.001f) |
| 133 | + println(s"[testMultiplePrograms] Correct: $correct") |
| 134 | + |
| 135 | +/** Test snippets from gpu-pipelines.md documentation |
| 136 | + */ |
| 137 | +object DocSnippets_GpuPipelines: |
| 138 | + |
| 139 | + case class DoubleLayout(input: GBuffer[Float32], output: GBuffer[Float32]) derives Layout |
| 140 | + |
| 141 | + val doubleProgram: GProgram[Int, DoubleLayout] = GProgram[Int, DoubleLayout]( |
| 142 | + layout = size => DoubleLayout(input = GBuffer[Float32](size), output = GBuffer[Float32](size)), |
| 143 | + dispatch = (_, size) => StaticDispatch(((size + 255) / 256, 1, 1)), |
| 144 | + workgroupSize = (256, 1, 1), |
| 145 | + ): layout => |
| 146 | + val idx = GIO.invocationId |
| 147 | + GIO.when(idx < 256): |
| 148 | + val value = layout.input.read(idx) |
| 149 | + layout.output.write(idx, value * 2.0f) |
| 150 | + |
| 151 | + case class AddParams(value: Float32) extends GStruct[AddParams] |
| 152 | + case class AddLayout(input: GBuffer[Float32], output: GBuffer[Float32], params: GUniform[AddParams]) derives Layout |
| 153 | + |
| 154 | + val addProgram: GProgram[Int, AddLayout] = GProgram[Int, AddLayout]( |
| 155 | + layout = size => AddLayout(input = GBuffer[Float32](size), output = GBuffer[Float32](size), params = GUniform[AddParams]()), |
| 156 | + dispatch = (_, size) => StaticDispatch(((size + 255) / 256, 1, 1)), |
| 157 | + workgroupSize = (256, 1, 1), |
| 158 | + ): layout => |
| 159 | + val idx = GIO.invocationId |
| 160 | + GIO.when(idx < 256): |
| 161 | + val value = layout.input.read(idx) |
| 162 | + val addValue = layout.params.read.value |
| 163 | + layout.output.write(idx, value + addValue) |
| 164 | + |
| 165 | + case class PipelineLayout(input: GBuffer[Float32], doubled: GBuffer[Float32], output: GBuffer[Float32], addParams: GUniform[AddParams]) |
| 166 | + derives Layout |
| 167 | + |
| 168 | + val doubleAndAddPipeline: GExecution[Int, PipelineLayout, PipelineLayout] = |
| 169 | + GExecution[Int, PipelineLayout]() |
| 170 | + .addProgram(doubleProgram)(size => size, layout => DoubleLayout(layout.input, layout.doubled)) |
| 171 | + .addProgram(addProgram)(size => size, layout => AddLayout(layout.doubled, layout.output, layout.addParams)) |
| 172 | + |
| 173 | + @main |
| 174 | + def testGExecutionPipeline(): Unit = VkCyfraRuntime.using: |
| 175 | + val size = 256 |
| 176 | + val inputData = (0 until size).map(_.toFloat).toArray |
| 177 | + val results = Array.ofDim[Float](size) |
| 178 | + |
| 179 | + val region = GBufferRegion |
| 180 | + .allocate[PipelineLayout] |
| 181 | + .map: layout => |
| 182 | + doubleAndAddPipeline.execute(size, layout) |
| 183 | + |
| 184 | + region.runUnsafe( |
| 185 | + init = PipelineLayout( |
| 186 | + input = GBuffer(inputData), |
| 187 | + doubled = GBuffer[Float32](size), |
| 188 | + output = GBuffer[Float32](size), |
| 189 | + addParams = GUniform(AddParams(10.0f)), |
| 190 | + ), |
| 191 | + onDone = layout => layout.output.readArray(results), |
| 192 | + ) |
| 193 | + |
| 194 | + println(s"[testGExecutionPipeline] Pipeline: input -> double -> add 10") |
| 195 | + println(s"[testGExecutionPipeline] Input: ${inputData.take(5).mkString(", ")}...") |
| 196 | + println(s"[testGExecutionPipeline] Output: ${results.take(5).mkString(", ")}...") |
| 197 | + val expected = inputData.map(x => x * 2.0f + 10.0f) |
| 198 | + val correct = results.zip(expected).forall((r, e) => Math.abs(r - e) < 0.001f) |
| 199 | + println(s"[testGExecutionPipeline] Correct: $correct") |
| 200 | + |
| 201 | +/** Run all documentation snippet tests |
| 202 | + */ |
| 203 | +object RunAllDocSnippetTests: |
| 204 | + @main |
| 205 | + def runAllDocSnippets(): Unit = |
| 206 | + println("=== Testing Documentation Snippets ===\n") |
| 207 | + |
| 208 | + println("--- gpu-programs.md snippets ---") |
| 209 | + DocSnippets_GpuPrograms.testDoubleProgram() |
| 210 | + println() |
| 211 | + DocSnippets_GpuPrograms.testMulProgram() |
| 212 | + println() |
| 213 | + DocSnippets_GpuPrograms.testMultiplePrograms() |
| 214 | + println() |
| 215 | + |
| 216 | + println("--- gpu-pipelines.md snippets ---") |
| 217 | + DocSnippets_GpuPipelines.testGExecutionPipeline() |
| 218 | + println() |
| 219 | + |
| 220 | + println("=== All documentation snippet tests completed! ===") |
0 commit comments