From 24678fc4fab27fdef49de97a5070c8cb030c48d7 Mon Sep 17 00:00:00 2001 From: spamegg Date: Sun, 27 Jul 2025 14:43:59 +0300 Subject: [PATCH 01/38] Fs2 interop --- .github/workflows/ci.yml | 2 + build.sbt | 10 ++- .../cyfra/core/layout/LayoutStruct.scala | 4 +- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 84 +++++++++++++++++ .../computenode/cyfra/fs2interop/Bridge.scala | 58 ++++++++++++ .../computenode/cyfra/fs2interop/GPipe.scala | 90 +++++++++++++++++++ 6 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala create mode 100644 cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala create mode 100644 cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c47d94a8..e0807fa9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,8 @@ on: tags: - "v*" pull_request: + branches: + - dev jobs: format_and_compile: diff --git a/build.sbt b/build.sbt index 35a46c2c..d9e1687c 100644 --- a/build.sbt +++ b/build.sbt @@ -58,6 +58,8 @@ lazy val commonSettings = Seq( lazy val runnerSettings = Seq(libraryDependencies += "org.apache.logging.log4j" % "log4j-slf4j2-impl" % "2.24.3") +lazy val fs2Settings = Seq(libraryDependencies ++= Seq("co.fs2" %% "fs2-core" % "3.12.0", "co.fs2" %% "fs2-io" % "3.12.0")) + lazy val utility = (project in file("cyfra-utility")) .settings(commonSettings) @@ -97,13 +99,17 @@ lazy val vscode = (project in file("cyfra-vscode")) .settings(commonSettings) .dependsOn(foton) +lazy val fs2interop = (project in file("cyfra-fs2")) + .settings(commonSettings, fs2Settings) + .dependsOn(runtime) + lazy val e2eTest = (project in file("cyfra-e2e-test")) .settings(commonSettings, runnerSettings) - .dependsOn(runtime) + .dependsOn(runtime, fs2interop) lazy val root = (project in file(".")) .settings(name := "Cyfra") - .aggregate(compiler, dsl, foton, core, runtime, vulkan, examples) + .aggregate(compiler, dsl, foton, core, runtime, vulkan, examples, fs2interop) e2eTest / Test / javaOptions ++= Seq("-Dorg.lwjgl.system.stackSize=1024", "-DuniqueLibraryNames=true") diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala index 3d79b409..f9bd8247 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala @@ -60,11 +60,11 @@ object LayoutStruct: ftype match case '[type tg <: GBuffer[?]; tg] => '{ - BufferRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }) + BufferRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using ${ tag.asExprOf[Tag[t]] }, ${ fromExpr.asExprOf[FromExpr[t]] }) } case '[type tg <: GUniform[?]; tg] => '{ - UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }) + UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using ${ tag.asExprOf[Tag[t]] }, ${ fromExpr.asExprOf[FromExpr[t]] }) } val constructor = sym.primaryConstructor diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala new file mode 100644 index 00000000..2339a22f --- /dev/null +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -0,0 +1,84 @@ +package io.computenode.cyfra.e2e.fs2interop + +import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA +import io.computenode.cyfra.dsl.{*, given}, algebra.VectorAlgebra +import io.computenode.cyfra.fs2interop.*, GPipe.*, Bridge.given +import io.computenode.cyfra.core.CyfraRuntime +import io.computenode.cyfra.runtime.VkCyfraRuntime + +import fs2.{io as fs2io, *} + +extension (f: fRGBA) + def neg = (-f._1, -f._2, -f._3, -f._4) + def scl(s: Float) = (f._1 * s, f._2 * s, f._3 * s, f._4 * s) + def add(g: fRGBA) = (f._1 + g._1, f._2 + g._2, f._3 + g._3, f._4 + g._4) + def close(g: fRGBA)(eps: Float): Boolean = + Math.abs(f._1 - g._1) < eps && Math.abs(f._2 - g._2) < eps && Math.abs(f._3 - g._3) < eps && Math.abs(f._4 - g._4) < eps + +class Fs2Tests extends munit.FunSuite: + given cr: CyfraRuntime = VkCyfraRuntime() + + test("fs2 through gPipeMap, just ints"): + val inSeq = (0 until 256).toSeq + val stream = Stream.emits(inSeq) + val pipe = gPipeMap[Pure, Int32, Int](_ + 1) + val result = stream.through(pipe).compile.toList + val expected = inSeq.map(_ + 1) + result + .zip(expected) + .foreach: (res, exp) => + assert(res == exp, s"Expected $exp, got $res") + + test("fs2 through gPipeMap, floats and vectors"): + val inSeq = (0 to 255).map(_.toFloat).toSeq + val stream = Stream.emits(inSeq) + val pipe = gPipeMap[Pure, Float32, Vec4[Float32], Float, fRGBA](f => (f, f + 1f, f + 2f, f + 3f)) + val result = stream.through(pipe).compile.toList + val expected = inSeq.map(f => (f, f + 1f, f + 2f, f + 3f)) + result + .zip(expected) + .foreach: (res, exp) => + assert(res.close(exp)(0.001f), s"Expected $exp, got $res") + +class Fs2LegacyTests extends munit.FunSuite: + given gc: GContext = GContext() + + test("fs2 Float stream (legacy)"): + val inSeq = (0 to 255).map(_.toFloat) + val inStream = Stream.emits(inSeq) + val outStream = inStream.gPipeFloat(_ + 1f).toList + val expected = inStream.map(_ + 1f).toList + outStream + .zip(expected) + .foreach: (res, exp) => + assert(Math.abs(res - exp) < 0.001f, s"Expected $exp but got $res") + + test("fs2 Int stream (legacy)"): + val inSeq = 0 to 255 + val inStream = Stream.emits(inSeq) + val outStream = inStream.gPipeInt(_ + 1).toList + val expected = inStream.map(_ + 1).toList + outStream + .zip(expected) + .foreach: (res, exp) => + assert(res == exp, s"Expected $exp but got $res") + + test("fs2 Vec4Float stream (legacy)"): + val k = -2.1f + val f = (1.2f, 2.3f, 3.4f, 4.5f) + val v = VectorAlgebra.vec4.tupled(f) + + val inSeq: Seq[fRGBA] = (0 to 1023) + .map(_.toFloat) + .grouped(4) + .map: + case Seq(a, b, c, d) => (a, b, c, d) + .toSeq + val inStream = Stream.emits(inSeq) + val outStream = inStream.gPipeVec4(vec => (-vec).*(k).+(v)).toList + val expected = inStream.map(vec => vec.neg.scl(k).add(f)).toList + + outStream + .zip(expected) + .foreach: (res, exp) => + assert(res.close(exp)(0.001f), s"Expected $exp but got $res") diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala new file mode 100644 index 00000000..41d366c3 --- /dev/null +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala @@ -0,0 +1,58 @@ +package io.computenode.cyfra.fs2interop + +import io.computenode.cyfra.core.archive.mem.GMem.fRGBA +import io.computenode.cyfra.dsl.* + +import fs2.* +import java.nio.ByteBuffer +import org.lwjgl.BufferUtils +import izumi.reflect.Tag + +import scala.reflect.ClassTag + +trait Bridge[CyfraType <: Value: FromExpr: Tag, ScalaType: ClassTag]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[ScalaType]): ByteBuffer + def fromByteBuffer(outBuf: ByteBuffer, arr: Array[ScalaType]): Array[ScalaType] + +object Bridge: + given Bridge[Int32, Int]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Int]): ByteBuffer = + inBuf.asIntBuffer().put(chunk.toArray[Int]).flip() + inBuf + def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Int]): Array[Int] = + outBuf.asIntBuffer().get(arr).flip() + arr + + given Bridge[Float32, Float]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Float]): ByteBuffer = + inBuf.asFloatBuffer().put(chunk.toArray[Float]).flip() + inBuf + def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Float]): Array[Float] = + outBuf.asFloatBuffer().get(arr).flip() + arr + + given Bridge[Vec4[Float32], fRGBA]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[fRGBA]): ByteBuffer = + val vecs = chunk.toArray[fRGBA] + vecs.foreach: + case (x, y, z, a) => + inBuf.putFloat(x) + inBuf.putFloat(y) + inBuf.putFloat(z) + inBuf.putFloat(a) + inBuf.flip() + inBuf + + def fromByteBuffer(outBuf: ByteBuffer, arr: Array[fRGBA]): Array[fRGBA] = + val res = outBuf.asFloatBuffer() + for i <- 0 until arr.size do arr(i) = (res.get(), res.get(), res.get(), res.get()) + outBuf.flip() + arr + + given Bridge[GBoolean, Boolean]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Boolean]): ByteBuffer = + inBuf.put(chunk.toArray.asInstanceOf[Array[Byte]]).flip() + inBuf + def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Boolean]): Array[Boolean] = + outBuf.get(arr.asInstanceOf[Array[Byte]]).flip() + arr diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala new file mode 100644 index 00000000..d3868465 --- /dev/null +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -0,0 +1,90 @@ +package io.computenode.cyfra.fs2interop + +import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA +import io.computenode.cyfra.core.{Allocation, layout}, layout.Layout +import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} +import io.computenode.cyfra.dsl.{*, given}, gio.GIO, binding.GBuffer +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import struct.GStruct, GStruct.Empty, Empty.given + +import fs2.* +import java.nio.ByteBuffer +import org.lwjgl.BufferUtils +import izumi.reflect.Tag + +import scala.reflect.ClassTag + +object GPipe: + def gPipeMap[F[_], C1 <: Value: FromExpr: Tag, C2 <: Value: FromExpr: Tag, S1: ClassTag, S2: ClassTag]( + f: C1 => C2, + )(using cr: CyfraRuntime, bridge1: Bridge[C1, S1], bridge2: Bridge[C2, S2]): Pipe[F, S1, S2] = + (stream: Stream[F, S1]) => + case class Params(inSize: Int) + case class PLayout(in: GBuffer[C1], out: GBuffer[C2]) extends Layout + case class PResult(out: GBuffer[C2]) extends Layout + + val params = Params(inSize = 256) + val inTypeSize = typeStride(Tag.apply[C1]) + val outTypeSize = typeStride(Tag.apply[C2]) + + val gProg = GProgram[Params, PLayout]( + layout = params => PLayout(in = GBuffer[C1](params.inSize), out = GBuffer[C2](params.inSize)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), + ): layout => + val invocId = GIO.invocationId + val element = GIO.read[C1](layout.in, invocId) + GIO.write[C2](layout.out, invocId, f(element)) // implicit bug + + val execution = GExecution[Params, PLayout]() + .addProgram(gProg)(params => Params(params.inSize), layout => PLayout(layout.in, layout.out)) + + val region = GBufferRegion + .allocate[PLayout] // implicit bug + .map: pLayout => + execution.execute(params, pLayout) // implicit bug + + // these are allocated once, reused for many chunks + val inBuf = BufferUtils.createByteBuffer(params.inSize * inTypeSize) + val outBuf = BufferUtils.createByteBuffer(params.inSize * outTypeSize) + + stream + .chunkMin(params.inSize) + .flatMap: chunk => + bridge1.toByteBuffer(inBuf, chunk) + region.runUnsafe(init = PLayout(in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), onDone = layout => layout.out.read(outBuf)) // implicit bug + Stream.emits(bridge2.fromByteBuffer(outBuf, new Array[S2](params.inSize))) + + // Syntax sugar for convenient single type version + def gPipeMap[F[_], C <: Value: FromExpr: Tag, S: ClassTag](f: C => C)(using CyfraRuntime, Bridge[C, S]): Pipe[F, S, S] = + gPipeMap[F, C, C, S, S](f) + + // legacy stuff working with GFunction + extension (stream: Stream[Pure, Float]) + def gPipeFloat(fn: Float32 => Float32)(using GContext): Stream[Pure, Float] = + val gf: GFunction[Empty, Float32, Float32] = GFunction(fn) + stream + .chunkMin(256) + .flatMap: chunk => + val gmem = FloatMem(chunk.toArray) + val res = gmem.map(gf).asInstanceOf[FloatMem].toArray + Stream.emits(res) + + extension (stream: Stream[Pure, Int]) + def gPipeInt(fn: Int32 => Int32)(using GContext): Stream[Pure, Int] = + val gf: GFunction[Empty, Int32, Int32] = GFunction(fn) + stream + .chunkMin(256) + .flatMap: chunk => + val gmem = IntMem(chunk.toArray) + val res = gmem.map(gf).asInstanceOf[IntMem].toArray + Stream.emits(res) + + extension (stream: Stream[Pure, fRGBA]) + def gPipeVec4(fn: Vec4[Float32] => Vec4[Float32])(using GContext): Stream[Pure, fRGBA] = + val gf: GFunction[Empty, Vec4[Float32], Vec4[Float32]] = GFunction(fn) + stream + .chunkMin(256) + .flatMap: chunk => + val gmem = Vec4FloatMem(chunk.toArray) + val res = gmem.map(gf).asInstanceOf[Vec4FloatMem].toArray + Stream.emits(res) From db2eb0c1094b7d5f30a6fd081ef1992a32919212 Mon Sep 17 00:00:00 2001 From: spamegg Date: Tue, 12 Aug 2025 18:51:13 +0300 Subject: [PATCH 02/38] progress on filter: upsweep and downsweep --- .../computenode/cyfra/fs2interop/GPipe.scala | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index d3868465..2e0e6a28 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -3,7 +3,7 @@ package io.computenode.cyfra.fs2interop import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA import io.computenode.cyfra.core.{Allocation, layout}, layout.Layout import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} -import io.computenode.cyfra.dsl.{*, given}, gio.GIO, binding.GBuffer +import io.computenode.cyfra.dsl.{*, given}, gio.GIO, binding.{GBuffer, GUniform, GBinding} import io.computenode.cyfra.spirv.SpirvTypes.typeStride import struct.GStruct, GStruct.Empty, Empty.given @@ -33,15 +33,15 @@ object GPipe: ): layout => val invocId = GIO.invocationId val element = GIO.read[C1](layout.in, invocId) - GIO.write[C2](layout.out, invocId, f(element)) // implicit bug + GIO.write[C2](layout.out, invocId, f(element)) val execution = GExecution[Params, PLayout]() .addProgram(gProg)(params => Params(params.inSize), layout => PLayout(layout.in, layout.out)) val region = GBufferRegion - .allocate[PLayout] // implicit bug + .allocate[PLayout] .map: pLayout => - execution.execute(params, pLayout) // implicit bug + execution.execute(params, pLayout) // these are allocated once, reused for many chunks val inBuf = BufferUtils.createByteBuffer(params.inSize * inTypeSize) @@ -58,6 +58,73 @@ object GPipe: def gPipeMap[F[_], C <: Value: FromExpr: Tag, S: ClassTag](f: C => C)(using CyfraRuntime, Bridge[C, S]): Pipe[F, S, S] = gPipeMap[F, C, C, S, S](f) + // https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda + // Prefix Sum and Stream Compaction + // 11 + // 012345678901 index + // [abcdefghijkl] starting collection + // [tfftfftttftf] convert to booleans + // [100100111010] integer equivalent + // [0111222345566] scan prefixsum, last number tells us the size of filtered collection + // [x..x..xxx.x.] take the indexes where the next scan number is 1 bigger. + // [adhijl] compact the collection + // 012345 prefixsum results are the new indices in compacted collection + def gPipeFilter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = + (stream: Stream[F, S]) => + case class Params(inSize: Int, intervalSize: Int) + case class PredLayout(cIn: GBuffer[C], boolOut: GBuffer[Int32]) extends Layout + case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] + case class ScanLayout(intIn: GBuffer[Int32], intOut: GBuffer[Int32], intervalSize: GUniform[ScanArgs]) extends Layout + case class CompactionLayout(cIn: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout + case class FilterResult(cOut: GBuffer[C]) extends Layout + + val predicateProgram = GProgram[Params, PredLayout]( + layout = params => PredLayout(cIn = GBuffer[C](params.inSize), boolOut = GBuffer[Int32](params.inSize)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), + ): layout => + val invocId = GIO.invocationId + val element = GIO.read[C](layout.cIn, invocId) + GIO.write[Int32](layout.boolOut, invocId, when(pred(element))(1: Int32).otherwise(0)) + + val upsweep = GProgram[Params, ScanLayout]( + layout = params => + ScanLayout( + intIn = GBuffer[Int32](params.inSize / params.intervalSize), + intOut = GBuffer[Int32](params.inSize), + intervalSize = GUniform(ScanArgs(params.intervalSize)), + ), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize, 1, 1)), + ): layout => + val ScanArgs(size) = layout.intervalSize.read + val invocId = GIO.invocationId + val root = invocId * size + val mid = root + (size / 2) - 1 + val end = root + size - 1 + val oldValue = GIO.read[Int32](layout.intOut, end) + val addValue = GIO.read[Int32](layout.intOut, mid) + val newValue = oldValue + addValue + GIO.write[Int32](layout.intOut, end, newValue) + + val downsweep = GProgram[Params, ScanLayout]( + layout = params => + ScanLayout( + intIn = GBuffer[Int32](params.inSize / params.intervalSize), + intOut = GBuffer[Int32](params.inSize), + intervalSize = GUniform(ScanArgs(params.intervalSize)), + ), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize, 1, 1)), + ): layout => + val ScanArgs(size) = layout.intervalSize.read + val invocId = GIO.invocationId + val root = invocId * size - 1 // if invocId = 0, this is -1 (out of bounds) + val mid = root + (size / 2) + val oldValue = GIO.read[Int32](layout.intOut, mid) + val addValue = when(root > 0)(GIO.read[Int32](layout.intOut, root)).otherwise(0) + val newValue = oldValue + addValue + GIO.write[Int32](layout.intOut, mid, newValue) + + ??? + // legacy stuff working with GFunction extension (stream: Stream[Pure, Float]) def gPipeFloat(fn: Float32 => Float32)(using GContext): Stream[Pure, Float] = From 386e101a30741ff66518c6d0993ef4a66a9ca6df Mon Sep 17 00:00:00 2001 From: spamegg Date: Wed, 13 Aug 2025 18:49:34 +0300 Subject: [PATCH 03/38] progress on upsweep / downsweep phases --- .../computenode/cyfra/fs2interop/GPipe.scala | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 2e0e6a28..abc50120 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -61,21 +61,21 @@ object GPipe: // https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda // Prefix Sum and Stream Compaction // 11 - // 012345678901 index - // [abcdefghijkl] starting collection - // [tfftfftttftf] convert to booleans - // [100100111010] integer equivalent - // [0111222345566] scan prefixsum, last number tells us the size of filtered collection - // [x..x..xxx.x.] take the indexes where the next scan number is 1 bigger. - // [adhijl] compact the collection - // 012345 prefixsum results are the new indices in compacted collection + // 012345678901 index + // [abcdefghijkl] starting collection + // [tfftfftttftf] convert to booleans + // [100100111010] integer equivalent + // [111222345566] prefixsum, last number tells us the size of filtered collection + // [x..x..xxx.x.] take the ones that are 1 bigger than previous. + // [adhijl] compact the collection + // 012345 (prefixsum result - 1) are the new indices in compacted collection def gPipeFilter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = (stream: Stream[F, S]) => case class Params(inSize: Int, intervalSize: Int) case class PredLayout(cIn: GBuffer[C], boolOut: GBuffer[Int32]) extends Layout case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] case class ScanLayout(intIn: GBuffer[Int32], intOut: GBuffer[Int32], intervalSize: GUniform[ScanArgs]) extends Layout - case class CompactionLayout(cIn: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout + case class CompactLayout(cIn: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout case class FilterResult(cOut: GBuffer[C]) extends Layout val predicateProgram = GProgram[Params, PredLayout]( @@ -86,6 +86,9 @@ object GPipe: val element = GIO.read[C](layout.cIn, invocId) GIO.write[Int32](layout.boolOut, invocId, when(pred(element))(1: Int32).otherwise(0)) + val predicateExec = GExecution[Params, PredLayout]() + .addProgram(predicateProgram)(params => Params(256, 2), layout => PredLayout(layout.cIn, layout.boolOut)) + val upsweep = GProgram[Params, ScanLayout]( layout = params => ScanLayout( @@ -123,6 +126,31 @@ object GPipe: val newValue = oldValue + addValue GIO.write[Int32](layout.intOut, mid, newValue) + @annotation.tailrec + def upsweepPhases(exec: GExecution[Params, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[Params, ScanLayout, ?] = + if intervalSize >= inSize then exec + else + val newExec = exec.addProgram(upsweep)(params => Params(inSize, intervalSize), layout => layout) + upsweepPhases(newExec, inSize, intervalSize * 2) + + val upsweepInitialExec = GExecution[Params, ScanLayout]() + val upsweepExec = upsweepPhases(upsweepInitialExec, 256, 2) + + @annotation.tailrec + def downsweepPhases(exec: GExecution[Params, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[Params, ScanLayout, ?] = + if intervalSize < 2 then exec + else + val newExec = exec.addProgram(downsweep)(params => Params(inSize, intervalSize), layout => layout) + downsweepPhases(newExec, inSize, intervalSize / 2) + + val downsweepExec = downsweepPhases(upsweepInitialExec, 256, 128) + + val startParams = Params(256, 2) + // val region = GBufferRegion + // .allocate[ScanLayout] + // .map: region => + // upsweepExec.execute(startParams, region) + ??? // legacy stuff working with GFunction From 3ea8eb9e31d1966d86259d18ecb59cef98c5ceee Mon Sep 17 00:00:00 2001 From: spamegg Date: Sat, 16 Aug 2025 15:36:48 +0300 Subject: [PATCH 04/38] factor out legacy gPipe code --- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 1 + .../computenode/cyfra/fs2interop/GPipe.scala | 136 ++++++++---------- .../cyfra/fs2interop/GPipeLegacy.scala | 39 +++++ 3 files changed, 101 insertions(+), 75 deletions(-) create mode 100644 cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index 2339a22f..20c04b9d 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -42,6 +42,7 @@ class Fs2Tests extends munit.FunSuite: class Fs2LegacyTests extends munit.FunSuite: given gc: GContext = GContext() + import GPipeLegacy.* test("fs2 Float stream (legacy)"): val inSeq = (0 to 255).map(_.toFloat) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index abc50120..439973d6 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -51,7 +51,7 @@ object GPipe: .chunkMin(params.inSize) .flatMap: chunk => bridge1.toByteBuffer(inBuf, chunk) - region.runUnsafe(init = PLayout(in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), onDone = layout => layout.out.read(outBuf)) // implicit bug + region.runUnsafe(init = PLayout(in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), onDone = layout => layout.out.read(outBuf)) Stream.emits(bridge2.fromByteBuffer(outBuf, new Array[S2](params.inSize))) // Syntax sugar for convenient single type version @@ -71,31 +71,31 @@ object GPipe: // 012345 (prefixsum result - 1) are the new indices in compacted collection def gPipeFilter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = (stream: Stream[F, S]) => - case class Params(inSize: Int, intervalSize: Int) - case class PredLayout(cIn: GBuffer[C], boolOut: GBuffer[Int32]) extends Layout - case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] - case class ScanLayout(intIn: GBuffer[Int32], intOut: GBuffer[Int32], intervalSize: GUniform[ScanArgs]) extends Layout - case class CompactLayout(cIn: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout - case class FilterResult(cOut: GBuffer[C]) extends Layout - val predicateProgram = GProgram[Params, PredLayout]( - layout = params => PredLayout(cIn = GBuffer[C](params.inSize), boolOut = GBuffer[Int32](params.inSize)), + // Predicate mapping + case class PredParams(inSize: Int) + case class PredLayout(in: GBuffer[C], out: GBuffer[Int32]) extends Layout + + val predicateProgram = GProgram[PredParams, PredLayout]( + layout = params => PredLayout(in = GBuffer[C](params.inSize), out = GBuffer[Int32](params.inSize)), dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), ): layout => val invocId = GIO.invocationId - val element = GIO.read[C](layout.cIn, invocId) - GIO.write[Int32](layout.boolOut, invocId, when(pred(element))(1: Int32).otherwise(0)) - - val predicateExec = GExecution[Params, PredLayout]() - .addProgram(predicateProgram)(params => Params(256, 2), layout => PredLayout(layout.cIn, layout.boolOut)) - - val upsweep = GProgram[Params, ScanLayout]( - layout = params => - ScanLayout( - intIn = GBuffer[Int32](params.inSize / params.intervalSize), - intOut = GBuffer[Int32](params.inSize), - intervalSize = GUniform(ScanArgs(params.intervalSize)), - ), + val element = GIO.read[C](layout.in, invocId) + val result = when(pred(element))(1: Int32).otherwise(0) + GIO.write[Int32](layout.out, invocId, result) + + val predExec = GExecution[PredParams, PredLayout]() + .addProgram(predicateProgram)(params => params, layout => layout) + val predParams = PredParams(256) + + // Prefix sum (inclusive), upsweep/downsweep + case class ScanParams(inSize: Int, intervalSize: Int) + case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] + case class ScanLayout(ints: GBuffer[Int32], intervalSize: GUniform[ScanArgs]) extends Layout + + val upsweep = GProgram[ScanParams, ScanLayout]( + layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read @@ -103,83 +103,69 @@ object GPipe: val root = invocId * size val mid = root + (size / 2) - 1 val end = root + size - 1 - val oldValue = GIO.read[Int32](layout.intOut, end) - val addValue = GIO.read[Int32](layout.intOut, mid) + val oldValue = GIO.read[Int32](layout.ints, end) + val addValue = GIO.read[Int32](layout.ints, mid) val newValue = oldValue + addValue - GIO.write[Int32](layout.intOut, end, newValue) - - val downsweep = GProgram[Params, ScanLayout]( - layout = params => - ScanLayout( - intIn = GBuffer[Int32](params.inSize / params.intervalSize), - intOut = GBuffer[Int32](params.inSize), - intervalSize = GUniform(ScanArgs(params.intervalSize)), - ), + GIO.write[Int32](layout.ints, end, newValue) + + val downsweep = GProgram[ScanParams, ScanLayout]( + layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read val invocId = GIO.invocationId val root = invocId * size - 1 // if invocId = 0, this is -1 (out of bounds) val mid = root + (size / 2) - val oldValue = GIO.read[Int32](layout.intOut, mid) - val addValue = when(root > 0)(GIO.read[Int32](layout.intOut, root)).otherwise(0) + val oldValue = GIO.read[Int32](layout.ints, mid) + val addValue = when(root > 0)(GIO.read[Int32](layout.ints, root)).otherwise(0) val newValue = oldValue + addValue - GIO.write[Int32](layout.intOut, mid, newValue) + GIO.write[Int32](layout.ints, mid, newValue) @annotation.tailrec - def upsweepPhases(exec: GExecution[Params, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[Params, ScanLayout, ?] = + def upsweepPhases(exec: GExecution[ScanParams, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[ScanParams, ScanLayout, ?] = if intervalSize >= inSize then exec else - val newExec = exec.addProgram(upsweep)(params => Params(inSize, intervalSize), layout => layout) + val newExec = exec.addProgram(upsweep)(params => ScanParams(inSize, intervalSize), layout => layout) upsweepPhases(newExec, inSize, intervalSize * 2) - val upsweepInitialExec = GExecution[Params, ScanLayout]() + val upsweepInitialExec = GExecution[ScanParams, ScanLayout]() val upsweepExec = upsweepPhases(upsweepInitialExec, 256, 2) @annotation.tailrec - def downsweepPhases(exec: GExecution[Params, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[Params, ScanLayout, ?] = + def downsweepPhases(exec: GExecution[ScanParams, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[ScanParams, ScanLayout, ?] = if intervalSize < 2 then exec else - val newExec = exec.addProgram(downsweep)(params => Params(inSize, intervalSize), layout => layout) + val newExec = exec.addProgram(downsweep)(params => ScanParams(inSize, intervalSize), layout => layout) downsweepPhases(newExec, inSize, intervalSize / 2) val downsweepExec = downsweepPhases(upsweepInitialExec, 256, 128) + val scanParams = ScanParams(256, 2) - val startParams = Params(256, 2) - // val region = GBufferRegion - // .allocate[ScanLayout] - // .map: region => - // upsweepExec.execute(startParams, region) + // Stream compaction + case class CompactLayout(in: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout + case class FilterResult(cOut: GBuffer[C]) extends Layout + // TODO implement compaction - ??? + // TODO: connect all the layouts/executions into one + case class PredScanCompactLayout() extends Layout + case class Result(out: GBuffer[C]) extends Layout - // legacy stuff working with GFunction - extension (stream: Stream[Pure, Float]) - def gPipeFloat(fn: Float32 => Float32)(using GContext): Stream[Pure, Float] = - val gf: GFunction[Empty, Float32, Float32] = GFunction(fn) - stream - .chunkMin(256) - .flatMap: chunk => - val gmem = FloatMem(chunk.toArray) - val res = gmem.map(gf).asInstanceOf[FloatMem].toArray - Stream.emits(res) + val region = GBufferRegion // TODO fix this! + .allocate[PredLayout] + .map: layout => + predExec.execute(predParams, layout) - extension (stream: Stream[Pure, Int]) - def gPipeInt(fn: Int32 => Int32)(using GContext): Stream[Pure, Int] = - val gf: GFunction[Empty, Int32, Int32] = GFunction(fn) - stream - .chunkMin(256) - .flatMap: chunk => - val gmem = IntMem(chunk.toArray) - val res = gmem.map(gf).asInstanceOf[IntMem].toArray - Stream.emits(res) + val typeSize = typeStride(Tag.apply[C]) - extension (stream: Stream[Pure, fRGBA]) - def gPipeVec4(fn: Vec4[Float32] => Vec4[Float32])(using GContext): Stream[Pure, fRGBA] = - val gf: GFunction[Empty, Vec4[Float32], Vec4[Float32]] = GFunction(fn) - stream - .chunkMin(256) - .flatMap: chunk => - val gmem = Vec4FloatMem(chunk.toArray) - val res = gmem.map(gf).asInstanceOf[Vec4FloatMem].toArray - Stream.emits(res) + // these are allocated once, reused for many chunks + val inBuf = BufferUtils.createByteBuffer(predParams.inSize * typeSize) + val outBuf = BufferUtils.createByteBuffer(predParams.inSize * typeSize) // TODO wrong size + + // stream + // .chunkMin(predParams.inSize) + // .flatMap: chunk => + // bridge.toByteBuffer(inBuf, chunk) + // region.runUnsafe(init = PredLayout(in = GBuffer[C](inBuf), out = GBuffer[Int32](outBuf)), onDone = layout => layout.out.read(outBuf)) + // val arr = bridge.fromByteBuffer(outBuf, new Array[S](params.inSize)) + // Stream.emits(arr) + ??? diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala new file mode 100644 index 00000000..78d36209 --- /dev/null +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala @@ -0,0 +1,39 @@ +package io.computenode.cyfra.fs2interop + +import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA +import io.computenode.cyfra.dsl.{*, given} +import struct.GStruct, GStruct.Empty, Empty.given + +import fs2.* + +// legacy stuff working with GFunction +object GPipeLegacy: + extension (stream: Stream[Pure, Float]) + def gPipeFloat(fn: Float32 => Float32)(using GContext): Stream[Pure, Float] = + val gf: GFunction[Empty, Float32, Float32] = GFunction(fn) + stream + .chunkMin(256) + .flatMap: chunk => + val gmem = FloatMem(chunk.toArray) + val res = gmem.map(gf).asInstanceOf[FloatMem].toArray + Stream.emits(res) + + extension (stream: Stream[Pure, Int]) + def gPipeInt(fn: Int32 => Int32)(using GContext): Stream[Pure, Int] = + val gf: GFunction[Empty, Int32, Int32] = GFunction(fn) + stream + .chunkMin(256) + .flatMap: chunk => + val gmem = IntMem(chunk.toArray) + val res = gmem.map(gf).asInstanceOf[IntMem].toArray + Stream.emits(res) + + extension (stream: Stream[Pure, fRGBA]) + def gPipeVec4(fn: Vec4[Float32] => Vec4[Float32])(using GContext): Stream[Pure, fRGBA] = + val gf: GFunction[Empty, Vec4[Float32], Vec4[Float32]] = GFunction(fn) + stream + .chunkMin(256) + .flatMap: chunk => + val gmem = Vec4FloatMem(chunk.toArray) + val res = gmem.map(gf).asInstanceOf[Vec4FloatMem].toArray + Stream.emits(res) From 24c333a7fe436ff7efc7f15434c28d66864144d1 Mon Sep 17 00:00:00 2001 From: spamegg Date: Mon, 18 Aug 2025 18:34:04 +0300 Subject: [PATCH 05/38] outline / sketch compaction and filter, add filter test --- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 21 +++-- .../computenode/cyfra/fs2interop/GPipe.scala | 83 +++++++++---------- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index 20c04b9d..daba84cd 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -2,7 +2,7 @@ package io.computenode.cyfra.e2e.fs2interop import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA import io.computenode.cyfra.dsl.{*, given}, algebra.VectorAlgebra -import io.computenode.cyfra.fs2interop.*, GPipe.*, Bridge.given +import io.computenode.cyfra.fs2interop.*, Bridge.given import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.runtime.VkCyfraRuntime @@ -18,10 +18,10 @@ extension (f: fRGBA) class Fs2Tests extends munit.FunSuite: given cr: CyfraRuntime = VkCyfraRuntime() - test("fs2 through gPipeMap, just ints"): + test("fs2 through GPipe map, just ints"): val inSeq = (0 until 256).toSeq val stream = Stream.emits(inSeq) - val pipe = gPipeMap[Pure, Int32, Int](_ + 1) + val pipe = GPipe.map[Pure, Int32, Int](_ + 1) val result = stream.through(pipe).compile.toList val expected = inSeq.map(_ + 1) result @@ -29,10 +29,10 @@ class Fs2Tests extends munit.FunSuite: .foreach: (res, exp) => assert(res == exp, s"Expected $exp, got $res") - test("fs2 through gPipeMap, floats and vectors"): + test("fs2 through GPipe map, floats and vectors"): val inSeq = (0 to 255).map(_.toFloat).toSeq val stream = Stream.emits(inSeq) - val pipe = gPipeMap[Pure, Float32, Vec4[Float32], Float, fRGBA](f => (f, f + 1f, f + 2f, f + 3f)) + val pipe = GPipe.map[Pure, Float32, Vec4[Float32], Float, fRGBA](f => (f, f + 1f, f + 2f, f + 3f)) val result = stream.through(pipe).compile.toList val expected = inSeq.map(f => (f, f + 1f, f + 2f, f + 3f)) result @@ -40,6 +40,17 @@ class Fs2Tests extends munit.FunSuite: .foreach: (res, exp) => assert(res.close(exp)(0.001f), s"Expected $exp, got $res") + test("fs2 through GPipe filter, just ints"): + val inSeq = (0 until 256).toSeq + val stream = Stream.emits(inSeq) + val pipe = GPipe.filter[Pure, Int32, Int](_.mod(2) === 0) + val result = stream.through(pipe).compile.toList + val expected = inSeq.filter(_ % 2 == 0) + result + .zip(expected) + .foreach: (res, exp) => + assert(res == exp, s"Expected $exp, got $res") + class Fs2LegacyTests extends munit.FunSuite: given gc: GContext = GContext() import GPipeLegacy.* diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 439973d6..92452737 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -1,6 +1,5 @@ package io.computenode.cyfra.fs2interop -import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA import io.computenode.cyfra.core.{Allocation, layout}, layout.Layout import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.{*, given}, gio.GIO, binding.{GBuffer, GUniform, GBinding} @@ -15,13 +14,12 @@ import izumi.reflect.Tag import scala.reflect.ClassTag object GPipe: - def gPipeMap[F[_], C1 <: Value: FromExpr: Tag, C2 <: Value: FromExpr: Tag, S1: ClassTag, S2: ClassTag]( + def map[F[_], C1 <: Value: FromExpr: Tag, C2 <: Value: FromExpr: Tag, S1: ClassTag, S2: ClassTag]( f: C1 => C2, )(using cr: CyfraRuntime, bridge1: Bridge[C1, S1], bridge2: Bridge[C2, S2]): Pipe[F, S1, S2] = (stream: Stream[F, S1]) => case class Params(inSize: Int) case class PLayout(in: GBuffer[C1], out: GBuffer[C2]) extends Layout - case class PResult(out: GBuffer[C2]) extends Layout val params = Params(inSize = 256) val inTypeSize = typeStride(Tag.apply[C1]) @@ -54,24 +52,12 @@ object GPipe: region.runUnsafe(init = PLayout(in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), onDone = layout => layout.out.read(outBuf)) Stream.emits(bridge2.fromByteBuffer(outBuf, new Array[S2](params.inSize))) - // Syntax sugar for convenient single type version - def gPipeMap[F[_], C <: Value: FromExpr: Tag, S: ClassTag](f: C => C)(using CyfraRuntime, Bridge[C, S]): Pipe[F, S, S] = - gPipeMap[F, C, C, S, S](f) - - // https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda - // Prefix Sum and Stream Compaction - // 11 - // 012345678901 index - // [abcdefghijkl] starting collection - // [tfftfftttftf] convert to booleans - // [100100111010] integer equivalent - // [111222345566] prefixsum, last number tells us the size of filtered collection - // [x..x..xxx.x.] take the ones that are 1 bigger than previous. - // [adhijl] compact the collection - // 012345 (prefixsum result - 1) are the new indices in compacted collection - def gPipeFilter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = - (stream: Stream[F, S]) => + // Overload for convenient single type version + def map[F[_], C <: Value: FromExpr: Tag, S: ClassTag](f: C => C)(using CyfraRuntime, Bridge[C, S]): Pipe[F, S, S] = + map[F, C, C, S, S](f) + def filter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = + (stream: Stream[F, S]) => // Predicate mapping case class PredParams(inSize: Int) case class PredLayout(in: GBuffer[C], out: GBuffer[Int32]) extends Layout @@ -85,9 +71,9 @@ object GPipe: val result = when(pred(element))(1: Int32).otherwise(0) GIO.write[Int32](layout.out, invocId, result) + val predParams = PredParams(256) val predExec = GExecution[PredParams, PredLayout]() .addProgram(predicateProgram)(params => params, layout => layout) - val predParams = PredParams(256) // Prefix sum (inclusive), upsweep/downsweep case class ScanParams(inSize: Int, intervalSize: Int) @@ -114,10 +100,10 @@ object GPipe: ): layout => val ScanArgs(size) = layout.intervalSize.read val invocId = GIO.invocationId - val root = invocId * size - 1 // if invocId = 0, this is -1 (out of bounds) - val mid = root + (size / 2) + val end = invocId * size - 1 // if invocId = 0, this is -1 (out of bounds) + val mid = end + (size / 2) val oldValue = GIO.read[Int32](layout.ints, mid) - val addValue = when(root > 0)(GIO.read[Int32](layout.ints, root)).otherwise(0) + val addValue = when(end > 0)(GIO.read[Int32](layout.ints, end)).otherwise(0) val newValue = oldValue + addValue GIO.write[Int32](layout.ints, mid, newValue) @@ -141,31 +127,42 @@ object GPipe: val downsweepExec = downsweepPhases(upsweepInitialExec, 256, 128) val scanParams = ScanParams(256, 2) - // Stream compaction - case class CompactLayout(in: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout - case class FilterResult(cOut: GBuffer[C]) extends Layout // TODO implement compaction + case class CompactParams() + case class CompactLayout(in: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout + val compactedStreamSize: Int = ??? // TODO how to get this out of prefix sum results + + val compactProgram = GProgram[CompactParams, CompactLayout](layout = params => ???, dispatch = (layout, params) => ???): compactLayout => + ??? + val compactExec = GExecution[CompactParams, CompactLayout]() // TODO: connect all the layouts/executions into one - case class PredScanCompactLayout() extends Layout - case class Result(out: GBuffer[C]) extends Layout + case class PredScanCompactParams(inSize: Int) + case class PredScanCompactLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout + + val predScanCompactExec = GExecution[PredScanCompactParams, PredScanCompactLayout]() // TODO + val predScanCompactParams = PredScanCompactParams(256) // TODO - val region = GBufferRegion // TODO fix this! - .allocate[PredLayout] + val region = GBufferRegion + .allocate[PredScanCompactLayout] .map: layout => - predExec.execute(predParams, layout) + predScanCompactExec.execute(predScanCompactParams, layout) val typeSize = typeStride(Tag.apply[C]) + val intSize = typeStride(Tag.apply[Int32]) // these are allocated once, reused for many chunks - val inBuf = BufferUtils.createByteBuffer(predParams.inSize * typeSize) - val outBuf = BufferUtils.createByteBuffer(predParams.inSize * typeSize) // TODO wrong size - - // stream - // .chunkMin(predParams.inSize) - // .flatMap: chunk => - // bridge.toByteBuffer(inBuf, chunk) - // region.runUnsafe(init = PredLayout(in = GBuffer[C](inBuf), out = GBuffer[Int32](outBuf)), onDone = layout => layout.out.read(outBuf)) - // val arr = bridge.fromByteBuffer(outBuf, new Array[S](params.inSize)) - // Stream.emits(arr) - ??? + val predBuf = BufferUtils.createByteBuffer(predParams.inSize * typeSize) + val scanBuf = BufferUtils.createByteBuffer(predParams.inSize * intSize) + val compactBuf = BufferUtils.createByteBuffer(compactedStreamSize * typeSize) + + stream + .chunkMin(predScanCompactParams.inSize) + .flatMap: chunk => + bridge.toByteBuffer(predBuf, chunk) + region.runUnsafe( + init = PredScanCompactLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf)), + onDone = layout => layout.out.read(compactBuf), + ) + val arr = bridge.fromByteBuffer(compactBuf, new Array[S](compactedStreamSize)) + Stream.emits(arr) From 278bbb0c00f548952c2b2819da1548dd66f62a9c Mon Sep 17 00:00:00 2001 From: spamegg Date: Wed, 20 Aug 2025 11:17:05 +0300 Subject: [PATCH 06/38] some comments --- .../main/scala/io/computenode/cyfra/fs2interop/GPipe.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 92452737..8bfbcfa9 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -140,7 +140,11 @@ object GPipe: case class PredScanCompactParams(inSize: Int) case class PredScanCompactLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout - val predScanCompactExec = GExecution[PredScanCompactParams, PredScanCompactLayout]() // TODO + // How to "connect" many GExecutions? with flatMap? + val predScanCompactExec = GExecution[PredScanCompactParams, PredScanCompactLayout]() + // predExec + // .flatMap(predLayout => ???) // add downsweepExec + // .flatMap(layout => ???) // add compactExec val predScanCompactParams = PredScanCompactParams(256) // TODO val region = GBufferRegion From c72e45cd56c2a6c56021ea39223538ed5bad91cf Mon Sep 17 00:00:00 2001 From: Szymon Date: Thu, 21 Aug 2025 00:52:56 +0200 Subject: [PATCH 07/38] GIO and Layouts WIP --- .../io/computenode/cyfra/spirv/Context.scala | 6 +- .../cyfra/spirv/compilers/DSLCompiler.scala | 12 +- .../spirv/compilers/ExpressionCompiler.scala | 19 +-- .../compilers/SpirvProgramCompiler.scala | 111 +++++++------ .../computenode/cyfra/core/GExecution.scala | 1 - .../cyfra/core/archive/GContext.scala | 1 - .../io/computenode/cyfra/dsl/Expression.scala | 3 +- .../cyfra/dsl/collections/GArray.scala | 14 -- .../cyfra/dsl/collections/GArray2D.scala | 12 -- .../cyfra/samples/TestingStuff.scala | 154 ------------------ .../cyfra/runtime/ExecutionHandler.scala | 2 +- 11 files changed, 77 insertions(+), 258 deletions(-) delete mode 100644 cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala delete mode 100644 cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala index 974f045f..20ffd53a 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala @@ -16,11 +16,11 @@ private[cyfra] case class Context( voidTypeRef: Int = -1, voidFuncTypeRef: Int = -1, workerIndexRef: Int = -1, - uniformVarRef: Int = -1, + uniformVarRefs: Map[Int, Int] = Map.empty, + bindingToStructType: Map[Int, Int] = Map.empty, constRefs: Map[(Tag[?], Any), Int] = Map(), exprRefs: Map[Int, Int] = Map(), - inBufferBlocks: List[ArrayBufferBlock] = List(), - outBufferBlocks: List[ArrayBufferBlock] = List(), + bufferBlocks: List[ArrayBufferBlock] = List(), nextResultId: Int = HEADER_REFS_TOP, nextBinding: Int = 0, exprNames: Map[Int, String] = Map(), diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala index 07ae9aab..fc2b30bd 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala @@ -47,23 +47,23 @@ private[cyfra] object DSLCompiler: allScopesCache(root.treeid) = result result - def compile(tree: Value, inTypes: List[Tag[?]], outTypes: List[Tag[?]], uniformSchema: GStructSchema[?]): ByteBuffer = + def compile(tree: Value, buffers: List[Tag[?]], uniformSchemaIns: List[GStructSchema[?]]): ByteBuffer = val treeExpr = tree.tree val allExprs = getAllExprsFlattened(treeExpr, visitDetached = true) val typesInCode = allExprs.map(_.tag).distinct - val allTypes = (typesInCode ::: inTypes ::: outTypes).distinct + val allTypes = (typesInCode ::: buffers).distinct def scalarTypes = allTypes.filter(_.tag <:< summon[Tag[Scalar]].tag) val (typeDefs, typedContext) = defineScalarTypes(scalarTypes, Context.initialContext) val structsInCode = (allExprs.collect { case cs: ComposeStruct[?] => cs.resultSchema case gf: GetField[?, ?] => gf.resultSchema - } :+ uniformSchema).distinct + } ::: uniformSchemaIns).distinct val (structDefs, structCtx) = defineStructTypes(structsInCode, typedContext) val structNames = getStructNames(structsInCode, structCtx) - val (decorations, uniformDefs, uniformContext) = initAndDecorateUniforms(inTypes, outTypes, structCtx) - val (uniformStructDecorations, uniformStructInsns, uniformStructContext) = createAndInitUniformBlock(uniformSchema, uniformContext) - val blockNames = getBlockNames(uniformContext, uniformSchema) + val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffers, structCtx) + val (uniformStructDecorations, uniformStructInsns, uniformStructContext) = createAndInitUniformBlocks(uniformSchemaIns, uniformContext) + val blockNames = getBlockNames(uniformContext, uniformSchemaIns) val (inputDefs, inputContext) = createInvocationId(uniformStructContext) val (constDefs, constCtx) = defineConstants(allExprs, inputContext) val (varDefs, varCtx) = defineVarNames(constCtx) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala index 1a8cd62b..5d99da3a 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala @@ -22,10 +22,6 @@ private[cyfra] object ExpressionCompiler: val WorkerIndexTag = "worker_index" - val WorkerIndex: Int32 = Int32(Dynamic(WorkerIndexTag)) - val UniformStructRefTag = "uniform_struct" - def UniformStructRef[G <: Value: Tag] = Dynamic(UniformStructRefTag) - private def binaryOpOpcode(expr: BinaryOpExpression[?]) = expr match case _: Sum[?] => (Op.OpIAdd, Op.OpFAdd) case _: Diff[?] => (Op.OpISub, Op.OpFSub) @@ -110,11 +106,11 @@ private[cyfra] object ExpressionCompiler: val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (c.treeid -> constRef)) (List(), updatedContext) - case d @ Dynamic(WorkerIndexTag) => - (Nil, ctx.copy(exprRefs = ctx.exprRefs + (d.treeid -> ctx.workerIndexRef))) + case w @ WorkerIndex => + (Nil, ctx.copy(exprRefs = ctx.exprRefs + (w.treeid -> ctx.workerIndexRef))) - case d @ Dynamic(UniformStructRefTag) => - (Nil, ctx.copy(exprRefs = ctx.exprRefs + (d.treeid -> ctx.uniformVarRef))) + case d @ Binding(id) => + (Nil, ctx.copy(exprRefs = ctx.exprRefs + (d.treeid -> ctx.uniformVarRefs(id)))) case c: ConvertExpression[?, ?] => compileConvertExpression(c, ctx) @@ -293,7 +289,7 @@ private[cyfra] object ExpressionCompiler: case fc: FunctionCall[?] => compileFunctionCall(fc, ctx) - case ga @ GArrayElem(index, i) => + case ReadBuffer(index, i) => val instructions = List( Instruction( Op.OpAccessChain, @@ -330,14 +326,15 @@ private[cyfra] object ExpressionCompiler: ) val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (cs.treeid -> ctx.nextResultId), nextResultId = ctx.nextResultId + 1) (insns, updatedContext) - case gf @ GetField(dynamic @ Dynamic(UniformStructRefTag), fieldIndex) => + + case gf @ GetField(binding @ Binding(bindingId), fieldIndex) => val insns: List[Instruction] = List( Instruction( Op.OpAccessChain, List( ResultRef(ctx.uniformPointerMap(ctx.valueTypeMap(gf.tag.tag))), ResultRef(ctx.nextResultId), - ResultRef(ctx.uniformVarRef), + ResultRef(ctx.uniformVarRefs(bindingId)), ResultRef(ctx.constRefs((Int32Tag, gf.fieldIndex))), ), ), diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index 8d16743c..1aea6fdb 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -101,13 +101,7 @@ private[cyfra] object SpirvProgramCompiler: ) val ctxWithVoid = context.copy(voidTypeRef = TYPE_VOID_REF, voidFuncTypeRef = VOID_FUNC_TYPE_REF) (voidDef, ctxWithVoid) - - def initAndDecorateUniforms(ins: List[Tag[?]], outs: List[Tag[?]], context: Context): (List[Words], List[Words], Context) = - val (inDecor, inDef, inCtx) = createAndInitBlocks(ins, in = true, context) - val (outDecor, outDef, outCtx) = createAndInitBlocks(outs, in = false, inCtx) - val (voidsDef, voidCtx) = defineVoids(outCtx) - (inDecor ::: outDecor, voidsDef ::: inDef ::: outDef, voidCtx) - + def createInvocationId(context: Context): (List[Words], Context) = val definitionInstructions = List( Instruction(Op.OpConstant, List(ResultRef(context.valueTypeMap(UInt32Tag.tag)), ResultRef(context.nextResultId + 0), IntWord(localSizeX))), @@ -125,8 +119,14 @@ private[cyfra] object SpirvProgramCompiler: ), ) (definitionInstructions, context.copy(nextResultId = context.nextResultId + 3)) + def initAndDecorateBuffers(buffers: List[Tag[?]], context: Context): (List[Words], List[Words], Context) = + val (blockDecor, blockDef, inCtx) = createAndInitBlocks(buffers, context) + val (voidsDef, voidCtx) = defineVoids(inCtx) + (blockDecor, voidsDef ::: blockDef, voidCtx) + - def createAndInitBlocks(blocks: List[Tag[?]], in: Boolean, context: Context): (List[Words], List[Words], Context) = + + def createAndInitBlocks(blocks: List[Tag[?]], context: Context): (List[Words], List[Words], Context) = val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), tpe) => val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, ctx.nextBinding) @@ -146,7 +146,7 @@ private[cyfra] object SpirvProgramCompiler: ) val contextWithBlock = - if in then ctx.copy(inBufferBlocks = block :: ctx.inBufferBlocks) else ctx.copy(outBufferBlocks = block :: ctx.outBufferBlocks) + ctx.copy(bufferBlocks = block :: ctx.bufferBlocks) ( decAcc ::: decorationInstructions, insnAcc ::: definitionInstructions, @@ -155,58 +155,61 @@ private[cyfra] object SpirvProgramCompiler: } (decoration, definition, newContext) - def getBlockNames(context: Context, uniformSchema: GStructSchema[?]): List[Words] = + def getBlockNames(context: Context, uniformSchemas: List[GStructSchema[?]]): List[Words] = def namesForBlock(block: ArrayBufferBlock, tpe: String): List[Words] = Instruction(Op.OpName, List(ResultRef(block.structTypeRef), Text(s"Buffer$tpe"))) :: Instruction(Op.OpName, List(ResultRef(block.blockVarRef), Text(s"data$tpe"))) :: Nil // todo name uniform context.inBufferBlocks.flatMap(namesForBlock(_, "In")) ::: context.outBufferBlocks.flatMap(namesForBlock(_, "Out")) - def createAndInitUniformBlock(schema: GStructSchema[?], ctx: Context): (List[Words], List[Words], Context) = - def totalStride(gs: GStructSchema[?]): Int = gs.fields - .map: - case (_, fromExpr, t) if t <:< gs.gStructTag => - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - case (_, _, t) => - typeStride(t) - .sum - val uniformStructTypeRef = ctx.valueTypeMap(schema.structTag.tag) - - val (offsetDecorations, _) = schema.fields.zipWithIndex.foldLeft[(List[Words], Int)](List.empty[Word], 0): - case ((acc, offset), ((name, fromExpr, tag), idx)) => - val stride = - if tag <:< schema.gStructTag then - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - else typeStride(tag) - val offsetDecoration = Instruction(Op.OpMemberDecorate, List(ResultRef(uniformStructTypeRef), IntWord(idx), Decoration.Offset, IntWord(offset))) - (acc :+ offsetDecoration, offset + stride) - - val uniformBlockDecoration = Instruction(Op.OpDecorate, List(ResultRef(uniformStructTypeRef), Decoration.Block)) - - val uniformPointerUniformRef = ctx.nextResultId - val uniformPointerUniform = - Instruction(Op.OpTypePointer, List(ResultRef(uniformPointerUniformRef), StorageClass.Uniform, ResultRef(uniformStructTypeRef))) - - val uniformVarRef = ctx.nextResultId + 1 - val uniformVar = Instruction(Op.OpVariable, List(ResultRef(uniformPointerUniformRef), ResultRef(uniformVarRef), StorageClass.Uniform)) - - val uniformDecorateDescriptorSet = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.DescriptorSet, IntWord(0))) - - assert(ctx.nextBinding == 2, "Currently the only legal layout is (in, out, uniform)") - val uniformDecorateBinding = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.Binding, IntWord(ctx.nextBinding))) + def totalStride(gs: GStructSchema[?]): Int = gs.fields + .map: + case (_, fromExpr, t) if t <:< gs.gStructTag => + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + case (_, _, t) => + typeStride(t) + .sum + + def createAndInitUniformBlocks(schemas: List[GStructSchema[?]], ctx: Context): (List[Words], List[Words], Context) = + schemas.foldLeft((List.empty[Words], List.empty[Words], ctx)) { case ((decorationsAcc, definitionsAcc, currentCtx), schema) => + val uniformStructTypeRef = currentCtx.valueTypeMap(schema.structTag.tag) + val currentBinding = currentCtx.nextBinding + + val (offsetDecorations, _) = schema.fields.zipWithIndex.foldLeft[(List[Words], Int)](List.empty[Words], 0): + case ((acc, offset), ((name, fromExpr, tag), idx)) => + val stride = + if tag <:< schema.gStructTag then + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + else typeStride(tag) + val offsetDecoration = Instruction(Op.OpMemberDecorate, List(ResultRef(uniformStructTypeRef), IntWord(idx), Decoration.Offset, IntWord(offset))) + (acc :+ offsetDecoration, offset + stride) + + val uniformBlockDecoration = Instruction(Op.OpDecorate, List(ResultRef(uniformStructTypeRef), Decoration.Block)) + + val uniformPointerUniformRef = currentCtx.nextResultId + val uniformPointerUniform = + Instruction(Op.OpTypePointer, List(ResultRef(uniformPointerUniformRef), StorageClass.Uniform, ResultRef(uniformStructTypeRef))) + + val uniformVarRef = currentCtx.nextResultId + 1 + val uniformVar = Instruction(Op.OpVariable, List(ResultRef(uniformPointerUniformRef), ResultRef(uniformVarRef), StorageClass.Uniform)) + + val uniformDecorateDescriptorSet = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.DescriptorSet, IntWord(0))) + val uniformDecorateBinding = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.Binding, IntWord(currentBinding))) + + val newDecorations = decorationsAcc ::: offsetDecorations ::: List(uniformDecorateDescriptorSet, uniformDecorateBinding, uniformBlockDecoration) + val newDefinitions = definitionsAcc ::: List(uniformPointerUniform, uniformVar) + val newCtx = currentCtx.copy( + nextResultId = currentCtx.nextResultId + 2, + nextBinding = currentCtx.nextBinding + 1, + uniformVarRefs = currentCtx.uniformVarRefs + (currentBinding -> uniformVarRef), + uniformPointerMap = currentCtx.uniformPointerMap + (uniformStructTypeRef -> uniformPointerUniformRef), + bindingToStructType = currentCtx.bindingToStructType + (currentBinding -> uniformStructTypeRef) + ) - ( - offsetDecorations ::: List(uniformDecorateDescriptorSet, uniformDecorateBinding, uniformBlockDecoration), - List(uniformPointerUniform, uniformVar), - ctx.copy( - nextResultId = ctx.nextResultId + 2, - nextBinding = ctx.nextBinding + 1, - uniformVarRef = uniformVarRef, - uniformPointerMap = ctx.uniformPointerMap + (uniformStructTypeRef -> uniformPointerUniformRef), - ), - ) + (newDecorations, newDefinitions, newCtx) + } val predefinedConsts = List((Int32Tag, 0), (UInt32Tag, 0), (Int32Tag, 1)) def defineConstants(exprs: List[E[?]], ctx: Context): (List[Words], Context) = diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala index 22418ead..7ea8ccaf 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala @@ -6,7 +6,6 @@ import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.dsl.binding.GBuffer import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} -import io.computenode.cyfra.spirv.compilers.ExpressionCompiler.UniformStructRef import izumi.reflect.Tag import GExecution.* diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala index 9f209ea4..59a04927 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala @@ -9,7 +9,6 @@ import io.computenode.cyfra.dsl.collections.GArray import io.computenode.cyfra.dsl.struct.* import io.computenode.cyfra.spirv.SpirvTypes.typeStride import io.computenode.cyfra.spirv.compilers.DSLCompiler -import io.computenode.cyfra.spirv.compilers.ExpressionCompiler.{UniformStructRef, WorkerIndex} import io.computenode.cyfra.spirvtools.SpirvToolsRunner import izumi.reflect.Tag diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala index 18f81033..ef723217 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala @@ -111,4 +111,5 @@ object Expression: case class Pass[T <: Value: Tag](value: T) extends E[T] - case class Dynamic[T <: Value: Tag](source: String) extends E[T] + case object WorkerIndex extends E[Int32] + case class Binding[T <: Value: Tag](binding: Int) extends E[T] diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala deleted file mode 100644 index 6e9daf13..00000000 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala +++ /dev/null @@ -1,14 +0,0 @@ -package io.computenode.cyfra.dsl.collections - -import io.computenode.cyfra.dsl.Value.* -import io.computenode.cyfra.dsl.collections.GArray.GArrayElem -import io.computenode.cyfra.dsl.macros.Source -import io.computenode.cyfra.dsl.{Expression, Value} -import izumi.reflect.Tag - -case class GArray[T <: Value: {Tag, FromExpr}](index: Int): - def at(i: Int32)(using Source): T = - summon[FromExpr[T]].fromExpr(GArrayElem(index, i.tree)) - -object GArray: - case class GArrayElem[T <: Value: Tag](index: Int, i: Expression[Int32]) extends Expression[T] diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala deleted file mode 100644 index 70d6df19..00000000 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala +++ /dev/null @@ -1,12 +0,0 @@ -package io.computenode.cyfra.dsl.collections - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.Int32 -import io.computenode.cyfra.dsl.algebra.ScalarAlgebra.{*, given} -import io.computenode.cyfra.dsl.macros.Source -import izumi.reflect.Tag -import io.computenode.cyfra.dsl.Value.FromExpr - -class GArray2D[T <: Value: {Tag, FromExpr}](width: Int, val arr: GArray[T]): - def at(x: Int32, y: Int32)(using Source): T = - arr.at(y * width + x) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index 8b9a5014..cd2fc227 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -122,157 +122,3 @@ object TestingStuff: .zipWithIndex .foreach: case ((e, a), i) => assert(e == a, s"Mismatch at index $i: expected $e, got $a") - -// Test case: Use one program 10 times, copying values from five input buffers to five output buffers and adding values from two uniforms - case class AddProgramParams(bufferSize: Int, addA: Int, addB: Int) - case class AddProgramUniform(a: Int32) extends GStruct[AddProgramUniform] - case class AddProgramLayout( - in1: GBuffer[Int32], - in2: GBuffer[Int32], - in3: GBuffer[Int32], - in4: GBuffer[Int32], - in5: GBuffer[Int32], - out1: GBuffer[Int32], - out2: GBuffer[Int32], - out3: GBuffer[Int32], - out4: GBuffer[Int32], - out5: GBuffer[Int32], - u1: GUniform[AddProgramUniform] = GUniform.fromParams, - u2: GUniform[AddProgramUniform] = GUniform.fromParams, - ) extends Layout - - case class AddProgramExecLayout( - in1: GBuffer[Int32], - in2: GBuffer[Int32], - in3: GBuffer[Int32], - in4: GBuffer[Int32], - in5: GBuffer[Int32], - out1: GBuffer[Int32], - out2: GBuffer[Int32], - out3: GBuffer[Int32], - out4: GBuffer[Int32], - out5: GBuffer[Int32], - ) extends Layout - - val addProgram: GProgram[AddProgramParams, AddProgramLayout] = GProgram[AddProgramParams, AddProgramLayout]( - layout = params => - AddProgramLayout( - in1 = GBuffer[Int32](params.bufferSize), - in2 = GBuffer[Int32](params.bufferSize), - in3 = GBuffer[Int32](params.bufferSize), - in4 = GBuffer[Int32](params.bufferSize), - in5 = GBuffer[Int32](params.bufferSize), - out1 = GBuffer[Int32](params.bufferSize), - out2 = GBuffer[Int32](params.bufferSize), - out3 = GBuffer[Int32](params.bufferSize), - out4 = GBuffer[Int32](params.bufferSize), - out5 = GBuffer[Int32](params.bufferSize), - u1 = GUniform(AddProgramUniform(params.addA)), - u2 = GUniform(AddProgramUniform(params.addB)), - ), - dispatch = (layout, args) => GProgram.StaticDispatch((args.bufferSize / 128, 1, 1)), - )(_ => ???) - def swap(l: AddProgramLayout): AddProgramLayout = - val AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5, u1, u2) = l - AddProgramLayout(out1, out2, out3, out4, out5, in1, in2, in3, in4, in5, u1, u2) - - def fromExecLayout(l: AddProgramExecLayout): AddProgramLayout = - val AddProgramExecLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) = l - AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) - - val execution = (0 until 11).foldLeft( - GExecution[AddProgramParams, AddProgramExecLayout]().asInstanceOf[GExecution[AddProgramParams, AddProgramExecLayout, AddProgramExecLayout]], - )((x, i) => - if i % 2 == 0 then x.addProgram(addProgram)(mapParams = identity[AddProgramParams], mapLayout = fromExecLayout) - else x.addProgram(addProgram)(mapParams = identity, mapLayout = x => swap(fromExecLayout(x))), - ) - - @main - def testAddProgram10Times = - given runtime: VkCyfraRuntime = VkCyfraRuntime() - val bufferSize = 1280 - val params = AddProgramParams(bufferSize, addA = 0, addB = 1) - val region = GBufferRegion - .allocate[AddProgramExecLayout] - .map: region => - execution.execute(params, region) - - val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) - val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) - val outBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) - val rbbList = outBuffers.map(MemoryUtil.memByteBuffer) - - val inData = (0 until bufferSize).toArray - inBuffers.foreach(_.put(inData).flip()) - region.runUnsafe( - init = AddProgramExecLayout( - in1 = GBuffer[Int32](wbbList(0)), - in2 = GBuffer[Int32](wbbList(1)), - in3 = GBuffer[Int32](wbbList(2)), - in4 = GBuffer[Int32](wbbList(3)), - in5 = GBuffer[Int32](wbbList(4)), - out1 = GBuffer[Int32](bufferSize), - out2 = GBuffer[Int32](bufferSize), - out3 = GBuffer[Int32](bufferSize), - out4 = GBuffer[Int32](bufferSize), - out5 = GBuffer[Int32](bufferSize), - ), - onDone = layout => { - layout.out1.read(rbbList(0)) - layout.out2.read(rbbList(1)) - layout.out3.read(rbbList(2)) - layout.out4.read(rbbList(3)) - layout.out5.read(rbbList(4)) - }, - ) - runtime.close() - val expected = inData.map(_ + 11 * (params.addA + params.addB)) - outBuffers.foreach { buf => - (0 until bufferSize).foreach { i => - assert(buf.get(i) == expected(i), s"Mismatch at index $i: expected ${expected(i)}, got ${buf.get(i)}") - } - } - - @main - def enduranceTest = - given runtime: VkCyfraRuntime = VkCyfraRuntime() - val bufferSize = 1280 - val params = AddProgramParams(bufferSize, addA = 0, addB = 1) - val region = GBufferRegion - .allocate[AddProgramExecLayout] - .map: region => - execution.execute(params, region) - val aInt = new AtomicInteger(0) - (1 to 10000).par.foreach: i => - val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) - val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) - val rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) - - val inData = (0 until bufferSize).toArray - inBuffers.foreach(_.put(inData).flip()) - region.runUnsafe( - init = AddProgramExecLayout( - in1 = GBuffer[Int32](wbbList(0)), - in2 = GBuffer[Int32](wbbList(1)), - in3 = GBuffer[Int32](wbbList(2)), - in4 = GBuffer[Int32](wbbList(3)), - in5 = GBuffer[Int32](wbbList(4)), - out1 = GBuffer[Int32](bufferSize), - out2 = GBuffer[Int32](bufferSize), - out3 = GBuffer[Int32](bufferSize), - out4 = GBuffer[Int32](bufferSize), - out5 = GBuffer[Int32](bufferSize), - ), - onDone = layout => { - layout.out1.read(rbbList(0)) - layout.out2.read(rbbList(1)) - layout.out3.read(rbbList(2)) - layout.out4.read(rbbList(3)) - layout.out5.read(rbbList(4)) - }, - ) - val prev = aInt.getAndAdd(1) - if prev % 100 == 0 then println(s"Iteration $prev completed") - - runtime.close() - println("Endurance test completed successfully") diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 782b2a85..00855e4e 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -75,7 +75,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .pCommandBuffers(pCommandBuffer) val fence = new Fence() - timed("Vulkan render command"): + timed("Vulkan execute command"): check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") fence.block().destroy() commandPool.freeCommandBuffer(commandBuffer) From 27888dc6e97ae191c456d7c0fcaea0b94021a61e Mon Sep 17 00:00:00 2001 From: Szymon Date: Sat, 23 Aug 2025 15:50:08 +0200 Subject: [PATCH 08/38] WIP2 --- .../cyfra/spirv/compilers/GIOCompiler.scala | 4 + .../compilers/SpirvProgramCompiler.scala | 6 +- cyfra-e2e-test/src/test/resources/addOne.comp | 48 ++++ .../src/test/resources/compileAll.ps1 | 4 + .../src/test/resources/compileAll.sh | 7 + cyfra-e2e-test/src/test/resources/emit.comp | 23 ++ cyfra-e2e-test/src/test/resources/filter.comp | 20 ++ .../cyfra/e2e/RuntimeEnduranceTest.scala | 221 ++++++++++++++++++ 8 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala create mode 100644 cyfra-e2e-test/src/test/resources/addOne.comp create mode 100644 cyfra-e2e-test/src/test/resources/compileAll.ps1 create mode 100644 cyfra-e2e-test/src/test/resources/compileAll.sh create mode 100644 cyfra-e2e-test/src/test/resources/emit.comp create mode 100644 cyfra-e2e-test/src/test/resources/filter.comp create mode 100644 cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala new file mode 100644 index 00000000..b291168a --- /dev/null +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -0,0 +1,4 @@ +package io.computenode.cyfra.spirv.compilers + +object GIOCompiler: + \ No newline at end of file diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index 1aea6fdb..e7330f66 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -4,6 +4,7 @@ import io.computenode.cyfra.spirv.Opcodes.* import io.computenode.cyfra.dsl.Expression.{Const, E} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.{GStructConstructor, GStructSchema} import io.computenode.cyfra.spirv.Context import io.computenode.cyfra.spirv.SpirvConstants.* @@ -18,7 +19,7 @@ private[cyfra] object SpirvProgramCompiler: case Instruction(Op.OpVariable, _) => true case _ => false - def compileMain(tree: Value, resultType: Tag[?], ctx: Context): (List[Words], Context) = + def compileMain(body: GIO[?], resultType: Tag[?], ctx: Context): (List[Words], Context) = val init = List( Instruction(Op.OpFunction, List(ResultRef(ctx.voidTypeRef), ResultRef(MAIN_FUNC_REF), SamplerAddressingMode.None, ResultRef(VOID_FUNC_TYPE_REF))), @@ -160,7 +161,8 @@ private[cyfra] object SpirvProgramCompiler: Instruction(Op.OpName, List(ResultRef(block.structTypeRef), Text(s"Buffer$tpe"))) :: Instruction(Op.OpName, List(ResultRef(block.blockVarRef), Text(s"data$tpe"))) :: Nil // todo name uniform - context.inBufferBlocks.flatMap(namesForBlock(_, "In")) ::: context.outBufferBlocks.flatMap(namesForBlock(_, "Out")) + //context.inBufferBlocks.flatMap(namesForBlock(_, "In")) ::: context.outBufferBlocks.flatMap(namesForBlock(_, "Out")) + List() def totalStride(gs: GStructSchema[?]): Int = gs.fields .map: diff --git a/cyfra-e2e-test/src/test/resources/addOne.comp b/cyfra-e2e-test/src/test/resources/addOne.comp new file mode 100644 index 00000000..091de31f --- /dev/null +++ b/cyfra-e2e-test/src/test/resources/addOne.comp @@ -0,0 +1,48 @@ +#version 450 + +layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; + +layout (set = 0, binding = 0) buffer In1 { + int in1[]; +}; +layout (set = 0, binding = 1) buffer In2 { + int in2[]; +}; +layout (set = 0, binding = 2) buffer In3 { + int in3[]; +}; +layout (set = 0, binding = 3) buffer In4 { + int in4[]; +}; +layout (set = 0, binding = 4) buffer In5 { + int in5[]; +}; +layout (set = 0, binding = 5) buffer Out1 { + int out1[]; +}; +layout (set = 0, binding = 6) buffer Out2 { + int out2[]; +}; +layout (set = 0, binding = 7) buffer Out3 { + int out3[]; +}; +layout (set = 0, binding = 8) buffer Out4 { + int out4[]; +}; +layout (set = 0, binding = 9) buffer Out5 { + int out5[]; +}; +layout (set = 0, binding = 10) uniform U1 { + int a; +}; +layout (set = 0, binding = 11) uniform U2 { + int b; +}; +void main(void) { + uint index = gl_GlobalInvocationID.x; + out1[index] = in1[index] + a + b; + out2[index] = in2[index] + a + b; + out3[index] = in3[index] + a + b; + out4[index] = in4[index] + a + b; + out5[index] = in5[index] + a + b; +} diff --git a/cyfra-e2e-test/src/test/resources/compileAll.ps1 b/cyfra-e2e-test/src/test/resources/compileAll.ps1 new file mode 100644 index 00000000..e1755a32 --- /dev/null +++ b/cyfra-e2e-test/src/test/resources/compileAll.ps1 @@ -0,0 +1,4 @@ +Get-ChildItem -Filter *.comp -Name | ForEach-Object -Process { + $name = $_.Replace(".comp", "") + "$Env:VULKAN_SDK\Bin\glslangValidator.exe -V $name.comp -o $name.spv" | Invoke-Expression +} diff --git a/cyfra-e2e-test/src/test/resources/compileAll.sh b/cyfra-e2e-test/src/test/resources/compileAll.sh new file mode 100644 index 00000000..e4f70140 --- /dev/null +++ b/cyfra-e2e-test/src/test/resources/compileAll.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +for f in *.comp +do + prefix=$(echo "$f" | cut -f 1 -d '.') + glslangValidator -V "$prefix.comp" -o "$prefix.spv" +done diff --git a/cyfra-e2e-test/src/test/resources/emit.comp b/cyfra-e2e-test/src/test/resources/emit.comp new file mode 100644 index 00000000..5789c424 --- /dev/null +++ b/cyfra-e2e-test/src/test/resources/emit.comp @@ -0,0 +1,23 @@ +#version 450 + +layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; + +layout (set = 0, binding = 0) buffer InputBuffer { + int inBuffer[]; +}; +layout (set = 0, binding = 1) buffer OutputBuffer { + int outBuffer[]; +}; + +layout (set = 0, binding = 2) uniform InputUniform { + int emitN; +}; + +void main(void) { + uint index = gl_GlobalInvocationID.x; + int element = inBuffer[index]; + uint offset = index * uint(emitN); + for (int i = 0; i < emitN; i++) { + outBuffer[offset + uint(i)] = element; + } +} diff --git a/cyfra-e2e-test/src/test/resources/filter.comp b/cyfra-e2e-test/src/test/resources/filter.comp new file mode 100644 index 00000000..37beef64 --- /dev/null +++ b/cyfra-e2e-test/src/test/resources/filter.comp @@ -0,0 +1,20 @@ +#version 450 + +layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; + +layout (set = 0, binding = 0) buffer InputBuffer { + int inBuffer[]; +}; +layout (set = 0, binding = 1) buffer OutputBuffer { + bool outBuffer[]; +}; + +layout (set = 0, binding = 2) uniform InputUniform { + int filterValue; +}; + +void main(void) { + uint index = gl_GlobalInvocationID.x; + int element = inBuffer[index]; + outBuffer[index] = (element == filterValue); +} diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala new file mode 100644 index 00000000..35ad5bb2 --- /dev/null +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala @@ -0,0 +1,221 @@ +package io.computenode.cyfra.e2e + +import io.computenode.cyfra.core.archive.GContext +import io.computenode.cyfra.core.layout.* +import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} +import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} +import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} +import io.computenode.cyfra.dsl.gio.GIO +import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.utility.Logger.logger +import org.lwjgl.BufferUtils +import org.lwjgl.system.MemoryUtil + +import scala.concurrent.ExecutionContext.Implicits.global +import java.util.concurrent.atomic.AtomicInteger +import scala.concurrent.{Await, Future} + + +class RuntimeEnduranceTest extends munit.FunSuite: + + test("Endurance test for GExecution with multiple programs"): + runEnduranceTest(10000) + + // === Emit program === + + case class EmitProgramParams(inSize: Int, emitN: Int) + + case class EmitProgramUniform(emitN: Int32) extends GStruct[EmitProgramUniform] + + case class EmitProgramLayout( + in: GBuffer[Int32], + out: GBuffer[Int32], + args: GUniform[EmitProgramUniform] = GUniform.fromParams, // todo will be different in the future + ) extends Layout + + val emitProgram = GProgram[EmitProgramParams, EmitProgramLayout]( + layout = params => + EmitProgramLayout( + in = GBuffer[Int32](params.inSize), + out = GBuffer[Int32](params.inSize * params.emitN), + args = GUniform(EmitProgramUniform(params.emitN)), + ), + dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), + ): layout => + val EmitProgramUniform(emitN) = layout.args.read + val invocId = GIO.invocationId + val element = GIO.read(layout.in, invocId) + val bufferOffset = invocId * emitN + GIO.repeat(emitN): i => + GIO.write(layout.out, bufferOffset + i, element) + + // === Filter program === + + case class FilterProgramParams(inSize: Int, filterValue: Int) + + case class FilterProgramUniform(filterValue: Int32) extends GStruct[FilterProgramUniform] + + case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) + extends Layout + + val filterProgram = GProgram[FilterProgramParams, FilterProgramLayout]( + layout = params => + FilterProgramLayout( + in = GBuffer[Int32](params.inSize), + out = GBuffer[GBoolean](params.inSize), + params = GUniform(FilterProgramUniform(params.filterValue)), + ), + dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), + ): layout => + val invocId = GIO.invocationId + val element = GIO.read(layout.in, invocId) + val isMatch = element === layout.params.read.filterValue + GIO.write(layout.out, invocId, isMatch) + + // === GExecution === + + case class EmitFilterParams(inSize: Int, emitN: Int, filterValue: Int) + + case class EmitFilterLayout(inBuffer: GBuffer[Int32], emitBuffer: GBuffer[Int32], filterBuffer: GBuffer[GBoolean]) extends Layout + + case class EmitFilterResult(out: GBuffer[GBoolean]) extends Layout + + val emitFilterExecution = GExecution[EmitFilterParams, EmitFilterLayout]() + .addProgram(emitProgram)( + params => EmitProgramParams(inSize = params.inSize, emitN = params.emitN), + layout => EmitProgramLayout(in = layout.inBuffer, out = layout.emitBuffer), + ) + .addProgram(filterProgram)( + params => FilterProgramParams(inSize = 2 * params.inSize, filterValue = params.filterValue), + layout => FilterProgramLayout(in = layout.emitBuffer, out = layout.filterBuffer), + ) + + // Test case: Use one program 10 times, copying values from five input buffers to five output buffers and adding values from two uniforms + case class AddProgramParams(bufferSize: Int, addA: Int, addB: Int) + + case class AddProgramUniform(a: Int32) extends GStruct[AddProgramUniform] + + case class AddProgramLayout( + in1: GBuffer[Int32], + in2: GBuffer[Int32], + in3: GBuffer[Int32], + in4: GBuffer[Int32], + in5: GBuffer[Int32], + out1: GBuffer[Int32], + out2: GBuffer[Int32], + out3: GBuffer[Int32], + out4: GBuffer[Int32], + out5: GBuffer[Int32], + u1: GUniform[AddProgramUniform] = GUniform.fromParams, + u2: GUniform[AddProgramUniform] = GUniform.fromParams, + ) extends Layout + + case class AddProgramExecLayout( + in1: GBuffer[Int32], + in2: GBuffer[Int32], + in3: GBuffer[Int32], + in4: GBuffer[Int32], + in5: GBuffer[Int32], + out1: GBuffer[Int32], + out2: GBuffer[Int32], + out3: GBuffer[Int32], + out4: GBuffer[Int32], + out5: GBuffer[Int32], + ) extends Layout + + val addProgram: GProgram[AddProgramParams, AddProgramLayout] = GProgram[AddProgramParams, AddProgramLayout]( + layout = params => + AddProgramLayout( + in1 = GBuffer[Int32](params.bufferSize), + in2 = GBuffer[Int32](params.bufferSize), + in3 = GBuffer[Int32](params.bufferSize), + in4 = GBuffer[Int32](params.bufferSize), + in5 = GBuffer[Int32](params.bufferSize), + out1 = GBuffer[Int32](params.bufferSize), + out2 = GBuffer[Int32](params.bufferSize), + out3 = GBuffer[Int32](params.bufferSize), + out4 = GBuffer[Int32](params.bufferSize), + out5 = GBuffer[Int32](params.bufferSize), + u1 = GUniform(AddProgramUniform(params.addA)), + u2 = GUniform(AddProgramUniform(params.addB)), + ), + dispatch = (layout, args) => GProgram.StaticDispatch((args.bufferSize / 128, 1, 1)), + ): + case AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5, u1, u2) => + val index = GIO.invocationId + val a = u1.read.a + val b = u2.read.a + for + _ <- GIO.write(out1, index, GIO.read(in1, index) + a + b) + _ <- GIO.write(out2, index, GIO.read(in2, index) + a + b) + _ <- GIO.write(out3, index, GIO.read(in3, index) + a + b) + _ <- GIO.write(out4, index, GIO.read(in4, index) + a + b) + _ <- GIO.write(out5, index, GIO.read(in5, index) + a + b) + yield () + + def swap(l: AddProgramLayout): AddProgramLayout = + val AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5, u1, u2) = l + AddProgramLayout(out1, out2, out3, out4, out5, in1, in2, in3, in4, in5, u1, u2) + + def fromExecLayout(l: AddProgramExecLayout): AddProgramLayout = + val AddProgramExecLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) = l + AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) + + val execution = (0 until 11).foldLeft( + GExecution[AddProgramParams, AddProgramExecLayout]().asInstanceOf[GExecution[AddProgramParams, AddProgramExecLayout, AddProgramExecLayout]], + )((x, i) => + if i % 2 == 0 then x.addProgram(addProgram)(mapParams = identity[AddProgramParams], mapLayout = fromExecLayout) + else x.addProgram(addProgram)(mapParams = identity, mapLayout = x => swap(fromExecLayout(x))), + ) + + def runEnduranceTest(nRuns: Int): Unit = + logger.info(s"Starting endurance test with ${nRuns} runs...") + + given runtime: VkCyfraRuntime = VkCyfraRuntime() + + val bufferSize = 1280 + val params = AddProgramParams(bufferSize, addA = 0, addB = 1) + val region = GBufferRegion + .allocate[AddProgramExecLayout] + .map: region => + execution.execute(params, region) + val aInt = new AtomicInteger(0) + val runs = (1 to nRuns).map: + i => Future: + val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) + val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) + val rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) + + val inData = (0 until bufferSize).toArray + inBuffers.foreach(_.put(inData).flip()) + region.runUnsafe( + init = AddProgramExecLayout( + in1 = GBuffer[Int32](wbbList(0)), + in2 = GBuffer[Int32](wbbList(1)), + in3 = GBuffer[Int32](wbbList(2)), + in4 = GBuffer[Int32](wbbList(3)), + in5 = GBuffer[Int32](wbbList(4)), + out1 = GBuffer[Int32](bufferSize), + out2 = GBuffer[Int32](bufferSize), + out3 = GBuffer[Int32](bufferSize), + out4 = GBuffer[Int32](bufferSize), + out5 = GBuffer[Int32](bufferSize), + ), + onDone = layout => { + layout.out1.read(rbbList(0)) + layout.out2.read(rbbList(1)) + layout.out3.read(rbbList(2)) + layout.out4.read(rbbList(3)) + layout.out5.read(rbbList(4)) + }, + ) + val prev = aInt.getAndAdd(1) + if prev % 50 == 0 then logger.info(s"Iteration $prev completed") + + val allRuns = Future.sequence(runs) + Await.result(allRuns, scala.concurrent.duration.Duration.Inf) + + runtime.close() + logger.info("Endurance test completed successfully") From 21494b8d4f9818310dcf3e7a7940f89bfc531188 Mon Sep 17 00:00:00 2001 From: Szymon Date: Sat, 23 Aug 2025 22:50:51 +0200 Subject: [PATCH 09/38] WIP3 --- .../io/computenode/cyfra/spirv/Context.scala | 3 +- .../cyfra/spirv/compilers/DSLCompiler.scala | 40 ++++++++++++--- .../spirv/compilers/ExpressionCompiler.scala | 10 ++-- .../cyfra/spirv/compilers/GIOCompiler.scala | 51 ++++++++++++++++++- .../compilers/SpirvProgramCompiler.scala | 39 +++++++------- .../io/computenode/cyfra/core/GProgram.scala | 1 - .../computenode/cyfra/core/GioProgram.scala | 7 +-- .../computenode/cyfra/core/SpirvProgram.scala | 16 +----- .../cyfra/core/archive/GContext.scala | 1 - .../cyfra/dsl/binding/GBinding.scala | 11 ++-- .../cyfra/dsl/binding/WriteBuffer.scala | 5 +- .../cyfra/dsl/binding/WriteUniform.scala | 5 +- .../cyfra/dsl/collections/GArray.scala | 13 +++++ .../cyfra/dsl/collections/GArray2D.scala | 13 +++++ .../io/computenode/cyfra/dsl/gio/GIO.scala | 28 +++++----- .../computenode/cyfra/runtime/VkShader.scala | 7 ++- 16 files changed, 170 insertions(+), 80 deletions(-) create mode 100644 cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala create mode 100644 cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala index 20ffd53a..35b39a46 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala @@ -1,5 +1,6 @@ package io.computenode.cyfra.spirv +import io.computenode.cyfra.dsl.binding.GBuffer import io.computenode.cyfra.dsl.macros.FnCall.FnIdentifier import io.computenode.cyfra.spirv.SpirvConstants.HEADER_REFS_TOP import io.computenode.cyfra.spirv.compilers.FunctionCompiler.SprivFunction @@ -20,7 +21,7 @@ private[cyfra] case class Context( bindingToStructType: Map[Int, Int] = Map.empty, constRefs: Map[(Tag[?], Any), Int] = Map(), exprRefs: Map[Int, Int] = Map(), - bufferBlocks: List[ArrayBufferBlock] = List(), + bufferBlocks: Map[GBuffer[?], ArrayBufferBlock] = Map(), nextResultId: Int = HEADER_REFS_TOP, nextBinding: Int = 0, exprNames: Map[Int, String] = Map(), diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala index fc2b30bd..f15c9d54 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala @@ -4,6 +4,8 @@ import io.computenode.cyfra.* import io.computenode.cyfra.dsl.* import io.computenode.cyfra.dsl.Expression.E import io.computenode.cyfra.dsl.Value.Scalar +import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform, WriteBuffer, WriteUniform} +import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.GStruct.* import io.computenode.cyfra.dsl.struct.GStructSchema import io.computenode.cyfra.spirv.Context @@ -24,6 +26,25 @@ import scala.runtime.stdLibPatches.Predef.summon private[cyfra] object DSLCompiler: + @tailrec + private def getAllExprsFlattened(pending: List[GIO[?]], acc: List[E[?]], visitDetached: Boolean): List[E[?]] = + pending match + case Nil => acc + case GIO.Pure(v) :: tail => + getAllExprsFlattened(tail, getAllExprsFlattened(v.tree, visitDetached), visitDetached) + case GIO.FlatMap(v, n) :: tail => + getAllExprsFlattened(v :: n :: tail, acc, visitDetached) + case GIO.Repeat(n, gio) :: tail => + val nAllExprs = getAllExprsFlattened(n.tree, visitDetached) + getAllExprsFlattened(gio :: tail, nAllExprs ::: acc, visitDetached) + case WriteBuffer(_, index, value) :: tail => + val indexAllExprs = getAllExprsFlattened(index.tree, visitDetached) + val valueAllExprs = getAllExprsFlattened(value.tree, visitDetached) + getAllExprsFlattened(tail, indexAllExprs ::: valueAllExprs ::: acc, visitDetached) + case WriteUniform(_, value) :: tail => + val valueAllExprs = getAllExprsFlattened(value.tree, visitDetached) + getAllExprsFlattened(tail, valueAllExprs ::: acc, visitDetached) + // TODO: Not traverse same fn scopes for each fn call private def getAllExprsFlattened(root: E[?], visitDetached: Boolean): List[E[?]] = var blockI = 0 @@ -33,7 +54,7 @@ private[cyfra] object DSLCompiler: def getAllScopesExprsAcc(toVisit: List[E[?]], acc: List[E[?]] = Nil): List[E[?]] = toVisit match case Nil => acc case e :: tail if visited.contains(e.treeid) => getAllScopesExprsAcc(tail, acc) - case e :: tail => + case e :: tail => // todo i don't think this really works (tail not used???) if allScopesCache.contains(root.treeid) then return allScopesCache(root.treeid) val eScopes = e.introducedScopes val filteredScopes = if visitDetached then eScopes else eScopes.filterNot(_.isDetached) @@ -47,18 +68,22 @@ private[cyfra] object DSLCompiler: allScopesCache(root.treeid) = result result - def compile(tree: Value, buffers: List[Tag[?]], uniformSchemaIns: List[GStructSchema[?]]): ByteBuffer = - val treeExpr = tree.tree - val allExprs = getAllExprsFlattened(treeExpr, visitDetached = true) + def compile(bodyIo: GIO[?], bindings: List[GBinding[?]]): ByteBuffer = + val allExprs = getAllExprsFlattened(List(bodyIo), Nil, visitDetached = true) val typesInCode = allExprs.map(_.tag).distinct - val allTypes = (typesInCode ::: buffers).distinct + val allTypes = (typesInCode ::: bindings.map(_.tag)).distinct def scalarTypes = allTypes.filter(_.tag <:< summon[Tag[Scalar]].tag) val (typeDefs, typedContext) = defineScalarTypes(scalarTypes, Context.initialContext) + val (buffers, uniforms) = bindings.partition: + case _: GBuffer[?] => true + case _: GUniform[?] => false + .asInstanceOf[(List[GBinding[?]], List[GUniform[?]])] + val uniformSchemas = uniforms.map(_.schema) val structsInCode = (allExprs.collect { case cs: ComposeStruct[?] => cs.resultSchema case gf: GetField[?, ?] => gf.resultSchema - } ::: uniformSchemaIns).distinct + } ::: uniformSchemas).distinct val (structDefs, structCtx) = defineStructTypes(structsInCode, typedContext) val structNames = getStructNames(structsInCode, structCtx) val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffers, structCtx) @@ -67,8 +92,7 @@ private[cyfra] object DSLCompiler: val (inputDefs, inputContext) = createInvocationId(uniformStructContext) val (constDefs, constCtx) = defineConstants(allExprs, inputContext) val (varDefs, varCtx) = defineVarNames(constCtx) - val resultType = tree.tree.tag - val (main, ctxAfterMain) = compileMain(tree, resultType, varCtx) + val (main, ctxAfterMain) = compileMain(bodyIo, varCtx) val (fnTypeDefs, fnDefs, ctxWithFnDefs) = compileFunctions(ctxAfterMain) val nameDecorations = getNameDecorations(ctxWithFnDefs) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala index 5d99da3a..7e7cb849 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala @@ -3,7 +3,7 @@ package io.computenode.cyfra.spirv.compilers import io.computenode.cyfra.dsl.* import io.computenode.cyfra.dsl.Expression.* import io.computenode.cyfra.dsl.Value.* -import io.computenode.cyfra.dsl.collections.GArray.GArrayElem +import io.computenode.cyfra.dsl.binding.* import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.macros.Source import io.computenode.cyfra.dsl.struct.GStruct.{ComposeStruct, GetField} @@ -289,19 +289,19 @@ private[cyfra] object ExpressionCompiler: case fc: FunctionCall[?] => compileFunctionCall(fc, ctx) - case ReadBuffer(index, i) => + case ReadBuffer(buffer, i) => val instructions = List( Instruction( Op.OpAccessChain, List( - ResultRef(ctx.uniformPointerMap(ctx.valueTypeMap(ga.tag.tag))), + ResultRef(ctx.uniformPointerMap(ctx.valueTypeMap(buffer.tag.tag))), ResultRef(ctx.nextResultId), - ResultRef(ctx.inBufferBlocks(index).blockVarRef), + ResultRef(ctx.bufferBlocks(buffer).blockVarRef), ResultRef(ctx.constRefs((Int32Tag, 0))), ResultRef(ctx.exprRefs(i.treeid)), ), ), - Instruction(Op.OpLoad, List(IntWord(ctx.valueTypeMap(ga.tag.tag)), ResultRef(ctx.nextResultId + 1), ResultRef(ctx.nextResultId))), + Instruction(Op.OpLoad, List(IntWord(ctx.valueTypeMap(buffer.tag.tag)), ResultRef(ctx.nextResultId + 1), ResultRef(ctx.nextResultId))), ) val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (expr.treeid -> (ctx.nextResultId + 1)), nextResultId = ctx.nextResultId + 2) (instructions, updatedContext) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala index b291168a..c64b9cae 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -1,4 +1,53 @@ package io.computenode.cyfra.spirv.compilers +import io.computenode.cyfra.dsl.gio.GIO +import io.computenode.cyfra.spirv.Context +import io.computenode.cyfra.spirv.Opcodes.* +import io.computenode.cyfra.dsl.binding.* +import io.computenode.cyfra.dsl.gio.GIO.CurrentRepeatIndex +import io.computenode.cyfra.spirv.SpirvTypes.{Int32Tag, LInt32Tag} + object GIOCompiler: - \ No newline at end of file + + def compileGio(gio: GIO[?], ctx: Context, acc: List[Words] = Nil): (List[Words], Context) = + gio match + + case GIO.Pure(v) => + val (insts, updatedCtx) = ExpressionCompiler.compileBlock(v.tree, ctx) + (acc ::: insts, updatedCtx) + + case WriteBuffer(buffer, index, value) => + val insns = List(Instruction( + Op.OpAccessChain, + List( + ResultRef(ctx.uniformPointerMap(ctx.valueTypeMap(buffer.tag.tag))), + ResultRef(ctx.nextResultId), + ResultRef(ctx.bufferBlocks(buffer).blockVarRef), + ResultRef(ctx.constRefs((Int32Tag, 0))), + ResultRef(ctx.workerIndexRef), + ), + ), + Instruction(Op.OpStore, List(ResultRef(ctx.nextResultId), ResultRef(ctx.exprRefs(value.tree.treeid)))) + ) + val updatedCtx = ctx.copy(nextResultId = ctx.nextResultId + 1) + (acc ::: insns, updatedCtx) + + case GIO.Repeat(n, f) => + val (nInsts, ctxWithN) = ExpressionCompiler.compileBlock(n.tree, ctx) + val loopHeaderId = ctxWithN.nextResultId + val loopMergeId = ctxWithN.nextResultId + 1 + val loopContinueId = ctxWithN.nextResultId + 2 + val counterVarId = ctxWithN.nextResultId + 3 + val ctxWithCounter = ctxWithN.copy(exprRefs = ctxWithN.exprRefs + (CurrentRepeatIndex.treeid -> counterVarId), nextResultId = ctxWithN.nextResultId + 4) + val counterInit = Instruction(Op.OpVariable, List(ResultRef(ctxWithCounter.constRefs((Int32Tag, 0))), ResultRef(counterVarId), StorageClass.Function)) + val counterStore = Instruction(Op.OpStore, List(ResultRef(counterVarId), ResultRef(ctxWithCounter.constRefs((Int32Tag, 0))))) + val (body, afterBodyCtx) = compileGio(f, ctxWithCounter) + // todo + ??? + + + + + + + \ No newline at end of file diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index e7330f66..73ebf1bf 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -4,6 +4,7 @@ import io.computenode.cyfra.spirv.Opcodes.* import io.computenode.cyfra.dsl.Expression.{Const, E} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.dsl.binding.GBuffer import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.{GStructConstructor, GStructSchema} import io.computenode.cyfra.spirv.Context @@ -19,7 +20,7 @@ private[cyfra] object SpirvProgramCompiler: case Instruction(Op.OpVariable, _) => true case _ => false - def compileMain(body: GIO[?], resultType: Tag[?], ctx: Context): (List[Words], Context) = + def compileMain(bodyIo: GIO[?], ctx: Context): (List[Words], Context) = val init = List( Instruction(Op.OpFunction, List(ResultRef(ctx.voidTypeRef), ResultRef(MAIN_FUNC_REF), SamplerAddressingMode.None, ResultRef(VOID_FUNC_TYPE_REF))), @@ -39,22 +40,23 @@ private[cyfra] object SpirvProgramCompiler: Instruction(Op.OpLoad, List(ResultRef(ctx.valueTypeMap(Int32Tag.tag)), ResultRef(ctx.nextResultId + 2), ResultRef(ctx.nextResultId + 1))), ) - val (body, codeCtx) = compileBlock(tree.tree, ctx.copy(nextResultId = ctx.nextResultId + 3, workerIndexRef = ctx.nextResultId + 2)) + val (body, codeCtx) = GIOCompiler.compileGio(bodyIo, ctx.copy(nextResultId = ctx.nextResultId + 3, workerIndexRef = ctx.nextResultId + 2)) val (vars, nonVarsBody) = bubbleUpVars(body) val end = List( - Instruction( - Op.OpAccessChain, - List( - ResultRef(codeCtx.uniformPointerMap(codeCtx.valueTypeMap(resultType.tag))), - ResultRef(codeCtx.nextResultId), - ResultRef(codeCtx.outBufferBlocks.head.blockVarRef), - ResultRef(codeCtx.constRefs((Int32Tag, 0))), - ResultRef(codeCtx.workerIndexRef), - ), - ), - Instruction(Op.OpStore, List(ResultRef(codeCtx.nextResultId), ResultRef(codeCtx.exprRefs(tree.tree.treeid)))), +// TODO Remove - Write is a part of GIO now +// Instruction( +// Op.OpAccessChain, +// List( +// ResultRef(codeCtx.uniformPointerMap(codeCtx.valueTypeMap(resultType.tag))), +// ResultRef(codeCtx.nextResultId), +// ResultRef(codeCtx.outBufferBlocks.head.blockVarRef), +// ResultRef(codeCtx.constRefs((Int32Tag, 0))), +// ResultRef(codeCtx.workerIndexRef), +// ), +// ), +// Instruction(Op.OpStore, List(ResultRef(codeCtx.nextResultId), ResultRef(codeCtx.exprRefs(tree.tree.treeid)))), Instruction(Op.OpReturn, List()), Instruction(Op.OpFunctionEnd, List()), ) @@ -120,15 +122,14 @@ private[cyfra] object SpirvProgramCompiler: ), ) (definitionInstructions, context.copy(nextResultId = context.nextResultId + 3)) - def initAndDecorateBuffers(buffers: List[Tag[?]], context: Context): (List[Words], List[Words], Context) = + def initAndDecorateBuffers(buffers: List[GBuffer[?]], context: Context): (List[Words], List[Words], Context) = val (blockDecor, blockDef, inCtx) = createAndInitBlocks(buffers, context) val (voidsDef, voidCtx) = defineVoids(inCtx) (blockDecor, voidsDef ::: blockDef, voidCtx) - - - def createAndInitBlocks(blocks: List[Tag[?]], context: Context): (List[Words], List[Words], Context) = - val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), tpe) => + def createAndInitBlocks(blocks: List[GBuffer[?]], context: Context): (List[Words], List[Words], Context) = + val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), buff) => + val tpe = buff.tag val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, ctx.nextBinding) val decorationInstructions = List[Words]( @@ -147,7 +148,7 @@ private[cyfra] object SpirvProgramCompiler: ) val contextWithBlock = - ctx.copy(bufferBlocks = block :: ctx.bufferBlocks) + ctx.copy(bufferBlocks = ctx.bufferBlocks + (buff -> block)) ( decAcc ::: decorationInstructions, insnAcc ::: definitionInstructions, diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index da5407df..d1a43226 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -16,7 +16,6 @@ trait GProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] extends GExec val layout: InitProgramLayout => Params => L val dispatch: (L, Params) => ProgramDispatch val workgroupSize: WorkDimensions - private[cyfra] def cacheKey: String // TODO better type def layoutStruct = summon[LayoutStruct[L]] object GProgram: diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala index d97f97b2..03158fea 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala @@ -11,9 +11,4 @@ case class GioProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( layout: InitProgramLayout => Params => L, dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, -) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = summon[LayoutStruct[L]].elementTypes match - case x if x.size == 12 => "addOne" - case x if x.contains(summon[Tag[GBoolean]]) && x.size == 3 => "filter" - case x if x.size == 3 => "emit" - case _ => ??? +) extends GProgram[Params, L] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index c7b9f29a..b4bcbd85 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -26,7 +26,6 @@ case class SpirvProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] priv code: ByteBuffer, entryPoint: String, shaderBindings: L => ShaderLayout, - cacheKey: String, ) extends GProgram[Params, L] object SpirvProgram: @@ -38,24 +37,13 @@ object SpirvProgram: case ReadWrite def apply[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( - path: String, layout: InitProgramLayout ?=> Params => L, dispatch: (L, Params) => ProgramDispatch, + code: ByteBuffer ): SpirvProgram[Params, L] = - val code = loadShader(path).get val workgroupSize = (128, 1, 1) // TODO Extract form shader val main = "main" val f: L => ShaderLayout = { case layout: Product => layout.productIterator.zipWithIndex.map { case (binding: GBinding[?], i) => Binding(binding, ReadWrite) }.toSeq.pipe(Seq(_)) } - val cacheKey = - val x = new File(path).getName - x.substring(0, x.lastIndexOf('.')) - new SpirvProgram[Params, L]((il: InitProgramLayout) => layout(using il), dispatch, workgroupSize, code, main, f, cacheKey) - - def loadShader(path: String, classLoader: ClassLoader = getClass.getClassLoader): Try[ByteBuffer] = - Using.Manager: use => - val file = new File(Objects.requireNonNull(classLoader.getResource(path)).getFile) - val fis = use(new FileInputStream(file)) - val fc = use(fis.getChannel) - fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) + new SpirvProgram[Params, L]((il: InitProgramLayout) => layout(using il), dispatch, workgroupSize, code, main, f) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala index 59a04927..7b141690 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala @@ -5,7 +5,6 @@ import io.computenode.cyfra.core.archive.mem.{FloatMem, GMem, IntMem, Vec4FloatM import io.computenode.cyfra.core.archive.{GFunction, UniformContext} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.{Float32, FromExpr, Int32, Vec4} -import io.computenode.cyfra.dsl.collections.GArray import io.computenode.cyfra.dsl.struct.* import io.computenode.cyfra.spirv.SpirvTypes.typeStride import io.computenode.cyfra.spirv.compilers.DSLCompiler diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala index 60c53cac..8bad4bb8 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala @@ -4,7 +4,8 @@ import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr as fromExprEval import io.computenode.cyfra.dsl.Value.{FromExpr, Int32} import io.computenode.cyfra.dsl.gio.GIO -import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} +import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag sealed trait GBinding[T <: Value: {Tag, FromExpr}]: @@ -14,14 +15,16 @@ sealed trait GBinding[T <: Value: {Tag, FromExpr}]: trait GBuffer[T <: Value: {FromExpr, Tag}] extends GBinding[T]: def read(index: Int32): T = FromExpr.fromExpr(ReadBuffer(this, index)) - def write(index: Int32, value: T): GIO[Unit] = GIO.write(this, index, value) + def write(index: Int32, value: T): GIO[Empty] = GIO.write(this, index, value) object GBuffer -trait GUniform[T <: Value: {Tag, FromExpr}] extends GBinding[T]: +trait GUniform[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}] extends GBinding[T]: def read: T = fromExprEval(ReadUniform(this)) - def write(value: T): GIO[Unit] = WriteUniform(this, value) + def write(value: T): GIO[Empty] = WriteUniform(this, value) + + def schema = summon[GStructSchema[T]] object GUniform: diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteBuffer.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteBuffer.scala index 53b0abf9..1856079a 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteBuffer.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteBuffer.scala @@ -3,6 +3,7 @@ package io.computenode.cyfra.dsl.binding import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.Int32 import io.computenode.cyfra.dsl.gio.GIO +import io.computenode.cyfra.dsl.struct.GStruct.Empty -case class WriteBuffer[T <: Value](buffer: GBuffer[T], index: Int32, value: T) extends GIO[Unit]: - override def underlying: Unit = () +case class WriteBuffer[T <: Value](buffer: GBuffer[T], index: Int32, value: T) extends GIO[Empty]: + override def underlying: Empty = Empty() diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala index 240aa643..a450a5e7 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala @@ -3,7 +3,8 @@ package io.computenode.cyfra.dsl.binding import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag -case class WriteUniform[T <: Value: Tag](uniform: GUniform[T], value: T) extends GIO[Unit]: - override def underlying: Unit = () +case class WriteUniform[T <: Value: Tag](uniform: GUniform[T], value: T) extends GIO[Empty]: + override def underlying: Empty = Empty() diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala new file mode 100644 index 00000000..4ffc51c3 --- /dev/null +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala @@ -0,0 +1,13 @@ +package io.computenode.cyfra.dsl.collections + +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.dsl.binding.{GBuffer, ReadBuffer} +import io.computenode.cyfra.dsl.macros.Source +import io.computenode.cyfra.dsl.{Expression, Value} +import izumi.reflect.Tag + +// todo temporary +case class GArray[T <: Value: {Tag, FromExpr}](underlying: GBuffer[T]): + def at(i: Int32)(using Source): T = + summon[FromExpr[T]].fromExpr(ReadBuffer(underlying, i)) + diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala new file mode 100644 index 00000000..e532eea2 --- /dev/null +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala @@ -0,0 +1,13 @@ +package io.computenode.cyfra.dsl.collections + +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.Int32 +import io.computenode.cyfra.dsl.algebra.ScalarAlgebra.{*, given} +import io.computenode.cyfra.dsl.macros.Source +import izumi.reflect.Tag +import io.computenode.cyfra.dsl.Value.FromExpr + +// todo temporary +class GArray2D[T <: Value: {Tag, FromExpr}](width: Int, val arr: GArray[T]): + def at(x: Int32, y: Int32)(using Source): T = + arr.at(y * width + x) diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala index 02a018f8..7c1fa7f4 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala @@ -6,36 +6,40 @@ import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr import io.computenode.cyfra.dsl.binding.{GBuffer, ReadBuffer, WriteBuffer} import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.gio.GIO.* +import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag -trait GIO[T]: +trait GIO[T <: Value]: - def flatMap[U](f: T => GIO[U]): GIO[U] = FlatMap(this, f(this.underlying)) + def flatMap[U <: Value](f: T => GIO[U]): GIO[U] = FlatMap(this, f(this.underlying)) - def map[U](f: T => U): GIO[U] = flatMap(t => GIO.pure(f(t))) + def map[U <: Value](f: T => U): GIO[U] = flatMap(t => GIO.pure(f(t))) private[cyfra] def underlying: T object GIO: - case class Pure[T](value: T) extends GIO[T]: + case class Pure[T <: Value](value: T) extends GIO[T]: override def underlying: T = value - case class FlatMap[T, U](gio: GIO[T], next: GIO[U]) extends GIO[U]: + case class FlatMap[T <: Value, U <: Value](gio: GIO[T], next: GIO[U]) extends GIO[U]: override def underlying: U = next.underlying // TODO repeat that collects results - case class Repeat(n: Int32, f: Int32 => GIO[?]) extends GIO[Unit]: - override def underlying: Unit = () + case class Repeat(n: Int32, f: GIO[?]) extends GIO[Empty]: + override def underlying: Empty = Empty() - def pure[T](value: T): GIO[T] = Pure(value) + def pure[T <: Value](value: T): GIO[T] = Pure(value) - def value[T](value: T): GIO[T] = Pure(value) + def value[T <: Value](value: T): GIO[T] = Pure(value) - def repeat(n: Int32)(f: Int32 => GIO[?]): GIO[Unit] = - Repeat(n, f) + case object CurrentRepeatIndex extends PhantomExpression[Int32] with CustomTreeId: + override val treeid: Int = treeidState.getAndIncrement() + + def repeat(n: Int32)(f: Int32 => GIO[?]): GIO[Empty] = + Repeat(n, f(fromExpr(CurrentRepeatIndex))) - def write[T <: Value](buffer: GBuffer[T], index: Int32, value: T): GIO[Unit] = + def write[T <: Value](buffer: GBuffer[T], index: Int32, value: T): GIO[Empty] = WriteBuffer(buffer, index, value) def read[T <: Value: {FromExpr, Tag}](buffer: GBuffer[T], index: Int32): T = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index f0885cb9..3086b15c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -5,6 +5,7 @@ import io.computenode.cyfra.core.SpirvProgram.* import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} +import io.computenode.cyfra.spirv.compilers.DSLCompiler import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.compute.ComputePipeline.* import io.computenode.cyfra.vulkan.core.Device @@ -36,7 +37,5 @@ object VkShader: def compile[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = val GioProgram(_, layout, dispatch, _) = program - val name = program.cacheKey + ".spv" - loadShader(name) match - case Failure(_) => ??? - case Success(_) => SpirvProgram(name, (il: InitProgramLayout) ?=> layout(il), dispatch) + val compiled = DSLCompiler.compile(program.body(summon[LayoutStruct[L]].layoutRef), ???) + SpirvProgram((il: InitProgramLayout) ?=> layout(il), dispatch, compiled) From 03fa272b6b18c540b66e0b37bf8198a1b480f01b Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sun, 24 Aug 2025 12:16:34 +0200 Subject: [PATCH 10/38] executing, not workin^g --- .../computenode/cyfra/spirv/SpirvTypes.scala | 1 + .../cyfra/runtime/ExecutionHandler.scala | 26 +++--- .../cyfra/runtime/PendingExecution.scala | 89 +++++++++++++++++++ .../cyfra/runtime/VkAllocation.scala | 50 ++++++----- .../computenode/cyfra/runtime/VkBinding.scala | 69 ++++++++++++++ .../computenode/cyfra/runtime/VkBuffer.scala | 23 ----- .../computenode/cyfra/runtime/VkUniform.scala | 21 ----- .../cyfra/vulkan/command/CommandPool.scala | 37 +++----- .../cyfra/vulkan/command/Semaphore.scala | 2 +- .../cyfra/vulkan/memory/Buffer.scala | 21 ++++- .../cyfra/vulkan/util/VulkanObject.scala | 1 + 11 files changed, 226 insertions(+), 114 deletions(-) create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala delete mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala delete mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala index 9fe1b386..7adeb972 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala @@ -54,6 +54,7 @@ private[cyfra] object SpirvTypes: case LGBooleanTag => 4 case v if v <:< LVecTag => vecSize(v) * typeStride(v.typeArgs.head) + case _ => 4 def typeStride(tag: Tag[?]): Int = typeStride(tag.tag) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 782b2a85..a20efbbb 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -51,7 +51,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .zip(layout) .map: case (set, bindings) => - set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) + set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding).buffer)) set val dispatches: Seq[Dispatch] = shaderCalls @@ -67,19 +67,15 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte else (steps.appended(step), dirty ++ bindings) val commandBuffer = recordCommandBuffer(executeSteps) - pushStack: stack => - val pCommandBuffer = stack.callocPointer(1).put(0, commandBuffer) - val submitInfo = VkSubmitInfo - .calloc(stack) - .sType$Default() - .pCommandBuffers(pCommandBuffer) - - val fence = new Fence() - timed("Vulkan render command"): - check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() - commandPool.freeCommandBuffer(commandBuffer) - descriptorSets.flatten.foreach(dsManager.free) + val cleanup = () => + descriptorSets.flatten.foreach(dsManager.free) + commandPool.freeCommandBuffer(commandBuffer) + + val externalBindings = (summon[LayoutBinding[EL]].toBindings(layout) ++ summon[LayoutBinding[RL]].toBindings(result)) + .map(VkAllocation.getUnderlying) + val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) + val pe = new PendingExecution(commandBuffer, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, deps, cleanup) + externalBindings.foreach(_.execution = Left(pe)) result private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( @@ -228,7 +224,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte dispatch match case Direct(x, y, z) => vkCmdDispatch(commandBuffer, x, y, z) - case Indirect(buffer, offset) => vkCmdDispatchIndirect(commandBuffer, VkAllocation.getUnderlying(buffer).get, offset) + case Indirect(buffer, offset) => vkCmdDispatchIndirect(commandBuffer, VkAllocation.getUnderlying(buffer).buffer.get, offset) check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") commandBuffer diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala new file mode 100644 index 00000000..b213c0c9 --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -0,0 +1,89 @@ +package io.computenode.cyfra.runtime + +import io.computenode.cyfra.vulkan.command.{CommandPool, Fence, Semaphore} +import io.computenode.cyfra.vulkan.core.{Device, Queue} +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import io.computenode.cyfra.vulkan.util.VulkanObject +import org.lwjgl.vulkan.VK13.{VK_PIPELINE_STAGE_2_COPY_BIT, vkQueueSubmit2} +import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} + +import scala.collection.mutable + +class PendingExecution(protected val handle: VkCommandBuffer, val waitStage: Long, val dependencies: Seq[PendingExecution], cleanup: () => Unit)( + using Device, +) extends VulkanObject[VkCommandBuffer]: + private val semaphore: Semaphore = Semaphore(waitStage) + private var fence: Option[Fence] = None + + override protected def close(): Unit = cleanup() + + private def setFence(otherFence: Fence): Unit = + if fence.isDefined then return + fence = Some(otherFence) + dependencies.foreach(_.setFence(otherFence)) + + private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = { + if fence.isDefined then return Seq.empty + val mySubmission = ((handle, semaphore), dependencies.map(_.semaphore).toSet) + dependencies.flatMap(_.gatherForSubmission).appended(mySubmission) + } + + def block(): Unit = + fence match + case Some(f) => f.block() + case None => throw new IllegalStateException("No fence set for this execution") + +object PendingExecution: + def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => + val gathered = executions.flatMap(_.gatherForSubmission).toSet.groupMap(_._2)(_._1).toSeq + + val submitInfos = VkSubmitInfo2.calloc(gathered.size, stack) + gathered.foreach: (semaphores, executions) => + val pCommandBuffersSI = VkCommandBufferSubmitInfo.calloc(executions.size, stack) + val signalSemaphoreSI = VkSemaphoreSubmitInfo.calloc(executions.size, stack) + executions.foreach: (cb, s) => + pCommandBuffersSI + .get() + .sType$Default() + .commandBuffer(cb) + .deviceMask(0) + signalSemaphoreSI + .get() + .sType$Default() + .semaphore(s.get) + .stageMask(s.stage) + + pCommandBuffersSI.flip() + signalSemaphoreSI.flip() + + val waitSemaphoreSI = VkSemaphoreSubmitInfo.calloc(semaphores.size, stack) + semaphores.foreach: s => + waitSemaphoreSI + .get() + .sType$Default() + .semaphore(s.get) + .stageMask(s.stage) + + waitSemaphoreSI.flip() + + submitInfos + .get() + .sType$Default() + .flags(0) + .pCommandBufferInfos(pCommandBuffersSI) + .pSignalSemaphoreInfos(signalSemaphoreSI) + .pWaitSemaphoreInfos(waitSemaphoreSI) + + submitInfos.flip() + + val fence = Fence() + executions.foreach(_.setFence(fence)) + check(vkQueueSubmit2(queue.get, submitInfos, fence.get), "Failed to submit command buffer to queue") + fence + + def cleanupAll(executions: Seq[PendingExecution]): Unit = + def cleanupRec(ex: PendingExecution): Unit = + if !ex.isAlive then return + ex.destroy() + ex.dependencies.foreach(cleanupRec) + executions.foreach(cleanupRec) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a038ac7b..a851389e 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -14,37 +14,48 @@ import io.computenode.cyfra.vulkan.command.CommandPool import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} import io.computenode.cyfra.vulkan.util.Util.pushStack import io.computenode.cyfra.dsl.Value.Int32 +import io.computenode.cyfra.vulkan.core.Device import izumi.reflect.Tag import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil import org.lwjgl.vulkan.VK10 +import org.lwjgl.vulkan.VK13.VK_PIPELINE_STAGE_2_COPY_BIT import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} import java.nio.ByteBuffer import scala.collection.mutable import scala.util.chaining.* -class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator) extends Allocation: +class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator, Device) extends Allocation: given VkAllocation = this extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit = val size = bb.remaining() - getUnderlying(buffer) match - case buffer: Buffer.HostBuffer => buffer.copyTo(bb, offset) - case buffer: Buffer.DeviceBuffer => + buffer match + case VkBinding(buffer: Buffer.HostBuffer) => buffer.copyTo(bb, offset) + case binding: VkBinding[?] => + binding.materialise(commandPool.queue) val stagingBuffer = getStagingBuffer(size) - Buffer.copyBuffer(buffer, stagingBuffer, offset, 0, size, commandPool) + Buffer.copyBuffer(binding.buffer, stagingBuffer, offset, 0, size, commandPool) stagingBuffer.copyTo(bb, 0) + case _ => throw new IllegalArgumentException(s"Tried to read from non-VkBinding $buffer") def write(bb: ByteBuffer, offset: Int = 0): Unit = val size = bb.remaining() - getUnderlying(buffer) match - case buffer: Buffer.HostBuffer => buffer.copyFrom(bb, offset) - case buffer: Buffer.DeviceBuffer => + buffer match + case VkBinding(buffer: Buffer.HostBuffer) => buffer.copyFrom(bb, offset) + case binding: VkBinding[?] => + binding.materialise(commandPool.queue) val stagingBuffer = getStagingBuffer(size) - stagingBuffer.copyFrom(bb, offset) - Buffer.copyBuffer(stagingBuffer, buffer, 0, offset, size, commandPool) + stagingBuffer.copyFrom(bb, 0) + val cb = Buffer.copyBufferCommandBuffer(stagingBuffer, binding.buffer, 0, offset, size, commandPool) + val cleanup = () => + commandPool.freeCommandBuffer(cb) + stagingBuffer.destroy() + val pe = new PendingExecution(cb, VK_PIPELINE_STAGE_2_COPY_BIT, binding.execution.fold(Seq(_), _.toSeq), cleanup) + binding.execution = Left(pe) + case _ => throw new IllegalArgumentException(s"Tried to write to non-VkBinding $buffer") extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = @@ -80,22 +91,13 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() private[cyfra] def close(): Unit = - bindings.map(getUnderlying).foreach(_.destroy()) - stagingBuffer.foreach(_.destroy()) + bindings.map(getUnderlying).foreach(_.buffer.destroy()) - private var stagingBuffer: Option[Buffer.HostBuffer] = None private def getStagingBuffer(size: Int): Buffer.HostBuffer = - stagingBuffer match - case Some(buffer) if buffer.size >= size => buffer - case _ => - stagingBuffer.foreach(_.destroy()) - val newBuffer = Buffer.HostBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) - stagingBuffer = Some(newBuffer) - newBuffer + Buffer.HostBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) object VkAllocation: - private[runtime] def getUnderlying(buffer: GBinding[?]): Buffer = + private[runtime] def getUnderlying(buffer: GBinding[?]): VkBinding[?] = buffer match - case buffer: VkBuffer[?] => buffer.underlying - case uniform: VkUniform[?] => uniform.underlying - case _ => throw new IllegalArgumentException(s"Tried to get underlying of non-VkBinding $buffer") + case buffer: VkBinding[?] => buffer + case _ => throw new IllegalArgumentException(s"Tried to get underlying of non-VkBinding $buffer") diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala new file mode 100644 index 00000000..f83f39fb --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -0,0 +1,69 @@ +package io.computenode.cyfra.runtime + +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import izumi.reflect.Tag +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer} +import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} +import io.computenode.cyfra.vulkan.core.Queue +import io.computenode.cyfra.vulkan.core.Device +import izumi.reflect.Tag +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import org.lwjgl.vulkan.VK10 +import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.binding.GUniform +import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} +import izumi.reflect.Tag +import org.lwjgl.vulkan.VK10 +import org.lwjgl.vulkan.VK10.* + +import scala.collection.mutable + +sealed abstract class VkBinding[T <: Value: {Tag, FromExpr}](val buffer: Buffer): + val sizeOfT: Int = typeStride(summon[Tag[T]]) + + /** Holds either: + * 1. a single execution that writes to this buffer + * 1. multiple executions that read from this buffer + */ + var execution: Either[PendingExecution, mutable.Buffer[PendingExecution]] = Right(mutable.Buffer.empty) + + def materialise(queue: Queue)(using Device): Unit = execution match + case Left(exec) if exec.isAlive => + PendingExecution.executeAll(Seq(exec), queue) + exec.block() + PendingExecution.cleanupAll(Seq(exec)) + case _ => () + +object VkBinding: + def unapply(binding: GBinding[?]): Option[Buffer] = binding match + case b: VkBinding[?] => Some(b.buffer) + case _ => None + +class VkBuffer[T <: Value: {Tag, FromExpr}] private (val length: Int, underlying: Buffer) extends VkBinding(underlying) with GBuffer[T] + +object VkBuffer: + private final val Padding = 64 + private final val UsageFlags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT + + def apply[T <: Value: {Tag, FromExpr}](length: Int)(using Allocator): VkBuffer[T] = + val sizeOfT = typeStride(summon[Tag[T]]) + val size = (length * sizeOfT + Padding - 1) / Padding * Padding + val buffer = new Buffer.DeviceBuffer(size, UsageFlags) + new VkBuffer[T](length, buffer) + +class VkUniform[T <: Value: {Tag, FromExpr}] private (underlying: Buffer) extends VkBinding[T](underlying) with GUniform[T] + +object VkUniform: + private final val UsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT + + def apply[T <: Value: {Tag, FromExpr}]()(using Allocator): VkUniform[T] = + val sizeOfT = typeStride(summon[Tag[T]]) + val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) + new VkUniform[T](buffer) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala deleted file mode 100644 index cda73868..00000000 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala +++ /dev/null @@ -1,23 +0,0 @@ -package io.computenode.cyfra.runtime - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr -import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer} -import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} -import izumi.reflect.Tag -import io.computenode.cyfra.spirv.SpirvTypes.typeStride -import org.lwjgl.vulkan.VK10 -import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} - -class VkBuffer[T <: Value: {Tag, FromExpr}] private (var length: Int, val underlying: Buffer) extends GBuffer[T]: - val sizeOfT: Int = typeStride(summon[Tag[T]]) - -object VkBuffer: - private final val Padding = 64 - private final val UsageFlags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT - - def apply[T <: Value: {Tag, FromExpr}](length: Int)(using Allocator): VkBuffer[T] = - val sizeOfT = typeStride(summon[Tag[T]]) - val size = (length * sizeOfT + Padding - 1) / Padding * Padding - val buffer = new Buffer.DeviceBuffer(size, UsageFlags) - new VkBuffer[T](length, buffer) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala deleted file mode 100644 index f8c75da7..00000000 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala +++ /dev/null @@ -1,21 +0,0 @@ -package io.computenode.cyfra.runtime - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr -import io.computenode.cyfra.dsl.binding.GUniform -import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} -import izumi.reflect.Tag -import org.lwjgl.vulkan.VK10 -import org.lwjgl.vulkan.VK10.* - -class VkUniform[T <: Value: {Tag, FromExpr}] private (val underlying: Buffer) extends GUniform[T]: - val sizeOfT: Int = 4 - -object VkUniform: - private final val UsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | - VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT - - def apply[T <: Value: {Tag, FromExpr}]()(using Allocator): VkUniform[T] = - val sizeOfT = 4 // typeStride(summon[Tag[T]]) - val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) - new VkUniform[T](buffer) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala index 3db65668..11d21e1a 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala @@ -39,35 +39,18 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) check(vkAllocateCommandBuffers(device.get, allocateInfo, pointerBuffer), "Failed to allocate command buffers") 0 until n map (i => pointerBuffer.get(i)) map (new VkCommandBuffer(_, device.get)) - def executeCommand(block: VkCommandBuffer => Unit): Unit = - val commandBuffer = beginSingleTimeCommands() - block(commandBuffer) - endSingleTimeCommands(commandBuffer).block().destroy() - freeCommandBuffer(commandBuffer) - - private def beginSingleTimeCommands(): VkCommandBuffer = - pushStack: stack => - val commandBuffer = this.createCommandBuffer() - - val beginInfo = VkCommandBufferBeginInfo - .calloc(stack) - .sType$Default() - .flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) + def recordSingleTimeCommand(block: VkCommandBuffer => Unit): VkCommandBuffer = pushStack: stack => + val commandBuffer = createCommandBuffer() - check(vkBeginCommandBuffer(commandBuffer, beginInfo), "Failed to begin single time command buffer") - commandBuffer + val beginInfo = VkCommandBufferBeginInfo + .calloc(stack) + .sType$Default() + .flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) - private def endSingleTimeCommands(commandBuffer: VkCommandBuffer): Fence = - pushStack: stack => - vkEndCommandBuffer(commandBuffer) - val pointerBuffer = stack.callocPointer(1).put(0, commandBuffer) - val submitInfo = VkSubmitInfo - .calloc(stack) - .sType$Default() - .pCommandBuffers(pointerBuffer) - val fence = Fence() - check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") - fence + check(vkBeginCommandBuffer(commandBuffer, beginInfo), "Failed to begin single time command buffer") + block(commandBuffer) + check(vkEndCommandBuffer(commandBuffer), "Failed to end single time command buffer") + commandBuffer def freeCommandBuffer(commandBuffer: VkCommandBuffer*): Unit = pushStack: stack => diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala index 04034b1c..88386710 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala @@ -9,7 +9,7 @@ import org.lwjgl.vulkan.VkSemaphoreCreateInfo /** @author * MarconZet Created 30.10.2019 */ -private[cyfra] class Semaphore(using device: Device) extends VulkanObjectHandle: +private[cyfra] class Semaphore(val stage: Long)(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val semaphoreCreateInfo = VkSemaphoreCreateInfo .calloc(stack) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala index 963aa1cd..1f677f04 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala @@ -1,13 +1,14 @@ package io.computenode.cyfra.vulkan.memory import io.computenode.cyfra.vulkan.command.{CommandPool, Fence} +import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObjectHandle import org.lwjgl.system.MemoryUtil.* import org.lwjgl.util.vma.Vma.* import org.lwjgl.util.vma.VmaAllocationCreateInfo import org.lwjgl.vulkan.VK10.* -import org.lwjgl.vulkan.{VkBufferCopy, VkBufferCreateInfo} +import org.lwjgl.vulkan.{VkBufferCopy, VkBufferCreateInfo, VkCommandBuffer, VkSubmitInfo} import java.nio.ByteBuffer @@ -61,8 +62,22 @@ object Buffer: def copyFrom(src: ByteBuffer, dstOffset: Int): Unit = pushStack: stack => vmaCopyMemoryToAllocation(allocator.get, src, allocation, dstOffset) - def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): Unit = - commandPool.executeCommand: commandBuffer => + def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool)(using Device): Unit = pushStack: + stack => + val cb = copyBufferCommandBuffer(src, dst, srcOffset, dstOffset, bytes, commandPool) + + val pCB = stack.callocPointer(1).put(0, cb) + val submitInfo = VkSubmitInfo + .calloc(stack) + .sType$Default() + .pCommandBuffers(pCB) + + val fence = Fence() + check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") + fence.block() + + def copyBufferCommandBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): VkCommandBuffer = + commandPool.recordSingleTimeCommand: commandBuffer => pushStack: stack => val copyRegion = VkBufferCopy .calloc(1, stack) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala index 3ec34726..50d3baf7 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala @@ -6,6 +6,7 @@ package io.computenode.cyfra.vulkan.util private[cyfra] abstract class VulkanObject[T]: protected val handle: T private var alive: Boolean = true + def isAlive: Boolean = alive def get: T = if !alive then throw new IllegalStateException() From 74d8d8b29fce13cb1a39aa0629c1a8de63df4154 Mon Sep 17 00:00:00 2001 From: Szymon Date: Sun, 24 Aug 2025 23:23:36 +0200 Subject: [PATCH 11/38] Works! --- .../io/computenode/cyfra/spirv/Context.scala | 4 +- .../cyfra/spirv/compilers/DSLCompiler.scala | 16 ++- .../spirv/compilers/ExpressionCompiler.scala | 12 +- .../cyfra/spirv/compilers/GIOCompiler.scala | 124 ++++++++++++++---- .../compilers/SpirvProgramCompiler.scala | 27 ++-- .../computenode/cyfra/core/Allocation.scala | 6 +- .../io/computenode/cyfra/core/GProgram.scala | 8 +- .../cyfra/core/binding/UniformRef.scala | 4 +- .../cyfra/core/layout/LayoutStruct.scala | 24 ++-- .../cyfra/dsl/binding/GBinding.scala | 6 +- .../cyfra/dsl/binding/ReadUniform.scala | 3 +- .../cyfra/dsl/binding/WriteUniform.scala | 4 +- .../cyfra/dsl/struct/GStruct.scala | 4 +- .../cyfra/dsl/struct/GStructSchema.scala | 4 +- .../cyfra/samples/TestingStuff.scala | 50 ++++++- .../cyfra/runtime/ExecutionHandler.scala | 22 ++-- .../cyfra/runtime/VkAllocation.scala | 10 +- .../cyfra/runtime/VkCyfraRuntime.scala | 25 +++- .../computenode/cyfra/runtime/VkShader.scala | 11 +- .../computenode/cyfra/runtime/VkUniform.scala | 5 +- .../cyfra/spirvtools/SpirvCross.scala | 2 +- .../cyfra/spirvtools/SpirvDisassembler.scala | 2 +- .../cyfra/spirvtools/SpirvOptimizer.scala | 2 +- .../cyfra/spirvtools/SpirvTool.scala | 19 ++- .../cyfra/spirvtools/SpirvToolsRunner.scala | 2 +- 25 files changed, 270 insertions(+), 126 deletions(-) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala index 35b39a46..8e8f5f96 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala @@ -1,6 +1,6 @@ package io.computenode.cyfra.spirv -import io.computenode.cyfra.dsl.binding.GBuffer +import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.macros.FnCall.FnIdentifier import io.computenode.cyfra.spirv.SpirvConstants.HEADER_REFS_TOP import io.computenode.cyfra.spirv.compilers.FunctionCompiler.SprivFunction @@ -17,7 +17,7 @@ private[cyfra] case class Context( voidTypeRef: Int = -1, voidFuncTypeRef: Int = -1, workerIndexRef: Int = -1, - uniformVarRefs: Map[Int, Int] = Map.empty, + uniformVarRefs: Map[GUniform[?], Int] = Map.empty, bindingToStructType: Map[Int, Int] = Map.empty, constRefs: Map[(Tag[?], Any), Int] = Map(), exprRefs: Map[Int, Int] = Map(), diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala index f15c9d54..09dd9999 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala @@ -74,10 +74,12 @@ private[cyfra] object DSLCompiler: val allTypes = (typesInCode ::: bindings.map(_.tag)).distinct def scalarTypes = allTypes.filter(_.tag <:< summon[Tag[Scalar]].tag) val (typeDefs, typedContext) = defineScalarTypes(scalarTypes, Context.initialContext) - val (buffers, uniforms) = bindings.partition: - case _: GBuffer[?] => true - case _: GUniform[?] => false - .asInstanceOf[(List[GBinding[?]], List[GUniform[?]])] + val (buffersWithIndices, uniformsWithIndices) = bindings.zipWithIndex.partition: + case (_: GBuffer[?], _) => true + case (_: GUniform[?], _) => false + .asInstanceOf[(List[(GBuffer[?], Int)], List[(GUniform[?], Int)])] + val uniforms = uniformsWithIndices.map(_._1) + val buffers = buffersWithIndices.map(_._1) val uniformSchemas = uniforms.map(_.schema) val structsInCode = (allExprs.collect { @@ -86,9 +88,9 @@ private[cyfra] object DSLCompiler: } ::: uniformSchemas).distinct val (structDefs, structCtx) = defineStructTypes(structsInCode, typedContext) val structNames = getStructNames(structsInCode, structCtx) - val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffers, structCtx) - val (uniformStructDecorations, uniformStructInsns, uniformStructContext) = createAndInitUniformBlocks(uniformSchemaIns, uniformContext) - val blockNames = getBlockNames(uniformContext, uniformSchemaIns) + val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffersWithIndices, structCtx) + val (uniformStructDecorations, uniformStructInsns, uniformStructContext) = createAndInitUniformBlocks(uniformsWithIndices, uniformContext) + val blockNames = getBlockNames(uniformContext, uniforms) val (inputDefs, inputContext) = createInvocationId(uniformStructContext) val (constDefs, constCtx) = defineConstants(allExprs, inputContext) val (varDefs, varCtx) = defineVarNames(constCtx) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala index 7e7cb849..c1459fcf 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala @@ -106,11 +106,11 @@ private[cyfra] object ExpressionCompiler: val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (c.treeid -> constRef)) (List(), updatedContext) - case w @ WorkerIndex => + case w @ InvocationId => (Nil, ctx.copy(exprRefs = ctx.exprRefs + (w.treeid -> ctx.workerIndexRef))) - case d @ Binding(id) => - (Nil, ctx.copy(exprRefs = ctx.exprRefs + (d.treeid -> ctx.uniformVarRefs(id)))) + case d @ ReadUniform(u) => + (Nil, ctx.copy(exprRefs = ctx.exprRefs + (d.treeid -> ctx.uniformVarRefs(u)))) case c: ConvertExpression[?, ?] => compileConvertExpression(c, ctx) @@ -306,6 +306,7 @@ private[cyfra] object ExpressionCompiler: val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (expr.treeid -> (ctx.nextResultId + 1)), nextResultId = ctx.nextResultId + 2) (instructions, updatedContext) + case when: WhenExpr[?] => compileWhen(when, ctx) @@ -327,14 +328,14 @@ private[cyfra] object ExpressionCompiler: val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (cs.treeid -> ctx.nextResultId), nextResultId = ctx.nextResultId + 1) (insns, updatedContext) - case gf @ GetField(binding @ Binding(bindingId), fieldIndex) => + case gf @ GetField(binding @ ReadUniform(uf), fieldIndex) => val insns: List[Instruction] = List( Instruction( Op.OpAccessChain, List( ResultRef(ctx.uniformPointerMap(ctx.valueTypeMap(gf.tag.tag))), ResultRef(ctx.nextResultId), - ResultRef(ctx.uniformVarRefs(bindingId)), + ResultRef(ctx.uniformVarRefs(uf)), ResultRef(ctx.constRefs((Int32Tag, gf.fieldIndex))), ), ), @@ -342,6 +343,7 @@ private[cyfra] object ExpressionCompiler: ) val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (expr.treeid -> (ctx.nextResultId + 1)), nextResultId = ctx.nextResultId + 2) (insns, updatedContext) + case gf: GetField[?, ?] => val insns: List[Instruction] = List( Instruction( diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala index c64b9cae..a4a55212 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -5,7 +5,7 @@ import io.computenode.cyfra.spirv.Context import io.computenode.cyfra.spirv.Opcodes.* import io.computenode.cyfra.dsl.binding.* import io.computenode.cyfra.dsl.gio.GIO.CurrentRepeatIndex -import io.computenode.cyfra.spirv.SpirvTypes.{Int32Tag, LInt32Tag} +import io.computenode.cyfra.spirv.SpirvTypes.{GBooleanTag, Int32Tag, LInt32Tag} object GIOCompiler: @@ -17,37 +17,113 @@ object GIOCompiler: (acc ::: insts, updatedCtx) case WriteBuffer(buffer, index, value) => + val (valueInsts, ctxWithValue) = ExpressionCompiler.compileBlock(value.tree, ctx) + val (indexInsts, ctxWithIndex) = ExpressionCompiler.compileBlock(index.tree, ctxWithValue) + val insns = List(Instruction( Op.OpAccessChain, List( - ResultRef(ctx.uniformPointerMap(ctx.valueTypeMap(buffer.tag.tag))), - ResultRef(ctx.nextResultId), - ResultRef(ctx.bufferBlocks(buffer).blockVarRef), - ResultRef(ctx.constRefs((Int32Tag, 0))), - ResultRef(ctx.workerIndexRef), + ResultRef(ctxWithIndex.uniformPointerMap(ctxWithIndex.valueTypeMap(buffer.tag.tag))), + ResultRef(ctxWithIndex.nextResultId), + ResultRef(ctxWithIndex.bufferBlocks(buffer).blockVarRef), + ResultRef(ctxWithIndex.constRefs((Int32Tag, 0))), + ResultRef(ctxWithIndex.exprRefs(index.tree.treeid)), ), ), - Instruction(Op.OpStore, List(ResultRef(ctx.nextResultId), ResultRef(ctx.exprRefs(value.tree.treeid)))) + Instruction(Op.OpStore, List(ResultRef(ctxWithIndex.nextResultId), ResultRef(ctxWithIndex.exprRefs(value.tree.treeid)))) ) - val updatedCtx = ctx.copy(nextResultId = ctx.nextResultId + 1) - (acc ::: insns, updatedCtx) - + val updatedCtx = ctxWithIndex.copy(nextResultId = ctxWithIndex.nextResultId + 1) + (acc ::: indexInsts ::: valueInsts ::: insns, updatedCtx) + case GIO.Repeat(n, f) => + + // Compile 'n' first (so we can use its id in the comparison) val (nInsts, ctxWithN) = ExpressionCompiler.compileBlock(n.tree, ctx) - val loopHeaderId = ctxWithN.nextResultId - val loopMergeId = ctxWithN.nextResultId + 1 - val loopContinueId = ctxWithN.nextResultId + 2 - val counterVarId = ctxWithN.nextResultId + 3 - val ctxWithCounter = ctxWithN.copy(exprRefs = ctxWithN.exprRefs + (CurrentRepeatIndex.treeid -> counterVarId), nextResultId = ctxWithN.nextResultId + 4) - val counterInit = Instruction(Op.OpVariable, List(ResultRef(ctxWithCounter.constRefs((Int32Tag, 0))), ResultRef(counterVarId), StorageClass.Function)) - val counterStore = Instruction(Op.OpStore, List(ResultRef(counterVarId), ResultRef(ctxWithCounter.constRefs((Int32Tag, 0))))) - val (body, afterBodyCtx) = compileGio(f, ctxWithCounter) - // todo - ??? - - - - + + // Types and constants + val intTy = ctxWithN.valueTypeMap(Int32Tag.tag) + val boolTy = ctxWithN.valueTypeMap(GBooleanTag.tag) + val zeroId = ctxWithN.constRefs((Int32Tag, 0)) + val oneId = ctxWithN.constRefs((Int32Tag, 1)) + val nId = ctxWithN.exprRefs(n.tree.treeid) + + // Reserve ids for blocks and results + val baseId = ctxWithN.nextResultId + val preHeaderId = baseId + val headerId = baseId + 1 + val bodyId = baseId + 2 + val continueId = baseId + 3 + val mergeId = baseId + 4 + val phiId = baseId + 5 + val cmpId = baseId + 6 + val addId = baseId + 7 + + // Bind CurrentRepeatIndex to the phi result for body compilation + val bodyCtx = ctxWithN.copy( + nextResultId = baseId + 8, + exprRefs = ctxWithN.exprRefs + (CurrentRepeatIndex.treeid -> phiId) + ) + val (bodyInsts, ctxAfterBody) = compileGio(f, bodyCtx) + + // Preheader: close current block and jump to header through a dedicated block + val preheader = List( + Instruction(Op.OpBranch, List(ResultRef(preHeaderId))), + Instruction(Op.OpLabel, List(ResultRef(preHeaderId))), + Instruction(Op.OpBranch, List(ResultRef(headerId))) + ) + + // Header: OpPhi first, then compute condition, then OpLoopMerge and the terminating branch + val header = List( + Instruction(Op.OpLabel, List(ResultRef(headerId))), + // OpPhi must be first in the block + Instruction( + Op.OpPhi, + List( + ResultRef(intTy), ResultRef(phiId), + ResultRef(zeroId), ResultRef(preHeaderId), + ResultRef(addId), ResultRef(continueId) + ) + ), + // cmp = (counter < n) + Instruction( + Op.OpSLessThan, + List(ResultRef(boolTy), ResultRef(cmpId), ResultRef(phiId), ResultRef(nId)) + ), + // OpLoopMerge must be the second-to-last instruction, before the terminating branch + Instruction(Op.OpLoopMerge, List(ResultRef(mergeId), ResultRef(continueId), LoopControlMask.MaskNone)), + Instruction(Op.OpBranchConditional, List(ResultRef(cmpId), ResultRef(bodyId), ResultRef(mergeId))) + ) + + val bodyBlk = List( + Instruction(Op.OpLabel, List(ResultRef(bodyId))) + ) ::: bodyInsts ::: List( + Instruction(Op.OpBranch, List(ResultRef(continueId))) + ) + + val contBlk = List( + Instruction(Op.OpLabel, List(ResultRef(continueId))), + Instruction( + Op.OpIAdd, + List(ResultRef(intTy), ResultRef(addId), ResultRef(phiId), ResultRef(oneId)) + ), + Instruction(Op.OpBranch, List(ResultRef(headerId))) + ) + + val mergeBlk = List( + Instruction(Op.OpLabel, List(ResultRef(mergeId))) + ) + + val finalNextId = math.max(ctxAfterBody.nextResultId, addId + 1) + val finalCtx = ctxAfterBody.copy( + nextResultId = finalNextId, + exprRefs = ctxAfterBody.exprRefs - CurrentRepeatIndex.treeid + ) + + (acc ::: nInsts ::: preheader ::: header ::: bodyBlk ::: contBlk ::: mergeBlk, finalCtx) + + + + \ No newline at end of file diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index 73ebf1bf..997c93b2 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -4,7 +4,7 @@ import io.computenode.cyfra.spirv.Opcodes.* import io.computenode.cyfra.dsl.Expression.{Const, E} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.* -import io.computenode.cyfra.dsl.binding.GBuffer +import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.{GStructConstructor, GStructSchema} import io.computenode.cyfra.spirv.Context @@ -122,15 +122,15 @@ private[cyfra] object SpirvProgramCompiler: ), ) (definitionInstructions, context.copy(nextResultId = context.nextResultId + 3)) - def initAndDecorateBuffers(buffers: List[GBuffer[?]], context: Context): (List[Words], List[Words], Context) = + def initAndDecorateBuffers(buffers: List[(GBuffer[?], Int)], context: Context): (List[Words], List[Words], Context) = val (blockDecor, blockDef, inCtx) = createAndInitBlocks(buffers, context) val (voidsDef, voidCtx) = defineVoids(inCtx) (blockDecor, voidsDef ::: blockDef, voidCtx) - def createAndInitBlocks(blocks: List[GBuffer[?]], context: Context): (List[Words], List[Words], Context) = - val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), buff) => + def createAndInitBlocks(blocks: List[(GBuffer[?], Int)], context: Context): (List[Words], List[Words], Context) = + val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), (buff, binding)) => val tpe = buff.tag - val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, ctx.nextBinding) + val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, binding) val decorationInstructions = List[Words]( Instruction(Op.OpDecorate, List(ResultRef(block.memberArrayTypeRef), Decoration.ArrayStride, IntWord(typeStride(tpe)))), // OpDecorate %_runtimearr_X ArrayStride [typeStride(type)] @@ -152,12 +152,12 @@ private[cyfra] object SpirvProgramCompiler: ( decAcc ::: decorationInstructions, insnAcc ::: definitionInstructions, - contextWithBlock.copy(nextResultId = contextWithBlock.nextResultId + 5, nextBinding = contextWithBlock.nextBinding + 1), + contextWithBlock.copy(nextResultId = contextWithBlock.nextResultId + 5), ) } (decoration, definition, newContext) - def getBlockNames(context: Context, uniformSchemas: List[GStructSchema[?]]): List[Words] = + def getBlockNames(context: Context, uniformSchemas: List[GUniform[?]]): List[Words] = def namesForBlock(block: ArrayBufferBlock, tpe: String): List[Words] = Instruction(Op.OpName, List(ResultRef(block.structTypeRef), Text(s"Buffer$tpe"))) :: Instruction(Op.OpName, List(ResultRef(block.blockVarRef), Text(s"data$tpe"))) :: Nil @@ -174,10 +174,10 @@ private[cyfra] object SpirvProgramCompiler: typeStride(t) .sum - def createAndInitUniformBlocks(schemas: List[GStructSchema[?]], ctx: Context): (List[Words], List[Words], Context) = - schemas.foldLeft((List.empty[Words], List.empty[Words], ctx)) { case ((decorationsAcc, definitionsAcc, currentCtx), schema) => + def createAndInitUniformBlocks(schemas: List[(GUniform[?], Int)], ctx: Context): (List[Words], List[Words], Context) = + schemas.foldLeft((List.empty[Words], List.empty[Words], ctx)) { case ((decorationsAcc, definitionsAcc, currentCtx), (uniform, binding)) => + val schema = uniform.schema val uniformStructTypeRef = currentCtx.valueTypeMap(schema.structTag.tag) - val currentBinding = currentCtx.nextBinding val (offsetDecorations, _) = schema.fields.zipWithIndex.foldLeft[(List[Words], Int)](List.empty[Words], 0): case ((acc, offset), ((name, fromExpr, tag), idx)) => @@ -199,16 +199,15 @@ private[cyfra] object SpirvProgramCompiler: val uniformVar = Instruction(Op.OpVariable, List(ResultRef(uniformPointerUniformRef), ResultRef(uniformVarRef), StorageClass.Uniform)) val uniformDecorateDescriptorSet = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.DescriptorSet, IntWord(0))) - val uniformDecorateBinding = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.Binding, IntWord(currentBinding))) + val uniformDecorateBinding = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.Binding, IntWord(binding))) val newDecorations = decorationsAcc ::: offsetDecorations ::: List(uniformDecorateDescriptorSet, uniformDecorateBinding, uniformBlockDecoration) val newDefinitions = definitionsAcc ::: List(uniformPointerUniform, uniformVar) val newCtx = currentCtx.copy( nextResultId = currentCtx.nextResultId + 2, - nextBinding = currentCtx.nextBinding + 1, - uniformVarRefs = currentCtx.uniformVarRefs + (currentBinding -> uniformVarRef), + uniformVarRefs = currentCtx.uniformVarRefs + (uniform -> uniformVarRef), uniformPointerMap = currentCtx.uniformPointerMap + (uniformStructTypeRef -> uniformPointerUniformRef), - bindingToStructType = currentCtx.bindingToStructType + (currentBinding -> uniformStructTypeRef) + bindingToStructType = currentCtx.bindingToStructType + (binding -> uniformStructTypeRef) ) (newDecorations, newDefinitions, newCtx) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index bdc1d5a7..65b5e367 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -4,7 +4,7 @@ import io.computenode.cyfra.core.layout.{Layout, LayoutBinding} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import izumi.reflect.Tag import java.nio.ByteBuffer @@ -24,6 +24,6 @@ trait Allocation: def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] extension (buffers: GUniform.type) - def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] + def apply[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}](buff: ByteBuffer): GUniform[T] - def apply[T <: Value: {Tag, FromExpr}](): GUniform[T] + def apply[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}](): GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index d1a43226..f1cef442 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -8,7 +8,7 @@ import GProgram.* import io.computenode.cyfra.dsl.{Expression, Value} import io.computenode.cyfra.dsl.Value.{FromExpr, GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag @@ -34,7 +34,7 @@ object GProgram: private[cyfra] class BufferLengthSpec[T <: Value: {Tag, FromExpr}](val length: Int) extends GBuffer[T]: private[cyfra] def materialise()(using Allocation): GBuffer[T] = GBuffer.apply[T](length) - private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}]() extends GUniform[T] trait InitProgramLayout: extension (_buffers: GBuffer.type) @@ -42,6 +42,6 @@ object GProgram: BufferLengthSpec[T](length) extension (_uniforms: GUniform.type) - def apply[T <: GStruct[T]: {Tag, FromExpr}](): GUniform[T] = + def apply[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}](): GUniform[T] = DynamicUniform[T]() - def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] + def apply[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](value: T): GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/binding/UniformRef.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/binding/UniformRef.scala index d7c3b308..8fc86c2f 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/binding/UniformRef.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/binding/UniformRef.scala @@ -3,8 +3,8 @@ package io.computenode.cyfra.core.binding import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} -import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import izumi.reflect.Tag import izumi.reflect.macrortti.LightTypeTag -case class UniformRef[T <: Value: {Tag, FromExpr}](layoutOffset: Int, valueTag: Tag[T]) extends GUniform[T] +case class UniformRef[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](layoutOffset: Int, valueTag: Tag[T]) extends GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala index 3d79b409..0703bbbb 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala @@ -4,6 +4,7 @@ import io.computenode.cyfra.core.binding.{BufferRef, UniformRef} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import izumi.reflect.Tag import izumi.reflect.macrortti.LightTypeTag @@ -55,17 +56,18 @@ object LayoutStruct: val buffers = typeGivens.zipWithIndex.map: case ((ftype, tpe, tag, fromExpr), i) => - tpe match - case '[type t <: Value; t] => - ftype match - case '[type tg <: GBuffer[?]; tg] => - '{ - BufferRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }) - } - case '[type tg <: GUniform[?]; tg] => - '{ - UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }) - } + (tpe, ftype) match + case ('[type t <: Value; t], '[type tg <: GBuffer[?]; tg]) => + '{ + BufferRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }) + } + case ('[type t <: GStruct[?]; t], '[type tg <: GUniform[?]; tg]) => + val structSchema = Expr.summon[GStructSchema[t]] match + case Some(s) => s + case None => report.errorAndAbort(s"Cannot summon GStructSchema for type") + '{ + UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }, ${ structSchema }) + } val constructor = sym.primaryConstructor diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala index 8bad4bb8..ce003c87 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala @@ -19,7 +19,7 @@ trait GBuffer[T <: Value: {FromExpr, Tag}] extends GBinding[T]: object GBuffer -trait GUniform[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}] extends GBinding[T]: +trait GUniform[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}] extends GBinding[T]: def read: T = fromExprEval(ReadUniform(this)) def write(value: T): GIO[Empty] = WriteUniform(this, value) @@ -28,6 +28,6 @@ trait GUniform[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}] extends GBinding object GUniform: - class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + class ParamUniform[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}]() extends GUniform[T] - def fromParams[T <: GStruct[T]: {Tag, FromExpr}] = ParamUniform[T]() + def fromParams[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}] = ParamUniform[T]() diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/ReadUniform.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/ReadUniform.scala index 9f75a278..85b2b53e 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/ReadUniform.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/ReadUniform.scala @@ -1,6 +1,7 @@ package io.computenode.cyfra.dsl.binding +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.dsl.{Expression, Value} import izumi.reflect.Tag -case class ReadUniform[T <: Value: Tag](uniform: GUniform[T]) extends Expression[T] +case class ReadUniform[T <: GStruct[?]: {Tag, GStructSchema}](uniform: GUniform[T]) extends Expression[T] diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala index a450a5e7..f176014a 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/WriteUniform.scala @@ -2,9 +2,9 @@ package io.computenode.cyfra.dsl.binding import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.gio.GIO -import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag -case class WriteUniform[T <: Value: Tag](uniform: GUniform[T], value: T) extends GIO[Empty]: +case class WriteUniform[T <: GStruct[?]: {Tag, GStructSchema}](uniform: GUniform[T], value: T) extends GIO[Empty]: override def underlying: Empty = Empty() diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala index 9ec4199b..f9f6b383 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala @@ -26,9 +26,9 @@ object GStruct: object Empty: given GStructSchema[Empty] = GStructSchema.derived - case class ComposeStruct[T <: GStruct[T]: Tag](fields: List[Value], resultSchema: GStructSchema[T]) extends Expression[T] + case class ComposeStruct[T <: GStruct[?]: Tag](fields: List[Value], resultSchema: GStructSchema[T]) extends Expression[T] - case class GetField[S <: GStruct[S]: GStructSchema, T <: Value: Tag](struct: E[S], fieldIndex: Int) extends Expression[T]: + case class GetField[S <: GStruct[?]: GStructSchema, T <: Value: Tag](struct: E[S], fieldIndex: Int) extends Expression[T]: val resultSchema: GStructSchema[S] = summon[GStructSchema[S]] given [T <: GStruct[T]: GStructSchema]: GStructConstructor[T] with diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStructSchema.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStructSchema.scala index e0cd5d8f..8c26aa4f 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStructSchema.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStructSchema.scala @@ -10,7 +10,7 @@ import izumi.reflect.Tag import scala.compiletime.{constValue, erasedValue, error, summonAll} import scala.deriving.Mirror -case class GStructSchema[T <: GStruct[T]: Tag](fields: List[(String, FromExpr[?], Tag[?])], dependsOn: Option[E[T]], fromTuple: (Tuple, Source) => T): +case class GStructSchema[T <: GStruct[?]: Tag](fields: List[(String, FromExpr[?], Tag[?])], dependsOn: Option[E[T]], fromTuple: (Tuple, Source) => T): given GStructSchema[T] = this val structTag = summon[Tag[T]] @@ -23,7 +23,7 @@ case class GStructSchema[T <: GStruct[T]: Tag](fields: List[(String, FromExpr[?] def create(values: List[Value], schema: GStructSchema[T])(using name: Source): T = val valuesTuple = Tuple.fromArray(values.toArray) val newStruct = fromTuple(valuesTuple, name) - newStruct._schema = schema + newStruct._schema = schema.asInstanceOf newStruct.tree.of = Some(newStruct) newStruct diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index cd2fc227..e431b719 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -9,9 +9,12 @@ import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.spirvtools.SpirvTool.ToFile +import io.computenode.cyfra.spirvtools.{SpirvCross, SpirvToolsRunner, SpirvValidator} import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import java.nio.file.Paths import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given @@ -88,9 +91,52 @@ object TestingStuff: layout => FilterProgramLayout(in = layout.emitBuffer, out = layout.filterBuffer), ) + @main + def testEmit = + given runtime: VkCyfraRuntime = VkCyfraRuntime( + spirvToolsRunner = SpirvToolsRunner( + crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))) + ) + ) + + val emitParams = EmitProgramParams(inSize = 1024, emitN = 2) + + val region = GBufferRegion + .allocate[EmitProgramLayout] + .map: region => + emitProgram.execute(emitParams, region) + + val data = (0 until 1024).toArray + val buffer = BufferUtils.createByteBuffer(data.length * 4) + buffer.asIntBuffer().put(data).flip() + + val result = BufferUtils.createIntBuffer(data.length * 2) + val rbb = MemoryUtil.memByteBuffer(result) + region.runUnsafe( + init = EmitProgramLayout( + in = GBuffer[Int32](buffer), + out = GBuffer[Int32](data.length * 2), + ), + onDone = layout => layout.out.read(rbb), + ) + runtime.close() + + val actual = (0 until 2 * 1024).map(i => result.get(i * 1)) + val expected = (0 until 1024).flatMap(x => Seq.fill(emitParams.emitN)(x)) + expected + .zip(actual) + .zipWithIndex + .foreach: + case ((e, a), i) => assert(e == a, s"Mismatch at index $i: expected $e, got $a") + @main def test = - given runtime: VkCyfraRuntime = VkCyfraRuntime() + given runtime: VkCyfraRuntime = VkCyfraRuntime( + spirvToolsRunner = SpirvToolsRunner( + crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), + validator = SpirvValidator.Disable + ) + ) val emitFilterParams = EmitFilterParams(inSize = 1024, emitN = 2, filterValue = 42) @@ -115,7 +161,7 @@ object TestingStuff: ) runtime.close() - val actual = (0 until 2 * 1024).map(i => result.get(i * 1) != 0) + val actual = (0 until 2 * 1024).map(i => result.get(i) != 0) val expected = (0 until 1024).flatMap(x => Seq.fill(emitFilterParams.emitN)(x)).map(_ == emitFilterParams.filterValue) expected .zip(actual) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 00855e4e..d0ad3a5a 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -8,15 +8,8 @@ import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{ - BindingLogicError, - Dispatch, - DispatchType, - ExecutionBinding, - ExecutionStep, - PipelineBarrier, - ShaderCall, -} +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} +import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionBinding, ExecutionStep, PipelineBarrier, ShaderCall} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed @@ -248,11 +241,16 @@ object ExecutionHandler: sealed trait ExecutionBinding[T <: Value: {FromExpr, Tag}] object ExecutionBinding: - class UniformBinding[T <: Value: {FromExpr, Tag}] extends ExecutionBinding[T] with GUniform[T] + class UniformBinding[T <: GStruct[?]: {FromExpr, Tag, GStructSchema}] extends ExecutionBinding[T] with GUniform[T] class BufferBinding[T <: Value: {FromExpr, Tag}] extends ExecutionBinding[T] with GBuffer[T] - def apply[T <: Value: {FromExpr, Tag}](binding: GBinding[T]): ExecutionBinding[T] & GBinding[T] = binding match - case _: GUniform[T] => new UniformBinding() + def apply[T <: Value: {FromExpr as fe, Tag as t}](binding: GBinding[T]): ExecutionBinding[T] & GBinding[T] = binding match + // todo types are a mess here + case u: GUniform[GStruct[?]] => new UniformBinding[GStruct[?]](using + fe.asInstanceOf[FromExpr[GStruct[?]]], + t.asInstanceOf[Tag[GStruct[?]]], + u.schema.asInstanceOf + ).asInstanceOf[UniformBinding[T]] case _: GBuffer[T] => new BufferBinding() case class BindingLogicError(bindings: Seq[GBinding[?]], message: String) extends RuntimeException(s"Error in binding logic for $bindings: $message") diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a038ac7b..350f0369 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -7,7 +7,7 @@ import io.computenode.cyfra.dsl.Expression.ConstInt32 import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.runtime.VkAllocation.getUnderlying import io.computenode.cyfra.spirv.SpirvTypes.typeStride import io.computenode.cyfra.vulkan.command.CommandPool @@ -57,22 +57,22 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) GBuffer[T](length).tap(_.write(buff)) extension (buffers: GUniform.type) - def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = + def apply[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](buff: ByteBuffer): GUniform[T] = GUniform[T]().tap(_.write(buff)) - def apply[T <: Value: {Tag, FromExpr}](): GUniform[T] = + def apply[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](): GUniform[T] = VkUniform[T]().tap(bindings += _) extension [Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](execution: GExecution[Params, EL, RL]) def execute(params: Params, layout: EL): RL = executionHandler.handle(execution, params, layout) - private def direct[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = + private def direct[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](buff: ByteBuffer): GUniform[T] = GUniform[T](buff) def getInitProgramLayout: GProgram.InitProgramLayout = new GProgram.InitProgramLayout: extension (uniforms: GUniform.type) - def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] = pushStack: stack => + def apply[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](value: T): GUniform[T] = pushStack: stack => val bb = value.productElement(0) match case Int32(tree: ConstInt32) => MemoryUtil.memByteBuffer(stack.ints(tree.value)) case _ => ??? diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index ccd6585f..c6383665 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -1,19 +1,34 @@ package io.computenode.cyfra.runtime +import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} -import io.computenode.cyfra.core.{Allocation, CyfraRuntime, GExecution, GProgram, SpirvProgram} +import io.computenode.cyfra.core.{Allocation, CyfraRuntime, GExecution, GProgram, GioProgram, SpirvProgram} +import io.computenode.cyfra.spirv.compilers.DSLCompiler +import io.computenode.cyfra.spirvtools.SpirvToolsRunner import io.computenode.cyfra.vulkan.VulkanContext import io.computenode.cyfra.vulkan.compute.ComputePipeline import scala.collection.mutable -class VkCyfraRuntime extends CyfraRuntime: +class VkCyfraRuntime(spirvToolsRunner: SpirvToolsRunner = SpirvToolsRunner()) extends CyfraRuntime: private val context = new VulkanContext() import context.given - private val shaderCache = mutable.Map.empty[String, VkShader[?]] - private[cyfra] def getOrLoadProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[Params, L]): VkShader[L] = - shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)).asInstanceOf[VkShader[L]] + private val shaderCache = mutable.Map[GProgram[?, ?], VkShader[?]]() + private[cyfra] def getOrLoadProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[Params, L]): VkShader[L] = synchronized: + val spirvProgram = program match + case p: GioProgram[?, ?] => compile(p) + case p: SpirvProgram[?, ?] => p + case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + + shaderCache.getOrElseUpdate(program, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] + + private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = + val GioProgram(_, layout, dispatch, _) = program + val bindings = lbinding.toBindings(lstruct.layoutRef).toList + val compiled = DSLCompiler.compile(program.body(summon[LayoutStruct[L]].layoutRef), bindings) + val optimizedShaderCode = spirvToolsRunner.processShaderCodeWithSpirvTools(compiled) + SpirvProgram((il: InitProgramLayout) ?=> layout(il), dispatch, optimizedShaderCode) override def withAllocation(f: Allocation => Unit): Unit = context.withThreadContext: threadContext => diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index 3086b15c..d2209fe1 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -16,11 +16,8 @@ import scala.util.{Failure, Success} case class VkShader[L](underlying: ComputePipeline, shaderBindings: L => ShaderLayout) object VkShader: - def apply[P, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[P, L])(using Device): VkShader[L] = - val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings, _) = program match - case p: GioProgram[?, ?] => compile(p) - case p: SpirvProgram[?, ?] => p - case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + def apply[P, L <: Layout: {LayoutBinding, LayoutStruct}](program: SpirvProgram[P, L])(using Device): VkShader[L] = + val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings) = program val shaderLayout = shaderBindings(summon[LayoutStruct[L]].layoutRef) val sets = shaderLayout.map: set => @@ -35,7 +32,3 @@ object VkShader: val pipeline = ComputePipeline(code, entryPoint, LayoutInfo(sets)) VkShader(pipeline, shaderBindings) - def compile[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = - val GioProgram(_, layout, dispatch, _) = program - val compiled = DSLCompiler.compile(program.body(summon[LayoutStruct[L]].layoutRef), ???) - SpirvProgram((il: InitProgramLayout) ?=> layout(il), dispatch, compiled) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala index f8c75da7..86ed671e 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala @@ -3,19 +3,20 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.GUniform +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} import izumi.reflect.Tag import org.lwjgl.vulkan.VK10 import org.lwjgl.vulkan.VK10.* -class VkUniform[T <: Value: {Tag, FromExpr}] private (val underlying: Buffer) extends GUniform[T]: +class VkUniform[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}] private (val underlying: Buffer) extends GUniform[T]: val sizeOfT: Int = 4 object VkUniform: private final val UsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT - def apply[T <: Value: {Tag, FromExpr}]()(using Allocator): VkUniform[T] = + def apply[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}]()(using Allocator): VkUniform[T] = val sizeOfT = 4 // typeStride(summon[Tag[T]]) val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) new VkUniform[T](buffer) diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala index 73304350..991fda38 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala @@ -20,7 +20,7 @@ object SpirvCross extends SpirvTool("spirv-cross"): case Right(crossCompiledCode) => toolOutput match case Ignore => - case toFile @ SpirvTool.ToFile(_) => + case toFile @ SpirvTool.ToFile(_, _) => toFile.write(crossCompiledCode) logger.debug(s"Saved cross compiled shader code in ${toFile.filePath}.") case ToLogger => logger.debug(s"SPIR-V Cross Compilation result:\n$crossCompiledCode") diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala index f0c7c38f..67730961 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala @@ -19,7 +19,7 @@ object SpirvDisassembler extends SpirvTool("spirv-dis"): case Right(disassembledShader) => toolOutput match case Ignore => - case toFile @ SpirvTool.ToFile(_) => + case toFile @ SpirvTool.ToFile(_, _) => toFile.write(disassembledShader) logger.debug(s"Saved disassembled shader code in ${toFile.filePath}.") case ToLogger => logger.debug(s"SPIR-V Assembly:\n$disassembledShader") diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala index b42c0651..509e763c 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala @@ -20,7 +20,7 @@ object SpirvOptimizer extends SpirvTool("spirv-opt"): case Right(optimizedShaderCode) => toolOutput match case SpirvTool.Ignore => - case toFile @ SpirvTool.ToFile(_) => + case toFile @ SpirvTool.ToFile(_, _) => toFile.write(optimizedShaderCode) logger.debug(s"Saved optimized shader code in ${toFile.filePath}.") Some(optimizedShaderCode) diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvTool.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvTool.scala index 729c0efd..58501a42 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvTool.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvTool.scala @@ -95,18 +95,27 @@ object SpirvTool: case class Param(value: String): def asStringParam: String = value - case class ToFile(filePath: Path) extends ToolOutput: + case class ToFile(filePath: Path, hashSuffix: Boolean = true) extends ToolOutput: require(filePath != null, "filePath must not be null") - def write(outputToSave: String | ByteBuffer): Unit = - Option(filePath.getParent).foreach { dir => + def write(outputToSave: String | ByteBuffer): Unit = { + val suffix = if hashSuffix then s"_${outputToSave.hashCode() & 0xffff}" else "" + // prefix before last dot + val suffixedPath = filePath.getFileName.toString.lastIndexOf('.') match + case -1 => filePath.getFileName.toString + suffix + case index => filePath.getFileName.toString.substring(0, index) + suffix + filePath.getFileName.toString.substring(index) + val updatedPath = filePath.getParent match + case null => Path.of(suffixedPath) + case dir => dir.resolve(suffixedPath) + Option(updatedPath.getParent).foreach { dir => if !Files.exists(dir) then Files.createDirectories(dir) logger.debug(s"Created output directory: $dir") outputToSave match - case stringOutput: String => Files.write(filePath, stringOutput.getBytes(StandardCharsets.UTF_8)) - case byteBuffer: ByteBuffer => dumpByteBufferToFile(byteBuffer, filePath) + case stringOutput: String => Files.write(updatedPath, stringOutput.getBytes(StandardCharsets.UTF_8)) + case byteBuffer: ByteBuffer => dumpByteBufferToFile(byteBuffer, updatedPath) } + } private def dumpByteBufferToFile(code: ByteBuffer, path: Path): Unit = Using.resource(new FileOutputStream(path.toAbsolutePath.toString).getChannel) { fc => diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvToolsRunner.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvToolsRunner.scala index 234fca7b..1467350e 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvToolsRunner.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvToolsRunner.scala @@ -20,7 +20,7 @@ class SpirvToolsRunner( SpirvValidator.validateSpirv(code, validator) originalSpirvOutput match - case toFile @ SpirvTool.ToFile(_) => + case toFile @ SpirvTool.ToFile(_, _) => toFile.write(shaderCode) logger.debug(s"Saved original shader code in ${toFile.filePath}.") case Ignore => From dbf17349127767c9368f85d7595bab07dd1f5f16 Mon Sep 17 00:00:00 2001 From: Szymon Date: Sun, 24 Aug 2025 23:29:17 +0200 Subject: [PATCH 12/38] FlatMap --- .../io/computenode/cyfra/spirv/compilers/GIOCompiler.scala | 6 +++++- .../io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala index a4a55212..dd1d853c 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -11,7 +11,7 @@ object GIOCompiler: def compileGio(gio: GIO[?], ctx: Context, acc: List[Words] = Nil): (List[Words], Context) = gio match - + case GIO.Pure(v) => val (insts, updatedCtx) = ExpressionCompiler.compileBlock(v.tree, ctx) (acc ::: insts, updatedCtx) @@ -35,6 +35,10 @@ object GIOCompiler: val updatedCtx = ctxWithIndex.copy(nextResultId = ctxWithIndex.nextResultId + 1) (acc ::: indexInsts ::: valueInsts ::: insns, updatedCtx) + case GIO.FlatMap(v, n) => + val (vInsts, ctxAfterV) = compileGio(v, ctx, acc) + compileGio(n, ctxAfterV, vInsts) + case GIO.Repeat(n, f) => // Compile 'n' first (so we can use its id in the comparison) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala index 35ad5bb2..4d002c19 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala @@ -7,6 +7,7 @@ import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.GStruct.Empty import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.runtime.VkCyfraRuntime import io.computenode.cyfra.utility.Logger.logger @@ -153,7 +154,7 @@ class RuntimeEnduranceTest extends munit.FunSuite: _ <- GIO.write(out3, index, GIO.read(in3, index) + a + b) _ <- GIO.write(out4, index, GIO.read(in4, index) + a + b) _ <- GIO.write(out5, index, GIO.read(in5, index) + a + b) - yield () + yield Empty() def swap(l: AddProgramLayout): AddProgramLayout = val AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5, u1, u2) = l From b2d53f87c8d19ba2b0eba7111fd414386787ea49 Mon Sep 17 00:00:00 2001 From: spamegg Date: Tue, 26 Aug 2025 15:48:56 +0300 Subject: [PATCH 13/38] added stream compaction program --- .../computenode/cyfra/fs2interop/GPipe.scala | 108 +++++++++++------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 8bfbcfa9..85346448 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -58,11 +58,12 @@ object GPipe: def filter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = (stream: Stream[F, S]) => + case class Params(inSize: Int) // used commonly by many program layouts + // Predicate mapping - case class PredParams(inSize: Int) case class PredLayout(in: GBuffer[C], out: GBuffer[Int32]) extends Layout - val predicateProgram = GProgram[PredParams, PredLayout]( + val predicateProgram = GProgram[Params, PredLayout]( layout = params => PredLayout(in = GBuffer[C](params.inSize), out = GBuffer[Int32](params.inSize)), dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), ): layout => @@ -71,10 +72,6 @@ object GPipe: val result = when(pred(element))(1: Int32).otherwise(0) GIO.write[Int32](layout.out, invocId, result) - val predParams = PredParams(256) - val predExec = GExecution[PredParams, PredLayout]() - .addProgram(predicateProgram)(params => params, layout => layout) - // Prefix sum (inclusive), upsweep/downsweep case class ScanParams(inSize: Int, intervalSize: Int) case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] @@ -107,66 +104,99 @@ object GPipe: val newValue = oldValue + addValue GIO.write[Int32](layout.ints, mid, newValue) + // Stitch together many upsweep / downsweep program phases recursively @annotation.tailrec - def upsweepPhases(exec: GExecution[ScanParams, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[ScanParams, ScanLayout, ?] = + def upsweepPhases( + exec: GExecution[ScanParams, ScanLayout, ScanLayout], + inSize: Int, + intervalSize: Int, + ): GExecution[ScanParams, ScanLayout, ScanLayout] = if intervalSize >= inSize then exec else val newExec = exec.addProgram(upsweep)(params => ScanParams(inSize, intervalSize), layout => layout) upsweepPhases(newExec, inSize, intervalSize * 2) - val upsweepInitialExec = GExecution[ScanParams, ScanLayout]() - val upsweepExec = upsweepPhases(upsweepInitialExec, 256, 2) - @annotation.tailrec - def downsweepPhases(exec: GExecution[ScanParams, ScanLayout, ?], inSize: Int, intervalSize: Int): GExecution[ScanParams, ScanLayout, ?] = + def downsweepPhases( + exec: GExecution[ScanParams, ScanLayout, ScanLayout], + inSize: Int, + intervalSize: Int, + ): GExecution[ScanParams, ScanLayout, ScanLayout] = if intervalSize < 2 then exec else val newExec = exec.addProgram(downsweep)(params => ScanParams(inSize, intervalSize), layout => layout) downsweepPhases(newExec, inSize, intervalSize / 2) - val downsweepExec = downsweepPhases(upsweepInitialExec, 256, 128) - val scanParams = ScanParams(256, 2) - - // TODO implement compaction - case class CompactParams() - case class CompactLayout(in: GBuffer[C], intIn: GBuffer[Int32], out: GBuffer[C]) extends Layout - val compactedStreamSize: Int = ??? // TODO how to get this out of prefix sum results - - val compactProgram = GProgram[CompactParams, CompactLayout](layout = params => ???, dispatch = (layout, params) => ???): compactLayout => - ??? - val compactExec = GExecution[CompactParams, CompactLayout]() + val initExec = GExecution[ScanParams, ScanLayout]() // no program + val upsweepExec = upsweepPhases(initExec, 256, 2) // add all upsweep phases + val scanExec = downsweepPhases(upsweepExec, 256, 128) // add all downsweep phases - // TODO: connect all the layouts/executions into one - case class PredScanCompactParams(inSize: Int) - case class PredScanCompactLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout + // Stream compaction + case class CompactLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout - // How to "connect" many GExecutions? with flatMap? - val predScanCompactExec = GExecution[PredScanCompactParams, PredScanCompactLayout]() - // predExec - // .flatMap(predLayout => ???) // add downsweepExec - // .flatMap(layout => ???) // add compactExec - val predScanCompactParams = PredScanCompactParams(256) // TODO + val compactProgram = GProgram[Params, CompactLayout]( + layout = params => CompactLayout(in = GBuffer[C](params.inSize), scan = GBuffer[Int32](params.inSize), out = GBuffer[C](params.inSize)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), + ): layout => + val invocId = GIO.invocationId + val element = GIO.read[C](layout.in, invocId) + val prefixSum = GIO.read[Int32](layout.scan, invocId) + val prevScan = when(invocId > 0)(GIO.read[Int32](layout.scan, invocId - 1)).otherwise(prefixSum) + val condt = when(invocId > 0)(when(prevScan < prefixSum)(1: Int32).otherwise(0)).otherwise(when(prefixSum > 0)(1: Int32).otherwise(0)) + val index = when(invocId > 0)(when(prevScan < prefixSum)(prevScan).otherwise(0)).otherwise(0) + GIO.repeat(condt): _ => + GIO.write[C](layout.out, index, element) + + // connect all the layouts/executions into one + case class FilterLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout + + val filterExec = GExecution[Params, FilterLayout]() + // val filterExec = predicateProgram + // .flatMap[ScanLayout, ScanParams]: predLayout => + // scanExec + // .contramap[ScanLayout](layout => ScanLayout(???, ???)) + // .contramapParams[ScanParams](params => ScanParams(???, ???)) + // .map(_ => predLayout) + // .flatMap[FilterLayout, Params]: scanLayout => + // compactProgram + // .contramap[FilterLayout](layout => CompactLayout(???, ???, ???)) + // .contramapParams[Params](params => params) + // .map(_ => scanLayout) + + val filterParams = Params(256) // TODO + + // case class FilterParams(intervalSize: ScanArgs()) + // case class FilterLayout(inBuf: GBuffer[C], scanBuf: GBuffer[Int32]) + // val filterExec = GExecution[FilterParams, FilterLayout]() + // .addProgram(predicateProgram)(params => PredParams(params.inSize), layout => PredLayout(layout.in, layout.pred)) + // .flatMap[FilterLayout, FilterParams](l => + // downsweepExec + // .contramap[FilterLayout](layout => ScanLayout(layout.pred, layout.intervalSize)) + // .contramapParams[FilterParams](p => ScanParams(p.inSize, 2)) + // .map(_ => l) + // ) val region = GBufferRegion - .allocate[PredScanCompactLayout] + .allocate[FilterLayout] .map: layout => - predScanCompactExec.execute(predScanCompactParams, layout) + filterExec.execute(filterParams, layout) val typeSize = typeStride(Tag.apply[C]) val intSize = typeStride(Tag.apply[Int32]) + val params = Params(256) // these are allocated once, reused for many chunks - val predBuf = BufferUtils.createByteBuffer(predParams.inSize * typeSize) - val scanBuf = BufferUtils.createByteBuffer(predParams.inSize * intSize) - val compactBuf = BufferUtils.createByteBuffer(compactedStreamSize * typeSize) + val predBuf = BufferUtils.createByteBuffer(params.inSize * typeSize) + val scanBuf = BufferUtils.createByteBuffer(params.inSize * intSize) + val compactBuf = BufferUtils.createByteBuffer(256 * typeSize) stream - .chunkMin(predScanCompactParams.inSize) + .chunkMin(filterParams.inSize) .flatMap: chunk => bridge.toByteBuffer(predBuf, chunk) region.runUnsafe( - init = PredScanCompactLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf)), + init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf)), onDone = layout => layout.out.read(compactBuf), ) - val arr = bridge.fromByteBuffer(compactBuf, new Array[S](compactedStreamSize)) + val arr = bridge.fromByteBuffer(compactBuf, new Array[S](256)) Stream.emits(arr) From 77deb47c1c70caa87b6ae3b4c47ab70bbfdb2d11 Mon Sep 17 00:00:00 2001 From: spamegg Date: Tue, 26 Aug 2025 17:22:00 +0300 Subject: [PATCH 14/38] finish filter --- .../computenode/cyfra/fs2interop/GPipe.scala | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 85346448..ddab40c1 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -58,12 +58,11 @@ object GPipe: def filter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = (stream: Stream[F, S]) => - case class Params(inSize: Int) // used commonly by many program layouts - // Predicate mapping + case class PredParams(inSize: Int) case class PredLayout(in: GBuffer[C], out: GBuffer[Int32]) extends Layout - val predicateProgram = GProgram[Params, PredLayout]( + val predicateProgram = GProgram[PredParams, PredLayout]( layout = params => PredLayout(in = GBuffer[C](params.inSize), out = GBuffer[Int32](params.inSize)), dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), ): layout => @@ -132,9 +131,10 @@ object GPipe: val scanExec = downsweepPhases(upsweepExec, 256, 128) // add all downsweep phases // Stream compaction + case class CompactParams(inSize: Int) case class CompactLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout - val compactProgram = GProgram[Params, CompactLayout]( + val compactProgram = GProgram[CompactParams, CompactLayout]( layout = params => CompactLayout(in = GBuffer[C](params.inSize), scan = GBuffer[Int32](params.inSize), out = GBuffer[C](params.inSize)), dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), ): layout => @@ -148,54 +148,48 @@ object GPipe: GIO.write[C](layout.out, index, element) // connect all the layouts/executions into one - case class FilterLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout - - val filterExec = GExecution[Params, FilterLayout]() - // val filterExec = predicateProgram - // .flatMap[ScanLayout, ScanParams]: predLayout => - // scanExec - // .contramap[ScanLayout](layout => ScanLayout(???, ???)) - // .contramapParams[ScanParams](params => ScanParams(???, ???)) - // .map(_ => predLayout) - // .flatMap[FilterLayout, Params]: scanLayout => - // compactProgram - // .contramap[FilterLayout](layout => CompactLayout(???, ???, ???)) - // .contramapParams[Params](params => params) - // .map(_ => scanLayout) - - val filterParams = Params(256) // TODO - - // case class FilterParams(intervalSize: ScanArgs()) - // case class FilterLayout(inBuf: GBuffer[C], scanBuf: GBuffer[Int32]) - // val filterExec = GExecution[FilterParams, FilterLayout]() - // .addProgram(predicateProgram)(params => PredParams(params.inSize), layout => PredLayout(layout.in, layout.pred)) - // .flatMap[FilterLayout, FilterParams](l => - // downsweepExec - // .contramap[FilterLayout](layout => ScanLayout(layout.pred, layout.intervalSize)) - // .contramapParams[FilterParams](p => ScanParams(p.inSize, 2)) - // .map(_ => l) - // ) - + case class FilterParams(inSize: Int, intervalSize: Int) + case class FilterLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C], intervalSize: GUniform[ScanArgs]) extends Layout + + val filterExec = GExecution[FilterParams, FilterLayout]() + .addProgram(predicateProgram)( + filterParams => PredParams(filterParams.inSize), + filterLayout => PredLayout(in = filterLayout.in, out = filterLayout.scan), + ) + .flatMap[FilterLayout, FilterParams]: filterLayout => + scanExec + .contramap[FilterLayout]: filterLayout => + ScanLayout(filterLayout.scan, filterLayout.intervalSize) + .contramapParams[FilterParams](filterParams => ScanParams(filterParams.inSize, filterParams.intervalSize)) + .map(scanLayout => filterLayout) + .flatMap[FilterLayout, FilterParams]: filterLayout => + compactProgram + .contramap[FilterLayout]: filterLayout => + CompactLayout(filterLayout.in, filterLayout.scan, filterLayout.out) + .contramapParams[FilterParams](filterParams => CompactParams(filterParams.inSize)) + .map(compactLayout => filterLayout) + + // finally setup buffers, region, parameters, and run the program + val filterParams = FilterParams(256, 2) val region = GBufferRegion .allocate[FilterLayout] - .map: layout => - filterExec.execute(filterParams, layout) + .map: filterLayout => + filterExec.execute(filterParams, filterLayout) val typeSize = typeStride(Tag.apply[C]) val intSize = typeStride(Tag.apply[Int32]) - val params = Params(256) // these are allocated once, reused for many chunks - val predBuf = BufferUtils.createByteBuffer(params.inSize * typeSize) - val scanBuf = BufferUtils.createByteBuffer(params.inSize * intSize) - val compactBuf = BufferUtils.createByteBuffer(256 * typeSize) + val predBuf = BufferUtils.createByteBuffer(filterParams.inSize * typeSize) + val scanBuf = BufferUtils.createByteBuffer(filterParams.inSize * intSize) + val compactBuf = BufferUtils.createByteBuffer(filterParams.inSize * typeSize) stream .chunkMin(filterParams.inSize) .flatMap: chunk => bridge.toByteBuffer(predBuf, chunk) region.runUnsafe( - init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf)), + init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf), intervalSize = ???), onDone = layout => layout.out.read(compactBuf), ) val arr = bridge.fromByteBuffer(compactBuf, new Array[S](256)) From dcfe4f9a9ca711553244a2d256fa950b34a946ed Mon Sep 17 00:00:00 2001 From: spamegg Date: Tue, 26 Aug 2025 17:23:41 +0300 Subject: [PATCH 15/38] small fix --- .../src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index ddab40c1..d3aa531e 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -189,7 +189,7 @@ object GPipe: .flatMap: chunk => bridge.toByteBuffer(predBuf, chunk) region.runUnsafe( - init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf), intervalSize = ???), + init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf), intervalSize = GUniform()), onDone = layout => layout.out.read(compactBuf), ) val arr = bridge.fromByteBuffer(compactBuf, new Array[S](256)) From 0f92e5d4360a95f86348f277d42fff274e972ba5 Mon Sep 17 00:00:00 2001 From: spamegg Date: Wed, 27 Aug 2025 14:26:32 +0300 Subject: [PATCH 16/38] implement GIO.when and use for-expression in compactProgram --- .../scala/io/computenode/cyfra/dsl/gio/GIO.scala | 6 ++++++ .../io/computenode/cyfra/fs2interop/GPipe.scala | 13 +++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala index 02a018f8..cbac2470 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala @@ -6,6 +6,7 @@ import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr import io.computenode.cyfra.dsl.binding.{GBuffer, ReadBuffer, WriteBuffer} import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.gio.GIO.* +import io.computenode.cyfra.dsl.control.When import izumi.reflect.Tag trait GIO[T]: @@ -35,6 +36,11 @@ object GIO: def repeat(n: Int32)(f: Int32 => GIO[?]): GIO[Unit] = Repeat(n, f) + def when(cond: GBoolean)(thenCode: GIO[?]): GIO[Unit] = + val n = When.when(cond)(1: Int32).otherwise(0) + repeat(n): _ => + thenCode + def write[T <: Value](buffer: GBuffer[T], index: Int32, value: T): GIO[Unit] = WriteBuffer(buffer, index, value) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index d3aa531e..00553c01 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -142,10 +142,15 @@ object GPipe: val element = GIO.read[C](layout.in, invocId) val prefixSum = GIO.read[Int32](layout.scan, invocId) val prevScan = when(invocId > 0)(GIO.read[Int32](layout.scan, invocId - 1)).otherwise(prefixSum) - val condt = when(invocId > 0)(when(prevScan < prefixSum)(1: Int32).otherwise(0)).otherwise(when(prefixSum > 0)(1: Int32).otherwise(0)) - val index = when(invocId > 0)(when(prevScan < prefixSum)(prevScan).otherwise(0)).otherwise(0) - GIO.repeat(condt): _ => - GIO.write[C](layout.out, index, element) + for + _ <- GIO.when(invocId > 0): + val prevScan = GIO.read[Int32](layout.scan, invocId - 1) + GIO.when(prevScan < prefixSum): + GIO.write(layout.out, prevScan, element) + _ <- GIO.when(invocId === 0): + GIO.when(prefixSum > 0): + GIO.write(layout.out, invocId, element) + yield () // connect all the layouts/executions into one case class FilterParams(inSize: Int, intervalSize: Int) From 2840c28e873c835777c5a83dbf2f9ecfb95058a5 Mon Sep 17 00:00:00 2001 From: spamegg Date: Wed, 27 Aug 2025 14:40:31 +0300 Subject: [PATCH 17/38] small cleanup --- .../src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 00553c01..cd61a423 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -141,7 +141,6 @@ object GPipe: val invocId = GIO.invocationId val element = GIO.read[C](layout.in, invocId) val prefixSum = GIO.read[Int32](layout.scan, invocId) - val prevScan = when(invocId > 0)(GIO.read[Int32](layout.scan, invocId - 1)).otherwise(prefixSum) for _ <- GIO.when(invocId > 0): val prevScan = GIO.read[Int32](layout.scan, invocId - 1) From 40aa8e2892055cc09e5672985f1cb83baa57aa91 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 3 Sep 2025 20:14:31 +0200 Subject: [PATCH 18/38] more^ --- .../cyfra/runtime/ExecutionHandler.scala | 2 +- .../cyfra/runtime/PendingExecution.scala | 22 ++++++++++--------- .../cyfra/runtime/VkAllocation.scala | 2 +- .../cyfra/vulkan/command/Semaphore.scala | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index a20efbbb..89473f8c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -74,7 +74,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val externalBindings = (summon[LayoutBinding[EL]].toBindings(layout) ++ summon[LayoutBinding[RL]].toBindings(result)) .map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) - val pe = new PendingExecution(commandBuffer, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, deps, cleanup) + val pe = new PendingExecution(commandBuffer, deps, cleanup) externalBindings.foreach(_.execution = Left(pe)) result diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index b213c0c9..63cf516f 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -5,14 +5,13 @@ import io.computenode.cyfra.vulkan.core.{Device, Queue} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject import org.lwjgl.vulkan.VK13.{VK_PIPELINE_STAGE_2_COPY_BIT, vkQueueSubmit2} -import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} +import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} import scala.collection.mutable -class PendingExecution(protected val handle: VkCommandBuffer, val waitStage: Long, val dependencies: Seq[PendingExecution], cleanup: () => Unit)( - using Device, -) extends VulkanObject[VkCommandBuffer]: - private val semaphore: Semaphore = Semaphore(waitStage) +class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device) + extends VulkanObject[VkCommandBuffer]: + private val semaphore: Semaphore = Semaphore() private var fence: Option[Fence] = None override protected def close(): Unit = cleanup() @@ -35,10 +34,13 @@ class PendingExecution(protected val handle: VkCommandBuffer, val waitStage: Lon object PendingExecution: def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => - val gathered = executions.flatMap(_.gatherForSubmission).toSet.groupMap(_._2)(_._1).toSeq + val exec = + val gathered = executions.flatMap(_.gatherForSubmission) + val ordering = gathered.zipWithIndex.map(x => (x._1._1._1, x._2)).toMap + gathered.toSet.groupMap(_._2)(_._1).toSeq.sortBy(x => x._2.map(_._1).map(ordering).min) - val submitInfos = VkSubmitInfo2.calloc(gathered.size, stack) - gathered.foreach: (semaphores, executions) => + val submitInfos = VkSubmitInfo2.calloc(exec.size, stack) + exec.foreach: (semaphores, executions) => val pCommandBuffersSI = VkCommandBufferSubmitInfo.calloc(executions.size, stack) val signalSemaphoreSI = VkSemaphoreSubmitInfo.calloc(executions.size, stack) executions.foreach: (cb, s) => @@ -51,7 +53,7 @@ object PendingExecution: .get() .sType$Default() .semaphore(s.get) - .stageMask(s.stage) + .stageMask(VK13.VK_PIPELINE_STAGE_2_COPY_BIT | VK13.VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) pCommandBuffersSI.flip() signalSemaphoreSI.flip() @@ -62,7 +64,7 @@ object PendingExecution: .get() .sType$Default() .semaphore(s.get) - .stageMask(s.stage) + .stageMask(VK13.VK_PIPELINE_STAGE_2_COPY_BIT | VK13.VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) waitSemaphoreSI.flip() diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a851389e..4ce9692c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -53,7 +53,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) val cleanup = () => commandPool.freeCommandBuffer(cb) stagingBuffer.destroy() - val pe = new PendingExecution(cb, VK_PIPELINE_STAGE_2_COPY_BIT, binding.execution.fold(Seq(_), _.toSeq), cleanup) + val pe = new PendingExecution(cb, binding.execution.fold(Seq(_), _.toSeq), cleanup) binding.execution = Left(pe) case _ => throw new IllegalArgumentException(s"Tried to write to non-VkBinding $buffer") diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala index 88386710..2e86ef68 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala @@ -9,7 +9,7 @@ import org.lwjgl.vulkan.VkSemaphoreCreateInfo /** @author * MarconZet Created 30.10.2019 */ -private[cyfra] class Semaphore(val stage: Long)(using device: Device) extends VulkanObjectHandle: +private[cyfra] class Semaphore()(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val semaphoreCreateInfo = VkSemaphoreCreateInfo .calloc(stack) From 1a01f99d2f8d596e5ee7220f7563bd4b0da6d12e Mon Sep 17 00:00:00 2001 From: Szymon Date: Sat, 6 Sep 2025 22:33:41 +0200 Subject: [PATCH 19/38] No warnings now --- .../io/computenode/cyfra/spirv/Context.scala | 2 +- .../cyfra/spirv/compilers/DSLCompiler.scala | 5 +- .../spirv/compilers/GStructCompiler.scala | 14 +++- .../compilers/SpirvProgramCompiler.scala | 81 +++++++++++-------- .../cyfra/e2e/RuntimeEnduranceTest.scala | 8 +- 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala index 8e8f5f96..62daf3d1 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala @@ -25,7 +25,7 @@ private[cyfra] case class Context( nextResultId: Int = HEADER_REFS_TOP, nextBinding: Int = 0, exprNames: Map[Int, String] = Map(), - memberNames: Map[Int, String] = Map(), + names: Set[String] = Set(), functions: Map[FnIdentifier, SprivFunction] = Map(), ): def joinNested(ctx: Context): Context = diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala index 09dd9999..84252ca7 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala @@ -79,7 +79,6 @@ private[cyfra] object DSLCompiler: case (_: GUniform[?], _) => false .asInstanceOf[(List[(GBuffer[?], Int)], List[(GUniform[?], Int)])] val uniforms = uniformsWithIndices.map(_._1) - val buffers = buffersWithIndices.map(_._1) val uniformSchemas = uniforms.map(_.schema) val structsInCode = (allExprs.collect { @@ -87,8 +86,8 @@ private[cyfra] object DSLCompiler: case gf: GetField[?, ?] => gf.resultSchema } ::: uniformSchemas).distinct val (structDefs, structCtx) = defineStructTypes(structsInCode, typedContext) - val structNames = getStructNames(structsInCode, structCtx) - val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffersWithIndices, structCtx) + val (structNames, structNamesCtx) = getStructNames(structsInCode, structCtx) + val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffersWithIndices, structNamesCtx) val (uniformStructDecorations, uniformStructInsns, uniformStructContext) = createAndInitUniformBlocks(uniformsWithIndices, uniformContext) val blockNames = getBlockNames(uniformContext, uniforms) val (inputDefs, inputContext) = createInvocationId(uniformStructContext) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala index fe3faacc..2542e1f8 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala @@ -29,13 +29,19 @@ private[cyfra] object GStructCompiler: ) } - def getStructNames(schemas: List[GStructSchema[?]], context: Context): List[Words] = - schemas.flatMap { schema => - val structName = schema.structTag.tag.shortName + def getStructNames(schemas: List[GStructSchema[?]], context: Context): (List[Words], Context) = + schemas.distinctBy(_.structTag).foldLeft((List.empty[Words], context)) { case ((wordsAcc, currCtx), schema) => + var structName = schema.structTag.tag.shortName + var nameSuffix = 0 + while currCtx.names.contains(structName) do + structName = s"${schema.structTag.tag.shortName}_$nameSuffix" + nameSuffix += 1 val structType = context.valueTypeMap(schema.structTag.tag) - Instruction(Op.OpName, List(ResultRef(structType), Text(structName))) :: schema.fields.zipWithIndex.map { case ((name, _, tag), i) => + val words = Instruction(Op.OpName, List(ResultRef(structType), Text(structName))) :: schema.fields.zipWithIndex.map { case ((name, _, tag), i) => Instruction(Op.OpMemberName, List(ResultRef(structType), IntWord(i), Text(name))) } + val updatedCtx = currCtx.copy(names = currCtx.names + structName) + (wordsAcc ::: words, updatedCtx) } private def sortSchemasDag(schemas: List[GStructSchema[?]]): List[GStructSchema[?]] = diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index 997c93b2..f9f86447 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -45,18 +45,6 @@ private[cyfra] object SpirvProgramCompiler: val (vars, nonVarsBody) = bubbleUpVars(body) val end = List( -// TODO Remove - Write is a part of GIO now -// Instruction( -// Op.OpAccessChain, -// List( -// ResultRef(codeCtx.uniformPointerMap(codeCtx.valueTypeMap(resultType.tag))), -// ResultRef(codeCtx.nextResultId), -// ResultRef(codeCtx.outBufferBlocks.head.blockVarRef), -// ResultRef(codeCtx.constRefs((Int32Tag, 0))), -// ResultRef(codeCtx.workerIndexRef), -// ), -// ), -// Instruction(Op.OpStore, List(ResultRef(codeCtx.nextResultId), ResultRef(codeCtx.exprRefs(tree.tree.treeid)))), Instruction(Op.OpReturn, List()), Instruction(Op.OpFunctionEnd, List()), ) @@ -128,21 +116,45 @@ private[cyfra] object SpirvProgramCompiler: (blockDecor, voidsDef ::: blockDef, voidCtx) def createAndInitBlocks(blocks: List[(GBuffer[?], Int)], context: Context): (List[Words], List[Words], Context) = + var membersVisited = Set[Int]() + var structsVisited = Set[Int]() val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), (buff, binding)) => val tpe = buff.tag val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, binding) - val decorationInstructions = List[Words]( - Instruction(Op.OpDecorate, List(ResultRef(block.memberArrayTypeRef), Decoration.ArrayStride, IntWord(typeStride(tpe)))), // OpDecorate %_runtimearr_X ArrayStride [typeStride(type)] - Instruction(Op.OpMemberDecorate, List(ResultRef(block.structTypeRef), IntWord(0), Decoration.Offset, IntWord(0))), // OpMemberDecorate %BufferX 0 Offset 0 - Instruction(Op.OpDecorate, List(ResultRef(block.structTypeRef), Decoration.BufferBlock)), // OpDecorate %BufferX BufferBlock + val (structDecoration, structDefinition) = if structsVisited.contains(block.structTypeRef) then + (Nil, Nil) + else + structsVisited += block.structTypeRef + ( + List( + Instruction(Op.OpMemberDecorate, List(ResultRef(block.structTypeRef), IntWord(0), Decoration.Offset, IntWord(0))), // OpMemberDecorate %BufferX 0 Offset 0 + Instruction(Op.OpDecorate, List(ResultRef(block.structTypeRef), Decoration.BufferBlock)), // OpDecorate %BufferX BufferBlock + ), + List( + Instruction(Op.OpTypeStruct, List(ResultRef(block.structTypeRef), IntWord(block.memberArrayTypeRef))), // %BufferX = OpTypeStruct %_runtimearr_X + ) + ) + + val (memberDecoration, memberDefinition) = if membersVisited.contains(block.memberArrayTypeRef) then + (Nil, Nil) + else + membersVisited += block.memberArrayTypeRef + ( + List( + Instruction(Op.OpDecorate, List(ResultRef(block.memberArrayTypeRef), Decoration.ArrayStride, IntWord(typeStride(tpe)))), // OpDecorate %_runtimearr_X ArrayStride [typeStride(type)] + ), + List( + Instruction(Op.OpTypeRuntimeArray, List(ResultRef(block.memberArrayTypeRef), IntWord(context.valueTypeMap(tpe.tag)))), // %_runtimearr_X = OpTypeRuntimeArray %[typeOf(tpe)] + ) + ) + + val decorationInstructions = memberDecoration ::: structDecoration ::: List[Words]( Instruction(Op.OpDecorate, List(ResultRef(block.blockVarRef), Decoration.DescriptorSet, IntWord(0))), // OpDecorate %_X DescriptorSet 0 Instruction(Op.OpDecorate, List(ResultRef(block.blockVarRef), Decoration.Binding, IntWord(block.binding))), // OpDecorate %_X Binding [binding] ) - val definitionInstructions = List[Words]( - Instruction(Op.OpTypeRuntimeArray, List(ResultRef(block.memberArrayTypeRef), IntWord(context.valueTypeMap(tpe.tag)))), // %_runtimearr_X = OpTypeRuntimeArray %[typeOf(tpe)] - Instruction(Op.OpTypeStruct, List(ResultRef(block.structTypeRef), IntWord(block.memberArrayTypeRef))), // %BufferX = OpTypeStruct %_runtimearr_X + val definitionInstructions = memberDefinition ::: structDefinition ::: List[Words]( Instruction(Op.OpTypePointer, List(ResultRef(block.blockPointerRef), StorageClass.Uniform, ResultRef(block.structTypeRef))), // %_ptr_Uniform_BufferX= OpTypePointer Uniform %BufferX Instruction(Op.OpVariable, List(ResultRef(block.blockPointerRef), ResultRef(block.blockVarRef), StorageClass.Uniform)), // %_X = OpVariable %_ptr_Uniform_X Uniform ) @@ -174,22 +186,26 @@ private[cyfra] object SpirvProgramCompiler: typeStride(t) .sum - def createAndInitUniformBlocks(schemas: List[(GUniform[?], Int)], ctx: Context): (List[Words], List[Words], Context) = + def createAndInitUniformBlocks(schemas: List[(GUniform[?], Int)], ctx: Context): (List[Words], List[Words], Context) = { + var decoratedOffsets = Set[Int]() schemas.foldLeft((List.empty[Words], List.empty[Words], ctx)) { case ((decorationsAcc, definitionsAcc, currentCtx), (uniform, binding)) => val schema = uniform.schema val uniformStructTypeRef = currentCtx.valueTypeMap(schema.structTag.tag) - val (offsetDecorations, _) = schema.fields.zipWithIndex.foldLeft[(List[Words], Int)](List.empty[Words], 0): - case ((acc, offset), ((name, fromExpr, tag), idx)) => - val stride = - if tag <:< schema.gStructTag then - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - else typeStride(tag) - val offsetDecoration = Instruction(Op.OpMemberDecorate, List(ResultRef(uniformStructTypeRef), IntWord(idx), Decoration.Offset, IntWord(offset))) - (acc :+ offsetDecoration, offset + stride) - - val uniformBlockDecoration = Instruction(Op.OpDecorate, List(ResultRef(uniformStructTypeRef), Decoration.Block)) + val structDecorations = + if decoratedOffsets.contains(uniformStructTypeRef) then Nil + else + decoratedOffsets += uniformStructTypeRef + schema.fields.zipWithIndex.foldLeft[(List[Words], Int)](List.empty[Words], 0): + case ((acc, offset), ((name, fromExpr, tag), idx)) => + val stride = + if tag <:< schema.gStructTag then + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + else typeStride(tag) + val offsetDecoration = Instruction(Op.OpMemberDecorate, List(ResultRef(uniformStructTypeRef), IntWord(idx), Decoration.Offset, IntWord(offset))) + (acc :+ offsetDecoration, offset + stride) + ._1 ::: List(Instruction(Op.OpDecorate, List(ResultRef(uniformStructTypeRef), Decoration.Block))) val uniformPointerUniformRef = currentCtx.nextResultId val uniformPointerUniform = @@ -201,7 +217,7 @@ private[cyfra] object SpirvProgramCompiler: val uniformDecorateDescriptorSet = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.DescriptorSet, IntWord(0))) val uniformDecorateBinding = Instruction(Op.OpDecorate, List(ResultRef(uniformVarRef), Decoration.Binding, IntWord(binding))) - val newDecorations = decorationsAcc ::: offsetDecorations ::: List(uniformDecorateDescriptorSet, uniformDecorateBinding, uniformBlockDecoration) + val newDecorations = decorationsAcc ::: structDecorations ::: List(uniformDecorateDescriptorSet, uniformDecorateBinding) val newDefinitions = definitionsAcc ::: List(uniformPointerUniform, uniformVar) val newCtx = currentCtx.copy( nextResultId = currentCtx.nextResultId + 2, @@ -212,6 +228,7 @@ private[cyfra] object SpirvProgramCompiler: (newDecorations, newDefinitions, newCtx) } + } val predefinedConsts = List((Int32Tag, 0), (UInt32Tag, 0), (Int32Tag, 1)) def defineConstants(exprs: List[E[?]], ctx: Context): (List[Words], Context) = diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala index 4d002c19..dec27507 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala @@ -10,10 +10,13 @@ import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.struct.GStruct.Empty import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.spirvtools.{SpirvCross, SpirvDisassembler, SpirvToolsRunner} +import io.computenode.cyfra.spirvtools.SpirvTool.ToFile import io.computenode.cyfra.utility.Logger.logger import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import java.nio.file.Paths import scala.concurrent.ExecutionContext.Implicits.global import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.{Await, Future} @@ -174,7 +177,10 @@ class RuntimeEnduranceTest extends munit.FunSuite: def runEnduranceTest(nRuns: Int): Unit = logger.info(s"Starting endurance test with ${nRuns} runs...") - given runtime: VkCyfraRuntime = VkCyfraRuntime() + given runtime: VkCyfraRuntime = VkCyfraRuntime( + spirvToolsRunner = SpirvToolsRunner( + crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), + disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/dis.spvdis"))))) val bufferSize = 1280 val params = AddProgramParams(bufferSize, addA = 0, addB = 1) From c5d7da0cc84779108d324de177bc54730e286902 Mon Sep 17 00:00:00 2001 From: Szymon Date: Sun, 7 Sep 2025 18:47:49 +0200 Subject: [PATCH 20/38] Rebase & adjustments --- .../io/computenode/cyfra/spirv/Context.scala | 1 + .../cyfra/spirv/SpirvConstants.scala | 5 +- .../cyfra/spirv/compilers/DSLCompiler.scala | 23 +++++- .../cyfra/spirv/compilers/GIOCompiler.scala | 16 ++++ .../compilers/SpirvProgramCompiler.scala | 16 ++++ .../cyfra/core/layout/LayoutStruct.scala | 4 +- .../io/computenode/cyfra/dsl/gio/GIO.scala | 8 +- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 23 ++++-- .../computenode/cyfra/fs2interop/GPipe.scala | 51 ++++++++----- .../cyfra/runtime/ExecutionHandler.scala | 69 ++++++++++++----- .../cyfra/runtime/VkAllocation.scala | 2 +- .../cyfra/vulkan/VulkanContext.scala | 2 +- .../cyfra/vulkan/core/Instance.scala | 76 +++++++++++++++++-- 13 files changed, 237 insertions(+), 59 deletions(-) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala index 62daf3d1..0312d9db 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala @@ -27,6 +27,7 @@ private[cyfra] case class Context( exprNames: Map[Int, String] = Map(), names: Set[String] = Set(), functions: Map[FnIdentifier, SprivFunction] = Map(), + stringLiterals: Map[String, Int] = Map() ): def joinNested(ctx: Context): Context = this.copy(nextResultId = ctx.nextResultId, exprNames = ctx.exprNames ++ this.exprNames, functions = ctx.functions ++ this.functions) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala index 6711afff..00a3f615 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala @@ -9,10 +9,13 @@ private[cyfra] object SpirvConstants: val BOUND_VARIABLE = "bound" val GLSL_EXT_NAME = "GLSL.std.450" + val NON_SEMANTIC_DEBUG_PRINTF = "NonSemantic.DebugPrintf" val GLSL_EXT_REF = 1 val TYPE_VOID_REF = 2 val VOID_FUNC_TYPE_REF = 3 val MAIN_FUNC_REF = 4 val GL_GLOBAL_INVOCATION_ID_REF = 5 val GL_WORKGROUP_SIZE_REF = 6 - val HEADER_REFS_TOP = 7 + val DEBUG_PRINTF_REF = 7 + + val HEADER_REFS_TOP = 8 diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala index 84252ca7..f5975295 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala @@ -31,7 +31,7 @@ private[cyfra] object DSLCompiler: pending match case Nil => acc case GIO.Pure(v) :: tail => - getAllExprsFlattened(tail, getAllExprsFlattened(v.tree, visitDetached), visitDetached) + getAllExprsFlattened(tail, getAllExprsFlattened(v.tree, visitDetached) ::: acc, visitDetached) case GIO.FlatMap(v, n) :: tail => getAllExprsFlattened(v :: n :: tail, acc, visitDetached) case GIO.Repeat(n, gio) :: tail => @@ -44,6 +44,9 @@ private[cyfra] object DSLCompiler: case WriteUniform(_, value) :: tail => val valueAllExprs = getAllExprsFlattened(value.tree, visitDetached) getAllExprsFlattened(tail, valueAllExprs ::: acc, visitDetached) + case GIO.Printf(_, args*) :: tail => + val argsAllExprs = args.flatMap(a => getAllExprsFlattened(a.tree, visitDetached)).toList + getAllExprsFlattened(tail, argsAllExprs ::: acc, visitDetached) // TODO: Not traverse same fn scopes for each fn call private def getAllExprsFlattened(root: E[?], visitDetached: Boolean): List[E[?]] = @@ -68,12 +71,26 @@ private[cyfra] object DSLCompiler: allScopesCache(root.treeid) = result result + // So far only used for printf + private def getAllStrings(pending: List[GIO[?]], acc: Set[String]): Set[String] = + pending match + case Nil => acc + case GIO.FlatMap(v, n) :: tail => + getAllStrings(v :: n :: tail, acc) + case GIO.Repeat(_, gio) :: tail => + getAllStrings(gio :: tail, acc) + case GIO.Printf(format, _*) :: tail => + getAllStrings(tail, acc + format) + case _ :: tail => getAllStrings(tail, acc) + def compile(bodyIo: GIO[?], bindings: List[GBinding[?]]): ByteBuffer = val allExprs = getAllExprsFlattened(List(bodyIo), Nil, visitDetached = true) val typesInCode = allExprs.map(_.tag).distinct val allTypes = (typesInCode ::: bindings.map(_.tag)).distinct def scalarTypes = allTypes.filter(_.tag <:< summon[Tag[Scalar]].tag) val (typeDefs, typedContext) = defineScalarTypes(scalarTypes, Context.initialContext) + val allStrings = getAllStrings(List(bodyIo), Set.empty) + val (stringDefs, ctxWithStrings) = defineStrings(allStrings.toList, typedContext) val (buffersWithIndices, uniformsWithIndices) = bindings.zipWithIndex.partition: case (_: GBuffer[?], _) => true case (_: GUniform[?], _) => false @@ -85,7 +102,7 @@ private[cyfra] object DSLCompiler: case cs: ComposeStruct[?] => cs.resultSchema case gf: GetField[?, ?] => gf.resultSchema } ::: uniformSchemas).distinct - val (structDefs, structCtx) = defineStructTypes(structsInCode, typedContext) + val (structDefs, structCtx) = defineStructTypes(structsInCode, ctxWithStrings) val (structNames, structNamesCtx) = getStructNames(structsInCode, structCtx) val (decorations, uniformDefs, uniformContext) = initAndDecorateBuffers(buffersWithIndices, structNamesCtx) val (uniformStructDecorations, uniformStructInsns, uniformStructContext) = createAndInitUniformBlocks(uniformsWithIndices, uniformContext) @@ -98,7 +115,7 @@ private[cyfra] object DSLCompiler: val nameDecorations = getNameDecorations(ctxWithFnDefs) val code: List[Words] = - SpirvProgramCompiler.headers ::: blockNames ::: nameDecorations ::: structNames ::: SpirvProgramCompiler.workgroupDecorations ::: + SpirvProgramCompiler.headers ::: stringDefs ::: blockNames ::: nameDecorations ::: structNames ::: SpirvProgramCompiler.workgroupDecorations ::: decorations ::: uniformStructDecorations ::: typeDefs ::: structDefs ::: fnTypeDefs ::: uniformDefs ::: uniformStructInsns ::: inputDefs ::: constDefs ::: varDefs ::: main ::: fnDefs diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala index dd1d853c..231d8f97 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -5,6 +5,7 @@ import io.computenode.cyfra.spirv.Context import io.computenode.cyfra.spirv.Opcodes.* import io.computenode.cyfra.dsl.binding.* import io.computenode.cyfra.dsl.gio.GIO.CurrentRepeatIndex +import io.computenode.cyfra.spirv.SpirvConstants.{DEBUG_PRINTF_REF, TYPE_VOID_REF} import io.computenode.cyfra.spirv.SpirvTypes.{GBooleanTag, Int32Tag, LInt32Tag} object GIOCompiler: @@ -125,6 +126,21 @@ object GIOCompiler: (acc ::: nInsts ::: preheader ::: header ::: bodyBlk ::: contBlk ::: mergeBlk, finalCtx) + case GIO.Printf(format, args*) => + val (argsInsts, ctxAfterArgs) = args.foldLeft((List.empty[Words], ctx)) { case ((instsAcc, cAcc), arg) => + val (argInsts, cAfterArg) = ExpressionCompiler.compileBlock(arg.tree, cAcc) + (instsAcc ::: argInsts, cAfterArg) + } + val argResults = args.map(a => ResultRef(ctxAfterArgs.exprRefs(a.tree.treeid))).toList + val printf = Instruction(Op.OpExtInst, List( + ResultRef(TYPE_VOID_REF), + ResultRef(ctxAfterArgs.nextResultId), + ResultRef(DEBUG_PRINTF_REF), + IntWord(1), + ResultRef(ctx.stringLiterals(format)), + ) ::: argResults) + (acc ::: argsInsts ::: List(printf), ctxAfterArgs.copy(nextResultId = ctxAfterArgs.nextResultId + 1)) + diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index f9f86447..ee523d35 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -74,7 +74,9 @@ private[cyfra] object SpirvProgramCompiler: WordVariable(BOUND_VARIABLE) :: // Bound: To be calculated Word(Array(0x00, 0x00, 0x00, 0x00)) :: // Schema: 0 Instruction(Op.OpCapability, List(Capability.Shader)) :: // OpCapability Shader + Instruction(Op.OpExtension, List(Text("SPV_KHR_non_semantic_info"))) :: // OpExtension "SPV_KHR_non_semantic_info" Instruction(Op.OpExtInstImport, List(ResultRef(GLSL_EXT_REF), Text(GLSL_EXT_NAME))) :: // OpExtInstImport "GLSL.std.450" + Instruction(Op.OpExtInstImport, List(ResultRef(DEBUG_PRINTF_REF), Text(NON_SEMANTIC_DEBUG_PRINTF))) :: // OpExtInstImport "NonSemantic.DebugPrintf" Instruction(Op.OpMemoryModel, List(AddressingModel.Logical, MemoryModel.GLSL450)) :: // OpMemoryModel Logical GLSL450 Instruction(Op.OpEntryPoint, List(ExecutionModel.GLCompute, ResultRef(MAIN_FUNC_REF), Text("main"), ResultRef(GL_GLOBAL_INVOCATION_ID_REF))) :: // OpEntryPoint GLCompute %MAIN_FUNC_REF "main" %GL_GLOBAL_INVOCATION_ID_REF Instruction(Op.OpExecutionMode, List(ResultRef(MAIN_FUNC_REF), ExecutionMode.LocalSize, IntWord(256), IntWord(1), IntWord(1))) :: // OpExecutionMode %4 LocalSize 128 1 1 @@ -185,6 +187,20 @@ private[cyfra] object SpirvProgramCompiler: case (_, _, t) => typeStride(t) .sum + + def defineStrings(strings: List[String], ctx: Context): (List[Words], Context) = + strings.foldLeft((List.empty[Words], ctx)): + case ((insnsAcc, currentCtx), str) => + if currentCtx.stringLiterals.contains(str) then + (insnsAcc, currentCtx) + else + val strRef = currentCtx.nextResultId + val strInsns = List( + Instruction(Op.OpString, List(ResultRef(strRef), Text(str))), + ) + val newCtx = currentCtx.copy(stringLiterals = currentCtx.stringLiterals + (str -> strRef), nextResultId = currentCtx.nextResultId + 1) + (insnsAcc ::: strInsns, newCtx) + def createAndInitUniformBlocks(schemas: List[(GUniform[?], Int)], ctx: Context): (List[Words], List[Words], Context) = { var decoratedOffsets = Set[Int]() diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala index 0703bbbb..76d8ffa8 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala @@ -59,14 +59,14 @@ object LayoutStruct: (tpe, ftype) match case ('[type t <: Value; t], '[type tg <: GBuffer[?]; tg]) => '{ - BufferRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }) + BufferRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using ${ tag.asExprOf[Tag[t]] }, ${ fromExpr.asExprOf[FromExpr[t]] }) } case ('[type t <: GStruct[?]; t], '[type tg <: GUniform[?]; tg]) => val structSchema = Expr.summon[GStructSchema[t]] match case Some(s) => s case None => report.errorAndAbort(s"Cannot summon GStructSchema for type") '{ - UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using summon[Tag[t]], ${ fromExpr.asExprOf[FromExpr[t]] }, ${ structSchema }) + UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using ${ tag.asExprOf[Tag[t]] }, ${ fromExpr.asExprOf[FromExpr[t]] }, ${ structSchema }) } val constructor = sym.primaryConstructor diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala index 9d5c5220..0448fcf0 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala @@ -29,6 +29,9 @@ object GIO: // TODO repeat that collects results case class Repeat(n: Int32, f: GIO[?]) extends GIO[Empty]: override def underlying: Empty = Empty() + + case class Printf(format: String, args: Value*) extends GIO[Empty]: + override def underlying: Empty = Empty() def pure[T <: Value](value: T): GIO[T] = Pure(value) @@ -42,8 +45,11 @@ object GIO: def write[T <: Value](buffer: GBuffer[T], index: Int32, value: T): GIO[Empty] = WriteBuffer(buffer, index, value) + + def printf(format: String, args: Value*): GIO[Empty] = + Printf(s"|$format", args*) - def when(cond: GBoolean)(thenCode: GIO[?]): GIO[Unit] = + def when(cond: GBoolean)(thenCode: GIO[?]): GIO[Empty] = val n = When.when(cond)(1: Int32).otherwise(0) repeat(n): _ => thenCode diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index daba84cd..941ba3cc 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -1,12 +1,19 @@ package io.computenode.cyfra.e2e.fs2interop -import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA -import io.computenode.cyfra.dsl.{*, given}, algebra.VectorAlgebra -import io.computenode.cyfra.fs2interop.*, Bridge.given +import io.computenode.cyfra.core.archive.* +import mem.* +import GMem.fRGBA +import io.computenode.cyfra.dsl.{*, given} +import algebra.VectorAlgebra +import io.computenode.cyfra.fs2interop.* +import Bridge.given import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.runtime.VkCyfraRuntime - import fs2.{io as fs2io, *} +import _root_.io.computenode.cyfra.spirvtools.{SpirvCross, SpirvDisassembler, SpirvToolsRunner} +import _root_.io.computenode.cyfra.spirvtools.SpirvTool.ToFile + +import java.nio.file.Paths extension (f: fRGBA) def neg = (-f._1, -f._2, -f._3, -f._4) @@ -16,7 +23,13 @@ extension (f: fRGBA) Math.abs(f._1 - g._1) < eps && Math.abs(f._2 - g._2) < eps && Math.abs(f._3 - g._3) < eps && Math.abs(f._4 - g._4) < eps class Fs2Tests extends munit.FunSuite: - given cr: CyfraRuntime = VkCyfraRuntime() + given cr: CyfraRuntime = VkCyfraRuntime( + spirvToolsRunner = SpirvToolsRunner( + crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), + disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/disassembled.spv"))) + ) + ) + test("fs2 through GPipe map, just ints"): val inSeq = (0 until 256).toSeq diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index cd61a423..77ceb323 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -1,11 +1,17 @@ package io.computenode.cyfra.fs2interop -import io.computenode.cyfra.core.{Allocation, layout}, layout.Layout +import io.computenode.cyfra.core.{Allocation, layout} +import layout.Layout import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} -import io.computenode.cyfra.dsl.{*, given}, gio.GIO, binding.{GBuffer, GUniform, GBinding} +import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.core.layout.LayoutBinding +import io.computenode.cyfra.core.layout.LayoutStruct +import gio.GIO +import binding.{GBinding, GBuffer, GUniform} import io.computenode.cyfra.spirv.SpirvTypes.typeStride -import struct.GStruct, GStruct.Empty, Empty.given - +import struct.GStruct +import GStruct.Empty +import Empty.given import fs2.* import java.nio.ByteBuffer import org.lwjgl.BufferUtils @@ -14,7 +20,7 @@ import izumi.reflect.Tag import scala.reflect.ClassTag object GPipe: - def map[F[_], C1 <: Value: FromExpr: Tag, C2 <: Value: FromExpr: Tag, S1: ClassTag, S2: ClassTag]( + def map[F[_], C1 <: Value: {FromExpr, Tag}, C2 <: Value: {FromExpr, Tag}, S1: ClassTag, S2: ClassTag]( f: C1 => C2, )(using cr: CyfraRuntime, bridge1: Bridge[C1, S1], bridge2: Bridge[C2, S2]): Pipe[F, S1, S2] = (stream: Stream[F, S1]) => @@ -27,11 +33,16 @@ object GPipe: val gProg = GProgram[Params, PLayout]( layout = params => PLayout(in = GBuffer[C1](params.inSize), out = GBuffer[C2](params.inSize)), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), - ): layout => + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / 256, 1, 1)), + )(layout => { val invocId = GIO.invocationId val element = GIO.read[C1](layout.in, invocId) - GIO.write[C2](layout.out, invocId, f(element)) + val res = f(element) + for + _ <- GIO.printf("Element %d -> %v4f", invocId, res) + _ <- GIO.write[C2](layout.out, invocId, res) + yield Empty() + }) val execution = GExecution[Params, PLayout]() .addProgram(gProg)(params => Params(params.inSize), layout => PLayout(layout.in, layout.out)) @@ -64,21 +75,24 @@ object GPipe: val predicateProgram = GProgram[PredParams, PredLayout]( layout = params => PredLayout(in = GBuffer[C](params.inSize), out = GBuffer[Int32](params.inSize)), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / 256, 1, 1)), ): layout => val invocId = GIO.invocationId val element = GIO.read[C](layout.in, invocId) val result = when(pred(element))(1: Int32).otherwise(0) - GIO.write[Int32](layout.out, invocId, result) + for + _ <- GIO.printf("Element %d -> %d", invocId, result) + _ <- GIO.write[Int32](layout.out, invocId, result) + yield Empty() // Prefix sum (inclusive), upsweep/downsweep case class ScanParams(inSize: Int, intervalSize: Int) case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] - case class ScanLayout(ints: GBuffer[Int32], intervalSize: GUniform[ScanArgs]) extends Layout + case class ScanLayout(ints: GBuffer[Int32], intervalSize: GUniform[ScanArgs] = GUniform.fromParams) extends Layout val upsweep = GProgram[ScanParams, ScanLayout]( layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize / 256, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read val invocId = GIO.invocationId @@ -92,7 +106,7 @@ object GPipe: val downsweep = GProgram[ScanParams, ScanLayout]( layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize / 256, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read val invocId = GIO.invocationId @@ -136,12 +150,13 @@ object GPipe: val compactProgram = GProgram[CompactParams, CompactLayout]( layout = params => CompactLayout(in = GBuffer[C](params.inSize), scan = GBuffer[Int32](params.inSize), out = GBuffer[C](params.inSize)), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / 256, 1, 1)), ): layout => val invocId = GIO.invocationId val element = GIO.read[C](layout.in, invocId) val prefixSum = GIO.read[Int32](layout.scan, invocId) for + _ <- GIO.printf("Element %d, prefix sum %d", invocId, prefixSum) _ <- GIO.when(invocId > 0): val prevScan = GIO.read[Int32](layout.scan, invocId - 1) GIO.when(prevScan < prefixSum): @@ -149,11 +164,11 @@ object GPipe: _ <- GIO.when(invocId === 0): GIO.when(prefixSum > 0): GIO.write(layout.out, invocId, element) - yield () + yield Empty() // connect all the layouts/executions into one case class FilterParams(inSize: Int, intervalSize: Int) - case class FilterLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C], intervalSize: GUniform[ScanArgs]) extends Layout + case class FilterLayout(in: GBuffer[C], scan: GBuffer[Int32], out: GBuffer[C]) extends Layout val filterExec = GExecution[FilterParams, FilterLayout]() .addProgram(predicateProgram)( @@ -163,7 +178,7 @@ object GPipe: .flatMap[FilterLayout, FilterParams]: filterLayout => scanExec .contramap[FilterLayout]: filterLayout => - ScanLayout(filterLayout.scan, filterLayout.intervalSize) + ScanLayout(filterLayout.scan) .contramapParams[FilterParams](filterParams => ScanParams(filterParams.inSize, filterParams.intervalSize)) .map(scanLayout => filterLayout) .flatMap[FilterLayout, FilterParams]: filterLayout => @@ -193,7 +208,7 @@ object GPipe: .flatMap: chunk => bridge.toByteBuffer(predBuf, chunk) region.runUnsafe( - init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf), intervalSize = GUniform()), + init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf)), onDone = layout => layout.out.read(compactBuf), ) val arr = bridge.fromByteBuffer(compactBuf, new Array[S](256)) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index d0ad3a5a..b87033ac 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -38,7 +38,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val (result, shaderCalls) = interpret(execution, params, layout) val descriptorSets = shaderCalls.map: - case ShaderCall(pipeline, layout, _) => + case ShaderCall(pipeline, layout, _, _) => pipeline.pipelineLayout.sets .map(dsManager.allocate) .zip(layout) @@ -50,7 +50,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val dispatches: Seq[Dispatch] = shaderCalls .zip(descriptorSets) .map: - case (ShaderCall(pipeline, layout, dispatch), sets) => + case (ShaderCall(pipeline, layout, dispatch, _), sets) => Dispatch(pipeline, layout, sets, dispatch) val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): @@ -90,9 +90,8 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte case x: ExecutionBinding[?] => x case x: GBinding[?] => val e = ExecutionBinding(x)(using x.fromExpr, x.tag) - bindingsAcc.put(e, mutable.Buffer(x)) + bindingsAcc.put(e, mutable.Buffer(x)) // store only base contribution here e - mapper.fromBindings(res) // noinspection TypeParameterShadow @@ -125,33 +124,51 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val layoutInit = val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout program.layout(initProgram)(params) - lb.toBindings(layout) - .zip(lb.toBindings(layoutInit)) - .foreach: - case (binding, initBinding) => - bindingsAcc(binding).append(initBinding) + + val callInits: Map[GBinding[?], Seq[GBinding[?]]] = + lb + .toBindings(layout) + .zip(lb.toBindings(layoutInit)) + .groupMap(_._1)(_._2) + val dispatch = program.dispatch(layout, params) match case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) // noinspection ScalaRedundantCast - (layout.asInstanceOf[RL], Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) + (layout.asInstanceOf[RL], Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch, callInits))) case _ => ??? val (rl, steps) = interpretImpl(execution, params, mockBindings(layout)) - val bingingToVk = bindingsAcc.map(x => (x._1, interpretBinding(x._1, x._2.toSeq))) + + val finalBindingForRl: mutable.Map[GBinding[?], GBinding[?]] = mutable.Map.empty val nextSteps = steps.map: - case ShaderCall(pipeline, layout, dispatch) => + case ShaderCall(pipeline, layout, dispatch, callInits) => val nextLayout = layout.map: _.map: - case Binding(binding, operation) => Binding(bingingToVk(binding), operation) + case Binding(binding, operation) => + val base = bindingsAcc.getOrElse(binding, mutable.Buffer.empty).toSeq + val extras = callInits.getOrElse(binding, Seq.empty) + val resolved = interpretBinding(binding, base ++ extras) + finalBindingForRl.update(binding, resolved) + Binding(resolved, operation) + val nextDispatch = dispatch match - case x: Direct => x - case Indirect(buffer, offset) => Indirect(bingingToVk(buffer), offset) - ShaderCall(pipeline, nextLayout, nextDispatch) + case x: DispatchType.Direct => x + case DispatchType.Indirect(buffer, offset) => + val base = bindingsAcc.getOrElse(buffer, mutable.Buffer.empty).toSeq + val extras = callInits.getOrElse(buffer, Seq.empty) + val resolved = interpretBinding(buffer, base ++ extras) + finalBindingForRl.update(buffer, resolved) + DispatchType.Indirect(resolved, offset) + + ShaderCall(pipeline, nextLayout, nextDispatch, Map.empty) val mapper = summon[LayoutBinding[RL]] - val res = mapper.fromBindings(mapper.toBindings(rl).map(bingingToVk.apply)) + val rlBindings = mapper.toBindings(rl).map: b => + finalBindingForRl.getOrElse(b, interpretBinding(b, bindingsAcc.getOrElse(b, mutable.Buffer.empty).toSeq)) + val res = mapper.fromBindings(rlBindings) + (res, nextSteps) private def interpretBinding(binding: GBinding[?], bindings: Seq[GBinding[?]])(using VkAllocation): GBinding[?] = @@ -227,13 +244,23 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte commandBuffer object ExecutionHandler: - case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) + case class ShaderCall( + pipeline: ComputePipeline, + layout: ShaderLayout, + dispatch: DispatchType, + callInits: Map[GBinding[?], Seq[GBinding[?]]] // per-program contributions + ) sealed trait ExecutionStep - case class Dispatch(pipeline: ComputePipeline, layout: ShaderLayout, descriptorSets: Seq[DescriptorSet], dispatch: DispatchType) - extends ExecutionStep - case object PipelineBarrier extends ExecutionStep + case class Dispatch( + pipeline: ComputePipeline, + layout: ShaderLayout, + descriptorSets: Seq[DescriptorSet], + dispatch: DispatchType + ) extends ExecutionStep + + case object PipelineBarrier extends ExecutionStep sealed trait DispatchType object DispatchType: case class Direct(x: Int, y: Int, z: Int) extends DispatchType diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index 350f0369..dc878c5b 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -56,7 +56,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) if buff.remaining() % sizeOfT != 0 then ??? GBuffer[T](length).tap(_.write(buff)) - extension (buffers: GUniform.type) + extension (uniforms: GUniform.type) def apply[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](buff: ByteBuffer): GUniform[T] = GUniform[T]().tap(_.write(buff)) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala index 9c8c99c6..e979d3ca 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala @@ -16,7 +16,7 @@ import scala.jdk.CollectionConverters.* */ private[cyfra] object VulkanContext: val ValidationLayer: String = "VK_LAYER_KHRONOS_validation" - private val ValidationLayers: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "false").toBoolean + private val ValidationLayers: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "true").toBoolean if Configuration.STACK_SIZE.get() < 100 then logger.warn(s"Small stack size. Increase with org.lwjgl.system.stackSize") private[cyfra] class VulkanContext: diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index 43072840..812c14d6 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala @@ -8,11 +8,13 @@ import org.lwjgl.system.{MemoryStack, MemoryUtil} import org.lwjgl.system.MemoryUtil.NULL import org.lwjgl.vulkan.* import org.lwjgl.vulkan.EXTDebugReport.VK_EXT_DEBUG_REPORT_EXTENSION_NAME -import org.lwjgl.vulkan.EXTLayerSettings.VK_LAYER_SETTING_TYPE_BOOL32_EXT +import org.lwjgl.vulkan.EXTLayerSettings.{VK_LAYER_SETTING_TYPE_BOOL32_EXT, VK_LAYER_SETTING_TYPE_STRING_EXT, VK_LAYER_SETTING_TYPE_UINT32_EXT} import org.lwjgl.vulkan.KHRPortabilityEnumeration.{VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME} import org.lwjgl.vulkan.VK10.* +import org.lwjgl.vulkan.EXTValidationFeatures.* +import org.lwjgl.vulkan.EXTDebugUtils.* -import java.nio.ByteBuffer +import java.nio.{ByteBuffer, LongBuffer} import scala.collection.mutable import scala.jdk.CollectionConverters.given import scala.util.chaining.* @@ -21,7 +23,11 @@ import scala.util.chaining.* * MarconZet Created 13.04.2020 */ object Instance: - val ValidationLayersExtensions: Seq[String] = List(VK_EXT_DEBUG_REPORT_EXTENSION_NAME) + val ValidationLayersExtensions: Seq[String] = List( + VK_EXT_DEBUG_REPORT_EXTENSION_NAME, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME + ) val MoltenVkExtensions: Seq[String] = List(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) lazy val (extensions, layers): (Seq[String], Seq[String]) = pushStack: stack => @@ -69,7 +75,7 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj .ppEnabledLayerNames(ppEnabledLayerNames) if enableValidationLayers then - val layerSettings = VkLayerSettingEXT.calloc(1, stack) + val layerSettings = VkLayerSettingEXT.calloc(2, stack) layerSettings .get(0) .pLayerName(stack.ASCII(ValidationLayer)) @@ -77,13 +83,69 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj .`type`(VK_LAYER_SETTING_TYPE_BOOL32_EXT) .valueCount(1) .pValues(MemoryUtil.memByteBuffer(stack.ints(1))) + + layerSettings + .get(1) + .pLayerName(stack.ASCII(ValidationLayer)) + .pSettingName(stack.ASCII("printf_buffer_size")) + .`type`(VK_LAYER_SETTING_TYPE_UINT32_EXT) + .valueCount(1) + .pValues(MemoryUtil.memByteBuffer(stack.ints(1024 * 1024))) + + val layerSettingsCI = VkLayerSettingsCreateInfoEXT.calloc(stack).sType$Default().pSettings(layerSettings) - pCreateInfo.pNext(layerSettingsCI) + + val validationFeatures = VkValidationFeaturesEXT.calloc(stack) + .sType$Default() + .pEnabledValidationFeatures(stack.ints(VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT)) + .pNext(0) + + layerSettingsCI.pNext(validationFeatures.address()) + pCreateInfo.pNext(layerSettingsCI.address()) val pInstance = stack.mallocPointer(1) check(vkCreateInstance(pCreateInfo, null, pInstance), "Failed to create VkInstance") new VkInstance(pInstance.get(0), pCreateInfo) + protected val debugMessenger: Option[LongBuffer] = + if enableValidationLayers then pushStack: stack => + Some: + val callback = new VkDebugUtilsMessengerCallbackEXT(): + override def invoke(messageSeverity: Int, messageTypes: Int, pCallbackData: Long, pUserData: Long): Int = + val message = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData).pMessageString() + val debugMessage = "[VK DEBUG] " + message.split("\\|").last + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0 then + logger.error(debugMessage) + else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0 then + logger.warn(debugMessage) + else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0 then + logger.info(debugMessage) + else + logger.debug(debugMessage) + VK_FALSE + + + val debugMessengerCreate = VkDebugUtilsMessengerCreateInfoEXT.calloc(stack) + .sType$Default() + .messageSeverity( + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + ) + .messageType( + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + ) + .pfnUserCallback(callback) + + val debugMessengerBuff = stack.callocLong(1) + check(vkCreateDebugUtilsMessengerEXT( + handle, + debugMessengerCreate, + null, + debugMessengerBuff + )) + debugMessengerBuff + else None + + lazy val enabledLayers: Seq[String] = List .empty[String] .pipe: x => @@ -93,8 +155,10 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj x else x - override protected def close(): Unit = + override protected def close(): Unit = { + debugMessenger.foreach(b => vkDestroyDebugUtilsMessengerEXT(handle, b.get(0), null)) vkDestroyInstance(handle, null) + } private def getInstanceExtensions(stack: MemoryStack) = val n = stack.callocInt(1) From aa91317208858b45f7638c65c57ce7cafbd9f5ce Mon Sep 17 00:00:00 2001 From: Szymon Date: Mon, 8 Sep 2025 00:48:20 +0200 Subject: [PATCH 21/38] More adjustments and printf --- .../cyfra/spirv/compilers/GIOCompiler.scala | 16 ++-- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 9 +- .../computenode/cyfra/fs2interop/Bridge.scala | 30 ++++-- .../computenode/cyfra/fs2interop/GPipe.scala | 26 +++-- .../cyfra/runtime/VkAllocation.scala | 7 +- .../cyfra/vulkan/command/CommandPool.scala | 2 + .../cyfra/vulkan/core/Instance.scala | 95 +++++++++++-------- .../cyfra/vulkan/memory/Buffer.scala | 1 - 8 files changed, 113 insertions(+), 73 deletions(-) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala index 231d8f97..5d54fc2b 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -39,9 +39,8 @@ object GIOCompiler: case GIO.FlatMap(v, n) => val (vInsts, ctxAfterV) = compileGio(v, ctx, acc) compileGio(n, ctxAfterV, vInsts) - + case GIO.Repeat(n, f) => - // Compile 'n' first (so we can use its id in the comparison) val (nInsts, ctxWithN) = ExpressionCompiler.compileBlock(n.tree, ctx) @@ -68,7 +67,7 @@ object GIOCompiler: nextResultId = baseId + 8, exprRefs = ctxWithN.exprRefs + (CurrentRepeatIndex.treeid -> phiId) ) - val (bodyInsts, ctxAfterBody) = compileGio(f, bodyCtx) + val (bodyInsts, ctxAfterBody) = compileGio(f, bodyCtx) // ← Capture the context after body compilation // Preheader: close current block and jump to header through a dedicated block val preheader = List( @@ -118,14 +117,13 @@ object GIOCompiler: Instruction(Op.OpLabel, List(ResultRef(mergeId))) ) - val finalNextId = math.max(ctxAfterBody.nextResultId, addId + 1) - val finalCtx = ctxAfterBody.copy( - nextResultId = finalNextId, - exprRefs = ctxAfterBody.exprRefs - CurrentRepeatIndex.treeid - ) + // Use the highest nextResultId to avoid ID collisions + val finalNextId = math.max(ctxAfterBody.nextResultId, addId + 1) // ← Use ctxAfterBody.nextResultId + // Use ctxWithN as base to prevent loop-local values from being referenced outside + val finalCtx = ctxWithN.copy(nextResultId = finalNextId) (acc ::: nInsts ::: preheader ::: header ::: bodyBlk ::: contBlk ::: mergeBlk, finalCtx) - + case GIO.Printf(format, args*) => val (argsInsts, ctxAfterArgs) = args.foldLeft((List.empty[Words], ctx)) { case ((instsAcc, cAcc), arg) => val (argInsts, cAfterArg) = ExpressionCompiler.compileBlock(arg.tree, cAcc) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index 941ba3cc..7c8414c9 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -23,13 +23,16 @@ extension (f: fRGBA) Math.abs(f._1 - g._1) < eps && Math.abs(f._2 - g._2) < eps && Math.abs(f._3 - g._3) < eps && Math.abs(f._4 - g._4) < eps class Fs2Tests extends munit.FunSuite: - given cr: CyfraRuntime = VkCyfraRuntime( + given cr: VkCyfraRuntime = VkCyfraRuntime( spirvToolsRunner = SpirvToolsRunner( crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/disassembled.spv"))) ) ) + override def afterAll(): Unit = + //cr.close() + super.afterAll() test("fs2 through GPipe map, just ints"): val inSeq = (0 until 256).toSeq @@ -43,11 +46,13 @@ class Fs2Tests extends munit.FunSuite: assert(res == exp, s"Expected $exp, got $res") test("fs2 through GPipe map, floats and vectors"): - val inSeq = (0 to 255).map(_.toFloat).toSeq + val n = 16 + val inSeq = (0 to (n * 256 - 1)).map(_.toFloat).toSeq val stream = Stream.emits(inSeq) val pipe = GPipe.map[Pure, Float32, Vec4[Float32], Float, fRGBA](f => (f, f + 1f, f + 2f, f + 3f)) val result = stream.through(pipe).compile.toList val expected = inSeq.map(f => (f, f + 1f, f + 2f, f + 3f)) + println("DONE!") result .zip(expected) .foreach: (res, exp) => diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala index 41d366c3..b148a53b 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala @@ -1,13 +1,12 @@ +// scala package io.computenode.cyfra.fs2interop import io.computenode.cyfra.core.archive.mem.GMem.fRGBA import io.computenode.cyfra.dsl.* - import fs2.* -import java.nio.ByteBuffer -import org.lwjgl.BufferUtils -import izumi.reflect.Tag +import java.nio.{ByteBuffer, ByteOrder} +import izumi.reflect.Tag import scala.reflect.ClassTag trait Bridge[CyfraType <: Value: FromExpr: Tag, ScalaType: ClassTag]: @@ -17,22 +16,33 @@ trait Bridge[CyfraType <: Value: FromExpr: Tag, ScalaType: ClassTag]: object Bridge: given Bridge[Int32, Int]: def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Int]): ByteBuffer = - inBuf.asIntBuffer().put(chunk.toArray[Int]).flip() + inBuf.clear().order(ByteOrder.nativeOrder()) + val ib = inBuf.asIntBuffer() + ib.put(chunk.toArray[Int]) + inBuf.position(ib.position() * java.lang.Integer.BYTES).flip() inBuf def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Int]): Array[Int] = - outBuf.asIntBuffer().get(arr).flip() + outBuf.order(ByteOrder.nativeOrder()) + outBuf.asIntBuffer().get(arr) + outBuf.rewind() arr given Bridge[Float32, Float]: def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Float]): ByteBuffer = - inBuf.asFloatBuffer().put(chunk.toArray[Float]).flip() + inBuf.clear().order(ByteOrder.nativeOrder()) + val fb = inBuf.asFloatBuffer() + fb.put(chunk.toArray[Float]) + inBuf.position(fb.position() * java.lang.Float.BYTES).flip() inBuf def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Float]): Array[Float] = - outBuf.asFloatBuffer().get(arr).flip() + outBuf.order(ByteOrder.nativeOrder()) + outBuf.asFloatBuffer().get(arr) + outBuf.rewind() arr given Bridge[Vec4[Float32], fRGBA]: def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[fRGBA]): ByteBuffer = + inBuf.clear().order(ByteOrder.nativeOrder()) val vecs = chunk.toArray[fRGBA] vecs.foreach: case (x, y, z, a) => @@ -46,7 +56,7 @@ object Bridge: def fromByteBuffer(outBuf: ByteBuffer, arr: Array[fRGBA]): Array[fRGBA] = val res = outBuf.asFloatBuffer() for i <- 0 until arr.size do arr(i) = (res.get(), res.get(), res.get(), res.get()) - outBuf.flip() + outBuf.rewind() arr given Bridge[GBoolean, Boolean]: @@ -55,4 +65,4 @@ object Bridge: inBuf def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Boolean]): Array[Boolean] = outBuf.get(arr.asInstanceOf[Array[Byte]]).flip() - arr + arr \ No newline at end of file diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 77ceb323..95c86efc 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -38,8 +38,7 @@ object GPipe: val invocId = GIO.invocationId val element = GIO.read[C1](layout.in, invocId) val res = f(element) - for - _ <- GIO.printf("Element %d -> %v4f", invocId, res) + for _ <- GIO.write[C2](layout.out, invocId, res) yield Empty() }) @@ -57,10 +56,15 @@ object GPipe: val outBuf = BufferUtils.createByteBuffer(params.inSize * outTypeSize) stream - .chunkMin(params.inSize) + .chunkN(params.inSize) .flatMap: chunk => bridge1.toByteBuffer(inBuf, chunk) - region.runUnsafe(init = PLayout(in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), onDone = layout => layout.out.read(outBuf)) + region.runUnsafe(init = PLayout( + in = GBuffer[C1](inBuf), + out = GBuffer[C2](outBuf)), + onDone = layout => + layout.out.read(outBuf) + ) Stream.emits(bridge2.fromByteBuffer(outBuf, new Array[S2](params.inSize))) // Overload for convenient single type version @@ -81,7 +85,7 @@ object GPipe: val element = GIO.read[C](layout.in, invocId) val result = when(pred(element))(1: Int32).otherwise(0) for - _ <- GIO.printf("Element %d -> %d", invocId, result) + _ <- GIO.printf("Pred: Element %d -> %d", invocId, result) _ <- GIO.write[Int32](layout.out, invocId, result) yield Empty() @@ -102,7 +106,10 @@ object GPipe: val oldValue = GIO.read[Int32](layout.ints, end) val addValue = GIO.read[Int32](layout.ints, mid) val newValue = oldValue + addValue - GIO.write[Int32](layout.ints, end, newValue) + for + _ <- GIO.printf("Upsweep: invocId %d, root %d, mid %d, end %d, oldValue %d, addValue %d, newValue %d", invocId, root, mid, end, oldValue, addValue, newValue) + _ <- GIO.write[Int32](layout.ints, end, newValue) + yield Empty() val downsweep = GProgram[ScanParams, ScanLayout]( layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), @@ -115,7 +122,10 @@ object GPipe: val oldValue = GIO.read[Int32](layout.ints, mid) val addValue = when(end > 0)(GIO.read[Int32](layout.ints, end)).otherwise(0) val newValue = oldValue + addValue - GIO.write[Int32](layout.ints, mid, newValue) + for + _ <- GIO.printf("Downsweep: invocId %d, end %d, mid %d, oldValue %d, addValue %d, newValue %d", invocId, end, mid, oldValue, addValue, newValue) + _ <- GIO.write[Int32](layout.ints, mid, newValue) + yield Empty() // Stitch together many upsweep / downsweep program phases recursively @annotation.tailrec @@ -204,7 +214,7 @@ object GPipe: val compactBuf = BufferUtils.createByteBuffer(filterParams.inSize * typeSize) stream - .chunkMin(filterParams.inSize) + .chunkN(filterParams.inSize) .flatMap: chunk => bridge.toByteBuffer(predBuf, chunk) region.runUnsafe( diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index dc878c5b..8cb67f8b 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -50,10 +50,11 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = VkBuffer[T](length).tap(bindings += _) - def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] = + def apply[T <: Value : {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] = val sizeOfT = typeStride(summon[Tag[T]]) - val length = buff.remaining() / sizeOfT - if buff.remaining() % sizeOfT != 0 then ??? + val length = buff.capacity() / sizeOfT + if buff.capacity() % sizeOfT != 0 then + throw new IllegalArgumentException(s"ByteBuffer size ${buff.capacity()} is not a multiple of element size $sizeOfT") GBuffer[T](length).tap(_.write(buff)) extension (uniforms: GUniform.type) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala index 3db65668..fe0d0636 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala @@ -43,6 +43,7 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) val commandBuffer = beginSingleTimeCommands() block(commandBuffer) endSingleTimeCommands(commandBuffer).block().destroy() + //vkDeviceWaitIdle(device.get) freeCommandBuffer(commandBuffer) private def beginSingleTimeCommands(): VkCommandBuffer = @@ -74,6 +75,7 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) val pointerBuffer = stack.callocPointer(commandBuffer.length) commandBuffer.foreach(pointerBuffer.put) pointerBuffer.flip() + vkDeviceWaitIdle(device.get) vkFreeCommandBuffers(device.get, commandPool, pointerBuffer) protected def close(): Unit = diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index 812c14d6..a1680c0e 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala @@ -90,61 +90,77 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj .pSettingName(stack.ASCII("printf_buffer_size")) .`type`(VK_LAYER_SETTING_TYPE_UINT32_EXT) .valueCount(1) - .pValues(MemoryUtil.memByteBuffer(stack.ints(1024 * 1024))) + .pValues(MemoryUtil.memByteBuffer(stack.ints(1024*1024))) val layerSettingsCI = VkLayerSettingsCreateInfoEXT.calloc(stack).sType$Default().pSettings(layerSettings) val validationFeatures = VkValidationFeaturesEXT.calloc(stack) .sType$Default() - .pEnabledValidationFeatures(stack.ints(VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT)) + .pEnabledValidationFeatures(stack.ints( + VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT, + )) + .pNext(0) + + val validationFeaturesPCreate = VkValidationFeaturesEXT.calloc(stack) + .sType$Default() + .pEnabledValidationFeatures(stack.ints( + VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT, + VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT, + )) .pNext(0) layerSettingsCI.pNext(validationFeatures.address()) pCreateInfo.pNext(layerSettingsCI.address()) + validationFeaturesPCreate.pNext(validationFeaturesPCreate.address()) val pInstance = stack.mallocPointer(1) check(vkCreateInstance(pCreateInfo, null, pInstance), "Failed to create VkInstance") new VkInstance(pInstance.get(0), pCreateInfo) - protected val debugMessenger: Option[LongBuffer] = - if enableValidationLayers then pushStack: stack => - Some: - val callback = new VkDebugUtilsMessengerCallbackEXT(): - override def invoke(messageSeverity: Int, messageTypes: Int, pCallbackData: Long, pUserData: Long): Int = - val message = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData).pMessageString() - val debugMessage = "[VK DEBUG] " + message.split("\\|").last - if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0 then - logger.error(debugMessage) - else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0 then - logger.warn(debugMessage) - else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0 then - logger.info(debugMessage) - else - logger.debug(debugMessage) - VK_FALSE - - - val debugMessengerCreate = VkDebugUtilsMessengerCreateInfoEXT.calloc(stack) - .sType$Default() - .messageSeverity( - VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT - ) - .messageType( - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT - ) - .pfnUserCallback(callback) - - val debugMessengerBuff = stack.callocLong(1) - check(vkCreateDebugUtilsMessengerEXT( - handle, - debugMessengerCreate, - null, - debugMessengerBuff - )) - debugMessengerBuff + protected val callback: Option[VkDebugUtilsMessengerCallbackEXT] = + if enableValidationLayers then Some: + new VkDebugUtilsMessengerCallbackEXT(): + override def invoke(messageSeverity: Int, messageTypes: Int, pCallbackData: Long, pUserData: Long): Int = + val message = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData).pMessageString() + val debugMessage = "[VK DEBUG] " + message.split("\\|").last + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0 then + logger.error(debugMessage) + else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0 then + logger.warn(debugMessage) + else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0 then + logger.info(debugMessage) + else + logger.debug(debugMessage) + VK_FALSE else None + protected val debugMessenger: Option[LongBuffer] = callback.map: c => + pushStack: stack => + val debugMessengerCreate = VkDebugUtilsMessengerCreateInfoEXT.calloc(1) + .sType$Default() + .messageSeverity( + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT + ) + .messageType( + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT + ) + .pfnUserCallback(c) + + val debugMessengerBuff = stack.callocLong(1) + check(vkCreateDebugUtilsMessengerEXT( + handle, + debugMessengerCreate.get(0), + null, + debugMessengerBuff + )) + debugMessengerBuff + lazy val enabledLayers: Seq[String] = List .empty[String] @@ -155,10 +171,9 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj x else x - override protected def close(): Unit = { + override protected def close(): Unit = debugMessenger.foreach(b => vkDestroyDebugUtilsMessengerEXT(handle, b.get(0), null)) vkDestroyInstance(handle, null) - } private def getInstanceExtensions(stack: MemoryStack) = val n = stack.callocInt(1) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala index 963aa1cd..78b99dc4 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala @@ -57,7 +57,6 @@ object Buffer: def copyTo(dst: ByteBuffer, srcOffset: Int): Unit = pushStack: stack => vmaCopyAllocationToMemory(allocator.get, allocation, srcOffset, dst) - def copyFrom(src: ByteBuffer, dstOffset: Int): Unit = pushStack: stack => vmaCopyMemoryToAllocation(allocator.get, src, allocation, dstOffset) From 6354e4214ae56383f3ad3f64b210b3131da631f2 Mon Sep 17 00:00:00 2001 From: Szymon Date: Mon, 8 Sep 2025 01:06:50 +0200 Subject: [PATCH 22/38] Works!!! --- .../scala/io/computenode/cyfra/fs2interop/GPipe.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 95c86efc..492a4d7d 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -33,7 +33,7 @@ object GPipe: val gProg = GProgram[Params, PLayout]( layout = params => PLayout(in = GBuffer[C1](params.inSize), out = GBuffer[C2](params.inSize)), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / 256, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize / 256f).toInt, 1, 1)), )(layout => { val invocId = GIO.invocationId val element = GIO.read[C1](layout.in, invocId) @@ -79,7 +79,7 @@ object GPipe: val predicateProgram = GProgram[PredParams, PredLayout]( layout = params => PredLayout(in = GBuffer[C](params.inSize), out = GBuffer[Int32](params.inSize)), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / 256, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / 256).toInt, 1, 1)), ): layout => val invocId = GIO.invocationId val element = GIO.read[C](layout.in, invocId) @@ -96,7 +96,7 @@ object GPipe: val upsweep = GProgram[ScanParams, ScanLayout]( layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize / 256, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / params.intervalSize / 256).toInt, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read val invocId = GIO.invocationId @@ -113,7 +113,7 @@ object GPipe: val downsweep = GProgram[ScanParams, ScanLayout]( layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / params.intervalSize / 256, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / params.intervalSize / 256).toInt, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read val invocId = GIO.invocationId @@ -160,7 +160,7 @@ object GPipe: val compactProgram = GProgram[CompactParams, CompactLayout]( layout = params => CompactLayout(in = GBuffer[C](params.inSize), scan = GBuffer[Int32](params.inSize), out = GBuffer[C](params.inSize)), - dispatch = (layout, params) => GProgram.StaticDispatch((params.inSize / 256, 1, 1)), + dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / 256).toInt, 1, 1)), ): layout => val invocId = GIO.invocationId val element = GIO.read[C](layout.in, invocId) From a052ba2265802ea7b6cb8e4c07d16f3ca825bae3 Mon Sep 17 00:00:00 2001 From: Szymon Date: Mon, 8 Sep 2025 21:34:17 +0200 Subject: [PATCH 23/38] Improvements --- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 9 +-- .../computenode/cyfra/fs2interop/GPipe.scala | 62 +++++++++++-------- .../cyfra/runtime/VkCyfraRuntime.scala | 15 +++-- .../cyfra/vulkan/command/CommandPool.scala | 4 +- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index 7c8414c9..289426ec 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -47,7 +47,7 @@ class Fs2Tests extends munit.FunSuite: test("fs2 through GPipe map, floats and vectors"): val n = 16 - val inSeq = (0 to (n * 256 - 1)).map(_.toFloat).toSeq + val inSeq = (0 until n * 256).map(_.toFloat) val stream = Stream.emits(inSeq) val pipe = GPipe.map[Pure, Float32, Vec4[Float32], Float, fRGBA](f => (f, f + 1f, f + 2f, f + 3f)) val result = stream.through(pipe).compile.toList @@ -59,11 +59,12 @@ class Fs2Tests extends munit.FunSuite: assert(res.close(exp)(0.001f), s"Expected $exp, got $res") test("fs2 through GPipe filter, just ints"): - val inSeq = (0 until 256).toSeq + val n = 16 + val inSeq = (0 until n * 256) val stream = Stream.emits(inSeq) - val pipe = GPipe.filter[Pure, Int32, Int](_.mod(2) === 0) + val pipe = GPipe.filter[Pure, Int32, Int](_.mod(7) === 0) val result = stream.through(pipe).compile.toList - val expected = inSeq.filter(_ % 2 == 0) + val expected = inSeq.filter(_ % 7 == 0) result .zip(expected) .foreach: (res, exp) => diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 492a4d7d..c79946be 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -73,6 +73,8 @@ object GPipe: def filter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = (stream: Stream[F, S]) => + val chunkInSize = 256 + // Predicate mapping case class PredParams(inSize: Int) case class PredLayout(in: GBuffer[C], out: GBuffer[Int32]) extends Layout @@ -99,33 +101,35 @@ object GPipe: dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / params.intervalSize / 256).toInt, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read - val invocId = GIO.invocationId - val root = invocId * size - val mid = root + (size / 2) - 1 - val end = root + size - 1 - val oldValue = GIO.read[Int32](layout.ints, end) - val addValue = GIO.read[Int32](layout.ints, mid) - val newValue = oldValue + addValue - for - _ <- GIO.printf("Upsweep: invocId %d, root %d, mid %d, end %d, oldValue %d, addValue %d, newValue %d", invocId, root, mid, end, oldValue, addValue, newValue) - _ <- GIO.write[Int32](layout.ints, end, newValue) - yield Empty() + GIO.when(GIO.invocationId < ((chunkInSize: Int32) / size)): + val invocId = GIO.invocationId + val root = invocId * size + val mid = root + (size / 2) - 1 + val end = root + size - 1 + val oldValue = GIO.read[Int32](layout.ints, end) + val addValue = GIO.read[Int32](layout.ints, mid) + val newValue = oldValue + addValue + for + _ <- GIO.printf("Upsweep: invocId %d, root %d, size %d, mid %d, end %d, oldValue %d, addValue %d, newValue %d", invocId, root, size, mid, end, oldValue, addValue, newValue) + _ <- GIO.write[Int32](layout.ints, end, newValue) + yield Empty() val downsweep = GProgram[ScanParams, ScanLayout]( layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / params.intervalSize / 256).toInt, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read - val invocId = GIO.invocationId - val end = invocId * size - 1 // if invocId = 0, this is -1 (out of bounds) - val mid = end + (size / 2) - val oldValue = GIO.read[Int32](layout.ints, mid) - val addValue = when(end > 0)(GIO.read[Int32](layout.ints, end)).otherwise(0) - val newValue = oldValue + addValue - for - _ <- GIO.printf("Downsweep: invocId %d, end %d, mid %d, oldValue %d, addValue %d, newValue %d", invocId, end, mid, oldValue, addValue, newValue) - _ <- GIO.write[Int32](layout.ints, mid, newValue) - yield Empty() + GIO.when(GIO.invocationId < ((chunkInSize: Int32) / size)): + val invocId = GIO.invocationId + val end = invocId * size - 1 // if invocId = 0, this is -1 (out of bounds) + val mid = end + (size / 2) + val oldValue = GIO.read[Int32](layout.ints, mid) + val addValue = when(end > 0)(GIO.read[Int32](layout.ints, end)).otherwise(0) + val newValue = oldValue + addValue + for + _ <- GIO.printf("Downsweep: invocId %d, end %d, mid %d, oldValue %d, addValue %d, newValue %d", invocId, end, mid, oldValue, addValue, newValue) + _ <- GIO.write[Int32](layout.ints, mid, newValue) + yield Empty() // Stitch together many upsweep / downsweep program phases recursively @annotation.tailrec @@ -199,7 +203,7 @@ object GPipe: .map(compactLayout => filterLayout) // finally setup buffers, region, parameters, and run the program - val filterParams = FilterParams(256, 2) + val filterParams = FilterParams(chunkInSize, 2) val region = GBufferRegion .allocate[FilterLayout] .map: filterLayout => @@ -210,16 +214,20 @@ object GPipe: // these are allocated once, reused for many chunks val predBuf = BufferUtils.createByteBuffer(filterParams.inSize * typeSize) - val scanBuf = BufferUtils.createByteBuffer(filterParams.inSize * intSize) + val filteredCount = BufferUtils.createByteBuffer(intSize) val compactBuf = BufferUtils.createByteBuffer(filterParams.inSize * typeSize) stream - .chunkN(filterParams.inSize) + .chunkN(chunkInSize) .flatMap: chunk => bridge.toByteBuffer(predBuf, chunk) region.runUnsafe( - init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](scanBuf), out = GBuffer[C](compactBuf)), - onDone = layout => layout.out.read(compactBuf), + init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](filterParams.inSize), out = GBuffer[C](filterParams.inSize)), + onDone = layout => { + layout.scan.read(filteredCount, (filterParams.inSize - 2) * intSize) + layout.out.read(compactBuf) + } ) - val arr = bridge.fromByteBuffer(compactBuf, new Array[S](256)) + val filteredN = filteredCount.getInt(0) + val arr = bridge.fromByteBuffer(compactBuf, new Array[S](filteredN)) Stream.emits(arr) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index c6383665..e38648f4 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -16,14 +16,17 @@ class VkCyfraRuntime(spirvToolsRunner: SpirvToolsRunner = SpirvToolsRunner()) ex private val shaderCache = mutable.Map[GProgram[?, ?], VkShader[?]]() private[cyfra] def getOrLoadProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[Params, L]): VkShader[L] = synchronized: - val spirvProgram = program match - case p: GioProgram[?, ?] => compile(p) - case p: SpirvProgram[?, ?] => p - case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + if (shaderCache.contains(program)) then + shaderCache(program).asInstanceOf[VkShader[L]] + else + val spirvProgram = program match + case p: GioProgram[?, ?] => compile(p) + case p: SpirvProgram[?, ?] => p + case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") - shaderCache.getOrElseUpdate(program, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] + shaderCache.getOrElseUpdate(program, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] - private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = + private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = val GioProgram(_, layout, dispatch, _) = program val bindings = lbinding.toBindings(lstruct.layoutRef).toList val compiled = DSLCompiler.compile(program.body(summon[LayoutStruct[L]].layoutRef), bindings) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala index fe0d0636..863770cf 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala @@ -43,7 +43,6 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) val commandBuffer = beginSingleTimeCommands() block(commandBuffer) endSingleTimeCommands(commandBuffer).block().destroy() - //vkDeviceWaitIdle(device.get) freeCommandBuffer(commandBuffer) private def beginSingleTimeCommands(): VkCommandBuffer = @@ -75,7 +74,8 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) val pointerBuffer = stack.callocPointer(commandBuffer.length) commandBuffer.foreach(pointerBuffer.put) pointerBuffer.flip() - vkDeviceWaitIdle(device.get) + // TODO remove vkQueueWaitIdle, but currently crashes without it - Likely the printf debug buffer is still in use? + vkQueueWaitIdle(queue.get) vkFreeCommandBuffers(device.get, commandPool, pointerBuffer) protected def close(): Unit = From 62c383c7b9b84e4256d21122ff13d8dc188e493e Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Mon, 8 Sep 2025 22:38:41 +0200 Subject: [PATCH 24/38] curret std^ --- .../io/computenode/cyfra/core/GBufferRegion.scala | 10 +++++----- .../cyfra/runtime/ExecutionHandler.scala | 13 ++----------- .../io/computenode/cyfra/vulkan/VulkanContext.scala | 1 - 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index cfd3ad8d..d0c02fbb 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -1,6 +1,7 @@ package io.computenode.cyfra.core import io.computenode.cyfra.core.Allocation +import io.computenode.cyfra.core.GBufferRegion.MapRegion import io.computenode.cyfra.core.GProgram.BufferLengthSpec import io.computenode.cyfra.core.layout.{Layout, LayoutBinding} import io.computenode.cyfra.dsl.Value @@ -10,8 +11,10 @@ import izumi.reflect.Tag import java.nio.ByteBuffer -sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding] - +sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]: + def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = + MapRegion(this, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) + object GBufferRegion: def allocate[Alloc <: Layout: LayoutBinding]: GBufferRegion[Alloc, Alloc] = AllocRegion() @@ -24,9 +27,6 @@ object GBufferRegion: ) extends GBufferRegion[ReqAlloc, ResAlloc] extension [ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding](region: GBufferRegion[ReqAlloc, ResAlloc]) - def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = - MapRegion(region, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) - def runUnsafe(init: Allocation ?=> ReqAlloc, onDone: Allocation ?=> ResAlloc => Unit)(using cyfraRuntime: CyfraRuntime): Unit = cyfraRuntime.withAllocation: allocation => diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 89473f8c..b15fa95a 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -8,15 +8,7 @@ import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{ - BindingLogicError, - Dispatch, - DispatchType, - ExecutionBinding, - ExecutionStep, - PipelineBarrier, - ShaderCall, -} +import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionBinding, ExecutionStep, PipelineBarrier, ShaderCall} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed @@ -29,7 +21,7 @@ import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import izumi.reflect.Tag import org.lwjgl.vulkan.VK10.* import org.lwjgl.vulkan.VK13.{VK_ACCESS_2_SHADER_READ_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, vkCmdPipelineBarrier2} -import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferBeginInfo, VkDependencyInfo, VkMemoryBarrier2, VkSubmitInfo} +import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferBeginInfo, VkDependencyInfo, VkMemoryBarrier2, VkSubmitInfo} import scala.collection.mutable @@ -198,7 +190,6 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .flags(0) check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") - steps.foreach: case PipelineBarrier => val memoryBarrier = VkMemoryBarrier2 // TODO don't synchronise everything diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala index 9c8c99c6..67d612fe 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala @@ -17,7 +17,6 @@ import scala.jdk.CollectionConverters.* private[cyfra] object VulkanContext: val ValidationLayer: String = "VK_LAYER_KHRONOS_validation" private val ValidationLayers: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "false").toBoolean - if Configuration.STACK_SIZE.get() < 100 then logger.warn(s"Small stack size. Increase with org.lwjgl.system.stackSize") private[cyfra] class VulkanContext: private val instance: Instance = new Instance(ValidationLayers) From 6993f85e3840d4d016ab379f3536bf9da0b8a505 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 00:16:35 +0200 Subject: [PATCH 25/38] working^ --- .../cyfra/samples/TestingStuff.scala | 15 ++++++++++++-- .../cyfra/runtime/ExecutionHandler.scala | 20 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index 8b9a5014..a389efc4 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -12,9 +12,16 @@ import io.computenode.cyfra.runtime.VkCyfraRuntime import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given +def printBuffer(bb: ByteBuffer): Unit = { + val l = bb.asIntBuffer() + val s = (0 until l.remaining()).map(l.get).toList + println(s.mkString(" ")) +} + object TestingStuff: given GContext = GContext() @@ -111,10 +118,12 @@ object TestingStuff: emitBuffer = GBuffer[Int32](data.length * 2), filterBuffer = GBuffer[GBoolean](data.length * 2), ), - onDone = layout => layout.filterBuffer.read(rbb), + onDone = layout => + layout.filterBuffer.read(rbb) ) runtime.close() + printBuffer(rbb) val actual = (0 until 2 * 1024).map(i => result.get(i * 1) != 0) val expected = (0 until 1024).flatMap(x => Seq.fill(emitFilterParams.emitN)(x)).map(_ == emitFilterParams.filterValue) expected @@ -191,7 +200,7 @@ object TestingStuff: def testAddProgram10Times = given runtime: VkCyfraRuntime = VkCyfraRuntime() val bufferSize = 1280 - val params = AddProgramParams(bufferSize, addA = 0, addB = 1) + val params = AddProgramParams(bufferSize, addA = 5, addB = 10) val region = GBufferRegion .allocate[AddProgramExecLayout] .map: region => @@ -226,6 +235,8 @@ object TestingStuff: }, ) runtime.close() + + printBuffer(rbbList(0)) val expected = inData.map(_ + 11 * (params.addA + params.addB)) outBuffers.foreach { buf => (0 until bufferSize).foreach { i => diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index b15fa95a..cca151fb 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -8,7 +8,15 @@ import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionBinding, ExecutionStep, PipelineBarrier, ShaderCall} +import io.computenode.cyfra.runtime.ExecutionHandler.{ + BindingLogicError, + Dispatch, + DispatchType, + ExecutionBinding, + ExecutionStep, + PipelineBarrier, + ShaderCall, +} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed @@ -63,8 +71,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte descriptorSets.flatten.foreach(dsManager.free) commandPool.freeCommandBuffer(commandBuffer) - val externalBindings = (summon[LayoutBinding[EL]].toBindings(layout) ++ summon[LayoutBinding[RL]].toBindings(result)) - .map(VkAllocation.getUnderlying) + val externalBindings = getAllBindings(executeSteps).map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) val pe = new PendingExecution(commandBuffer, deps, cleanup) externalBindings.foreach(_.execution = Left(pe)) @@ -220,6 +227,13 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") commandBuffer + private def getAllBindings(steps: Seq[ExecutionStep]): Seq[GBinding[?]] = + steps + .flatMap: + case Dispatch(_, layout, _, _) => layout.flatten.map(_.binding) + case PipelineBarrier => Seq.empty + .distinct + object ExecutionHandler: case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) From 226c1215e37d196be1c7768dcc8510bdd4cac5f8 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 00:30:33 +0200 Subject: [PATCH 26/38] todo^ --- .../io/computenode/cyfra/runtime/ExecutionHandler.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index cca151fb..f8624b27 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -74,7 +74,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val externalBindings = getAllBindings(executeSteps).map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) val pe = new PendingExecution(commandBuffer, deps, cleanup) - externalBindings.foreach(_.execution = Left(pe)) + externalBindings.foreach(_.execution = Left(pe)) // TODO we assume all accesses are read-write result private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( @@ -231,8 +231,8 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte steps .flatMap: case Dispatch(_, layout, _, _) => layout.flatten.map(_.binding) - case PipelineBarrier => Seq.empty - .distinct + case PipelineBarrier => Seq.empty + .distinct object ExecutionHandler: case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) From fdc9ee2134581ebe2f4168998b92f70a5186111b Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 00:34:23 +0200 Subject: [PATCH 27/38] foramt^^ --- .../main/scala/io/computenode/cyfra/core/GBufferRegion.scala | 2 +- .../main/scala/io/computenode/cyfra/samples/TestingStuff.scala | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index d0c02fbb..633b3e1b 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -14,7 +14,7 @@ import java.nio.ByteBuffer sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]: def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = MapRegion(this, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) - + object GBufferRegion: def allocate[Alloc <: Layout: LayoutBinding]: GBufferRegion[Alloc, Alloc] = AllocRegion() diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index a389efc4..c45680ea 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -118,8 +118,7 @@ object TestingStuff: emitBuffer = GBuffer[Int32](data.length * 2), filterBuffer = GBuffer[GBoolean](data.length * 2), ), - onDone = layout => - layout.filterBuffer.read(rbb) + onDone = layout => layout.filterBuffer.read(rbb), ) runtime.close() From 1ab927aea92c9023b5bf816cc73d8c040110ecf6 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 21:44:31 +0200 Subject: [PATCH 28/38] not working destroy^ --- .../computenode/cyfra/core/Allocation.scala | 2 + .../cyfra/core/GBufferRegion.scala | 16 +++-- .../cyfra/runtime/ExecutionHandler.scala | 1 + .../cyfra/runtime/PendingExecution.scala | 58 ++++++++++++------- .../cyfra/runtime/VkAllocation.scala | 16 +++++ .../computenode/cyfra/runtime/VkBinding.scala | 15 +++-- 6 files changed, 76 insertions(+), 32 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index bdc1d5a7..d279bb88 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -10,6 +10,8 @@ import izumi.reflect.Tag import java.nio.ByteBuffer trait Allocation: + def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit + extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index 633b3e1b..65ad0b41 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -9,9 +9,13 @@ import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.GBuffer import izumi.reflect.Tag +import scala.util.chaining.given import java.nio.ByteBuffer sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]: + def reqAllocBinding: LayoutBinding[ReqAlloc] = summon[LayoutBinding[ReqAlloc]] + def resAllocBinding: LayoutBinding[ResAlloc] = summon[LayoutBinding[ResAlloc]] + def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = MapRegion(this, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) @@ -31,13 +35,13 @@ object GBufferRegion: cyfraRuntime.withAllocation: allocation => // noinspection ScalaRedundantCast - val steps: Seq[Allocation => Layout => Layout] = Seq.unfold(region: GBufferRegion[?, ?]): - case _: AllocRegion[?] => None - case MapRegion(req, f) => - Some((f.asInstanceOf[Allocation => Layout => Layout], req)) + val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): + case _: AllocRegion[?] => None + case m @ MapRegion(req, f) => + Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) - val initAlloc = init(using allocation) + val initAlloc = init(using allocation).tap(allocation.reportLayout) val bodyAlloc = steps.foldLeft[Layout](initAlloc): (acc, step) => - step(allocation)(acc) + step._1(allocation)(acc).tap(allocation.reportLayout(_)(using step._2)) onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index f8624b27..9b9b385d 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -74,6 +74,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val externalBindings = getAllBindings(executeSteps).map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) val pe = new PendingExecution(commandBuffer, deps, cleanup) + summon[VkAllocation].addExecution(pe) externalBindings.foreach(_.execution = Left(pe)) // TODO we assume all accesses are read-write result diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 63cf516f..15e595f1 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -9,32 +9,50 @@ import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSem import scala.collection.mutable -class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device) - extends VulkanObject[VkCommandBuffer]: +class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device): private val semaphore: Semaphore = Semaphore() private var fence: Option[Fence] = None - override protected def close(): Unit = cleanup() - - private def setFence(otherFence: Fence): Unit = - if fence.isDefined then return - fence = Some(otherFence) - dependencies.foreach(_.setFence(otherFence)) + def isPending: Boolean = fence.isEmpty + def isRunning: Boolean = fence.exists(!_.isSignaled) + def isFinished: Boolean = fence.exists(_.isSignaled) + + def block(): Unit = fence.foreach(_.block()) + + private var closed = false + def isClosed: Boolean = closed + private def close(): Unit = + assert(!closed, "PendingExecution already closed") + assert(isFinished, "Cannot close a PendingExecution that is not finished") + cleanup() + closed = true + + private var destroyed = false + def destroy(): Unit = + assert(!destroyed, "PendingExecution already destroyed") + assert(isFinished, "Cannot destroy a PendingExecution that is not finished") + if !closed then close() + semaphore.destroy() + fence.foreach(x => if x.isAlive then x.destroy()) + destroyed = true + + private def setFence(f: Fence): Unit = { + if !isPending then return + fence = Some(f) + dependencies.foreach(_.setFence(f)) + } - private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = { - if fence.isDefined then return Seq.empty + private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = + if !isPending then return Seq.empty val mySubmission = ((handle, semaphore), dependencies.map(_.semaphore).toSet) dependencies.flatMap(_.gatherForSubmission).appended(mySubmission) - } - - def block(): Unit = - fence match - case Some(f) => f.block() - case None => throw new IllegalStateException("No fence set for this execution") object PendingExecution: def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => - val exec = + assert(executions.forall(_.isPending), "All executions must be pending") + assert(executions.nonEmpty, "At least one execution must be provided") + + val exec: Seq[(Set[Semaphore], Set[(VkCommandBuffer, Semaphore)])] = val gathered = executions.flatMap(_.gatherForSubmission) val ordering = gathered.zipWithIndex.map(x => (x._1._1._1, x._2)).toMap gathered.toSet.groupMap(_._2)(_._1).toSeq.sortBy(x => x._2.map(_._1).map(ordering).min) @@ -79,13 +97,13 @@ object PendingExecution: submitInfos.flip() val fence = Fence() - executions.foreach(_.setFence(fence)) check(vkQueueSubmit2(queue.get, submitInfos, fence.get), "Failed to submit command buffer to queue") + executions.foreach(_.setFence(fence)) fence def cleanupAll(executions: Seq[PendingExecution]): Unit = def cleanupRec(ex: PendingExecution): Unit = - if !ex.isAlive then return - ex.destroy() + if !ex.isClosed then return + ex.close() ex.dependencies.foreach(cleanupRec) executions.foreach(cleanupRec) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index 4ce9692c..0db6a108 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -29,6 +29,15 @@ import scala.util.chaining.* class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator, Device) extends Allocation: given VkAllocation = this + override def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit = + val executions = summon[LayoutBinding[L]] + .toBindings(layout) + .map(getUnderlying) + .flatMap(_.execution.fold(Seq(_), _.toSeq)) + .filter(_.isPending) + + PendingExecution.executeAll(executions, commandPool.queue) + extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit = val size = bb.remaining() @@ -54,6 +63,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) commandPool.freeCommandBuffer(cb) stagingBuffer.destroy() val pe = new PendingExecution(cb, binding.execution.fold(Seq(_), _.toSeq), cleanup) + addExecution(pe) binding.execution = Left(pe) case _ => throw new IllegalArgumentException(s"Tried to write to non-VkBinding $buffer") @@ -89,8 +99,14 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) case _ => ??? direct(bb) + private val executions = mutable.Buffer[PendingExecution]() + + def addExecution(pe: PendingExecution): Unit = + executions += pe + private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() private[cyfra] def close(): Unit = + executions.foreach(_.destroy()) bindings.map(getUnderlying).foreach(_.buffer.destroy()) private def getStagingBuffer(size: Int): Buffer.HostBuffer = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala index f83f39fb..acda99f3 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -33,12 +33,15 @@ sealed abstract class VkBinding[T <: Value: {Tag, FromExpr}](val buffer: Buffer) */ var execution: Either[PendingExecution, mutable.Buffer[PendingExecution]] = Right(mutable.Buffer.empty) - def materialise(queue: Queue)(using Device): Unit = execution match - case Left(exec) if exec.isAlive => - PendingExecution.executeAll(Seq(exec), queue) - exec.block() - PendingExecution.cleanupAll(Seq(exec)) - case _ => () + def materialise(queue: Queue)(using Device): Unit = + val (pendingExecs, runningExecs) = execution.fold(Seq(_), _.toSeq).partition(_.isPending) // TODO better handle read only executions + if pendingExecs.nonEmpty then + val fence = PendingExecution.executeAll(pendingExecs, queue) + fence.block() + PendingExecution.cleanupAll(pendingExecs) + + runningExecs.foreach(_.block()) + PendingExecution.cleanupAll(runningExecs) object VkBinding: def unapply(binding: GBinding[?]): Option[Buffer] = binding match From 2d75933e926e4a3f11c70bb9aed58986ecf3b546 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 10 Sep 2025 00:04:59 +0200 Subject: [PATCH 29/38] working intermediete submission^ --- .../io/computenode/cyfra/runtime/PendingExecution.scala | 9 ++++----- .../io/computenode/cyfra/runtime/VkAllocation.scala | 1 + .../io/computenode/cyfra/vulkan/memory/Buffer.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 15e595f1..52aea0f2 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -4,6 +4,7 @@ import io.computenode.cyfra.vulkan.command.{CommandPool, Fence, Semaphore} import io.computenode.cyfra.vulkan.core.{Device, Queue} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject +import org.lwjgl.vulkan.VK10.VK_TRUE import org.lwjgl.vulkan.VK13.{VK_PIPELINE_STAGE_2_COPY_BIT, vkQueueSubmit2} import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} @@ -22,16 +23,14 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: private var closed = false def isClosed: Boolean = closed private def close(): Unit = - assert(!closed, "PendingExecution already closed") - assert(isFinished, "Cannot close a PendingExecution that is not finished") + if closed then return cleanup() closed = true private var destroyed = false def destroy(): Unit = - assert(!destroyed, "PendingExecution already destroyed") - assert(isFinished, "Cannot destroy a PendingExecution that is not finished") - if !closed then close() + if destroyed then return + close() semaphore.destroy() fence.foreach(x => if x.isAlive then x.destroy()) destroyed = true diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index 0db6a108..a799f98c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -48,6 +48,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) val stagingBuffer = getStagingBuffer(size) Buffer.copyBuffer(binding.buffer, stagingBuffer, offset, 0, size, commandPool) stagingBuffer.copyTo(bb, 0) + stagingBuffer.destroy() case _ => throw new IllegalArgumentException(s"Tried to read from non-VkBinding $buffer") def write(bb: ByteBuffer, offset: Int = 0): Unit = diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala index 1f677f04..c1f34b40 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala @@ -74,7 +74,7 @@ object Buffer: val fence = Fence() check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") - fence.block() + fence.block().destroy() def copyBufferCommandBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): VkCommandBuffer = commandPool.recordSingleTimeCommand: commandBuffer => From 19c0bfec5a57b4fd8801130da5edd4166764eba1 Mon Sep 17 00:00:00 2001 From: Szymon Date: Wed, 10 Sep 2025 01:29:23 +0200 Subject: [PATCH 30/38] Various fixes and improvements --- .../io/computenode/cyfra/core/GProgram.scala | 17 ++ .../computenode/cyfra/core/SpirvProgram.scala | 25 ++- .../cyfra/e2e/SpirvRuntimeEnduranceTest.scala | 209 ++++++++++++++++++ .../e2e/{ => dsl}/ArithmeticsE2eTest.scala | 6 +- .../e2e/{ => dsl}/FunctionsE2eTest.scala | 7 +- .../cyfra/e2e/{ => dsl}/GStructE2eTest.scala | 6 +- .../cyfra/e2e/{ => dsl}/GseqE2eTest.scala | 6 +- .../cyfra/e2e/{ => dsl}/WhenE2eTest.scala | 6 +- .../computenode/cyfra/fs2interop/GPipe.scala | 7 +- .../cyfra/runtime/VkCyfraRuntime.scala | 23 +- .../cyfra/vulkan/core/Instance.scala | 2 +- 11 files changed, 283 insertions(+), 31 deletions(-) create mode 100644 cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala rename cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/{ => dsl}/ArithmeticsE2eTest.scala (95%) rename cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/{ => dsl}/FunctionsE2eTest.scala (94%) rename cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/{ => dsl}/GStructE2eTest.scala (96%) rename cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/{ => dsl}/GseqE2eTest.scala (94%) rename cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/{ => dsl}/WhenE2eTest.scala (91%) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index f1cef442..7154e47f 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -12,6 +12,10 @@ import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag +import java.io.FileInputStream +import java.nio.file.Path +import scala.util.Using + trait GProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] extends GExecution[Params, L, L]: val layout: InitProgramLayout => Params => L val dispatch: (L, Params) => ProgramDispatch @@ -32,6 +36,19 @@ object GProgram: )(body: L => GIO[?]): GProgram[Params, L] = new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) + def fromSpirvFile[Params, L <: Layout : {LayoutBinding, LayoutStruct}]( + layout: InitProgramLayout ?=> Params => L, + dispatch: (L, Params) => ProgramDispatch, + path: Path + ): SpirvProgram[Params, L] = + Using.resource(new FileInputStream(path.toFile)): fis => + val fc = fis.getChannel + val size = fc.size().toInt + val bb = ByteBuffer.allocateDirect(size) + fc.read(bb) + bb.flip() + SpirvProgram(layout, dispatch, bb) + private[cyfra] class BufferLengthSpec[T <: Value: {Tag, FromExpr}](val length: Int) extends GBuffer[T]: private[cyfra] def materialise()(using Allocation): GBuffer[T] = GBuffer.apply[T](length) private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr, GStructSchema}]() extends GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index b4bcbd85..f60c02e3 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -14,6 +14,8 @@ import java.io.File import java.io.FileInputStream import java.nio.ByteBuffer import java.nio.channels.FileChannel +import java.nio.file.Path +import java.security.MessageDigest import java.util.Objects import scala.util.Try import scala.util.Using @@ -26,7 +28,26 @@ case class SpirvProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] priv code: ByteBuffer, entryPoint: String, shaderBindings: L => ShaderLayout, -) extends GProgram[Params, L] +) extends GProgram[Params, L]: + + /** + * A hash of the shader code, entry point, workgroup size, and layout bindings. + * Layout and dispatch are not taken into account. + */ + lazy val shaderHash: (Long, Long) = + val md = MessageDigest.getInstance("SHA-256") + md.update(code) + code.rewind() + md.update(entryPoint.getBytes) + md.update(workgroupSize.toList + .flatMap(BigInt(_).toByteArray).toArray) + val layout = shaderBindings(summon[LayoutStruct[L]].layoutRef) + layout.flatten.foreach: binding => + md.update(binding.binding.tag.toString.getBytes) + md.update(binding.operation.toString.getBytes) + val digest = md.digest() + val bb = java.nio.ByteBuffer.wrap(digest) + (bb.getLong(), bb.getLong()) object SpirvProgram: type ShaderLayout = Seq[Seq[Binding]] @@ -41,7 +62,7 @@ object SpirvProgram: dispatch: (L, Params) => ProgramDispatch, code: ByteBuffer ): SpirvProgram[Params, L] = - val workgroupSize = (128, 1, 1) // TODO Extract form shader + val workgroupSize = (128, 1, 1) // TODO Extract form shader val main = "main" val f: L => ShaderLayout = { case layout: Product => layout.productIterator.zipWithIndex.map { case (binding: GBinding[?], i) => Binding(binding, ReadWrite) }.toSeq.pipe(Seq(_)) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala new file mode 100644 index 00000000..95833a22 --- /dev/null +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala @@ -0,0 +1,209 @@ +package io.computenode.cyfra.e2e + +import io.computenode.cyfra.core.archive.GContext +import io.computenode.cyfra.core.layout.* +import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} +import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} +import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} +import io.computenode.cyfra.dsl.gio.GIO +import io.computenode.cyfra.dsl.struct.GStruct +import io.computenode.cyfra.dsl.struct.GStruct.Empty +import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.spirvtools.{SpirvCross, SpirvDisassembler, SpirvToolsRunner} +import io.computenode.cyfra.spirvtools.SpirvTool.ToFile +import io.computenode.cyfra.utility.Logger.logger +import org.lwjgl.BufferUtils +import org.lwjgl.system.MemoryUtil + +import java.nio.file.Paths +import scala.concurrent.ExecutionContext.Implicits.global +import java.util.concurrent.atomic.AtomicInteger +import scala.concurrent.{Await, Future} + + +class SpirvRuntimeEnduranceTest extends munit.FunSuite: + + test("Endurance test for GExecution with multiple SPIRV programs loaded from files"): + runEnduranceTest(10000) + + // === Emit program === + + case class EmitProgramParams(inSize: Int, emitN: Int) + + case class EmitProgramUniform(emitN: Int32) extends GStruct[EmitProgramUniform] + + case class EmitProgramLayout( + in: GBuffer[Int32], + out: GBuffer[Int32], + args: GUniform[EmitProgramUniform] = GUniform.fromParams, // todo will be different in the future + ) extends Layout + + val emitProgram = GProgram.fromSpirvFile[EmitProgramParams, EmitProgramLayout]( + layout = params => + EmitProgramLayout( + in = GBuffer[Int32](params.inSize), + out = GBuffer[Int32](params.inSize * params.emitN), + args = GUniform(EmitProgramUniform(params.emitN)), + ), + dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), + Paths.get(getClass.getResource("/emit.spv").toURI) + ) + + // === Filter program === + + case class FilterProgramParams(inSize: Int, filterValue: Int) + + case class FilterProgramUniform(filterValue: Int32) extends GStruct[FilterProgramUniform] + + case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) + extends Layout + + val filterProgram = GProgram.fromSpirvFile[FilterProgramParams, FilterProgramLayout]( + layout = params => + FilterProgramLayout( + in = GBuffer[Int32](params.inSize), + out = GBuffer[GBoolean](params.inSize), + params = GUniform(FilterProgramUniform(params.filterValue)), + ), + dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), + Paths.get(getClass.getResource("/filter.spv").toURI) + ) + // === GExecution === + + case class EmitFilterParams(inSize: Int, emitN: Int, filterValue: Int) + + case class EmitFilterLayout(inBuffer: GBuffer[Int32], emitBuffer: GBuffer[Int32], filterBuffer: GBuffer[GBoolean]) extends Layout + + case class EmitFilterResult(out: GBuffer[GBoolean]) extends Layout + + val emitFilterExecution = GExecution[EmitFilterParams, EmitFilterLayout]() + .addProgram(emitProgram)( + params => EmitProgramParams(inSize = params.inSize, emitN = params.emitN), + layout => EmitProgramLayout(in = layout.inBuffer, out = layout.emitBuffer), + ) + .addProgram(filterProgram)( + params => FilterProgramParams(inSize = 2 * params.inSize, filterValue = params.filterValue), + layout => FilterProgramLayout(in = layout.emitBuffer, out = layout.filterBuffer), + ) + + // Test case: Use one program 10 times, copying values from five input buffers to five output buffers and adding values from two uniforms + case class AddProgramParams(bufferSize: Int, addA: Int, addB: Int) + + case class AddProgramUniform(a: Int32) extends GStruct[AddProgramUniform] + + case class AddProgramLayout( + in1: GBuffer[Int32], + in2: GBuffer[Int32], + in3: GBuffer[Int32], + in4: GBuffer[Int32], + in5: GBuffer[Int32], + out1: GBuffer[Int32], + out2: GBuffer[Int32], + out3: GBuffer[Int32], + out4: GBuffer[Int32], + out5: GBuffer[Int32], + u1: GUniform[AddProgramUniform] = GUniform.fromParams, + u2: GUniform[AddProgramUniform] = GUniform.fromParams, + ) extends Layout + + case class AddProgramExecLayout( + in1: GBuffer[Int32], + in2: GBuffer[Int32], + in3: GBuffer[Int32], + in4: GBuffer[Int32], + in5: GBuffer[Int32], + out1: GBuffer[Int32], + out2: GBuffer[Int32], + out3: GBuffer[Int32], + out4: GBuffer[Int32], + out5: GBuffer[Int32], + ) extends Layout + + val addProgram: GProgram[AddProgramParams, AddProgramLayout] = GProgram.fromSpirvFile[AddProgramParams, AddProgramLayout]( + layout = params => + AddProgramLayout( + in1 = GBuffer[Int32](params.bufferSize), + in2 = GBuffer[Int32](params.bufferSize), + in3 = GBuffer[Int32](params.bufferSize), + in4 = GBuffer[Int32](params.bufferSize), + in5 = GBuffer[Int32](params.bufferSize), + out1 = GBuffer[Int32](params.bufferSize), + out2 = GBuffer[Int32](params.bufferSize), + out3 = GBuffer[Int32](params.bufferSize), + out4 = GBuffer[Int32](params.bufferSize), + out5 = GBuffer[Int32](params.bufferSize), + u1 = GUniform(AddProgramUniform(params.addA)), + u2 = GUniform(AddProgramUniform(params.addB)), + ), + dispatch = (layout, args) => GProgram.StaticDispatch((args.bufferSize / 128, 1, 1)), + Paths.get(getClass.getResource("/addOne.spv").toURI) + ) + + def swap(l: AddProgramLayout): AddProgramLayout = + val AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5, u1, u2) = l + AddProgramLayout(out1, out2, out3, out4, out5, in1, in2, in3, in4, in5, u1, u2) + + def fromExecLayout(l: AddProgramExecLayout): AddProgramLayout = + val AddProgramExecLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) = l + AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) + + val execution = (0 until 11).foldLeft( + GExecution[AddProgramParams, AddProgramExecLayout]().asInstanceOf[GExecution[AddProgramParams, AddProgramExecLayout, AddProgramExecLayout]], + )((x, i) => + if i % 2 == 0 then x.addProgram(addProgram)(mapParams = identity[AddProgramParams], mapLayout = fromExecLayout) + else x.addProgram(addProgram)(mapParams = identity, mapLayout = x => swap(fromExecLayout(x))), + ) + + def runEnduranceTest(nRuns: Int): Unit = + logger.info(s"Starting endurance test with ${nRuns} runs...") + + given runtime: VkCyfraRuntime = VkCyfraRuntime( + spirvToolsRunner = SpirvToolsRunner( + crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), + disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/dis.spvdis"))))) + + val bufferSize = 1280 + val params = AddProgramParams(bufferSize, addA = 0, addB = 1) + val region = GBufferRegion + .allocate[AddProgramExecLayout] + .map: region => + execution.execute(params, region) + val aInt = new AtomicInteger(0) + val runs = (1 to nRuns).map: + i => Future: + val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) + val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) + val rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) + + val inData = (0 until bufferSize).toArray + inBuffers.foreach(_.put(inData).flip()) + region.runUnsafe( + init = AddProgramExecLayout( + in1 = GBuffer[Int32](wbbList(0)), + in2 = GBuffer[Int32](wbbList(1)), + in3 = GBuffer[Int32](wbbList(2)), + in4 = GBuffer[Int32](wbbList(3)), + in5 = GBuffer[Int32](wbbList(4)), + out1 = GBuffer[Int32](bufferSize), + out2 = GBuffer[Int32](bufferSize), + out3 = GBuffer[Int32](bufferSize), + out4 = GBuffer[Int32](bufferSize), + out5 = GBuffer[Int32](bufferSize), + ), + onDone = layout => { + layout.out1.read(rbbList(0)) + layout.out2.read(rbbList(1)) + layout.out3.read(rbbList(2)) + layout.out4.read(rbbList(3)) + layout.out5.read(rbbList(4)) + }, + ) + val prev = aInt.getAndAdd(1) + if prev % 50 == 0 then logger.info(s"Iteration $prev completed") + + val allRuns = Future.sequence(runs) + Await.result(allRuns, scala.concurrent.duration.Duration.Inf) + + runtime.close() + logger.info("Endurance test completed successfully") diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/ArithmeticsE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala similarity index 95% rename from cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/ArithmeticsE2eTest.scala rename to cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala index 17797a77..78843776 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/ArithmeticsE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala @@ -1,8 +1,8 @@ -package io.computenode.cyfra.e2e +package io.computenode.cyfra.e2e.dsl import io.computenode.cyfra.core.archive.* -import mem.* -import GMem.fRGBA +import io.computenode.cyfra.core.archive.mem.* +import io.computenode.cyfra.core.archive.mem.GMem.fRGBA import io.computenode.cyfra.dsl.algebra.VectorAlgebra import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/FunctionsE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala similarity index 94% rename from cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/FunctionsE2eTest.scala rename to cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala index 31966edf..990906e8 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/FunctionsE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala @@ -1,9 +1,10 @@ -package io.computenode.cyfra.e2e +package io.computenode.cyfra.e2e.dsl -import io.computenode.cyfra.core.archive.*, mem.* +import io.computenode.cyfra.core.archive.* +import io.computenode.cyfra.core.archive.mem.* +import io.computenode.cyfra.core.archive.mem.GMem.fRGBA import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} -import GMem.fRGBA class FunctionsE2eTest extends munit.FunSuite: given gc: GContext = GContext() diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/GStructE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala similarity index 96% rename from cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/GStructE2eTest.scala rename to cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala index c1183c2a..20e15843 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/GStructE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala @@ -1,9 +1,9 @@ -package io.computenode.cyfra.e2e +package io.computenode.cyfra.e2e.dsl +import io.computenode.cyfra.core.archive.* +import io.computenode.cyfra.core.archive.mem.* import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.struct.GStruct -import io.computenode.cyfra.core.archive.* -import mem.* import io.computenode.cyfra.dsl.{*, given} class GStructE2eTest extends munit.FunSuite: diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/GseqE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala similarity index 94% rename from cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/GseqE2eTest.scala rename to cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala index d10cca51..318201b1 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/GseqE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala @@ -1,9 +1,9 @@ -package io.computenode.cyfra.e2e +package io.computenode.cyfra.e2e.dsl +import io.computenode.cyfra.core.archive.* +import io.computenode.cyfra.core.archive.mem.* import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.struct.GStruct -import io.computenode.cyfra.core.archive.* -import mem.* import io.computenode.cyfra.dsl.{*, given} class GseqE2eTest extends munit.FunSuite: diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/WhenE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala similarity index 91% rename from cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/WhenE2eTest.scala rename to cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala index 0a374c26..416f9148 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/WhenE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala @@ -1,8 +1,8 @@ -package io.computenode.cyfra.e2e +package io.computenode.cyfra.e2e.dsl -import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.core.archive.* -import mem.* +import io.computenode.cyfra.core.archive.mem.* +import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} class WhenE2eTest extends munit.FunSuite: diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index c79946be..0efefce1 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -138,7 +138,7 @@ object GPipe: inSize: Int, intervalSize: Int, ): GExecution[ScanParams, ScanLayout, ScanLayout] = - if intervalSize >= inSize then exec + if intervalSize > inSize then exec else val newExec = exec.addProgram(upsweep)(params => ScanParams(inSize, intervalSize), layout => layout) upsweepPhases(newExec, inSize, intervalSize * 2) @@ -170,7 +170,7 @@ object GPipe: val element = GIO.read[C](layout.in, invocId) val prefixSum = GIO.read[Int32](layout.scan, invocId) for - _ <- GIO.printf("Element %d, prefix sum %d", invocId, prefixSum) + _ <- GIO.printf("Compact: Element %d, prefix sum %d", invocId, prefixSum) _ <- GIO.when(invocId > 0): val prevScan = GIO.read[Int32](layout.scan, invocId - 1) GIO.when(prevScan < prefixSum): @@ -224,10 +224,11 @@ object GPipe: region.runUnsafe( init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](filterParams.inSize), out = GBuffer[C](filterParams.inSize)), onDone = layout => { - layout.scan.read(filteredCount, (filterParams.inSize - 2) * intSize) + layout.scan.read(filteredCount, (filterParams.inSize - 1) * intSize) layout.out.read(compactBuf) } ) val filteredN = filteredCount.getInt(0) val arr = bridge.fromByteBuffer(compactBuf, new Array[S](filteredN)) + println(arr) Stream.emits(arr) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index e38648f4..17b90fab 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -8,23 +8,26 @@ import io.computenode.cyfra.spirvtools.SpirvToolsRunner import io.computenode.cyfra.vulkan.VulkanContext import io.computenode.cyfra.vulkan.compute.ComputePipeline +import java.security.MessageDigest import scala.collection.mutable class VkCyfraRuntime(spirvToolsRunner: SpirvToolsRunner = SpirvToolsRunner()) extends CyfraRuntime: private val context = new VulkanContext() import context.given - private val shaderCache = mutable.Map[GProgram[?, ?], VkShader[?]]() + private val gProgramCache = mutable.Map[GProgram[?, ?], SpirvProgram[?, ?]]() + private val shaderCache = mutable.Map[(Long, Long), VkShader[?]]() + private[cyfra] def getOrLoadProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[Params, L]): VkShader[L] = synchronized: - if (shaderCache.contains(program)) then - shaderCache(program).asInstanceOf[VkShader[L]] - else - val spirvProgram = program match - case p: GioProgram[?, ?] => compile(p) - case p: SpirvProgram[?, ?] => p - case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") - - shaderCache.getOrElseUpdate(program, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] + + val spirvProgram: SpirvProgram[Params, L] = program match + case p: GioProgram[Params, L] if gProgramCache.contains(p) => + gProgramCache(p).asInstanceOf + case p: GioProgram[Params, L] => compile(p) + case p: SpirvProgram[Params, L] => p + case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + + shaderCache.getOrElseUpdate(spirvProgram.shaderHash, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = val GioProgram(_, layout, dispatch, _) = program diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index a1680c0e..2f3d3df5 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala @@ -152,7 +152,7 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj ) .pfnUserCallback(c) - val debugMessengerBuff = stack.callocLong(1) + val debugMessengerBuff = MemoryUtil.memAllocLong(1) check(vkCreateDebugUtilsMessengerEXT( handle, debugMessengerCreate.get(0), From 92d021907fc3b0d7bb7911154d5f3226a146fcaf Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 10 Sep 2025 11:43:13 +0200 Subject: [PATCH 31/38] fix --- .../main/scala/io/computenode/cyfra/core/Allocation.scala | 2 +- .../scala/io/computenode/cyfra/samples/TestingStuff.scala | 8 ++++---- .../scala/io/computenode/cyfra/runtime/VkBinding.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index d279bb88..908c452e 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -10,7 +10,7 @@ import izumi.reflect.Tag import java.nio.ByteBuffer trait Allocation: - def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit + def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index c45680ea..2f1b2799 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -16,11 +16,11 @@ import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given -def printBuffer(bb: ByteBuffer): Unit = { +def printBuffer(bb: ByteBuffer): Unit = val l = bb.asIntBuffer() - val s = (0 until l.remaining()).map(l.get).toList - println(s.mkString(" ")) -} + val a = new Array[Int](l.remaining()) + l.get(a) + println(a.mkString(" ")) object TestingStuff: diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala index acda99f3..6283ad78 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -39,7 +39,7 @@ sealed abstract class VkBinding[T <: Value: {Tag, FromExpr}](val buffer: Buffer) val fence = PendingExecution.executeAll(pendingExecs, queue) fence.block() PendingExecution.cleanupAll(pendingExecs) - + runningExecs.foreach(_.block()) PendingExecution.cleanupAll(runningExecs) From cdb7900c2fbb0a8ffbe2f0b7e4ba0eecb5772534 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Thu, 11 Sep 2025 13:16:45 +0200 Subject: [PATCH 32/38] fixes^ --- .../computenode/cyfra/core/Allocation.scala | 2 +- .../cyfra/core/GBufferRegion.scala | 8 ++--- .../cyfra/runtime/PendingExecution.scala | 32 ++++++++++++------- .../cyfra/runtime/VkAllocation.scala | 2 +- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index 908c452e..493b6a6e 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -10,7 +10,7 @@ import izumi.reflect.Tag import java.nio.ByteBuffer trait Allocation: - def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit + def submitLayout[L <: Layout: LayoutBinding](layout: L): Unit extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index 65ad0b41..b80bc679 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -36,12 +36,12 @@ object GBufferRegion: // noinspection ScalaRedundantCast val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): - case _: AllocRegion[?] => None - case m @ MapRegion(req, f) => + case AllocRegion => None + case MapRegion(req, f) => Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) - val initAlloc = init(using allocation).tap(allocation.reportLayout) + val initAlloc = init(using allocation).tap(allocation.submitLayout) val bodyAlloc = steps.foldLeft[Layout](initAlloc): (acc, step) => - step._1(allocation)(acc).tap(allocation.reportLayout(_)(using step._2)) + step._1(allocation)(acc).tap(allocation.submitLayout(_)(using step._2)) onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 52aea0f2..1fe09725 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -10,6 +10,12 @@ import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSem import scala.collection.mutable +/** A command buffer that is pending execution, along with its dependencies and cleanup actions. + * + * You can call `close()` only when `isFinished || isPending` is true + * + * You can call `destroy()` only when all dependants are `isClosed` + */ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device): private val semaphore: Semaphore = Semaphore() private var fence: Option[Fence] = None @@ -23,6 +29,7 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: private var closed = false def isClosed: Boolean = closed private def close(): Unit = + assert(isFinished || isPending, "Cannot close a PendingExecution that is not finished or pending") if closed then return cleanup() closed = true @@ -35,24 +42,29 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: fence.foreach(x => if x.isAlive then x.destroy()) destroyed = true - private def setFence(f: Fence): Unit = { - if !isPending then return - fence = Some(f) - dependencies.foreach(_.setFence(f)) - } - - private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = + /** Gathers all command buffers and their semaphores for submission to the queue, in the correct order. + * + * When you call this method, you are expected to submit the command buffers to the queue, and signal the provided fence when done. + * @param f + * The fence to signal when the command buffers are done executing. + * @return + * A sequence of tuples, each containing a command buffer, semaphore to signal, and a set of semaphores to wait on. + */ + private def gatherForSubmission(f: Fence): Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = if !isPending then return Seq.empty val mySubmission = ((handle, semaphore), dependencies.map(_.semaphore).toSet) - dependencies.flatMap(_.gatherForSubmission).appended(mySubmission) + fence = Some(f) + dependencies.flatMap(_.gatherForSubmission(f)).appended(mySubmission) object PendingExecution: def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => assert(executions.forall(_.isPending), "All executions must be pending") assert(executions.nonEmpty, "At least one execution must be provided") + val fence = Fence() + val exec: Seq[(Set[Semaphore], Set[(VkCommandBuffer, Semaphore)])] = - val gathered = executions.flatMap(_.gatherForSubmission) + val gathered = executions.flatMap(_.gatherForSubmission(fence)) val ordering = gathered.zipWithIndex.map(x => (x._1._1._1, x._2)).toMap gathered.toSet.groupMap(_._2)(_._1).toSeq.sortBy(x => x._2.map(_._1).map(ordering).min) @@ -95,9 +107,7 @@ object PendingExecution: submitInfos.flip() - val fence = Fence() check(vkQueueSubmit2(queue.get, submitInfos, fence.get), "Failed to submit command buffer to queue") - executions.foreach(_.setFence(fence)) fence def cleanupAll(executions: Seq[PendingExecution]): Unit = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a799f98c..ea80f7c6 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -29,7 +29,7 @@ import scala.util.chaining.* class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator, Device) extends Allocation: given VkAllocation = this - override def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit = + override def submitLayout[L <: Layout: LayoutBinding](layout: L): Unit = val executions = summon[LayoutBinding[L]] .toBindings(layout) .map(getUnderlying) From 4eb491ea5369a8b2a710ee7d19b283003212c62b Mon Sep 17 00:00:00 2001 From: Szymon Date: Thu, 11 Sep 2025 22:52:12 +0200 Subject: [PATCH 33/38] Progres on porting GFunc --- .../cyfra/core/GBufferRegion.scala | 2 +- .../io/computenode/cyfra/core/GCodec.scala | 37 ++++---- .../cyfra/core/archive/GFunction.scala | 93 ++++++++++++++++--- .../cyfra/core/layout/LayoutStruct.scala | 34 ++++++- .../cyfra/dsl/binding/GBinding.scala | 2 +- .../cyfra/dsl/collections/GArray2D.scala | 5 +- .../cyfra/e2e/dsl/GStructE2eTest.scala | 5 +- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 47 +--------- .../computenode/cyfra/fs2interop/GPipe.scala | 13 +-- .../cyfra/fs2interop/GPipeLegacy.scala | 39 -------- .../cyfra/runtime/PendingExecution.scala | 4 +- .../cyfra/runtime/VkAllocation.scala | 1 - .../computenode/cyfra/runtime/VkBinding.scala | 5 +- .../cyfra/vulkan/command/Fence.scala | 2 +- 14 files changed, 151 insertions(+), 138 deletions(-) rename cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala => cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala (67%) delete mode 100644 cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index b80bc679..5d613a56 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -36,7 +36,7 @@ object GBufferRegion: // noinspection ScalaRedundantCast val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): - case AllocRegion => None + case AllocRegion() => None case MapRegion(req, f) => Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala similarity index 67% rename from cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala rename to cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala index b148a53b..fe059872 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/Bridge.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala @@ -1,21 +1,21 @@ // scala -package io.computenode.cyfra.fs2interop +package io.computenode.cyfra.core -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA +import io.computenode.cyfra.core.archive.mem.GMem.{fRGBA, totalStride} import io.computenode.cyfra.dsl.* -import fs2.* +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} +import izumi.reflect.Tag import java.nio.{ByteBuffer, ByteOrder} -import izumi.reflect.Tag import scala.reflect.ClassTag -trait Bridge[CyfraType <: Value: FromExpr: Tag, ScalaType: ClassTag]: - def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[ScalaType]): ByteBuffer +trait GCodec[CyfraType <: Value: {FromExpr, Tag}, ScalaType: ClassTag]: + def toByteBuffer(inBuf: ByteBuffer, arr: Array[ScalaType]): ByteBuffer def fromByteBuffer(outBuf: ByteBuffer, arr: Array[ScalaType]): Array[ScalaType] -object Bridge: - given Bridge[Int32, Int]: - def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Int]): ByteBuffer = +object GCodec: + given GCodec[Int32, Int]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Array[Int]): ByteBuffer = inBuf.clear().order(ByteOrder.nativeOrder()) val ib = inBuf.asIntBuffer() ib.put(chunk.toArray[Int]) @@ -27,8 +27,8 @@ object Bridge: outBuf.rewind() arr - given Bridge[Float32, Float]: - def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Float]): ByteBuffer = + given GCodec[Float32, Float]: + def toByteBuffer(inBuf: ByteBuffer, chunk: Array[Float]): ByteBuffer = inBuf.clear().order(ByteOrder.nativeOrder()) val fb = inBuf.asFloatBuffer() fb.put(chunk.toArray[Float]) @@ -40,11 +40,10 @@ object Bridge: outBuf.rewind() arr - given Bridge[Vec4[Float32], fRGBA]: - def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[fRGBA]): ByteBuffer = + given GCodec[Vec4[Float32], fRGBA]: + def toByteBuffer(inBuf: ByteBuffer, arr: Array[fRGBA]): ByteBuffer = inBuf.clear().order(ByteOrder.nativeOrder()) - val vecs = chunk.toArray[fRGBA] - vecs.foreach: + arr.foreach: case (x, y, z, a) => inBuf.putFloat(x) inBuf.putFloat(y) @@ -59,10 +58,10 @@ object Bridge: outBuf.rewind() arr - given Bridge[GBoolean, Boolean]: - def toByteBuffer(inBuf: ByteBuffer, chunk: Chunk[Boolean]): ByteBuffer = - inBuf.put(chunk.toArray.asInstanceOf[Array[Byte]]).flip() + given GCodec[GBoolean, Boolean]: + def toByteBuffer(inBuf: ByteBuffer, arr: Array[Boolean]): ByteBuffer = + inBuf.put(arr.asInstanceOf[Array[Byte]]).flip() inBuf def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Boolean]): Array[Boolean] = outBuf.get(arr.asInstanceOf[Array[Byte]]).flip() - arr \ No newline at end of file + arr diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala index c5613bb7..2798508c 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala @@ -1,28 +1,99 @@ package io.computenode.cyfra.core.archive +import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GCodec, GProgram} +import io.computenode.cyfra.core.GBufferRegion.* +import io.computenode.cyfra.core.GProgram.StaticDispatch import io.computenode.cyfra.core.archive.GFunction +import io.computenode.cyfra.core.archive.GFunction.{GFunctionLayout, GFunctionParams} +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.collections.{GArray, GArray2D} +import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.* import io.computenode.cyfra.dsl.{*, given} - +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import io.computenode.cyfra.spirv.compilers.SpirvProgramCompiler.totalStride import izumi.reflect.Tag +import org.lwjgl.BufferUtils + +import scala.reflect.ClassTag + -case class GFunction[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: (G, Int32, GArray[H]) => R)( - implicit context: GContext, +case class GFunction[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( + underlying: GProgram[GFunctionParams, GFunctionLayout[G, H, R]] ): - def arrayInputs: List[Tag[?]] = List(summon[Tag[H]]) - def arrayOutputs: List[Tag[?]] = List(summon[Tag[R]]) - val pipeline: Nothing = ??? + def run[HS, GS, RS : ClassTag] (input: Seq[HS], g: GS)( + using gCodec: GCodec[G, GS], + hCodec: GCodec[H, HS], + rCodec: GCodec[R, RS], + runtime: CyfraRuntime + ): Seq[RS] = + + val inTypeSize = typeStride(Tag.apply[H]) + val outTypeSize = typeStride(Tag.apply[R]) + val uniformStride = totalStride(summon[GStructSchema[G]]) + val params = GFunctionParams(size = input.size) + + val in = BufferUtils.createByteBuffer(inTypeSize * input.size) + val out = BufferUtils.createByteBuffer(outTypeSize * input.size) + val uniform = BufferUtils.createByteBuffer(uniformStride) + + GBufferRegion.allocate[GFunctionLayout[G, H, R]] + .map: layout => + underlying.execute(params, layout) + .runUnsafe( + init = GFunctionLayout( + in = GBuffer[H](in), + out = GBuffer[R](outTypeSize * input.size), + uniform = GUniform[G](uniform), + ), + onDone = layout => + layout.out.read(out) + ) + val resultArray = Array.ofDim[RS](input.size) + rCodec.fromByteBuffer(out, resultArray).toSeq + + object GFunction: - def apply[H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: H => R)(using context: GContext): GFunction[GStruct.Empty, H, R] = - new GFunction[GStruct.Empty, H, R]((_, index: Int32, gArray: GArray[H]) => fn(gArray.at(index))) - + case class GFunctionParams( + size: Int + ) + + case class GFunctionLayout[G <: GStruct[G], H <: Value, R <: Value]( + in: GBuffer[H], + out: GBuffer[R], + uniform: GUniform[G] + ) extends Layout + + def apply[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: (G, Int32, GBuffer[H]) => R): GFunction[G, H, R] = + val body = (layout: GFunctionLayout[G, H, R]) => + val g = layout.uniform.read + val result = fn(g, GIO.invocationId, layout.in) + layout.out.write(GIO.invocationId, result) + + summon[LayoutStruct[GFunctionLayout[G, H, R]]] + + GFunction( + underlying = GProgram.apply[GFunctionParams, GFunctionLayout[G, H, R]]( + layout = (p: GFunctionParams) => GFunctionLayout[G, H, R]( + in = GBuffer[H](p.size), + out = GBuffer[R](p.size), + uniform = GUniform[G](), + ), + dispatch = (l, p) => StaticDispatch((p.size + 255) / 256, 1, 1), + workgroupSize = (256, 1, 1), + )(body) + ) + + def apply[H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: H => R): GFunction[GStruct.Empty, H, R] = + GFunction[GStruct.Empty, H, R]((g: GStruct.Empty, index: Int32, a: GBuffer[H]) => fn(a.read(index))) + def from2D[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( width: Int, - )(fn: (G, (Int32, Int32), GArray2D[H]) => R)(using context: GContext): GFunction[G, H, R] = - GFunction[G, H, R]((g: G, index: Int32, a: GArray[H]) => + )(fn: (G, (Int32, Int32), GArray2D[H]) => R): GFunction[G, H, R] = + GFunction[G, H, R]((g: G, index: Int32, a: GBuffer[H]) => val x: Int32 = index mod width val y: Int32 = index / width val arr = GArray2D(width, a) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala index 76d8ffa8..69327f73 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala @@ -36,7 +36,18 @@ object LayoutStruct: report.errorAndAbort("LayoutStruct can only be derived for case classes with GBinding elements") val valueTypes = fieldTypes.map: ftype => - (ftype, ftype.typeArgs.headOption.getOrElse(report.errorAndAbort("GBuffer must have a value type"))) + ftype match + case AppliedType(_, args) if args.nonEmpty => + val valueType = args.head + // Ensure we're working with the original type parameter, not the instance type + val resolvedType = valueType match + case tr if tr.typeSymbol.isTypeParam => + // Find the corresponding type parameter from the original class + tpe.typeArgs.find(_.typeSymbol.name == tr.typeSymbol.name).getOrElse(tr) + case tr => tr + (ftype, resolvedType) + case _ => + report.errorAndAbort("GBinding must have a value type") // summon izumi tags val typeGivens = valueTypes.map: @@ -48,10 +59,10 @@ object LayoutStruct: farg.asType, Expr.summon[Tag[t]] match case Some(tagExpr) => tagExpr - case None => report.errorAndAbort(s"Cannot summon Tag for type ${tpe.show}"), + case None => report.errorAndAbort(s"Cannot summon Tag for type ${farg.show}"), Expr.summon[FromExpr[t]] match case Some(fromExpr) => fromExpr - case None => report.errorAndAbort(s"Cannot summon FromExpr for type ${tpe.show}"), + case None => report.errorAndAbort(s"Cannot summon FromExpr for type ${farg.show}"), ) val buffers = typeGivens.zipWithIndex.map: @@ -70,8 +81,21 @@ object LayoutStruct: } val constructor = sym.primaryConstructor - - val layoutInstance = Apply(Select(New(TypeIdent(sym)), constructor), buffers.map(_.asTerm)) + report.info(s"Constructor: ${constructor.fullName} with params ${constructor.paramSymss.flatten.map(_.name).mkString(", ")}") + + val typeArgs = tpe.typeArgs + + val layoutInstance = + if (typeArgs.isEmpty) then + Apply(Select(New(TypeIdent(sym)), constructor), buffers.map(_.asTerm)) + else + Apply( + TypeApply( + Select(New(TypeIdent(sym)), constructor), + typeArgs.map(arg => TypeTree.of(using arg.asType)) + ), + buffers.map(_.asTerm) + ) val layoutRef = layoutInstance.asExprOf[T] diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala index ce003c87..27f25d04 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala @@ -23,7 +23,7 @@ trait GUniform[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}] extends GBinding def read: T = fromExprEval(ReadUniform(this)) def write(value: T): GIO[Empty] = WriteUniform(this, value) - + def schema = summon[GStructSchema[T]] object GUniform: diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala index e532eea2..9671e288 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray2D.scala @@ -6,8 +6,9 @@ import io.computenode.cyfra.dsl.algebra.ScalarAlgebra.{*, given} import io.computenode.cyfra.dsl.macros.Source import izumi.reflect.Tag import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.binding.GBuffer // todo temporary -class GArray2D[T <: Value: {Tag, FromExpr}](width: Int, val arr: GArray[T]): +class GArray2D[T <: Value: {Tag, FromExpr}](width: Int, val arr: GBuffer[T]): def at(x: Int32, y: Int32)(using Source): T = - arr.at(y * width + x) + arr.read(y * width + x) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala index 20e15843..bfc88e0e 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala @@ -2,6 +2,7 @@ package io.computenode.cyfra.e2e.dsl import io.computenode.cyfra.core.archive.* import io.computenode.cyfra.core.archive.mem.* +import io.computenode.cyfra.dsl.binding.GBuffer import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} @@ -19,7 +20,7 @@ class GStructE2eTest extends munit.FunSuite: test("GStruct passed as uniform"): UniformContext.withUniform(custom1): val gf: GFunction[Custom, Float32, Float32] = GFunction: - case (Custom(f, v), index, gArray) => v.*(f).dot(v) + gArray.at(index) * f + case (Custom(f, v), index: Int32, gBuff: GBuffer[Float32]) => v.*(f).dot(v) + gBuff.read(index) * f val inArr = (0 to 255).map(_.toFloat).toArray val gmem = FloatMem(inArr) @@ -34,7 +35,7 @@ class GStructE2eTest extends munit.FunSuite: test("GStruct of GStructs".ignore): UniformContext.withUniform(nested): val gf: GFunction[Nested, Float32, Float32] = GFunction: - case (Nested(Custom(f1, v1), Custom(f2, v2)), index, gArray) => + case (Nested(Custom(f1, v1), Custom(f2, v2)), index, gArray: GBuffer[Float32]) => v1.*(f2).dot(v2) + gArray.at(index) * f1 val inArr = (0 to 255).map(_.toFloat).toArray diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index 289426ec..e5c2667f 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -6,7 +6,6 @@ import GMem.fRGBA import io.computenode.cyfra.dsl.{*, given} import algebra.VectorAlgebra import io.computenode.cyfra.fs2interop.* -import Bridge.given import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.runtime.VkCyfraRuntime import fs2.{io as fs2io, *} @@ -68,48 +67,4 @@ class Fs2Tests extends munit.FunSuite: result .zip(expected) .foreach: (res, exp) => - assert(res == exp, s"Expected $exp, got $res") - -class Fs2LegacyTests extends munit.FunSuite: - given gc: GContext = GContext() - import GPipeLegacy.* - - test("fs2 Float stream (legacy)"): - val inSeq = (0 to 255).map(_.toFloat) - val inStream = Stream.emits(inSeq) - val outStream = inStream.gPipeFloat(_ + 1f).toList - val expected = inStream.map(_ + 1f).toList - outStream - .zip(expected) - .foreach: (res, exp) => - assert(Math.abs(res - exp) < 0.001f, s"Expected $exp but got $res") - - test("fs2 Int stream (legacy)"): - val inSeq = 0 to 255 - val inStream = Stream.emits(inSeq) - val outStream = inStream.gPipeInt(_ + 1).toList - val expected = inStream.map(_ + 1).toList - outStream - .zip(expected) - .foreach: (res, exp) => - assert(res == exp, s"Expected $exp but got $res") - - test("fs2 Vec4Float stream (legacy)"): - val k = -2.1f - val f = (1.2f, 2.3f, 3.4f, 4.5f) - val v = VectorAlgebra.vec4.tupled(f) - - val inSeq: Seq[fRGBA] = (0 to 1023) - .map(_.toFloat) - .grouped(4) - .map: - case Seq(a, b, c, d) => (a, b, c, d) - .toSeq - val inStream = Stream.emits(inSeq) - val outStream = inStream.gPipeVec4(vec => (-vec).*(k).+(v)).toList - val expected = inStream.map(vec => vec.neg.scl(k).add(f)).toList - - outStream - .zip(expected) - .foreach: (res, exp) => - assert(res.close(exp)(0.001f), s"Expected $exp but got $res") + assert(res == exp, s"Expected $exp, got $res") \ No newline at end of file diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 0efefce1..f75ac208 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -1,6 +1,6 @@ package io.computenode.cyfra.fs2interop -import io.computenode.cyfra.core.{Allocation, layout} +import io.computenode.cyfra.core.{Allocation, layout, GCodec} import layout.Layout import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.{*, given} @@ -13,6 +13,7 @@ import struct.GStruct import GStruct.Empty import Empty.given import fs2.* + import java.nio.ByteBuffer import org.lwjgl.BufferUtils import izumi.reflect.Tag @@ -22,7 +23,7 @@ import scala.reflect.ClassTag object GPipe: def map[F[_], C1 <: Value: {FromExpr, Tag}, C2 <: Value: {FromExpr, Tag}, S1: ClassTag, S2: ClassTag]( f: C1 => C2, - )(using cr: CyfraRuntime, bridge1: Bridge[C1, S1], bridge2: Bridge[C2, S2]): Pipe[F, S1, S2] = + )(using cr: CyfraRuntime, bridge1: GCodec[C1, S1], bridge2: GCodec[C2, S2]): Pipe[F, S1, S2] = (stream: Stream[F, S1]) => case class Params(inSize: Int) case class PLayout(in: GBuffer[C1], out: GBuffer[C2]) extends Layout @@ -58,7 +59,7 @@ object GPipe: stream .chunkN(params.inSize) .flatMap: chunk => - bridge1.toByteBuffer(inBuf, chunk) + bridge1.toByteBuffer(inBuf, chunk.toArray) region.runUnsafe(init = PLayout( in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), @@ -68,10 +69,10 @@ object GPipe: Stream.emits(bridge2.fromByteBuffer(outBuf, new Array[S2](params.inSize))) // Overload for convenient single type version - def map[F[_], C <: Value: FromExpr: Tag, S: ClassTag](f: C => C)(using CyfraRuntime, Bridge[C, S]): Pipe[F, S, S] = + def map[F[_], C <: Value: FromExpr: Tag, S: ClassTag](f: C => C)(using CyfraRuntime, GCodec[C, S]): Pipe[F, S, S] = map[F, C, C, S, S](f) - def filter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: Bridge[C, S]): Pipe[F, S, S] = + def filter[F[_], C <: Value: FromExpr: Tag, S: ClassTag](pred: C => GBoolean)(using cr: CyfraRuntime, bridge: GCodec[C, S]): Pipe[F, S, S] = (stream: Stream[F, S]) => val chunkInSize = 256 @@ -220,7 +221,7 @@ object GPipe: stream .chunkN(chunkInSize) .flatMap: chunk => - bridge.toByteBuffer(predBuf, chunk) + bridge.toByteBuffer(predBuf, chunk.toArray) region.runUnsafe( init = FilterLayout(in = GBuffer[C](predBuf), scan = GBuffer[Int32](filterParams.inSize), out = GBuffer[C](filterParams.inSize)), onDone = layout => { diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala deleted file mode 100644 index 78d36209..00000000 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipeLegacy.scala +++ /dev/null @@ -1,39 +0,0 @@ -package io.computenode.cyfra.fs2interop - -import io.computenode.cyfra.core.archive.*, mem.*, GMem.fRGBA -import io.computenode.cyfra.dsl.{*, given} -import struct.GStruct, GStruct.Empty, Empty.given - -import fs2.* - -// legacy stuff working with GFunction -object GPipeLegacy: - extension (stream: Stream[Pure, Float]) - def gPipeFloat(fn: Float32 => Float32)(using GContext): Stream[Pure, Float] = - val gf: GFunction[Empty, Float32, Float32] = GFunction(fn) - stream - .chunkMin(256) - .flatMap: chunk => - val gmem = FloatMem(chunk.toArray) - val res = gmem.map(gf).asInstanceOf[FloatMem].toArray - Stream.emits(res) - - extension (stream: Stream[Pure, Int]) - def gPipeInt(fn: Int32 => Int32)(using GContext): Stream[Pure, Int] = - val gf: GFunction[Empty, Int32, Int32] = GFunction(fn) - stream - .chunkMin(256) - .flatMap: chunk => - val gmem = IntMem(chunk.toArray) - val res = gmem.map(gf).asInstanceOf[IntMem].toArray - Stream.emits(res) - - extension (stream: Stream[Pure, fRGBA]) - def gPipeVec4(fn: Vec4[Float32] => Vec4[Float32])(using GContext): Stream[Pure, fRGBA] = - val gf: GFunction[Empty, Vec4[Float32], Vec4[Float32]] = GFunction(fn) - stream - .chunkMin(256) - .flatMap: chunk => - val gmem = Vec4FloatMem(chunk.toArray) - val res = gmem.map(gf).asInstanceOf[Vec4FloatMem].toArray - Stream.emits(res) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 1fe09725..9ed42d7d 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -21,8 +21,8 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: private var fence: Option[Fence] = None def isPending: Boolean = fence.isEmpty - def isRunning: Boolean = fence.exists(!_.isSignaled) - def isFinished: Boolean = fence.exists(_.isSignaled) + def isRunning: Boolean = fence.exists(f => f.isAlive && !f.isSignaled) + def isFinished: Boolean = fence.exists(f => !f.isAlive || f.isSignaled) def block(): Unit = fence.foreach(_.block()) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index f7935491..4a3d3024 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -91,7 +91,6 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) private def direct[T <: GStruct[?]: {Tag, FromExpr, GStructSchema}](buff: ByteBuffer): GUniform[T] = GUniform[T](buff) - def getInitProgramLayout: GProgram.InitProgramLayout = new GProgram.InitProgramLayout: extension (uniforms: GUniform.type) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala index 6283ad78..00c2d280 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -17,6 +17,7 @@ import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAG import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.GUniform +import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} import izumi.reflect.Tag import org.lwjgl.vulkan.VK10 @@ -60,13 +61,13 @@ object VkBuffer: val buffer = new Buffer.DeviceBuffer(size, UsageFlags) new VkBuffer[T](length, buffer) -class VkUniform[T <: Value: {Tag, FromExpr}] private (underlying: Buffer) extends VkBinding[T](underlying) with GUniform[T] +class VkUniform[T <: GStruct[_]: {Tag, FromExpr, GStructSchema}] private (underlying: Buffer) extends VkBinding[T](underlying) with GUniform[T] object VkUniform: private final val UsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT - def apply[T <: Value: {Tag, FromExpr}]()(using Allocator): VkUniform[T] = + def apply[T <: GStruct[_]: {Tag, FromExpr, GStructSchema}]()(using Allocator): VkUniform[T] = val sizeOfT = typeStride(summon[Tag[T]]) val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) new VkUniform[T](buffer) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala index 630fa924..7664a3c6 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala @@ -19,7 +19,7 @@ private[cyfra] class Fence(flags: Int = 0)(using device: Device) extends VulkanO val pFence = stack.callocLong(1) check(vkCreateFence(device.get, fenceInfo, null, pFence), "Failed to create fence") - pFence.get(), + pFence.get() ) override def close(): Unit = From 763c3a510ae0c8320397ae517981781a9841bc2e Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Mon, 15 Sep 2025 00:43:23 +0200 Subject: [PATCH 34/38] Old samples work now --- .../io/computenode/cyfra/core/GCodec.scala | 83 ++- .../computenode/cyfra/core/GExecution.scala | 1 - .../cyfra/core/archive/Executable.scala | 9 - .../cyfra/core/archive/GContext.scala | 73 --- .../cyfra/core/archive/GFunction.scala | 38 +- .../cyfra/core/archive/UniformContext.scala | 13 - .../cyfra/core/archive/mem/FloatMem.scala | 27 - .../cyfra/core/archive/mem/GMem.scala | 52 -- .../cyfra/core/archive/mem/IntMem.scala | 27 - .../cyfra/core/archive/mem/RamGMem.scala | 9 - .../cyfra/core/archive/mem/Vec4FloatMem.scala | 33 -- .../io/computenode/cyfra/dsl/Expression.scala | 8 +- .../io/computenode/cyfra/dsl/Value.scala | 3 +- .../cyfra/dsl/struct/GStruct.scala | 4 +- .../cyfra/e2e/RuntimeEnduranceTest.scala | 1 - .../cyfra/e2e/SpirvRuntimeEnduranceTest.scala | 1 - .../cyfra/e2e/dsl/ArithmeticsE2eTest.scala | 18 +- .../cyfra/e2e/dsl/FunctionsE2eTest.scala | 15 +- .../cyfra/e2e/dsl/GStructE2eTest.scala | 53 +- .../cyfra/e2e/dsl/GseqE2eTest.scala | 14 +- .../cyfra/e2e/dsl/WhenE2eTest.scala | 11 +- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 2 - .../cyfra/e2e/juliaset/JuliaSet.scala | 15 +- .../cyfra/samples/TestingStuff.scala | 5 +- .../cyfra/samples/oldsamples/Raytracing.scala | 535 ------------------ .../cyfra/samples/slides/1sample.scala | 17 - .../cyfra/samples/slides/2simpleray.scala | 44 -- .../cyfra/samples/slides/3rays.scala | 153 ----- .../cyfra/samples/slides/4random.scala | 10 +- .../animation/AnimatedFunctionRenderer.scala | 12 +- .../foton/animation/AnimationRenderer.scala | 1 - .../cyfra/foton/rt/ImageRtRenderer.scala | 17 +- .../cyfra/foton/rt/RtRenderer.scala | 3 - .../rt/animation/AnimationRtRenderer.scala | 17 +- 34 files changed, 202 insertions(+), 1122 deletions(-) delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/Executable.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/UniformContext.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/FloatMem.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/GMem.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/IntMem.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/RamGMem.scala delete mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/Vec4FloatMem.scala delete mode 100644 cyfra-examples/src/main/scala/io/computenode/cyfra/samples/oldsamples/Raytracing.scala delete mode 100644 cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/1sample.scala delete mode 100644 cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/2simpleray.scala delete mode 100644 cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/3rays.scala diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala index fe059872..a686e9ff 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala @@ -1,9 +1,11 @@ // scala package io.computenode.cyfra.core -import io.computenode.cyfra.core.archive.mem.GMem.{fRGBA, totalStride} import io.computenode.cyfra.dsl.* -import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} +import io.computenode.cyfra.dsl.macros.Source +import io.computenode.cyfra.dsl.struct.GStruct.ComposeStruct +import io.computenode.cyfra.dsl.struct.{GStruct, GStructConstructor, GStructSchema} +import io.computenode.cyfra.spirv.SpirvTypes.typeStride import izumi.reflect.Tag import java.nio.{ByteBuffer, ByteOrder} @@ -12,8 +14,19 @@ import scala.reflect.ClassTag trait GCodec[CyfraType <: Value: {FromExpr, Tag}, ScalaType: ClassTag]: def toByteBuffer(inBuf: ByteBuffer, arr: Array[ScalaType]): ByteBuffer def fromByteBuffer(outBuf: ByteBuffer, arr: Array[ScalaType]): Array[ScalaType] + def fromByteBufferUnchecked(outBuf: ByteBuffer, arr: Array[Any]): Array[ScalaType] = + fromByteBuffer(outBuf, arr.asInstanceOf[Array[ScalaType]]) object GCodec: + + def totalStride(gs: GStructSchema[?]): Int = gs.fields.map: + case (_, fromExpr, t) if t <:< gs.gStructTag => + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + case (_, _, t) => + typeStride(t) + .sum + given GCodec[Int32, Int]: def toByteBuffer(inBuf: ByteBuffer, chunk: Array[Int]): ByteBuffer = inBuf.clear().order(ByteOrder.nativeOrder()) @@ -43,12 +56,8 @@ object GCodec: given GCodec[Vec4[Float32], fRGBA]: def toByteBuffer(inBuf: ByteBuffer, arr: Array[fRGBA]): ByteBuffer = inBuf.clear().order(ByteOrder.nativeOrder()) - arr.foreach: - case (x, y, z, a) => - inBuf.putFloat(x) - inBuf.putFloat(y) - inBuf.putFloat(z) - inBuf.putFloat(a) + arr.foreach: tuple => + writePrimitive(inBuf, tuple) inBuf.flip() inBuf @@ -65,3 +74,61 @@ object GCodec: def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Boolean]): Array[Boolean] = outBuf.get(arr.asInstanceOf[Array[Byte]]).flip() arr + + given [T <: GStruct[T]: {GStructSchema as schema, Tag, ClassTag}]: GCodec[T, T] with + def toByteBuffer(inBuf: ByteBuffer, arr: Array[T]): ByteBuffer = + inBuf.clear().order(ByteOrder.nativeOrder()) + for + struct <- arr + field <- struct.productIterator + do + writeConstPrimitive(inBuf, field.asInstanceOf[Value]) + inBuf.flip() + inBuf + def fromByteBuffer(outBuf: ByteBuffer, arr: Array[T]): Array[T] = + val stride = totalStride(schema) + val nElems = outBuf.remaining() / stride + for _ <- 0 to nElems do + val values = schema.fields.map[Value] { case (_, fromExpr, t) => + t match + case t if t <:< schema.gStructTag => + val constructor = fromExpr.asInstanceOf[GStructConstructor[T]] + val nestedValues = constructor.schema.fields.map { case (_, _, nt) => + readPrimitive(outBuf, nt) + } + constructor.fromExpr(ComposeStruct(nestedValues, constructor.schema)) + case _ => + readPrimitive(outBuf, t) + } + val newStruct = schema.create(values, schema.copy(dependsOn = None))(using Source("Input")) + arr.appended(newStruct) + outBuf.rewind() + arr + + private def readPrimitive(buffer: ByteBuffer, value: Tag[_]): Value = + value.tag match + case t if t =:= summon[Tag[Int]].tag => Int32(ConstInt32(buffer.getInt())) + case t if t =:= summon[Tag[Float]].tag => Float32(ConstFloat32(buffer.getFloat())) + case t if t =:= summon[Tag[Boolean]].tag => GBoolean(ConstGB(buffer.get() != 0)) + case t if t =:= summon[Tag[(Float, Float, Float, Float)]].tag => // todo other tuples + Vec4(ComposeVec4(Float32(ConstFloat32(buffer.getFloat())), Float32(ConstFloat32(buffer.getFloat())), Float32(ConstFloat32(buffer.getFloat())), Float32(ConstFloat32(buffer.getFloat())))) + case illegal => + throw new IllegalArgumentException(s"Unable to deserialize value of type $illegal") + + private def writeConstPrimitive(buff: ByteBuffer, value: Value): Unit = value.tree match + case c: Const[?] => writePrimitive(buff, c.value) + case compose: ComposeVec[_] => + compose.productIterator.foreach: v => + writeConstPrimitive(buff, v.asInstanceOf[Value]) + case illegal => + throw new IllegalArgumentException(s"Only constant Cyfra values can be serialized (got $illegal)") + + private def writePrimitive(buff: ByteBuffer, value: Any): Unit = value match + case i: Int => buff.putInt(i) + case f: Float => buff.putFloat(f) + case b: Boolean => buff.put(if b then 1.toByte else 0.toByte) + case t: Tuple => + t.productIterator.foreach(writePrimitive(buff, _)) + case illegal => + throw new IllegalArgumentException(s"Unable to serialize value $illegal of type ${illegal.getClass}") + diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala index 7ea8ccaf..9fab9d52 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala @@ -1,7 +1,6 @@ package io.computenode.cyfra.core import io.computenode.cyfra.core.GExecution.* -import io.computenode.cyfra.core.archive.GContext import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.dsl.binding.GBuffer import io.computenode.cyfra.dsl.gio.GIO diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/Executable.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/Executable.scala deleted file mode 100644 index 68e0b273..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/Executable.scala +++ /dev/null @@ -1,9 +0,0 @@ -package io.computenode.cyfra.core.archive - -import io.computenode.cyfra.core.archive.mem.{GMem, RamGMem} -import io.computenode.cyfra.dsl.Value - -import scala.concurrent.Future - -trait Executable[H <: Value, R <: Value]: - def execute(input: GMem[H], output: RamGMem[R, ?]): Future[Unit] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala deleted file mode 100644 index 7b141690..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GContext.scala +++ /dev/null @@ -1,73 +0,0 @@ -package io.computenode.cyfra.core.archive - -import io.computenode.cyfra.core.archive.mem.GMem.totalStride -import io.computenode.cyfra.core.archive.mem.{FloatMem, GMem, IntMem, Vec4FloatMem} -import io.computenode.cyfra.core.archive.{GFunction, UniformContext} -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.{Float32, FromExpr, Int32, Vec4} -import io.computenode.cyfra.dsl.struct.* -import io.computenode.cyfra.spirv.SpirvTypes.typeStride -import io.computenode.cyfra.spirv.compilers.DSLCompiler -import io.computenode.cyfra.spirvtools.SpirvToolsRunner - -import izumi.reflect.Tag -import org.lwjgl.system.Configuration - -import java.util.concurrent.Executors -import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} - -class GContext(spirvToolsRunner: SpirvToolsRunner = SpirvToolsRunner()): - Configuration.STACK_SIZE.set(1024) // fix lwjgl stack size - - val vkContext = ??? - - implicit val ec: ExecutionContextExecutor = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(16)) - - def compile[G <: GStruct[G]: {Tag, GStructSchema}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( - function: GFunction[G, H, R], - ): Nothing = ??? -// val uniformStructSchema = summon[GStructSchema[G]] -// val uniformStruct = uniformStructSchema.fromTree(UniformStructRef) -// val tree = function.fn -// .apply(uniformStruct, WorkerIndex, GArray[H](0)) -// -// val optimizedShaderCode = -// spirvToolsRunner.processShaderCodeWithSpirvTools(DSLCompiler.compile(tree, function.arrayInputs, function.arrayOutputs, uniformStructSchema)) -// -// val inOut = 0 to 1 map (Binding(_, InputBufferSize(typeStride(summon[Tag[H]])))) -// val uniform = Option.when(uniformStructSchema.fields.nonEmpty)(Binding(2, UniformSize(totalStride(uniformStructSchema)))) -// val layoutInfo = LayoutInfo(Seq(LayoutSet(0, inOut ++ uniform))) -// -// val shader = Shader(optimizedShaderCode, org.joml.Vector3i(256, 1, 1), layoutInfo, "main", vkContext.device) -// ComputePipeline(shader, vkContext) - - def execute[G <: GStruct[G]: {Tag, GStructSchema}, H <: Value, R <: Value](mem: GMem[H], fn: GFunction[G, H, R])(using - uniformContext: UniformContext[G], - ): GMem[R] = ??? -// val isUniformEmpty = uniformContext.uniform.schema.fields.isEmpty -// val actions = Map(LayoutLocation(0, 0) -> BufferAction.LoadTo, LayoutLocation(0, 1) -> BufferAction.LoadFrom) ++ -// ( -// if isUniformEmpty then Map.empty -// else Map(LayoutLocation(0, 2) -> BufferAction.LoadTo) -// ) -// val sequence = ComputationSequence(Seq(Compute(fn.pipeline, actions)), Seq.empty) -// val executor = new SequenceExecutor(sequence, vkContext) -// -// val data = mem.toReadOnlyBuffer -// val inData = -// if isUniformEmpty then Seq(data) -// else Seq(data, GMem.serializeUniform(uniformContext.uniform)) -// val out = executor.execute(inData, mem.size) -// executor.destroy() -// -// val outTags = fn.arrayOutputs -// assert(outTags.size == 1) -// -// outTags.head match -// case t if t == Tag[Float32] => -// new FloatMem(mem.size, out.head).asInstanceOf[GMem[R]] -// case t if t == Tag[Int32] => -// new IntMem(mem.size, out.head).asInstanceOf[GMem[R]] -// case t if t == Tag[Vec4[Float32]] => -// new Vec4FloatMem(mem.size, out.head).asInstanceOf[GMem[R]] -// case _ => assert(false, "Supported output types are Float32 and Vec4[Float32]") diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala index 2798508c..0d559284 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala @@ -18,17 +18,18 @@ import izumi.reflect.Tag import org.lwjgl.BufferUtils import scala.reflect.ClassTag - +import io.computenode.cyfra.core.GCodec.{*, given} +import io.computenode.cyfra.dsl.struct.GStruct.Empty case class GFunction[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( underlying: GProgram[GFunctionParams, GFunctionLayout[G, H, R]] ): - def run[HS, GS, RS : ClassTag] (input: Seq[HS], g: GS)( + def run[GS : ClassTag, HS, RS : ClassTag] (input: Array[HS], g: GS)( using gCodec: GCodec[G, GS], hCodec: GCodec[H, HS], rCodec: GCodec[R, RS], runtime: CyfraRuntime - ): Seq[RS] = + ): Array[RS] = val inTypeSize = typeStride(Tag.apply[H]) val outTypeSize = typeStride(Tag.apply[R]) @@ -36,8 +37,10 @@ case class GFunction[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, Fr val params = GFunctionParams(size = input.size) val in = BufferUtils.createByteBuffer(inTypeSize * input.size) + hCodec.toByteBuffer(in, input) val out = BufferUtils.createByteBuffer(outTypeSize * input.size) val uniform = BufferUtils.createByteBuffer(uniformStride) + gCodec.toByteBuffer(uniform, Array(g)) GBufferRegion.allocate[GFunctionLayout[G, H, R]] .map: layout => @@ -45,16 +48,14 @@ case class GFunction[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, Fr .runUnsafe( init = GFunctionLayout( in = GBuffer[H](in), - out = GBuffer[R](outTypeSize * input.size), + out = GBuffer[R](input.size), uniform = GUniform[G](uniform), ), onDone = layout => layout.out.read(out) ) val resultArray = Array.ofDim[RS](input.size) - rCodec.fromByteBuffer(out, resultArray).toSeq - - + rCodec.fromByteBuffer(out, resultArray) object GFunction: case class GFunctionParams( @@ -67,13 +68,16 @@ object GFunction: uniform: GUniform[G] ) extends Layout - def apply[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: (G, Int32, GBuffer[H]) => R): GFunction[G, H, R] = + def forEachIndex[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: (G, Int32, GBuffer[H]) => R): GFunction[G, H, R] = val body = (layout: GFunctionLayout[G, H, R]) => val g = layout.uniform.read val result = fn(g, GIO.invocationId, layout.in) - layout.out.write(GIO.invocationId, result) - - summon[LayoutStruct[GFunctionLayout[G, H, R]]] + for + _ <- layout.out.write(GIO.invocationId, result) + yield Empty() + + val inTypeSize = typeStride(Tag.apply[H]) + val outTypeSize = typeStride(Tag.apply[R]) GFunction( underlying = GProgram.apply[GFunctionParams, GFunctionLayout[G, H, R]]( @@ -88,14 +92,22 @@ object GFunction: ) def apply[H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: H => R): GFunction[GStruct.Empty, H, R] = - GFunction[GStruct.Empty, H, R]((g: GStruct.Empty, index: Int32, a: GBuffer[H]) => fn(a.read(index))) + GFunction.forEachIndex[GStruct.Empty, H, R]((g: GStruct.Empty, index: Int32, a: GBuffer[H]) => fn(a.read(index))) def from2D[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( width: Int, )(fn: (G, (Int32, Int32), GArray2D[H]) => R): GFunction[G, H, R] = - GFunction[G, H, R]((g: G, index: Int32, a: GBuffer[H]) => + GFunction.forEachIndex[G, H, R]((g: G, index: Int32, a: GBuffer[H]) => val x: Int32 = index mod width val y: Int32 = index / width val arr = GArray2D(width, a) fn(g, (x, y), arr), ) + + extension [H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](gf: GFunction[GStruct.Empty, H, R]) + def run[HS, RS : ClassTag](input: Array[HS])( + using hCodec: GCodec[H, HS], + rCodec: GCodec[R, RS], + runtime: CyfraRuntime + ): Array[RS] = + gf.run(input, GStruct.Empty()) \ No newline at end of file diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/UniformContext.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/UniformContext.scala deleted file mode 100644 index 093698ae..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/UniformContext.scala +++ /dev/null @@ -1,13 +0,0 @@ -package io.computenode.cyfra.core.archive - -import io.computenode.cyfra.core.archive.UniformContext -import io.computenode.cyfra.dsl.struct.* -import io.computenode.cyfra.dsl.struct.GStruct.Empty -import izumi.reflect.Tag - -class UniformContext[G <: GStruct[G]: {Tag, GStructSchema}](val uniform: G) - -object UniformContext: - def withUniform[G <: GStruct[G]: {Tag, GStructSchema}, T](uniform: G)(fn: UniformContext[G] ?=> T): T = - fn(using UniformContext(uniform)) - given empty: UniformContext[Empty] = new UniformContext(Empty()) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/FloatMem.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/FloatMem.scala deleted file mode 100644 index 2e51f2ee..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/FloatMem.scala +++ /dev/null @@ -1,27 +0,0 @@ -package io.computenode.cyfra.core.archive.mem - -import io.computenode.cyfra.dsl.Value.Float32 -import org.lwjgl.BufferUtils - -import java.nio.ByteBuffer - -class FloatMem(val size: Int, protected val data: ByteBuffer) extends RamGMem[Float32, Float]: - def toArray: Array[Float] = - val res = data.asFloatBuffer() - val result = new Array[Float](size) - res.get(result) - result - -object FloatMem: - val FloatSize = 4 - - def apply(floats: Array[Float]): FloatMem = - val size = floats.length - val data = BufferUtils.createByteBuffer(size * FloatSize) - data.asFloatBuffer().put(floats) - data.rewind() - new FloatMem(size, data) - - def apply(size: Int): FloatMem = - val data = BufferUtils.createByteBuffer(size * FloatSize) - new FloatMem(size, data) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/GMem.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/GMem.scala deleted file mode 100644 index 41961aa4..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/GMem.scala +++ /dev/null @@ -1,52 +0,0 @@ -package io.computenode.cyfra.core.archive.mem - -import io.computenode.cyfra.core.archive.{GContext, GFunction, UniformContext} -import io.computenode.cyfra.dsl.Value.FromExpr -import io.computenode.cyfra.dsl.struct.* -import io.computenode.cyfra.dsl.{*, given} -import io.computenode.cyfra.spirv.SpirvTypes.typeStride -import izumi.reflect.Tag -import org.lwjgl.BufferUtils - -import java.nio.ByteBuffer - -trait GMem[H <: Value]: - def size: Int - def toReadOnlyBuffer: ByteBuffer - def map[G <: GStruct[G]: {Tag, GStructSchema}, R <: Value: {FromExpr, Tag}]( - fn: GFunction[G, H, R], - )(using context: GContext, uc: UniformContext[G]): GMem[R] = - context.execute(this, fn) - -object GMem: - type fRGBA = (Float, Float, Float, Float) - - def totalStride(gs: GStructSchema[?]): Int = gs.fields.map { - case (_, fromExpr, t) if t <:< gs.gStructTag => - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - case (_, _, t) => - typeStride(t) - }.sum - - def serializeUniform(g: GStruct[?]): ByteBuffer = - val data = BufferUtils.createByteBuffer(totalStride(g.schema)) - g.productIterator.foreach: - case Int32(ConstInt32(i)) => data.putInt(i) - case Float32(ConstFloat32(f)) => data.putFloat(f) - case Vec4(ComposeVec4(Float32(ConstFloat32(x)), Float32(ConstFloat32(y)), Float32(ConstFloat32(z)), Float32(ConstFloat32(a)))) => - data.putFloat(x) - data.putFloat(y) - data.putFloat(z) - data.putFloat(a) - case Vec3(ComposeVec3(Float32(ConstFloat32(x)), Float32(ConstFloat32(y)), Float32(ConstFloat32(z)))) => - data.putFloat(x) - data.putFloat(y) - data.putFloat(z) - case Vec2(ComposeVec2(Float32(ConstFloat32(x)), Float32(ConstFloat32(y)))) => - data.putFloat(x) - data.putFloat(y) - case illegal => - throw new IllegalArgumentException(s"Uniform must be constructed from constants (got field $illegal)") - data.rewind() - data diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/IntMem.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/IntMem.scala deleted file mode 100644 index ee9e61e8..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/IntMem.scala +++ /dev/null @@ -1,27 +0,0 @@ -package io.computenode.cyfra.core.archive.mem - -import io.computenode.cyfra.dsl.Value.Int32 -import org.lwjgl.BufferUtils - -import java.nio.ByteBuffer - -class IntMem(val size: Int, protected val data: ByteBuffer) extends RamGMem[Int32, Int]: - def toArray: Array[Int] = - val res = data.asIntBuffer() - val result = new Array[Int](size) - res.get(result) - result - -object IntMem: - val IntSize = 4 - - def apply(ints: Array[Int]): IntMem = - val size = ints.length - val data = BufferUtils.createByteBuffer(size * IntSize) - data.asIntBuffer().put(ints) - data.rewind() - new IntMem(size, data) - - def apply(size: Int): IntMem = - val data = BufferUtils.createByteBuffer(size * IntSize) - new IntMem(size, data) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/RamGMem.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/RamGMem.scala deleted file mode 100644 index a136d7d4..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/RamGMem.scala +++ /dev/null @@ -1,9 +0,0 @@ -package io.computenode.cyfra.core.archive.mem - -import io.computenode.cyfra.dsl.Value - -import java.nio.ByteBuffer - -trait RamGMem[T <: Value, R] extends GMem[T]: - protected val data: ByteBuffer - def toReadOnlyBuffer: ByteBuffer = data.asReadOnlyBuffer() diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/Vec4FloatMem.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/Vec4FloatMem.scala deleted file mode 100644 index 0fdb63cb..00000000 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/mem/Vec4FloatMem.scala +++ /dev/null @@ -1,33 +0,0 @@ -package io.computenode.cyfra.core.archive.mem - -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA -import io.computenode.cyfra.dsl.Value.{Float32, Vec4} -import org.lwjgl.BufferUtils - -import java.nio.ByteBuffer - -class Vec4FloatMem(val size: Int, protected val data: ByteBuffer) extends RamGMem[Vec4[Float32], fRGBA]: - def toArray: Array[fRGBA] = - val res = data.asFloatBuffer() - val result = new Array[fRGBA](size) - for i <- 0 until size do result(i) = (res.get(), res.get(), res.get(), res.get()) - result - -object Vec4FloatMem: - val Vec4FloatSize = 16 - - def apply(vecs: Array[fRGBA]): Vec4FloatMem = - val size = vecs.length - val data = BufferUtils.createByteBuffer(size * Vec4FloatSize) - vecs.foreach { case (x, y, z, a) => - data.putFloat(x) - data.putFloat(y) - data.putFloat(z) - data.putFloat(a) - } - data.rewind() - new Vec4FloatMem(size, data) - - def apply(size: Int): Vec4FloatMem = - val data = BufferUtils.createByteBuffer(size * Vec4FloatSize) - new Vec4FloatMem(size, data) diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala index ef723217..7d52eb5e 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Expression.scala @@ -101,9 +101,11 @@ object Expression: case class ConstUInt32(value: Int) extends Const[UInt32] case class ConstGB(value: Boolean) extends Const[GBoolean] - case class ComposeVec2[T <: Scalar: Tag](a: T, b: T) extends Expression[Vec2[T]] - case class ComposeVec3[T <: Scalar: Tag](a: T, b: T, c: T) extends Expression[Vec3[T]] - case class ComposeVec4[T <: Scalar: Tag](a: T, b: T, c: T, d: T) extends Expression[Vec4[T]] + trait ComposeVec[T <: Vec[?]: Tag] extends Expression[T] + + case class ComposeVec2[T <: Scalar: Tag](a: T, b: T) extends ComposeVec[Vec2[T]] + case class ComposeVec3[T <: Scalar: Tag](a: T, b: T, c: T) extends ComposeVec[Vec3[T]] + case class ComposeVec4[T <: Scalar: Tag](a: T, b: T, c: T, d: T) extends ComposeVec[Vec4[T]] case class ExtFunctionCall[R <: Value: Tag](fn: FunctionName, args: List[Value]) extends Expression[R] case class FunctionCall[R <: Value: Tag](fn: FnIdentifier, body: Scope[R], args: List[Value]) extends E[R] diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Value.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Value.scala index 985357e7..1e8a0e92 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Value.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/Value.scala @@ -1,6 +1,5 @@ package io.computenode.cyfra.dsl -import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Expression.{E, E as T} import io.computenode.cyfra.dsl.macros.Source import izumi.reflect.Tag @@ -56,3 +55,5 @@ object Value: case class Vec4[T <: Value](tree: E[Vec4[T]])(using val source: Source) extends Vec[T] given [T <: Scalar]: FromExpr[Vec4[T]] with def fromExpr(f: E[Vec4[T]])(using Source) = Vec4(f) + + type fRGBA = (Float, Float, Float, Float) diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala index f9f6b383..38a642ee 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/struct/GStruct.scala @@ -2,7 +2,7 @@ package io.computenode.cyfra.dsl.struct import io.computenode.cyfra.* import io.computenode.cyfra.dsl.Expression.* -import io.computenode.cyfra.dsl.{Expression, Value} +import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.dsl.Value.* import io.computenode.cyfra.dsl.macros.Source import izumi.reflect.Tag @@ -21,7 +21,7 @@ abstract class GStruct[T <: GStruct[T]: {Tag, GStructSchema}] extends Value with override def source: Source = _name object GStruct: - case class Empty() extends GStruct[Empty] + case class Empty(_placeholder: Int32 = 0) extends GStruct[Empty] object Empty: given GStructSchema[Empty] = GStructSchema.derived diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala index dec27507..2a36c7c2 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala @@ -1,6 +1,5 @@ package io.computenode.cyfra.e2e -import io.computenode.cyfra.core.archive.GContext import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala index 95833a22..3a69f09f 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala @@ -1,6 +1,5 @@ package io.computenode.cyfra.e2e -import io.computenode.cyfra.core.archive.GContext import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala index 78843776..26ab573c 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala @@ -1,23 +1,23 @@ package io.computenode.cyfra.e2e.dsl +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.* -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA import io.computenode.cyfra.dsl.algebra.VectorAlgebra import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.core.GCodec.{*, given} class ArithmeticsE2eTest extends munit.FunSuite: - given gc: GContext = GContext() - + given CyfraRuntime = VkCyfraRuntime() + test("Float32 arithmetics"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: fl => (fl + 1.2f) * (fl - 3.4f) / 5.6f // We need to use multiples of 256 for Vulkan buffer alignment. val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) val expected = inArr.map(f => (f + 1.2f) * (f - 3.4f) / 5.6f) result @@ -30,8 +30,7 @@ class ArithmeticsE2eTest extends munit.FunSuite: ((n + 2) * (n - 3) / 5).mod(7) val inArr = (0 to 255).toArray - val gmem = IntMem(inArr) - val result = gmem.map(gf).asInstanceOf[IntMem].toArray + val result: Array[Int] = gf.run(inArr) // With negative values and mod, Scala and Vulkan behave differently val expected = inArr.map: n => @@ -63,8 +62,7 @@ class ArithmeticsE2eTest extends munit.FunSuite: case Seq(a, b, c, d) => (a, b, c, d) .toArray - val gmem = Vec4FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) extension (f: fRGBA) def neg = (-f._1, -f._2, -f._3, -f._4) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala index 990906e8..057ecddc 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala @@ -1,14 +1,15 @@ package io.computenode.cyfra.e2e.dsl +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.* -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.core.GCodec.{*, given} class FunctionsE2eTest extends munit.FunSuite: - given gc: GContext = GContext() - + given CyfraRuntime = VkCyfraRuntime() + test("Functions"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: f => val res1 = pow(sqrt(exp(sin(cos(tan(f))))), 2f) @@ -16,8 +17,7 @@ class FunctionsE2eTest extends munit.FunSuite: abs(min(res1, res2) - max(res1, res2)) val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) val expected = inArr.map: f => val res1 = math.pow(math.sqrt(math.exp(math.sin(math.cos(math.tan(f))))), 2) @@ -42,8 +42,7 @@ class FunctionsE2eTest extends munit.FunSuite: v5.dot(v1) val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) extension (f: fRGBA) def neg: fRGBA = (-f._1, -f._2, -f._3, -f._4) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala index bfc88e0e..750085fe 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GStructE2eTest.scala @@ -1,13 +1,17 @@ package io.computenode.cyfra.e2e.dsl +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.* import io.computenode.cyfra.dsl.binding.GBuffer import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.core.GCodec.{*, given} class GStructE2eTest extends munit.FunSuite: + given CyfraRuntime = VkCyfraRuntime() + case class Custom(f: Float32, v: Vec4[Float32]) extends GStruct[Custom] val custom1 = Custom(2f, (1f, 2f, 3f, 4f)) val custom2 = Custom(-0.5f, (-0.5f, -1.5f, -2.5f, -3.5f)) @@ -15,38 +19,32 @@ class GStructE2eTest extends munit.FunSuite: case class Nested(c1: Custom, c2: Custom) extends GStruct[Nested] val nested = Nested(custom1, custom2) - given gc: GContext = GContext() - test("GStruct passed as uniform"): - UniformContext.withUniform(custom1): - val gf: GFunction[Custom, Float32, Float32] = GFunction: - case (Custom(f, v), index: Int32, gBuff: GBuffer[Float32]) => v.*(f).dot(v) + gBuff.read(index) * f + val gf: GFunction[Custom, Float32, Float32] = GFunction.forEachIndex: + case (Custom(f, v), index: Int32, buff: GBuffer[Float32]) => v.*(f).dot(v) + buff.read(index) * f - val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val inArr = (0 to 255).map(_.toFloat).toArray + val result: Array[Float] = gf.run(inArr, custom1) - val expected = inArr.map(f => 2f * f + 60f) - result - .zip(expected) - .foreach: (res, exp) => - assert(Math.abs(res - exp) < 0.001f, s"Expected $exp but got $res") + val expected = inArr.map(f => 2f * f + 60f) + result + .zip(expected) + .foreach: (res, exp) => + assert(Math.abs(res - exp) < 0.001f, s"Expected $exp but got $res") test("GStruct of GStructs".ignore): - UniformContext.withUniform(nested): - val gf: GFunction[Nested, Float32, Float32] = GFunction: - case (Nested(Custom(f1, v1), Custom(f2, v2)), index, gArray: GBuffer[Float32]) => - v1.*(f2).dot(v2) + gArray.at(index) * f1 + val gf: GFunction[Nested, Float32, Float32] = GFunction.forEachIndex[Nested, Float32, Float32]: + case (Nested(Custom(f1, v1), Custom(f2, v2)), index: Int32, buff: GBuffer[Float32]) => + v1.*(f2).dot(v2) + buff.read(index) * f1 - val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val inArr = (0 to 255).map(_.toFloat).toArray + val result: Array[Float] = gf.run(inArr, nested) - val expected = inArr.map(f => 2f * f + 12.5f) - result - .zip(expected) - .foreach: (res, exp) => - assert(Math.abs(res - exp) < 0.001f, s"Expected $exp but got $res") + val expected = inArr.map(f => 2f * f + 12.5f) + result + .zip(expected) + .foreach: (res, exp) => + assert(Math.abs(res - exp) < 0.001f, s"Expected $exp but got $res") test("GSeq of GStructs"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: fl => @@ -56,8 +54,7 @@ class GStructE2eTest extends munit.FunSuite: .fold[Float32](0f, (f, c) => f + c.f * (c.v.w + c.v.x + c.v.y + c.v.z)) + fl val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) val expected = inArr.map(f => f + 420f) result diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala index 318201b1..9844c7f9 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala @@ -1,14 +1,16 @@ package io.computenode.cyfra.e2e.dsl +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.* import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.core.GCodec.{*, given} class GseqE2eTest extends munit.FunSuite: - given gc: GContext = GContext() - + given CyfraRuntime = VkCyfraRuntime() + test("GSeq gen limit map fold"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: f => GSeq @@ -18,8 +20,7 @@ class GseqE2eTest extends munit.FunSuite: .fold[Float32](0f, _ + _) val inArr = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) val expected = inArr.map(f => 10 * f + 65.0f) result @@ -36,8 +37,7 @@ class GseqE2eTest extends munit.FunSuite: .count val inArr = (0 to 255).toArray - val gmem = IntMem(inArr) - val result = gmem.map(gf).asInstanceOf[IntMem].toArray + val result: Array[Int] = gf.run(inArr) val expected = inArr.map: n => List diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala index 416f9148..ce202128 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/WhenE2eTest.scala @@ -1,12 +1,14 @@ package io.computenode.cyfra.e2e.dsl -import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.* +import io.computenode.cyfra.core.CyfraRuntime +import io.computenode.cyfra.core.archive.GFunction import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.runtime.VkCyfraRuntime +import io.computenode.cyfra.core.GCodec.{*, given} class WhenE2eTest extends munit.FunSuite: - given gc: GContext = GContext() + given CyfraRuntime = VkCyfraRuntime() test("when elseWhen otherwise"): val oneHundred = 100.0f @@ -17,8 +19,7 @@ class WhenE2eTest extends munit.FunSuite: .otherwise(2.0f) val inArr: Array[Float] = (0 to 255).map(_.toFloat).toArray - val gmem = FloatMem(inArr) - val result = gmem.map(gf).asInstanceOf[FloatMem].toArray + val result: Array[Float] = gf.run(inArr) val expected = inArr.map: f => if f <= oneHundred then 0.0f else if f <= twoHundred then 1.0f else 2.0f diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index e5c2667f..367a3066 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -1,8 +1,6 @@ package io.computenode.cyfra.e2e.fs2interop import io.computenode.cyfra.core.archive.* -import mem.* -import GMem.fRGBA import io.computenode.cyfra.dsl.{*, given} import algebra.VectorAlgebra import io.computenode.cyfra.fs2interop.* diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/juliaset/JuliaSet.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/juliaset/JuliaSet.scala index df9b0191..b0d70672 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/juliaset/JuliaSet.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/juliaset/JuliaSet.scala @@ -2,12 +2,14 @@ package io.computenode.cyfra.e2e.juliaset import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.* +import io.computenode.cyfra.core.GCodec.{*, given} +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.control.Pure.pure import io.computenode.cyfra.dsl.struct.GStruct.Empty import io.computenode.cyfra.e2e.ImageTests -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.core.archive.{GContext, GFunction} +import io.computenode.cyfra.core.archive.GFunction +import io.computenode.cyfra.runtime.VkCyfraRuntime import io.computenode.cyfra.spirvtools.* import io.computenode.cyfra.spirvtools.SpirvTool.{Param, ToFile} import io.computenode.cyfra.utility.ImageUtility @@ -21,7 +23,7 @@ import scala.concurrent.ExecutionContext.Implicits class JuliaSet extends FunSuite: given ExecutionContext = Implicits.global - def runJuliaSet(referenceImgName: String)(using GContext): Unit = + def runJuliaSet(referenceImgName: String)(using CyfraRuntime): Unit = val dim = 4096 val max = 1 val RECURSION_LIMIT = 1000 @@ -65,18 +67,19 @@ class JuliaSet extends FunSuite: .otherwise: (8f / 255f, 22f / 255f, 104f / 255f, 1.0f) - val r = Vec4FloatMem(dim * dim).map(function).asInstanceOf[Vec4FloatMem].toArray + val vec4arr = Array.ofDim[fRGBA](dim * dim) + val r: Array[fRGBA] = function.run(vec4arr, Empty()) val outputTemp = File.createTempFile("julia", ".png") ImageUtility.renderToImage(r, dim, outputTemp.toPath) val referenceImage = getClass.getResource(referenceImgName) ImageTests.assertImagesEquals(outputTemp, new File(referenceImage.getPath)) test("Render julia set"): - given GContext = new GContext + given CyfraRuntime = VkCyfraRuntime() runJuliaSet("/julia.png") test("Render julia set optimized"): - given GContext = new GContext( + given CyfraRuntime = new VkCyfraRuntime( SpirvToolsRunner( validator = SpirvValidator.Enable(throwOnFail = true), optimizer = SpirvOptimizer.Enable(toolOutput = ToFile(Paths.get("output/optimized.spv")), settings = Seq(Param("-O"))), diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index e431b719..ecea9250 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -1,6 +1,5 @@ package io.computenode.cyfra.samples -import io.computenode.cyfra.core.archive.GContext import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} @@ -19,9 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given object TestingStuff: - - given GContext = GContext() - + // === Emit program === case class EmitProgramParams(inSize: Int, emitN: Int) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/oldsamples/Raytracing.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/oldsamples/Raytracing.scala deleted file mode 100644 index ab35810e..00000000 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/oldsamples/Raytracing.scala +++ /dev/null @@ -1,535 +0,0 @@ -package io.computenode.cyfra.samples.oldsamples - -import io.computenode.cyfra.dsl.collections.GSeq -import io.computenode.cyfra.dsl.{*, given} -import io.computenode.cyfra.dsl.struct.GStruct -import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.utility.ImageUtility - -import java.nio.file.Paths -import scala.annotation.tailrec -import scala.collection.mutable -import scala.concurrent.ExecutionContext -import scala.concurrent.ExecutionContext.Implicits - -given GContext = new GContext() -given ExecutionContext = Implicits.global - -/** Raytracing example - */ - -@main -def main = - - val dim = 2048 - val minRayHitTime = 0.01f - val rayPosNormalNudge = 0.01f - val superFar = 1000.0f - val fovDeg = 60 - val fovRad = fovDeg * math.Pi.toFloat / 180.0f - val maxBounces = 8 - val pixelIterationsPerFrame = 1000 - val bgColor = (0.2f, 0.2f, 0.2f) - val exposure = 1f - - case class Random[T <: Value](value: T, nextSeed: UInt32) - - def lessThan(f: Vec3[Float32], f2: Float32): Vec3[Float32] = - (when(f.x < f2)(1.0f).otherwise(0.0f), when(f.y < f2)(1.0f).otherwise(0.0f), when(f.z < f2)(1.0f).otherwise(0.0f)) - - def linearToSRGB(rgb: Vec3[Float32]): Vec3[Float32] = - val clampedRgb = vclamp(rgb, 0.0f, 1.0f) - mix(pow(clampedRgb, vec3(1.0f / 2.4f)) * 1.055f - vec3(0.055f), clampedRgb * 12.92f, lessThan(clampedRgb, 0.0031308f)) - - def SRGBToLinear(rgb: Vec3[Float32]): Vec3[Float32] = - val clampedRgb = vclamp(rgb, 0.0f, 1.0f) - mix(pow((clampedRgb + vec3(0.055f)) * (1.0f / 1.055f), vec3(2.4f)), clampedRgb * (1.0f / 12.92f), lessThan(clampedRgb, 0.04045f)) - - def ACESFilm(x: Vec3[Float32]): Vec3[Float32] = - val a = 2.51f - val b = 0.03f - val c = 2.43f - val d = 0.59f - val e = 0.14f - vclamp((x mulV (x * a + vec3(b))) divV (x mulV (x * c + vec3(d)) + vec3(e)), 0.0f, 1.0f) - - case class RayHitInfo( - dist: Float32, - normal: Vec3[Float32], - albedo: Vec3[Float32], - emissive: Vec3[Float32], - percentSpecular: Float32 = 0f, - roughness: Float32 = 0f, - specularColor: Vec3[Float32] = vec3(0f), - indexOfRefraction: Float32 = 1.0f, - refractionChance: Float32 = 0f, - refractionRoughness: Float32 = 0f, - refractionColor: Vec3[Float32] = vec3(0f), - fromInside: GBoolean = false, - ) extends GStruct[RayHitInfo] - - case class Sphere( - center: Vec3[Float32], - radius: Float32, - color: Vec3[Float32], - emissive: Vec3[Float32], - percentSpecular: Float32 = 0f, - roughness: Float32 = 0f, - specularColor: Vec3[Float32] = vec3(0f), - indexOfRefraction: Float32 = 1f, - refractionChance: Float32 = 0f, - refractionRoughness: Float32 = 0f, - refractionColor: Vec3[Float32] = vec3(0f), - ) extends GStruct[Sphere] - - case class Quad( - a: Vec3[Float32], - b: Vec3[Float32], - c: Vec3[Float32], - d: Vec3[Float32], - color: Vec3[Float32], - emissive: Vec3[Float32], - percentSpecular: Float32 = 0f, - roughness: Float32 = 0f, - specularColor: Vec3[Float32] = vec3(0f), - indexOfRefraction: Float32 = 1f, - refractionChance: Float32 = 0f, - refractionRoughness: Float32 = 0f, - refractionColor: Vec3[Float32] = vec3(0f), - ) extends GStruct[Quad] - - case class RayTraceState( - rayPos: Vec3[Float32], - rayDir: Vec3[Float32], - color: Vec3[Float32], - throughput: Vec3[Float32], - rngState: UInt32, - finished: GBoolean = false, - ) extends GStruct[RayTraceState] - - val sceneTranslation = vec4(0f, 0f, 10f, 0f) - // 7 is cool - val rd = scala.util.Random(3) - - def scalaTwoSpheresIntersect(sphereA: (Float, Float, Float), radiusA: Float, sphereB: (Float, Float, Float), radiusB: Float): Boolean = - val dist = Math.sqrt( - (sphereA._1 - sphereB._1) * - (sphereA._1 - sphereB._1) + - (sphereA._2 - sphereB._2) * - (sphereA._2 - sphereB._2) + - (sphereA._3 - sphereB._3) * - (sphereA._3 - sphereB._3), - ) - dist < radiusA + radiusB - - val existingSpheres = mutable.Set.empty[((Float, Float, Float), Float)] - @tailrec - def randomSphere(iter: Int = 0): Sphere = - if iter > 1000 then throw new Exception("Could not find a non-intersecting sphere") - def nextFloatAny = rd.nextFloat() * 2f - 1f - - def nextFloatPos = rd.nextFloat() - - val center = (nextFloatAny * 10, nextFloatAny * 10, nextFloatPos * 10 + 8f) - val radius = nextFloatPos + 1.5f - if existingSpheres.exists(s => scalaTwoSpheresIntersect(s._1, s._2, center, radius)) then randomSphere(iter + 1) - else - existingSpheres.add((center, radius)) - def color = (nextFloatPos * 0.5f + 0.5f, nextFloatPos * 0.5f + 0.5f, nextFloatPos * 0.5f + 0.5f) - val emissive = (0f, 0f, 0f) - Sphere( - center, - radius, - color, - emissive, - 0.45f, - 0.1f, - (nextFloatPos + 0.2f, nextFloatPos + 0.2f, nextFloatPos + 0.2f), - 1.1f, - 0.6f, - 0.1f, - (nextFloatPos, nextFloatPos, nextFloatPos), - ) - - def randomSpheres(n: Int) = List.fill(n)(randomSphere()) - - val flash = // flash - val x = -10f - val mX = -5f - val y = -10f - val mY = 0f - val z = -5f - Sphere((-7.5f, -12f, -5f), 3f, (1f, 1f, 1f), (20f, 20f, 20f)) - val spheres = (flash :: randomSpheres(20)).map(sp => sp.copy(center = sp.center + sceneTranslation.xyz)) - - val walls = List( - Quad( // back - (-15.5f, -15.5f, 25.0f), - (15.5f, -15.5f, 25.0f), - (15.5f, 15.5f, 25.0f), - (-15.5f, 15.5f, 25.0f), - (0.8f, 0.8f, 0.8f), - (0f, 0f, 0f), - ), - Quad( // right - (15f, -15.5f, 25.5f), - (15f, -15.5f, -15.5f), - (15f, 15.5f, -15.5f), - (15f, 15.5f, 25.5f), - (0.0f, 0.8f, 0.0f), - (0f, 0f, 0f), - ), - Quad( // left - (-15f, -15.5f, 25.5f), - (-15f, -15.5f, -15.5f), - (-15f, 15.5f, -15.5f), - (-15f, 15.5f, 25.5f), - (0.8f, 0.0f, 0.0f), - (0f, 0f, 0f), - ), - Quad( // bottom - (-15.5f, 15f, 25.5f), - (15.5f, 15f, 25.5f), - (15.5f, 15f, -15.5f), - (-15.5f, 15f, -15.5f), - (0.8f, 0.8f, 0.8f), - (0f, 0f, 0f), - ), - Quad( // top - (-15.5f, -15f, 25.5f), - (15.5f, -15f, 25.5f), - (15.5f, -15f, -15.5f), - (-15.5f, -15f, -15.5f), - (0.8f, 0.8f, 0.8f), - (0f, 0f, 0f), - ), - Quad( // front - (-15.5f, -15.5f, -15.5f), - (15.5f, -15.5f, -15.5f), - (15.5f, 15.5f, -15.5f), - (-15.5f, 15.5f, -15.5f), - (0.8f, 0.8f, 0.8f), - (0f, 0f, 0f), - ), - Quad( // light - (-2.5f, -14.95f, 17.5f), - (2.5f, -14.95f, 17.5f), - (2.5f, -14.95f, 12.5f), - (-2.5f, -14.95f, 12.5f), - (1f, 1f, 1f), - (20f, 18f, 14f), - ), - ).map(quad => - quad.copy( - a = quad.a + sceneTranslation.xyz, - b = quad.b + sceneTranslation.xyz, - c = quad.c + sceneTranslation.xyz, - d = quad.d + sceneTranslation.xyz, - ), - ) - - case class RaytracingIteration(frame: Int32) extends GStruct[RaytracingIteration] - - def function(): GFunction[RaytracingIteration, Vec4[Float32], Vec4[Float32]] = GFunction.from2D(dim): - case (RaytracingIteration(frame), (xi: Int32, yi: Int32), lastFrame) => - def wangHash(seed: UInt32): UInt32 = - val s1 = (seed ^ 61) ^ (seed >> 16) - val s2 = s1 * 9 - val s3 = s2 ^ (s2 >> 4) - val s4 = s3 * 0x27d4eb2d - s4 ^ (s4 >> 15) - - def randomFloat(seed: UInt32): Random[Float32] = - val nextSeed = wangHash(seed) - val f = nextSeed.asFloat / 4294967296.0f - Random(f, nextSeed) - - def randomVector(seed: UInt32): Random[Vec3[Float32]] = - val Random(z, seed1) = randomFloat(seed) - val z2 = z * 2.0f - 1.0f - val Random(a, seed2) = randomFloat(seed1) - val a2 = a * 2.0f * math.Pi.toFloat - val r = sqrt(1.0f - z2 * z2) - val x = r * cos(a2) - val y = r * sin(a2) - Random((x, y, z2), seed2) - - def scalarTriple(u: Vec3[Float32], v: Vec3[Float32], w: Vec3[Float32]): Float32 = (u cross v) dot w - - def testQuadTrace(rayPos: Vec3[Float32], rayDir: Vec3[Float32], currentHit: RayHitInfo, quad: Quad): RayHitInfo = - val normal = normalize((quad.c - quad.a) cross (quad.c - quad.b)) - val fixedQuad = - when((normal dot rayDir) > 0f): - Quad(quad.d, quad.c, quad.b, quad.a, quad.color, quad.emissive) - .otherwise: - quad - - val fixedNormal = when((normal dot rayDir) > 0f)(-normal).otherwise(normal) - val p = rayPos - val q = rayPos + rayDir - val pq = q - p - val pa = fixedQuad.a - p - val pb = fixedQuad.b - p - val pc = fixedQuad.c - p - val m = pc cross pq - val v = pa dot m - - def checkHit(intersectPoint: Vec3[Float32]): RayHitInfo = - val dist = - when(abs(rayDir.x) > 0.1f): - (intersectPoint.x - rayPos.x) / rayDir.x - .elseWhen(abs(rayDir.y) > 0.1f): - (intersectPoint.y - rayPos.y) / rayDir.y - .otherwise: - (intersectPoint.z - rayPos.z) / rayDir.z - - when(dist > minRayHitTime && dist < currentHit.dist): - RayHitInfo( - dist, - fixedNormal, - quad.color, - quad.emissive, - quad.percentSpecular, - quad.roughness, - quad.specularColor, - quad.indexOfRefraction, - quad.refractionChance, - quad.refractionRoughness, - quad.refractionColor, - ) - .otherwise: - currentHit - - when(v >= 0f): - val u = -(pb dot m) - val w = scalarTriple(pq, pb, pa) - when(u >= 0f && w >= 0f): - val denom = 1f / (u + v + w) - val uu = u * denom - val vv = v * denom - val ww = w * denom - val intersectPos = fixedQuad.a * uu + fixedQuad.b * vv + fixedQuad.c * ww - checkHit(intersectPos) - .otherwise: - currentHit - .otherwise: - val pd = fixedQuad.d - p - val u = pd dot m - val w = scalarTriple(pq, pa, pd) - when(u >= 0f && w >= 0f): - val negV = -v - val denom = 1f / (u + negV + w) - val uu = u * denom - val vv = negV * denom - val ww = w * denom - val intersectPos = fixedQuad.a * uu + fixedQuad.d * vv + fixedQuad.c * ww - checkHit(intersectPos) - .otherwise: - currentHit - - def testSphereTrace(rayPos: Vec3[Float32], rayDir: Vec3[Float32], currentHit: RayHitInfo, sphere: Sphere): RayHitInfo = - val toRay = rayPos - sphere.center - val b = toRay dot rayDir - val c = (toRay dot toRay) - (sphere.radius * sphere.radius) - val notHit = currentHit - when(c > 0f && b > 0f): - notHit - .otherwise: - val discr = b * b - c - when(discr > 0f): - val initDist = -b - sqrt(discr) - val fromInside = initDist < 0f - val dist = when(fromInside)(-b + sqrt(discr)).otherwise(initDist) - when(dist > minRayHitTime && dist < currentHit.dist): - val normal = normalize((rayPos + rayDir * dist - sphere.center) * when(fromInside)(-1f).otherwise(1f)) - RayHitInfo( - dist, - normal, - sphere.color, - sphere.emissive, - sphere.percentSpecular, - sphere.roughness, - sphere.specularColor, - sphere.indexOfRefraction, - sphere.refractionChance, - sphere.refractionRoughness, - sphere.refractionColor, - fromInside, - ) - .otherwise: - notHit - .otherwise: - notHit - - def testScene(rayPos: Vec3[Float32], rayDir: Vec3[Float32], currentHit: RayHitInfo): RayHitInfo = - - val spheresHit = GSeq - .of(spheres) - .fold( - currentHit, - { case (hit, sphere) => - testSphereTrace(rayPos, rayDir, hit, sphere) - }, - ) - - GSeq.of(walls).fold(spheresHit, (hit, wall) => testQuadTrace(rayPos, rayDir, hit, wall)) - - def fresnelReflectAmount(n1: Float32, n2: Float32, normal: Vec3[Float32], incident: Vec3[Float32], f0: Float32, f90: Float32): Float32 = - val r0 = ((n1 - n2) / (n1 + n2)) * ((n1 - n2) / (n1 + n2)) - val cosX = -(normal dot incident) - when(n1 > n2): - val n = n1 / n2 - val sinT2 = n * n * (1f - cosX * cosX) - when(sinT2 > 1f): - f90 - .otherwise: - val cosX2 = sqrt(1.0f - sinT2) - val x = 1.0f - cosX2 - val ret = r0 + ((1.0f - r0) * x * x * x * x * x) - mix(f0, f90, ret) - .otherwise: - val x = 1.0f - cosX - val ret = r0 + ((1.0f - r0) * x * x * x * x * x) - mix(f0, f90, ret) - - val MaxBounces = 8 - def getColorForRay(startRayPos: Vec3[Float32], startRayDir: Vec3[Float32], initRngState: UInt32): RayTraceState = - val initState = RayTraceState(startRayPos, startRayDir, (0f, 0f, 0f), (1f, 1f, 1f), initRngState) - GSeq - .gen[RayTraceState]( - first = initState, - next = - case state @ RayTraceState(rayPos, rayDir, color, throughput, rngState, _) => - val noHit = RayHitInfo(superFar, (0f, 0f, 0f), (0f, 0f, 0f), (0f, 0f, 0f)) - val testResult = testScene(rayPos, rayDir, noHit) - when(testResult.dist < superFar): - val throughput2 = when(testResult.fromInside): - throughput mulV exp[Vec3[Float32]](-testResult.refractionColor * testResult.dist) - .otherwise: - throughput - - val specularChance = when(testResult.percentSpecular > 0.0f): - fresnelReflectAmount( - when(testResult.fromInside)(testResult.indexOfRefraction).otherwise(1.0f), - when(!testResult.fromInside)(testResult.indexOfRefraction).otherwise(1.0f), - rayDir, - testResult.normal, - testResult.percentSpecular, - 1.0f, - ) - .otherwise: - 0f - - val refractionChance = when(specularChance > 0.0f): - testResult.refractionChance * ((1.0f - specularChance) / (1.0f - testResult.percentSpecular)) - .otherwise: - testResult.refractionChance - - val Random(rayRoll, nextRngState1) = randomFloat(rngState) - val doSpecular = when(specularChance > 0.0f && rayRoll < specularChance): - 1.0f - .otherwise: - 0.0f - - val doRefraction = when(refractionChance > 0.0f && doSpecular === 0.0f && rayRoll < specularChance + refractionChance): - 1.0f - .otherwise: - 0.0f - - val rayProbability = when(doSpecular === 1.0f): - specularChance - .elseWhen(doRefraction === 1.0f): - refractionChance - .otherwise: - 1.0f - (specularChance + refractionChance) - - val rayProbabilityCorrected = max(rayProbability, 0.01f) - - val nextRayPos = when(doRefraction === 1.0f): - (rayPos + rayDir * testResult.dist) - (testResult.normal * rayPosNormalNudge) - .otherwise: - (rayPos + rayDir * testResult.dist) + (testResult.normal * rayPosNormalNudge) - - val Random(randomVec1, nextRngState2) = randomVector(nextRngState1) - val diffuseRayDir = normalize(testResult.normal + randomVec1) - val specularRayDirPerfect = reflect(rayDir, testResult.normal) - val specularRayDir = normalize(mix(specularRayDirPerfect, diffuseRayDir, testResult.roughness * testResult.roughness)) - - val Random(randomVec2, nextRngState3) = randomVector(nextRngState2) - val refractionRayDirPerfect = - refract( - rayDir, - testResult.normal, - when(testResult.fromInside)(testResult.indexOfRefraction).otherwise(1.0f / testResult.indexOfRefraction), - ) - val refractionRayDir = - normalize( - mix( - refractionRayDirPerfect, - normalize(-testResult.normal + randomVec2), - testResult.refractionRoughness * testResult.refractionRoughness, - ), - ) - - val rayDirSpecular = mix(diffuseRayDir, specularRayDir, doSpecular) - val rayDirRefracted = mix(rayDirSpecular, refractionRayDir, doRefraction) - - val nextColor = (throughput2 mulV testResult.emissive) addV color - - val nextThroughput = when(doRefraction === 0.0f): - throughput2 mulV mix[Vec3[Float32]](testResult.albedo, testResult.specularColor, doSpecular) - .otherwise: - throughput2 - - val throughputRayProb = nextThroughput * (1.0f / rayProbabilityCorrected) - - RayTraceState(nextRayPos, rayDirRefracted, nextColor, throughputRayProb, nextRngState3) - .otherwise: - RayTraceState(rayPos, rayDir, color, throughput, rngState, true), - ) - .limit(MaxBounces) - .takeWhile(!_.finished) - .lastOr(initState) - - val rngState = xi * 1973 + yi * 9277 + frame * 26699 | 1 - case class RenderIteration(color: Vec3[Float32], rngState: UInt32) extends GStruct[RenderIteration] - val color = - GSeq - .gen( - first = RenderIteration((0f, 0f, 0f), rngState.unsigned), - next = { case RenderIteration(_, rngState) => - val Random(wiggleX, rngState1) = randomFloat(rngState) - val Random(wiggleY, rngState2) = randomFloat(rngState1) - val x = ((xi.asFloat + wiggleX) / dim.toFloat) * 2f - 1f - val y = ((yi.asFloat + wiggleY) / dim.toFloat) * 2f - 1f - val xy = (x, y) - - val rayPosition = (0f, 0f, 0f) - val cameraDist = 1.0f / tan(fovDeg * 0.6f * math.Pi.toFloat / 180.0f) - val rayTarget = (x, y, cameraDist) - - val rayDir = normalize(rayTarget - rayPosition) - val rtResult = getColorForRay(rayPosition, rayDir, rngState) - val withBg = vclamp(rtResult.color + (SRGBToLinear(bgColor) mulV rtResult.throughput), 0.0f, 20.0f) - RenderIteration(withBg, rtResult.rngState) - }, - ) - .limit(pixelIterationsPerFrame) - .fold((0f, 0f, 0f), { case (acc, RenderIteration(color, _)) => acc + (color * (1.0f / pixelIterationsPerFrame.toFloat)) }) - - when(frame === 0): - (color, 1.0f) - .otherwise: - mix(lastFrame.at(xi, yi), (color, 1.0f), vec4(1.0f / (frame.asFloat + 1f))) - - val initialMem = Array.fill(dim * dim)((0.5f, 0.5f, 0.5f, 0.5f)) - val renders = 100 - val code = function() - List.range(0, renders).foldLeft(initialMem) { case (mem, i) => - UniformContext.withUniform(RaytracingIteration(i)): - val newMem = Vec4FloatMem(mem).map(code).asInstanceOf[Vec4FloatMem].toArray - ImageUtility.renderToImage(newMem, dim, Paths.get(s"generated.png")) - println(s"Finished render $i") - newMem - } diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/1sample.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/1sample.scala deleted file mode 100644 index 16121da4..00000000 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/1sample.scala +++ /dev/null @@ -1,17 +0,0 @@ -package io.computenode.cyfra.samples.slides - -import io.computenode.cyfra.dsl.{*, given} -import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.FloatMem - -given GContext = new GContext() - -@main -def sample() = - val gpuFunction = GFunction: (value: Float32) => - value * 2f - - val data = FloatMem((1 to 128).map(_.toFloat).toArray) - - val result = data.map(gpuFunction).asInstanceOf[FloatMem].toArray - println(result.mkString(", ")) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/2simpleray.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/2simpleray.scala deleted file mode 100644 index 145fe5e6..00000000 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/2simpleray.scala +++ /dev/null @@ -1,44 +0,0 @@ -package io.computenode.cyfra.samples.slides - -import io.computenode.cyfra.dsl.{*, given} -import io.computenode.cyfra.dsl.struct.GStruct -import io.computenode.cyfra.dsl.struct.GStruct.Empty -import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.utility.ImageUtility - -import java.nio.file.Paths - -@main -def simpleRay() = - val dim = 1024 - val fovDeg = 60 - - case class Sphere(center: Vec3[Float32], radius: Float32, color: Vec3[Float32], emissive: Vec3[Float32]) extends GStruct[Sphere] - - def getColorForRay(rayPos: Vec3[Float32], rayDirection: Vec3[Float32]): Vec4[Float32] = - val sphereCenter = (0f, 0.5f, 3f) - val sphereRadius = 1f - val toRay = rayPos - sphereCenter - val b = toRay dot rayDirection - val c = (toRay dot toRay) - (sphereRadius * sphereRadius) - when((c < 0f || b < 0f) && b * b - c > 0f): - (1f, 1f, 1f, 1f) - .otherwise: - (0f, 0f, 0f, 1f) - - val raytracing: GFunction[Empty, Vec4[Float32], Vec4[Float32]] = GFunction.from2D(dim): - case (_, (xi: Int32, yi: Int32), _) => - val x = (xi.asFloat / dim.toFloat) * 2f - 1f - val y = (yi.asFloat / dim.toFloat) * 2f - 1f - - val rayPosition = (0f, 0f, 0f) - val cameraDist = 1.0f / tan(fovDeg * 0.6f * math.Pi.toFloat / 180.0f) - val rayTarget = (x, y, cameraDist) - - val rayDir = normalize(rayTarget - rayPosition) - getColorForRay(rayPosition, rayDir) - - val mem = Vec4FloatMem(Array.fill(dim * dim)((0f, 0f, 0f, 0f))) - val result = mem.map(raytracing).asInstanceOf[Vec4FloatMem].toArray - ImageUtility.renderToImage(result, dim, Paths.get(s"generated2.png")) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/3rays.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/3rays.scala deleted file mode 100644 index 49ff0d46..00000000 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/3rays.scala +++ /dev/null @@ -1,153 +0,0 @@ -package io.computenode.cyfra.samples.slides - -import io.computenode.cyfra.* -import io.computenode.cyfra.dsl.collections.GSeq -import io.computenode.cyfra.dsl.{*, given} -import io.computenode.cyfra.dsl.struct.GStruct -import io.computenode.cyfra.dsl.struct.GStruct.Empty -import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.utility.ImageUtility - -import java.nio.file.Paths - -@main -def rays() = - val raysPerPixel = 10 - val dim = 1024 - val fovDeg = 60 - val minRayHitTime = 0.01f - val superFar = 999f - val maxBounces = 10 - val rayPosNudge = 0.001f - - def scalarTriple(u: Vec3[Float32], v: Vec3[Float32], w: Vec3[Float32]): Float32 = (u cross v) dot w - - case class Sphere(center: Vec3[Float32], radius: Float32, color: Vec3[Float32], emissive: Vec3[Float32]) extends GStruct[Sphere] - - case class Quad(a: Vec3[Float32], b: Vec3[Float32], c: Vec3[Float32], d: Vec3[Float32], color: Vec3[Float32], emissive: Vec3[Float32]) - extends GStruct[Quad] - - case class RayHitInfo(dist: Float32, normal: Vec3[Float32], albedo: Vec3[Float32], emissive: Vec3[Float32]) extends GStruct[RayHitInfo] - - case class RayTraceState(rayPos: Vec3[Float32], rayDir: Vec3[Float32], color: Vec3[Float32], throughput: Vec3[Float32], finished: GBoolean = false) - extends GStruct[RayTraceState] - - def testSphereTrace(rayPos: Vec3[Float32], rayDir: Vec3[Float32], currentHit: RayHitInfo, sphere: Sphere): RayHitInfo = - val toRay = rayPos - sphere.center - val b = toRay dot rayDir - val c = (toRay dot toRay) - (sphere.radius * sphere.radius) - val notHit = currentHit - when(c > 0f && b > 0f): - notHit - .otherwise: - val discr = b * b - c - when(discr > 0f): - val initDist = -b - sqrt(discr) - val fromInside = initDist < 0f - val dist = when(fromInside)(-b + sqrt(discr)).otherwise(initDist) - when(dist > minRayHitTime && dist < currentHit.dist): - val normal = normalize(rayPos + rayDir * dist - sphere.center) - RayHitInfo(dist, normal, sphere.color, sphere.emissive) - .otherwise: - notHit - .otherwise: - notHit - - def testQuadTrace(rayPos: Vec3[Float32], rayDir: Vec3[Float32], currentHit: RayHitInfo, quad: Quad): RayHitInfo = - val normal = normalize((quad.c - quad.a) cross (quad.c - quad.b)) - val fixedQuad = when((normal dot rayDir) > 0f): - Quad(quad.d, quad.c, quad.b, quad.a, quad.color, quad.emissive) - .otherwise: - quad - val fixedNormal = when((normal dot rayDir) > 0f)(-normal).otherwise(normal) - val p = rayPos - val q = rayPos + rayDir - val pq = q - p - val pa = fixedQuad.a - p - val pb = fixedQuad.b - p - val pc = fixedQuad.c - p - val m = pc cross pq - val v = pa dot m - - def checkHit(intersectPoint: Vec3[Float32]): RayHitInfo = - val dist = when(abs(rayDir.x) > 0.1f): - (intersectPoint.x - rayPos.x) / rayDir.x - .elseWhen(abs(rayDir.y) > 0.1f): - (intersectPoint.y - rayPos.y) / rayDir.y - .otherwise: - (intersectPoint.z - rayPos.z) / rayDir.z - - when(dist > minRayHitTime && dist < currentHit.dist): - RayHitInfo(dist, fixedNormal, quad.color, quad.emissive) - .otherwise: - currentHit - - when(v >= 0f): - val u = -(pb dot m) - val w = scalarTriple(pq, pb, pa) - when(u >= 0f && w >= 0f): - val denom = 1f / (u + v + w) - val uu = u * denom - val vv = v * denom - val ww = w * denom - val intersectPos = fixedQuad.a * uu + fixedQuad.b * vv + fixedQuad.c * ww - checkHit(intersectPos) - .otherwise: - currentHit - .otherwise: - val pd = fixedQuad.d - p - val u = pd dot m - val w = scalarTriple(pq, pa, pd) - when(u >= 0f && w >= 0f): - val negV = -v - val denom = 1f / (u + negV + w) - val uu = u * denom - val vv = negV * denom - val ww = w * denom - val intersectPos = fixedQuad.a * uu + fixedQuad.d * vv + fixedQuad.c * ww - checkHit(intersectPos) - .otherwise: - currentHit - - val sphere = Sphere(center = (1.5f, 1.5f, 4f), radius = 0.5f, color = (1f, 1f, 1f), emissive = (3f, 3f, 3f)) - - val backWall = Quad(a = (-2f, -2f, 5f), b = (2f, -2f, 5f), c = (2f, 2f, 5f), d = (-2f, 2f, 5f), color = (0f, 1f, 1f), emissive = (0f, 0f, 0f)) - - def getColorForRay(rayPos: Vec3[Float32], rayDirection: Vec3[Float32]): Vec4[Float32] = - GSeq - .gen[RayTraceState]( - first = RayTraceState(rayPos = rayPos, rayDir = rayDirection, color = (0f, 0f, 0f), throughput = (1f, 1f, 1f)), - next = { case state @ RayTraceState(rayPos, rayDir, color, throughput, _) => - val noHit = RayHitInfo(1000f, (0f, 0f, 0f), (0f, 0f, 0f), (0f, 0f, 0f)) - val sphereHit = testSphereTrace(rayPos, rayDir, noHit, sphere) - val wallHit = testQuadTrace(rayPos, rayDir, sphereHit, backWall) - RayTraceState( - rayPos = rayPos + rayDir * wallHit.dist + wallHit.normal * rayPosNudge, - rayDir = reflect(rayDir, wallHit.normal), - color = color + wallHit.emissive mulV throughput, - throughput = throughput mulV wallHit.albedo, - finished = wallHit.dist > superFar, - ) - }, - ) - .limit(maxBounces) - .takeWhile(!_.finished) - .map(state => (state.color, 1f)) - .lastOr((0f, 0f, 0f, 1f)) - - val raytracing: GFunction[Empty, Vec4[Float32], Vec4[Float32]] = GFunction.from2D(dim): - case (_, (xi: Int32, yi: Int32), _) => - val x = (xi.asFloat / dim.toFloat) * 2f - 1f - val y = (yi.asFloat / dim.toFloat) * 2f - 1f - - val rayPosition = (0f, 0f, 0f) - val cameraDist = 1.0f / tan(fovDeg * 0.6f * math.Pi.toFloat / 180.0f) - val rayTarget = (x, y, cameraDist) - - val rayDir = normalize(rayTarget - rayPosition) - getColorForRay(rayPosition, rayDir) - - val mem = Vec4FloatMem(Array.fill(dim * dim)((0f, 0f, 0f, 0f))) - val result = mem.map(raytracing).asInstanceOf[Vec4FloatMem].toArray - ImageUtility.renderToImage(result, dim, Paths.get(s"generated3.png")) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/4random.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/4random.scala index bac16f3b..8d3488a7 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/4random.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/slides/4random.scala @@ -1,11 +1,12 @@ package io.computenode.cyfra.samples.slides +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.dsl.collections.GSeq import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.struct.GStruct.Empty import io.computenode.cyfra.core.archive.* -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem +import io.computenode.cyfra.runtime.VkCyfraRuntime import io.computenode.cyfra.utility.ImageUtility import java.nio.file.Paths @@ -36,6 +37,9 @@ def randomVector(seed: UInt32): Random[Vec3[Float32]] = @main def randomRays() = + + given CyfraRuntime = VkCyfraRuntime() + val raysPerPixel = 10 val dim = 1024 val fovDeg = 80 @@ -202,6 +206,6 @@ def randomRays() = .fold((0f, 0f, 0f), { case (acc, RenderIteration(color, _)) => acc + (color * (1.0f / pixelIterationsPerFrame.toFloat)) }) (color, 1f) - val mem = Vec4FloatMem(Array.fill(dim * dim)((0f, 0f, 0f, 0f))) - val result = mem.map(raytracing).asInstanceOf[Vec4FloatMem].toArray + val mem = Array.fill(dim * dim)((0f, 0f, 0f, 0f)) + val result: Array[fRGBA] = raytracing.run(mem) ImageUtility.renderToImage(result, dim, Paths.get(s"generated4.png")) diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimatedFunctionRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimatedFunctionRenderer.scala index 1e18cd28..49a5feed 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimatedFunctionRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimatedFunctionRenderer.scala @@ -1,14 +1,14 @@ package io.computenode.cyfra.foton.animation import io.computenode.cyfra +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.dsl.Value.* import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.foton.animation.AnimatedFunctionRenderer.{AnimationIteration, RenderFn} import io.computenode.cyfra.foton.animation.AnimationFunctions.AnimationInstant -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.core.archive.{GContext, GFunction, UniformContext} +import io.computenode.cyfra.core.archive.GFunction +import io.computenode.cyfra.runtime.VkCyfraRuntime import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext.Implicits @@ -16,15 +16,13 @@ import scala.concurrent.ExecutionContext.Implicits class AnimatedFunctionRenderer(params: AnimatedFunctionRenderer.Parameters) extends AnimationRenderer[AnimatedFunction, AnimatedFunctionRenderer.RenderFn](params): - given GContext = new GContext() + given CyfraRuntime = new VkCyfraRuntime() given ExecutionContext = Implicits.global override protected def renderFrame(scene: AnimatedFunction, time: Float32, fn: RenderFn): Array[fRGBA] = val mem = Array.fill(params.width * params.height)((0.5f, 0.5f, 0.5f, 0.5f)) - UniformContext.withUniform(AnimationIteration(time)): - val fmem = Vec4FloatMem(mem) - fmem.map(fn).asInstanceOf[Vec4FloatMem].toArray + fn.run(mem, AnimationIteration(time)) override protected def renderFunction(scene: AnimatedFunction): RenderFn = GFunction.from2D(params.width): diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimationRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimationRenderer.scala index 9a262e95..015be533 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimationRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/animation/AnimationRenderer.scala @@ -4,7 +4,6 @@ import io.computenode.cyfra import io.computenode.cyfra.dsl.Value.* import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.core.archive.GFunction -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA import io.computenode.cyfra.utility.ImageUtility import io.computenode.cyfra.utility.Units.Milliseconds import io.computenode.cyfra.utility.Utility.timed diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala index 8f4d2b70..8a77c384 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala @@ -2,13 +2,13 @@ package io.computenode.cyfra.foton.rt import io.computenode.cyfra import io.computenode.cyfra.* +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.dsl.Value.* import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.foton.rt.ImageRtRenderer.RaytracingIteration -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.core.archive.{GFunction, UniformContext} +import io.computenode.cyfra.core.archive.GFunction +import io.computenode.cyfra.runtime.VkCyfraRuntime import io.computenode.cyfra.utility.ImageUtility import io.computenode.cyfra.utility.Utility.timed @@ -16,6 +16,8 @@ import java.nio.file.Path class ImageRtRenderer(params: ImageRtRenderer.Parameters) extends RtRenderer(params): + given CyfraRuntime = VkCyfraRuntime() + def renderToFile(scene: Scene, destinationPath: Path): Unit = val images = render(scene) for image <- images do ImageUtility.renderToImage(image, params.width, params.height, destinationPath) @@ -26,12 +28,11 @@ class ImageRtRenderer(params: ImageRtRenderer.Parameters) extends RtRenderer(par private def render(scene: Scene, fn: GFunction[RaytracingIteration, Vec4[Float32], Vec4[Float32]]): LazyList[Array[fRGBA]] = val initialMem = Array.fill(params.width * params.height)((0.5f, 0.5f, 0.5f, 0.5f)) LazyList - .iterate((initialMem, 0), params.iterations + 1) { case (mem, render) => - UniformContext.withUniform(RaytracingIteration(render)): - val fmem = Vec4FloatMem(mem) - val result = timed(s"Rendered iteration $render")(fmem.map(fn).asInstanceOf[Vec4FloatMem].toArray) + .iterate((initialMem, 0), params.iterations + 1): + case (mem, render) => + val result: Array[fRGBA] = timed(s"Render iteration $render"): + fn.run(mem, RaytracingIteration(render)) (result, render + 1) - } .drop(1) .map(_._1) diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala index b59765b1..de38af62 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala @@ -10,15 +10,12 @@ import io.computenode.cyfra.dsl.library.Random import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.foton.rt.RtRenderer.RayHitInfo -import io.computenode.cyfra.core.archive.GContext import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext.Implicits class RtRenderer(params: RtRenderer.Parameters): - given GContext = new GContext() - given ExecutionContext = Implicits.global private case class RayTraceState( diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala index c339bec9..ee6e77ac 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala @@ -1,29 +1,30 @@ package io.computenode.cyfra.foton.rt.animation import io.computenode.cyfra +import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.dsl.Value.* import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.foton.animation.AnimationRenderer import io.computenode.cyfra.foton.rt.RtRenderer import io.computenode.cyfra.foton.rt.animation.AnimationRtRenderer.RaytracingIteration -import io.computenode.cyfra.core.archive.mem.GMem.fRGBA -import io.computenode.cyfra.core.archive.mem.Vec4FloatMem -import io.computenode.cyfra.core.archive.{GFunction, UniformContext} +import io.computenode.cyfra.core.archive.GFunction +import io.computenode.cyfra.runtime.VkCyfraRuntime class AnimationRtRenderer(params: AnimationRtRenderer.Parameters) extends RtRenderer(params) with AnimationRenderer[AnimatedScene, AnimationRtRenderer.RenderFn](params): + + given CyfraRuntime = VkCyfraRuntime() protected def renderFrame(scene: AnimatedScene, time: Float32, fn: GFunction[RaytracingIteration, Vec4[Float32], Vec4[Float32]]): Array[fRGBA] = val initialMem = Array.fill(params.width * params.height)((0.5f, 0.5f, 0.5f, 0.5f)) List - .iterate((initialMem, 0), params.iterations + 1) { case (mem, render) => - UniformContext.withUniform(RaytracingIteration(render, time)): - val fmem = Vec4FloatMem(mem) - val result = fmem.map(fn).asInstanceOf[Vec4FloatMem].toArray + .iterate((initialMem, 0), params.iterations + 1): + case (mem, render) => + val result: Array[fRGBA] = fn.run(mem, RaytracingIteration(render, time)) (result, render + 1) - } + .map(_._1) .last From bc26f581729e35f1f6dab028d9d20382979c3bc5 Mon Sep 17 00:00:00 2001 From: MarconZet <25779550+MarconZet@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:30:15 +0200 Subject: [PATCH 35/38] change instance creation^ --- .../cyfra/e2e/fs2interop/Fs2Tests.scala | 21 ++-- .../cyfra/samples/TestingStuff.scala | 37 +++--- .../computenode/cyfra/fs2interop/GPipe.scala | 29 ++--- .../cyfra/vulkan/VulkanContext.scala | 16 +-- .../vulkan/core/DebugMessengerCallback.scala | 58 +++++++++ ...llback.scala => DebugReportCallback.scala} | 46 ++++--- .../cyfra/vulkan/core/Device.scala | 1 - .../cyfra/vulkan/core/Instance.scala | 112 +++++------------- 8 files changed, 152 insertions(+), 168 deletions(-) create mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugMessengerCallback.scala rename cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/{DebugCallback.scala => DebugReportCallback.scala} (56%) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala index 367a3066..6c6e5b14 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/fs2interop/Fs2Tests.scala @@ -6,9 +6,10 @@ import algebra.VectorAlgebra import io.computenode.cyfra.fs2interop.* import io.computenode.cyfra.core.CyfraRuntime import io.computenode.cyfra.runtime.VkCyfraRuntime -import fs2.{io as fs2io, *} -import _root_.io.computenode.cyfra.spirvtools.{SpirvCross, SpirvDisassembler, SpirvToolsRunner} -import _root_.io.computenode.cyfra.spirvtools.SpirvTool.ToFile +import io.computenode.cyfra.spirvtools.{SpirvCross, SpirvDisassembler, SpirvToolsRunner} +import io.computenode.cyfra.spirvtools.SpirvTool.ToFile + +import fs2.* import java.nio.file.Paths @@ -20,15 +21,15 @@ extension (f: fRGBA) Math.abs(f._1 - g._1) < eps && Math.abs(f._2 - g._2) < eps && Math.abs(f._3 - g._3) < eps && Math.abs(f._4 - g._4) < eps class Fs2Tests extends munit.FunSuite: - given cr: VkCyfraRuntime = VkCyfraRuntime( - spirvToolsRunner = SpirvToolsRunner( + given cr: VkCyfraRuntime = VkCyfraRuntime(spirvToolsRunner = + SpirvToolsRunner( crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), - disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/disassembled.spv"))) - ) + disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/disassembled.spv"))), + ), ) override def afterAll(): Unit = - //cr.close() + // cr.close() super.afterAll() test("fs2 through GPipe map, just ints"): @@ -57,7 +58,7 @@ class Fs2Tests extends munit.FunSuite: test("fs2 through GPipe filter, just ints"): val n = 16 - val inSeq = (0 until n * 256) + val inSeq = 0 until n * 256 val stream = Stream.emits(inSeq) val pipe = GPipe.filter[Pure, Int32, Int](_.mod(7) === 0) val result = stream.through(pipe).compile.toList @@ -65,4 +66,4 @@ class Fs2Tests extends munit.FunSuite: result .zip(expected) .foreach: (res, exp) => - assert(res == exp, s"Expected $exp, got $res") \ No newline at end of file + assert(res == exp, s"Expected $exp, got $res") diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index ecea9250..0e1781df 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given object TestingStuff: - + // === Emit program === case class EmitProgramParams(inSize: Int, emitN: Int) @@ -53,14 +53,13 @@ object TestingStuff: case class FilterProgramUniform(filterValue: Int32) extends GStruct[FilterProgramUniform] - case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) - extends Layout + case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[Int32], params: GUniform[FilterProgramUniform] = GUniform.fromParams) extends Layout val filterProgram = GProgram[FilterProgramParams, FilterProgramLayout]( layout = params => FilterProgramLayout( in = GBuffer[Int32](params.inSize), - out = GBuffer[GBoolean](params.inSize), + out = GBuffer[Int32](params.inSize), params = GUniform(FilterProgramUniform(params.filterValue)), ), dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), @@ -68,15 +67,16 @@ object TestingStuff: val invocId = GIO.invocationId val element = GIO.read(layout.in, invocId) val isMatch = element === layout.params.read.filterValue - GIO.write(layout.out, invocId, isMatch) + val a: Int32 = when[Int32](isMatch)(1).otherwise(0) + GIO.write(layout.out, invocId, a) // === GExecution === case class EmitFilterParams(inSize: Int, emitN: Int, filterValue: Int) - case class EmitFilterLayout(inBuffer: GBuffer[Int32], emitBuffer: GBuffer[Int32], filterBuffer: GBuffer[GBoolean]) extends Layout + case class EmitFilterLayout(inBuffer: GBuffer[Int32], emitBuffer: GBuffer[Int32], filterBuffer: GBuffer[Int32]) extends Layout - case class EmitFilterResult(out: GBuffer[GBoolean]) extends Layout + case class EmitFilterResult(out: GBuffer[Int32]) extends Layout val emitFilterExecution = GExecution[EmitFilterParams, EmitFilterLayout]() .addProgram(emitProgram)( @@ -90,11 +90,8 @@ object TestingStuff: @main def testEmit = - given runtime: VkCyfraRuntime = VkCyfraRuntime( - spirvToolsRunner = SpirvToolsRunner( - crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))) - ) - ) + given runtime: VkCyfraRuntime = + VkCyfraRuntime(spirvToolsRunner = SpirvToolsRunner(crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))))) val emitParams = EmitProgramParams(inSize = 1024, emitN = 2) @@ -110,10 +107,7 @@ object TestingStuff: val result = BufferUtils.createIntBuffer(data.length * 2) val rbb = MemoryUtil.memByteBuffer(result) region.runUnsafe( - init = EmitProgramLayout( - in = GBuffer[Int32](buffer), - out = GBuffer[Int32](data.length * 2), - ), + init = EmitProgramLayout(in = GBuffer[Int32](buffer), out = GBuffer[Int32](data.length * 2)), onDone = layout => layout.out.read(rbb), ) runtime.close() @@ -128,11 +122,11 @@ object TestingStuff: @main def test = - given runtime: VkCyfraRuntime = VkCyfraRuntime( - spirvToolsRunner = SpirvToolsRunner( + given runtime: VkCyfraRuntime = VkCyfraRuntime(spirvToolsRunner = + SpirvToolsRunner( crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), - validator = SpirvValidator.Disable - ) + validator = SpirvValidator.Disable, + ), ) val emitFilterParams = EmitFilterParams(inSize = 1024, emitN = 2, filterValue = 42) @@ -152,7 +146,7 @@ object TestingStuff: init = EmitFilterLayout( inBuffer = GBuffer[Int32](buffer), emitBuffer = GBuffer[Int32](data.length * 2), - filterBuffer = GBuffer[GBoolean](data.length * 2), + filterBuffer = GBuffer[Int32](data.length * 2), ), onDone = layout => layout.filterBuffer.read(rbb), ) @@ -165,3 +159,4 @@ object TestingStuff: .zipWithIndex .foreach: case ((e, a), i) => assert(e == a, s"Mismatch at index $i: expected $e, got $a") + println("DONE") diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index f75ac208..90c76906 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -35,14 +35,13 @@ object GPipe: val gProg = GProgram[Params, PLayout]( layout = params => PLayout(in = GBuffer[C1](params.inSize), out = GBuffer[C2](params.inSize)), dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize / 256f).toInt, 1, 1)), - )(layout => { + ) { layout => val invocId = GIO.invocationId val element = GIO.read[C1](layout.in, invocId) val res = f(element) - for - _ <- GIO.write[C2](layout.out, invocId, res) + for _ <- GIO.write[C2](layout.out, invocId, res) yield Empty() - }) + } val execution = GExecution[Params, PLayout]() .addProgram(gProg)(params => Params(params.inSize), layout => PLayout(layout.in, layout.out)) @@ -60,12 +59,7 @@ object GPipe: .chunkN(params.inSize) .flatMap: chunk => bridge1.toByteBuffer(inBuf, chunk.toArray) - region.runUnsafe(init = PLayout( - in = GBuffer[C1](inBuf), - out = GBuffer[C2](outBuf)), - onDone = layout => - layout.out.read(outBuf) - ) + region.runUnsafe(init = PLayout(in = GBuffer[C1](inBuf), out = GBuffer[C2](outBuf)), onDone = layout => layout.out.read(outBuf)) Stream.emits(bridge2.fromByteBuffer(outBuf, new Array[S2](params.inSize))) // Overload for convenient single type version @@ -87,9 +81,7 @@ object GPipe: val invocId = GIO.invocationId val element = GIO.read[C](layout.in, invocId) val result = when(pred(element))(1: Int32).otherwise(0) - for - _ <- GIO.printf("Pred: Element %d -> %d", invocId, result) - _ <- GIO.write[Int32](layout.out, invocId, result) + for _ <- GIO.write[Int32](layout.out, invocId, result) yield Empty() // Prefix sum (inclusive), upsweep/downsweep @@ -110,9 +102,7 @@ object GPipe: val oldValue = GIO.read[Int32](layout.ints, end) val addValue = GIO.read[Int32](layout.ints, mid) val newValue = oldValue + addValue - for - _ <- GIO.printf("Upsweep: invocId %d, root %d, size %d, mid %d, end %d, oldValue %d, addValue %d, newValue %d", invocId, root, size, mid, end, oldValue, addValue, newValue) - _ <- GIO.write[Int32](layout.ints, end, newValue) + for _ <- GIO.write[Int32](layout.ints, end, newValue) yield Empty() val downsweep = GProgram[ScanParams, ScanLayout]( @@ -127,9 +117,7 @@ object GPipe: val oldValue = GIO.read[Int32](layout.ints, mid) val addValue = when(end > 0)(GIO.read[Int32](layout.ints, end)).otherwise(0) val newValue = oldValue + addValue - for - _ <- GIO.printf("Downsweep: invocId %d, end %d, mid %d, oldValue %d, addValue %d, newValue %d", invocId, end, mid, oldValue, addValue, newValue) - _ <- GIO.write[Int32](layout.ints, mid, newValue) + for _ <- GIO.write[Int32](layout.ints, mid, newValue) yield Empty() // Stitch together many upsweep / downsweep program phases recursively @@ -171,7 +159,6 @@ object GPipe: val element = GIO.read[C](layout.in, invocId) val prefixSum = GIO.read[Int32](layout.scan, invocId) for - _ <- GIO.printf("Compact: Element %d, prefix sum %d", invocId, prefixSum) _ <- GIO.when(invocId > 0): val prevScan = GIO.read[Int32](layout.scan, invocId - 1) GIO.when(prevScan < prefixSum): @@ -227,7 +214,7 @@ object GPipe: onDone = layout => { layout.scan.read(filteredCount, (filterParams.inSize - 1) * intSize) layout.out.read(compactBuf) - } + }, ) val filteredN = filteredCount.getInt(0) val arr = bridge.fromByteBuffer(compactBuf, new Array[S](filteredN)) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala index 67d612fe..eade4596 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala @@ -1,9 +1,9 @@ package io.computenode.cyfra.vulkan import io.computenode.cyfra.utility.Logger.logger -import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayers +import io.computenode.cyfra.vulkan.VulkanContext.{validation, vulkanPrintf} import io.computenode.cyfra.vulkan.command.CommandPool -import io.computenode.cyfra.vulkan.core.{DebugCallback, Device, Instance, PhysicalDevice, Queue} +import io.computenode.cyfra.vulkan.core.{DebugMessengerCallback, DebugReportCallback, Device, Instance, PhysicalDevice, Queue} import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool, DescriptorPoolManager, DescriptorSetManager} import org.lwjgl.system.Configuration @@ -15,12 +15,13 @@ import scala.jdk.CollectionConverters.* * MarconZet Created 13.04.2020 */ private[cyfra] object VulkanContext: - val ValidationLayer: String = "VK_LAYER_KHRONOS_validation" - private val ValidationLayers: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "false").toBoolean + private val validation: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "false").toBoolean + private val vulkanPrintf: Boolean = System.getProperty("io.computenode.cyfra.vulkan.printf", "false").toBoolean private[cyfra] class VulkanContext: - private val instance: Instance = new Instance(ValidationLayers) - private val debugCallback: Option[DebugCallback] = if ValidationLayers then Some(new DebugCallback(instance)) else None + private val instance: Instance = new Instance(validation, vulkanPrintf) + private val debugReport: Option[DebugReportCallback] = if validation then Some(new DebugReportCallback(instance)) else None + private val debugMessenger: Option[DebugMessengerCallback] = if validation & vulkanPrintf then Some(new DebugMessengerCallback(instance)) else None private val physicalDevice = new PhysicalDevice(instance) physicalDevice.assertRequirements() @@ -54,5 +55,6 @@ private[cyfra] class VulkanContext: descriptorPoolManager.destroy() allocator.destroy() device.destroy() - debugCallback.foreach(_.destroy()) + debugReport.foreach(_.destroy()) + debugMessenger.foreach(_.destroy()) instance.destroy() diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugMessengerCallback.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugMessengerCallback.scala new file mode 100644 index 00000000..ad319665 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugMessengerCallback.scala @@ -0,0 +1,58 @@ +package io.computenode.cyfra.vulkan.core + +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import io.computenode.cyfra.vulkan.util.VulkanObjectHandle +import org.lwjgl.BufferUtils +import org.lwjgl.system.MemoryUtil +import org.lwjgl.vulkan.EXTDebugUtils.{ + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + vkCreateDebugUtilsMessengerEXT, + vkDestroyDebugUtilsMessengerEXT, +} +import org.lwjgl.vulkan.VK10.VK_FALSE +import org.lwjgl.vulkan.{VkDebugUtilsMessengerCallbackDataEXT, VkDebugUtilsMessengerCallbackEXT, VkDebugUtilsMessengerCreateInfoEXT} +import org.slf4j.LoggerFactory + +import java.lang.Integer.highestOneBit +import java.nio.LongBuffer + +class DebugMessengerCallback(instance: Instance) extends VulkanObjectHandle: + private val logger = LoggerFactory.getLogger("Cyfra-DebugMessenger") + + protected val handle: Long = pushStack: stack => + val callback = + new VkDebugUtilsMessengerCallbackEXT(): + override def invoke(messageSeverity: Int, messageTypes: Int, pCallbackData: Long, pUserData: Long): Int = + val message = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData).pMessageString() + val debugMessage = message.split("\\|").last + highestOneBit(messageSeverity) match + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT => logger.error(debugMessage) + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT => logger.warn(debugMessage) + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT => logger.info(debugMessage) + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT => logger.debug(debugMessage) + case x => logger.error(s"Unexpected message severity: $messageSeverity, message: $debugMessage") + VK_FALSE + + val debugMessengerCreate = VkDebugUtilsMessengerCreateInfoEXT + .calloc(stack) + .sType$Default() + .messageSeverity( + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + ) + .messageType( + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + ) + .pfnUserCallback(callback) + + val debugMessengerBuff = stack.callocLong(1) + check(vkCreateDebugUtilsMessengerEXT(instance.get, debugMessengerCreate, null, debugMessengerBuff), "Failed to create debug messenger") + debugMessengerBuff.get() + + override protected def close(): Unit = vkDestroyDebugUtilsMessengerEXT(instance.get, handle, null) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugReportCallback.scala similarity index 56% rename from cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala rename to cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugReportCallback.scala index 3721e9b3..2e43450d 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugReportCallback.scala @@ -1,24 +1,28 @@ package io.computenode.cyfra.vulkan.core import io.computenode.cyfra.utility.Logger.logger -import io.computenode.cyfra.vulkan.core.DebugCallback.DebugReport +import io.computenode.cyfra.vulkan.core.DebugReportCallback.DebugReport +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.{VulkanAssertionError, VulkanObjectHandle} import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil.NULL import org.lwjgl.vulkan.EXTDebugReport.* import org.lwjgl.vulkan.VK10.VK_SUCCESS import org.lwjgl.vulkan.{VkDebugReportCallbackCreateInfoEXT, VkDebugReportCallbackEXT} +import org.slf4j.LoggerFactory import java.lang.Integer.highestOneBit /** @author * MarconZet Created 13.04.2020 */ -object DebugCallback: +object DebugReportCallback: val DebugReport: Int = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT -private[cyfra] class DebugCallback(instance: Instance) extends VulkanObjectHandle: - override protected val handle: Long = +private[cyfra] class DebugReportCallback(instance: Instance) extends VulkanObjectHandle: + private val logger = LoggerFactory.getLogger("Cyfra-DebugReport") + + protected val handle: Long = pushStack: stack => val debugCallback = new VkDebugReportCallbackEXT(): def invoke( flags: Int, @@ -32,31 +36,23 @@ private[cyfra] class DebugCallback(instance: Instance) extends VulkanObjectHandl ): Int = val decodedMessage = VkDebugReportCallbackEXT.getString(pMessage) highestOneBit(flags) match - case VK_DEBUG_REPORT_DEBUG_BIT_EXT => - logger.debug(decodedMessage) - case VK_DEBUG_REPORT_ERROR_BIT_EXT => - logger.error(decodedMessage) - case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT => - logger.warn(decodedMessage) - case VK_DEBUG_REPORT_INFORMATION_BIT_EXT => - logger.info(decodedMessage) + case VK_DEBUG_REPORT_DEBUG_BIT_EXT => logger.debug(decodedMessage) + case VK_DEBUG_REPORT_ERROR_BIT_EXT => logger.error(decodedMessage) + case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT => logger.warn(decodedMessage) + case VK_DEBUG_REPORT_INFORMATION_BIT_EXT => logger.info(decodedMessage) case x => logger.error(s"Unexpected value: x, message: $decodedMessage") 0 - setupDebugging(DebugReport, debugCallback) - - override protected def close(): Unit = - vkDestroyDebugReportCallbackEXT(instance.get, handle, null) - private def setupDebugging(flags: Int, callback: VkDebugReportCallbackEXT): Long = val dbgCreateInfo = VkDebugReportCallbackCreateInfoEXT - .create() + .calloc(stack) .sType$Default() .pNext(0) - .pfnCallback(callback) + .pfnCallback(debugCallback) .pUserData(0) - .flags(flags) - val pCallback = BufferUtils.createLongBuffer(1) - val err = vkCreateDebugReportCallbackEXT(instance.get, dbgCreateInfo, null, pCallback) - val callbackHandle = pCallback.get(0) - if err != VK_SUCCESS then throw new VulkanAssertionError("Failed to create DebugCallback", err) - callbackHandle + .flags(DebugReport) + val pCallback = stack.callocLong(1) + check(vkCreateDebugReportCallbackEXT(instance.get, dbgCreateInfo, null, pCallback), "Failed to create DebugCallback") + pCallback.get() + + override protected def close(): Unit = + vkDestroyDebugReportCallbackEXT(instance.get, handle, null) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala index 6908fcfa..290ac699 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala @@ -1,6 +1,5 @@ package io.computenode.cyfra.vulkan.core -import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayer import Device.MacOsExtension import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.{VulkanObject, VulkanObjectHandle} diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index 2f3d3df5..f8661f6d 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala @@ -1,7 +1,7 @@ package io.computenode.cyfra.vulkan.core import io.computenode.cyfra.utility.Logger.logger -import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayer +import io.computenode.cyfra.vulkan.core.Instance.ValidationLayer import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject import org.lwjgl.system.{MemoryStack, MemoryUtil} @@ -10,6 +10,7 @@ import org.lwjgl.vulkan.* import org.lwjgl.vulkan.EXTDebugReport.VK_EXT_DEBUG_REPORT_EXTENSION_NAME import org.lwjgl.vulkan.EXTLayerSettings.{VK_LAYER_SETTING_TYPE_BOOL32_EXT, VK_LAYER_SETTING_TYPE_STRING_EXT, VK_LAYER_SETTING_TYPE_UINT32_EXT} import org.lwjgl.vulkan.KHRPortabilityEnumeration.{VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME} +import org.lwjgl.vulkan.EXTLayerSettings.VK_EXT_LAYER_SETTINGS_EXTENSION_NAME import org.lwjgl.vulkan.VK10.* import org.lwjgl.vulkan.EXTValidationFeatures.* import org.lwjgl.vulkan.EXTDebugUtils.* @@ -23,12 +24,10 @@ import scala.util.chaining.* * MarconZet Created 13.04.2020 */ object Instance: - val ValidationLayersExtensions: Seq[String] = List( - VK_EXT_DEBUG_REPORT_EXTENSION_NAME, - VK_EXT_DEBUG_UTILS_EXTENSION_NAME, - VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME - ) - val MoltenVkExtensions: Seq[String] = List(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) + private val ValidationLayer: String = "VK_LAYER_KHRONOS_validation" + private val ValidationLayersExtensions: Seq[String] = + List(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_LAYER_SETTINGS_EXTENSION_NAME) + private val MoltenVkExtensions: Seq[String] = List(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) lazy val (extensions, layers): (Seq[String], Seq[String]) = pushStack: stack => val ip = stack.ints(1) @@ -46,7 +45,7 @@ object Instance: lazy val version: Int = VK.getInstanceVersionSupported -private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObject[VkInstance]: +private[cyfra] class Instance(enableValidationLayers: Boolean, enablePrinting: Boolean) extends VulkanObject[VkInstance]: protected val handle: VkInstance = pushStack: stack => val appInfo = VkApplicationInfo @@ -75,93 +74,33 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj .ppEnabledLayerNames(ppEnabledLayerNames) if enableValidationLayers then - val layerSettings = VkLayerSettingEXT.calloc(2, stack) - layerSettings - .get(0) - .pLayerName(stack.ASCII(ValidationLayer)) - .pSettingName(stack.ASCII("validate_sync")) - .`type`(VK_LAYER_SETTING_TYPE_BOOL32_EXT) - .valueCount(1) - .pValues(MemoryUtil.memByteBuffer(stack.ints(1))) + val layerSettings = VkLayerSettingEXT.calloc(10, stack) + + setTrue(layerSettings.get(), "validate_sync", stack) + setTrue(layerSettings.get(), "gpuav_enable", stack) + setTrue(layerSettings.get(), "validate_best_practices", stack) + + if enablePrinting then + setTrue(layerSettings.get(), "printf_enable", stack) layerSettings - .get(1) + .get() .pLayerName(stack.ASCII(ValidationLayer)) .pSettingName(stack.ASCII("printf_buffer_size")) .`type`(VK_LAYER_SETTING_TYPE_UINT32_EXT) .valueCount(1) - .pValues(MemoryUtil.memByteBuffer(stack.ints(1024*1024))) + .pValues(MemoryUtil.memByteBuffer(stack.ints(1024 * 1024))) + layerSettings.flip() val layerSettingsCI = VkLayerSettingsCreateInfoEXT.calloc(stack).sType$Default().pSettings(layerSettings) - val validationFeatures = VkValidationFeaturesEXT.calloc(stack) - .sType$Default() - .pEnabledValidationFeatures(stack.ints( - VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT, - )) - .pNext(0) - - val validationFeaturesPCreate = VkValidationFeaturesEXT.calloc(stack) - .sType$Default() - .pEnabledValidationFeatures(stack.ints( - VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT, - VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT, - )) - .pNext(0) - - layerSettingsCI.pNext(validationFeatures.address()) - pCreateInfo.pNext(layerSettingsCI.address()) - validationFeaturesPCreate.pNext(validationFeaturesPCreate.address()) + pCreateInfo.pNext(layerSettingsCI) val pInstance = stack.mallocPointer(1) check(vkCreateInstance(pCreateInfo, null, pInstance), "Failed to create VkInstance") new VkInstance(pInstance.get(0), pCreateInfo) - protected val callback: Option[VkDebugUtilsMessengerCallbackEXT] = - if enableValidationLayers then Some: - new VkDebugUtilsMessengerCallbackEXT(): - override def invoke(messageSeverity: Int, messageTypes: Int, pCallbackData: Long, pUserData: Long): Int = - val message = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData).pMessageString() - val debugMessage = "[VK DEBUG] " + message.split("\\|").last - if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0 then - logger.error(debugMessage) - else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0 then - logger.warn(debugMessage) - else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0 then - logger.info(debugMessage) - else - logger.debug(debugMessage) - VK_FALSE - else None - - protected val debugMessenger: Option[LongBuffer] = callback.map: c => - pushStack: stack => - val debugMessengerCreate = VkDebugUtilsMessengerCreateInfoEXT.calloc(1) - .sType$Default() - .messageSeverity( - VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT - ) - .messageType( - VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT - ) - .pfnUserCallback(c) - - val debugMessengerBuff = MemoryUtil.memAllocLong(1) - check(vkCreateDebugUtilsMessengerEXT( - handle, - debugMessengerCreate.get(0), - null, - debugMessengerBuff - )) - debugMessengerBuff - - lazy val enabledLayers: Seq[String] = List .empty[String] .pipe: x => @@ -171,8 +110,7 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj x else x - override protected def close(): Unit = - debugMessenger.foreach(b => vkDestroyDebugUtilsMessengerEXT(handle, b.get(0), null)) + override protected def close(): Unit = vkDestroyInstance(handle, null) private def getInstanceExtensions(stack: MemoryStack) = @@ -193,10 +131,18 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj val filteredExtensions = extensions.filter(ext => availableExtensions .contains(ext) - .tap: x => // TODO detect when this extension is needed + .tap: x => // TODO better handle missing extensions if !x then logger.warn(s"Requested Vulkan instance extension '$ext' is not available"), ) val ppEnabledExtensionNames = stack.callocPointer(extensions.size) filteredExtensions.foreach(x => ppEnabledExtensionNames.put(stack.ASCII(x))) ppEnabledExtensionNames.flip() + + private def setTrue(setting: VkLayerSettingEXT, name: String, stack: MemoryStack) = + setting + .pLayerName(stack.ASCII(ValidationLayer)) + .pSettingName(stack.ASCII(name)) + .`type`(VK_LAYER_SETTING_TYPE_BOOL32_EXT) + .valueCount(1) + .pValues(MemoryUtil.memByteBuffer(stack.ints(1))) From 3172910fd3db7b53ee7157198486159f1f5fd45d Mon Sep 17 00:00:00 2001 From: MarconZet <25779550+MarconZet@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:30:37 +0200 Subject: [PATCH 36/38] fomrating^ --- .../io/computenode/cyfra/spirv/Context.scala | 2 +- .../cyfra/spirv/SpirvConstants.scala | 2 +- .../cyfra/spirv/compilers/DSLCompiler.scala | 13 +- .../spirv/compilers/ExpressionCompiler.scala | 3 +- .../cyfra/spirv/compilers/GIOCompiler.scala | 78 ++++------- .../spirv/compilers/GStructCompiler.scala | 5 +- .../compilers/SpirvProgramCompiler.scala | 128 ++++++++---------- .../cyfra/core/GBufferRegion.scala | 2 +- .../io/computenode/cyfra/core/GCodec.scala | 52 +++---- .../io/computenode/cyfra/core/GProgram.scala | 4 +- .../computenode/cyfra/core/SpirvProgram.scala | 15 +- .../cyfra/core/archive/GFunction.scala | 75 ++++------ .../cyfra/core/layout/LayoutStruct.scala | 18 +-- .../cyfra/dsl/collections/GArray.scala | 1 - .../io/computenode/cyfra/dsl/gio/GIO.scala | 4 +- .../cyfra/e2e/RuntimeEnduranceTest.scala | 17 +-- .../cyfra/e2e/SpirvRuntimeEnduranceTest.scala | 23 ++-- .../cyfra/e2e/dsl/ArithmeticsE2eTest.scala | 2 +- .../cyfra/e2e/dsl/FunctionsE2eTest.scala | 2 +- .../cyfra/e2e/dsl/GseqE2eTest.scala | 2 +- .../cyfra/foton/rt/ImageRtRenderer.scala | 2 +- .../rt/animation/AnimationRtRenderer.scala | 3 +- .../cyfra/runtime/ExecutionHandler.scala | 38 +++--- .../cyfra/runtime/VkAllocation.scala | 2 +- .../cyfra/runtime/VkCyfraRuntime.scala | 8 +- .../computenode/cyfra/runtime/VkShader.scala | 3 +- .../cyfra/spirvtools/SpirvCross.scala | 2 +- .../cyfra/spirvtools/SpirvDisassembler.scala | 2 +- .../cyfra/spirvtools/SpirvOptimizer.scala | 2 +- .../cyfra/vulkan/command/Fence.scala | 2 +- 30 files changed, 237 insertions(+), 275 deletions(-) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala index 0312d9db..96490071 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/Context.scala @@ -27,7 +27,7 @@ private[cyfra] case class Context( exprNames: Map[Int, String] = Map(), names: Set[String] = Set(), functions: Map[FnIdentifier, SprivFunction] = Map(), - stringLiterals: Map[String, Int] = Map() + stringLiterals: Map[String, Int] = Map(), ): def joinNested(ctx: Context): Context = this.copy(nextResultId = ctx.nextResultId, exprNames = ctx.exprNames ++ this.exprNames, functions = ctx.functions ++ this.functions) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala index 00a3f615..ec3c4d0b 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvConstants.scala @@ -17,5 +17,5 @@ private[cyfra] object SpirvConstants: val GL_GLOBAL_INVOCATION_ID_REF = 5 val GL_WORKGROUP_SIZE_REF = 6 val DEBUG_PRINTF_REF = 7 - + val HEADER_REFS_TOP = 8 diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala index f5975295..8bdafb24 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/DSLCompiler.scala @@ -29,7 +29,7 @@ private[cyfra] object DSLCompiler: @tailrec private def getAllExprsFlattened(pending: List[GIO[?]], acc: List[E[?]], visitDetached: Boolean): List[E[?]] = pending match - case Nil => acc + case Nil => acc case GIO.Pure(v) :: tail => getAllExprsFlattened(tail, getAllExprsFlattened(v.tree, visitDetached) ::: acc, visitDetached) case GIO.FlatMap(v, n) :: tail => @@ -74,7 +74,7 @@ private[cyfra] object DSLCompiler: // So far only used for printf private def getAllStrings(pending: List[GIO[?]], acc: Set[String]): Set[String] = pending match - case Nil => acc + case Nil => acc case GIO.FlatMap(v, n) :: tail => getAllStrings(v :: n :: tail, acc) case GIO.Repeat(_, gio) :: tail => @@ -91,10 +91,11 @@ private[cyfra] object DSLCompiler: val (typeDefs, typedContext) = defineScalarTypes(scalarTypes, Context.initialContext) val allStrings = getAllStrings(List(bodyIo), Set.empty) val (stringDefs, ctxWithStrings) = defineStrings(allStrings.toList, typedContext) - val (buffersWithIndices, uniformsWithIndices) = bindings.zipWithIndex.partition: - case (_: GBuffer[?], _) => true - case (_: GUniform[?], _) => false - .asInstanceOf[(List[(GBuffer[?], Int)], List[(GUniform[?], Int)])] + val (buffersWithIndices, uniformsWithIndices) = bindings.zipWithIndex + .partition: + case (_: GBuffer[?], _) => true + case (_: GUniform[?], _) => false + .asInstanceOf[(List[(GBuffer[?], Int)], List[(GUniform[?], Int)])] val uniforms = uniformsWithIndices.map(_._1) val uniformSchemas = uniforms.map(_.schema) val structsInCode = diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala index c1459fcf..6e859bd3 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/ExpressionCompiler.scala @@ -306,7 +306,6 @@ private[cyfra] object ExpressionCompiler: val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (expr.treeid -> (ctx.nextResultId + 1)), nextResultId = ctx.nextResultId + 2) (instructions, updatedContext) - case when: WhenExpr[?] => compileWhen(when, ctx) @@ -327,7 +326,7 @@ private[cyfra] object ExpressionCompiler: ) val updatedContext = ctx.copy(exprRefs = ctx.exprRefs + (cs.treeid -> ctx.nextResultId), nextResultId = ctx.nextResultId + 1) (insns, updatedContext) - + case gf @ GetField(binding @ ReadUniform(uf), fieldIndex) => val insns: List[Instruction] = List( Instruction( diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala index 5d54fc2b..11adc24c 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GIOCompiler.scala @@ -9,19 +9,20 @@ import io.computenode.cyfra.spirv.SpirvConstants.{DEBUG_PRINTF_REF, TYPE_VOID_RE import io.computenode.cyfra.spirv.SpirvTypes.{GBooleanTag, Int32Tag, LInt32Tag} object GIOCompiler: - + def compileGio(gio: GIO[?], ctx: Context, acc: List[Words] = Nil): (List[Words], Context) = gio match case GIO.Pure(v) => val (insts, updatedCtx) = ExpressionCompiler.compileBlock(v.tree, ctx) (acc ::: insts, updatedCtx) - + case WriteBuffer(buffer, index, value) => val (valueInsts, ctxWithValue) = ExpressionCompiler.compileBlock(value.tree, ctx) val (indexInsts, ctxWithIndex) = ExpressionCompiler.compileBlock(index.tree, ctxWithValue) - val insns = List(Instruction( + val insns = List( + Instruction( Op.OpAccessChain, List( ResultRef(ctxWithIndex.uniformPointerMap(ctxWithIndex.valueTypeMap(buffer.tag.tag))), @@ -31,7 +32,7 @@ object GIOCompiler: ResultRef(ctxWithIndex.exprRefs(index.tree.treeid)), ), ), - Instruction(Op.OpStore, List(ResultRef(ctxWithIndex.nextResultId), ResultRef(ctxWithIndex.exprRefs(value.tree.treeid)))) + Instruction(Op.OpStore, List(ResultRef(ctxWithIndex.nextResultId), ResultRef(ctxWithIndex.exprRefs(value.tree.treeid)))), ) val updatedCtx = ctxWithIndex.copy(nextResultId = ctxWithIndex.nextResultId + 1) (acc ::: indexInsts ::: valueInsts ::: insns, updatedCtx) @@ -39,7 +40,7 @@ object GIOCompiler: case GIO.FlatMap(v, n) => val (vInsts, ctxAfterV) = compileGio(v, ctx, acc) compileGio(n, ctxAfterV, vInsts) - + case GIO.Repeat(n, f) => // Compile 'n' first (so we can use its id in the comparison) val (nInsts, ctxWithN) = ExpressionCompiler.compileBlock(n.tree, ctx) @@ -63,17 +64,14 @@ object GIOCompiler: val addId = baseId + 7 // Bind CurrentRepeatIndex to the phi result for body compilation - val bodyCtx = ctxWithN.copy( - nextResultId = baseId + 8, - exprRefs = ctxWithN.exprRefs + (CurrentRepeatIndex.treeid -> phiId) - ) - val (bodyInsts, ctxAfterBody) = compileGio(f, bodyCtx) // ← Capture the context after body compilation + val bodyCtx = ctxWithN.copy(nextResultId = baseId + 8, exprRefs = ctxWithN.exprRefs + (CurrentRepeatIndex.treeid -> phiId)) + val (bodyInsts, ctxAfterBody) = compileGio(f, bodyCtx) // ← Capture the context after body compilation // Preheader: close current block and jump to header through a dedicated block val preheader = List( Instruction(Op.OpBranch, List(ResultRef(preHeaderId))), Instruction(Op.OpLabel, List(ResultRef(preHeaderId))), - Instruction(Op.OpBranch, List(ResultRef(headerId))) + Instruction(Op.OpBranch, List(ResultRef(headerId))), ) // Header: OpPhi first, then compute condition, then OpLoopMerge and the terminating branch @@ -82,66 +80,46 @@ object GIOCompiler: // OpPhi must be first in the block Instruction( Op.OpPhi, - List( - ResultRef(intTy), ResultRef(phiId), - ResultRef(zeroId), ResultRef(preHeaderId), - ResultRef(addId), ResultRef(continueId) - ) + List(ResultRef(intTy), ResultRef(phiId), ResultRef(zeroId), ResultRef(preHeaderId), ResultRef(addId), ResultRef(continueId)), ), // cmp = (counter < n) - Instruction( - Op.OpSLessThan, - List(ResultRef(boolTy), ResultRef(cmpId), ResultRef(phiId), ResultRef(nId)) - ), + Instruction(Op.OpSLessThan, List(ResultRef(boolTy), ResultRef(cmpId), ResultRef(phiId), ResultRef(nId))), // OpLoopMerge must be the second-to-last instruction, before the terminating branch Instruction(Op.OpLoopMerge, List(ResultRef(mergeId), ResultRef(continueId), LoopControlMask.MaskNone)), - Instruction(Op.OpBranchConditional, List(ResultRef(cmpId), ResultRef(bodyId), ResultRef(mergeId))) + Instruction(Op.OpBranchConditional, List(ResultRef(cmpId), ResultRef(bodyId), ResultRef(mergeId))), ) - val bodyBlk = List( - Instruction(Op.OpLabel, List(ResultRef(bodyId))) - ) ::: bodyInsts ::: List( - Instruction(Op.OpBranch, List(ResultRef(continueId))) - ) + val bodyBlk = List(Instruction(Op.OpLabel, List(ResultRef(bodyId)))) ::: bodyInsts ::: List(Instruction(Op.OpBranch, List(ResultRef(continueId)))) val contBlk = List( Instruction(Op.OpLabel, List(ResultRef(continueId))), - Instruction( - Op.OpIAdd, - List(ResultRef(intTy), ResultRef(addId), ResultRef(phiId), ResultRef(oneId)) - ), - Instruction(Op.OpBranch, List(ResultRef(headerId))) + Instruction(Op.OpIAdd, List(ResultRef(intTy), ResultRef(addId), ResultRef(phiId), ResultRef(oneId))), + Instruction(Op.OpBranch, List(ResultRef(headerId))), ) - val mergeBlk = List( - Instruction(Op.OpLabel, List(ResultRef(mergeId))) - ) + val mergeBlk = List(Instruction(Op.OpLabel, List(ResultRef(mergeId)))) // Use the highest nextResultId to avoid ID collisions - val finalNextId = math.max(ctxAfterBody.nextResultId, addId + 1) // ← Use ctxAfterBody.nextResultId + val finalNextId = math.max(ctxAfterBody.nextResultId, addId + 1) // ← Use ctxAfterBody.nextResultId // Use ctxWithN as base to prevent loop-local values from being referenced outside val finalCtx = ctxWithN.copy(nextResultId = finalNextId) (acc ::: nInsts ::: preheader ::: header ::: bodyBlk ::: contBlk ::: mergeBlk, finalCtx) - + case GIO.Printf(format, args*) => val (argsInsts, ctxAfterArgs) = args.foldLeft((List.empty[Words], ctx)) { case ((instsAcc, cAcc), arg) => val (argInsts, cAfterArg) = ExpressionCompiler.compileBlock(arg.tree, cAcc) (instsAcc ::: argInsts, cAfterArg) } val argResults = args.map(a => ResultRef(ctxAfterArgs.exprRefs(a.tree.treeid))).toList - val printf = Instruction(Op.OpExtInst, List( - ResultRef(TYPE_VOID_REF), - ResultRef(ctxAfterArgs.nextResultId), - ResultRef(DEBUG_PRINTF_REF), - IntWord(1), - ResultRef(ctx.stringLiterals(format)), - ) ::: argResults) + val printf = Instruction( + Op.OpExtInst, + List( + ResultRef(TYPE_VOID_REF), + ResultRef(ctxAfterArgs.nextResultId), + ResultRef(DEBUG_PRINTF_REF), + IntWord(1), + ResultRef(ctx.stringLiterals(format)), + ) ::: argResults, + ) (acc ::: argsInsts ::: List(printf), ctxAfterArgs.copy(nextResultId = ctxAfterArgs.nextResultId + 1)) - - - - - - - \ No newline at end of file diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala index 2542e1f8..78683deb 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/GStructCompiler.scala @@ -37,8 +37,9 @@ private[cyfra] object GStructCompiler: structName = s"${schema.structTag.tag.shortName}_$nameSuffix" nameSuffix += 1 val structType = context.valueTypeMap(schema.structTag.tag) - val words = Instruction(Op.OpName, List(ResultRef(structType), Text(structName))) :: schema.fields.zipWithIndex.map { case ((name, _, tag), i) => - Instruction(Op.OpMemberName, List(ResultRef(structType), IntWord(i), Text(name))) + val words = Instruction(Op.OpName, List(ResultRef(structType), Text(structName))) :: schema.fields.zipWithIndex.map { + case ((name, _, tag), i) => + Instruction(Op.OpMemberName, List(ResultRef(structType), IntWord(i), Text(name))) } val updatedCtx = currCtx.copy(names = currCtx.names + structName) (wordsAcc ::: words, updatedCtx) diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala index ee523d35..e80ed296 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/compilers/SpirvProgramCompiler.scala @@ -44,10 +44,7 @@ private[cyfra] object SpirvProgramCompiler: val (vars, nonVarsBody) = bubbleUpVars(body) - val end = List( - Instruction(Op.OpReturn, List()), - Instruction(Op.OpFunctionEnd, List()), - ) + val end = List(Instruction(Op.OpReturn, List()), Instruction(Op.OpFunctionEnd, List())) (init ::: vars ::: initWorkerIndex ::: nonVarsBody ::: end, codeCtx.copy(nextResultId = codeCtx.nextResultId + 1)) def getNameDecorations(ctx: Context): List[Instruction] = @@ -94,7 +91,7 @@ private[cyfra] object SpirvProgramCompiler: ) val ctxWithVoid = context.copy(voidTypeRef = TYPE_VOID_REF, voidFuncTypeRef = VOID_FUNC_TYPE_REF) (voidDef, ctxWithVoid) - + def createInvocationId(context: Context): (List[Words], Context) = val definitionInstructions = List( Instruction(Op.OpConstant, List(ResultRef(context.valueTypeMap(UInt32Tag.tag)), ResultRef(context.nextResultId + 0), IntWord(localSizeX))), @@ -120,54 +117,51 @@ private[cyfra] object SpirvProgramCompiler: def createAndInitBlocks(blocks: List[(GBuffer[?], Int)], context: Context): (List[Words], List[Words], Context) = var membersVisited = Set[Int]() var structsVisited = Set[Int]() - val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { case ((decAcc, insnAcc, ctx), (buff, binding)) => - val tpe = buff.tag - val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, binding) - - val (structDecoration, structDefinition) = if structsVisited.contains(block.structTypeRef) then - (Nil, Nil) - else - structsVisited += block.structTypeRef - ( - List( - Instruction(Op.OpMemberDecorate, List(ResultRef(block.structTypeRef), IntWord(0), Decoration.Offset, IntWord(0))), // OpMemberDecorate %BufferX 0 Offset 0 - Instruction(Op.OpDecorate, List(ResultRef(block.structTypeRef), Decoration.BufferBlock)), // OpDecorate %BufferX BufferBlock - ), - List( - Instruction(Op.OpTypeStruct, List(ResultRef(block.structTypeRef), IntWord(block.memberArrayTypeRef))), // %BufferX = OpTypeStruct %_runtimearr_X - ) - ) + val (decoration, definition, newContext) = blocks.foldLeft((List[Words](), List[Words](), context)) { + case ((decAcc, insnAcc, ctx), (buff, binding)) => + val tpe = buff.tag + val block = ArrayBufferBlock(ctx.nextResultId, ctx.nextResultId + 1, ctx.nextResultId + 2, ctx.nextResultId + 3, binding) - val (memberDecoration, memberDefinition) = if membersVisited.contains(block.memberArrayTypeRef) then - (Nil, Nil) - else - membersVisited += block.memberArrayTypeRef - ( - List( - Instruction(Op.OpDecorate, List(ResultRef(block.memberArrayTypeRef), Decoration.ArrayStride, IntWord(typeStride(tpe)))), // OpDecorate %_runtimearr_X ArrayStride [typeStride(type)] - ), - List( - Instruction(Op.OpTypeRuntimeArray, List(ResultRef(block.memberArrayTypeRef), IntWord(context.valueTypeMap(tpe.tag)))), // %_runtimearr_X = OpTypeRuntimeArray %[typeOf(tpe)] - ) - ) + val (structDecoration, structDefinition) = + if structsVisited.contains(block.structTypeRef) then (Nil, Nil) + else + structsVisited += block.structTypeRef + ( + List( + Instruction(Op.OpMemberDecorate, List(ResultRef(block.structTypeRef), IntWord(0), Decoration.Offset, IntWord(0))), // OpMemberDecorate %BufferX 0 Offset 0 + Instruction(Op.OpDecorate, List(ResultRef(block.structTypeRef), Decoration.BufferBlock)), // OpDecorate %BufferX BufferBlock + ), + List( + Instruction(Op.OpTypeStruct, List(ResultRef(block.structTypeRef), IntWord(block.memberArrayTypeRef))), // %BufferX = OpTypeStruct %_runtimearr_X + ), + ) - val decorationInstructions = memberDecoration ::: structDecoration ::: List[Words]( - Instruction(Op.OpDecorate, List(ResultRef(block.blockVarRef), Decoration.DescriptorSet, IntWord(0))), // OpDecorate %_X DescriptorSet 0 - Instruction(Op.OpDecorate, List(ResultRef(block.blockVarRef), Decoration.Binding, IntWord(block.binding))), // OpDecorate %_X Binding [binding] - ) + val (memberDecoration, memberDefinition) = + if membersVisited.contains(block.memberArrayTypeRef) then (Nil, Nil) + else + membersVisited += block.memberArrayTypeRef + ( + List( + Instruction(Op.OpDecorate, List(ResultRef(block.memberArrayTypeRef), Decoration.ArrayStride, IntWord(typeStride(tpe)))), // OpDecorate %_runtimearr_X ArrayStride [typeStride(type)] + ), + List( + Instruction(Op.OpTypeRuntimeArray, List(ResultRef(block.memberArrayTypeRef), IntWord(context.valueTypeMap(tpe.tag)))), // %_runtimearr_X = OpTypeRuntimeArray %[typeOf(tpe)] + ), + ) - val definitionInstructions = memberDefinition ::: structDefinition ::: List[Words]( - Instruction(Op.OpTypePointer, List(ResultRef(block.blockPointerRef), StorageClass.Uniform, ResultRef(block.structTypeRef))), // %_ptr_Uniform_BufferX= OpTypePointer Uniform %BufferX - Instruction(Op.OpVariable, List(ResultRef(block.blockPointerRef), ResultRef(block.blockVarRef), StorageClass.Uniform)), // %_X = OpVariable %_ptr_Uniform_X Uniform - ) + val decorationInstructions = memberDecoration ::: structDecoration ::: List[Words]( + Instruction(Op.OpDecorate, List(ResultRef(block.blockVarRef), Decoration.DescriptorSet, IntWord(0))), // OpDecorate %_X DescriptorSet 0 + Instruction(Op.OpDecorate, List(ResultRef(block.blockVarRef), Decoration.Binding, IntWord(block.binding))), // OpDecorate %_X Binding [binding] + ) - val contextWithBlock = - ctx.copy(bufferBlocks = ctx.bufferBlocks + (buff -> block)) - ( - decAcc ::: decorationInstructions, - insnAcc ::: definitionInstructions, - contextWithBlock.copy(nextResultId = contextWithBlock.nextResultId + 5), - ) + val definitionInstructions = memberDefinition ::: structDefinition ::: List[Words]( + Instruction(Op.OpTypePointer, List(ResultRef(block.blockPointerRef), StorageClass.Uniform, ResultRef(block.structTypeRef))), // %_ptr_Uniform_BufferX= OpTypePointer Uniform %BufferX + Instruction(Op.OpVariable, List(ResultRef(block.blockPointerRef), ResultRef(block.blockVarRef), StorageClass.Uniform)), // %_X = OpVariable %_ptr_Uniform_X Uniform + ) + + val contextWithBlock = + ctx.copy(bufferBlocks = ctx.bufferBlocks + (buff -> block)) + (decAcc ::: decorationInstructions, insnAcc ::: definitionInstructions, contextWithBlock.copy(nextResultId = contextWithBlock.nextResultId + 5)) } (decoration, definition, newContext) @@ -176,7 +170,7 @@ private[cyfra] object SpirvProgramCompiler: Instruction(Op.OpName, List(ResultRef(block.structTypeRef), Text(s"Buffer$tpe"))) :: Instruction(Op.OpName, List(ResultRef(block.blockVarRef), Text(s"data$tpe"))) :: Nil // todo name uniform - //context.inBufferBlocks.flatMap(namesForBlock(_, "In")) ::: context.outBufferBlocks.flatMap(namesForBlock(_, "Out")) + // context.inBufferBlocks.flatMap(namesForBlock(_, "In")) ::: context.outBufferBlocks.flatMap(namesForBlock(_, "Out")) List() def totalStride(gs: GStructSchema[?]): Int = gs.fields @@ -187,20 +181,16 @@ private[cyfra] object SpirvProgramCompiler: case (_, _, t) => typeStride(t) .sum - + def defineStrings(strings: List[String], ctx: Context): (List[Words], Context) = strings.foldLeft((List.empty[Words], ctx)): case ((insnsAcc, currentCtx), str) => - if currentCtx.stringLiterals.contains(str) then - (insnsAcc, currentCtx) + if currentCtx.stringLiterals.contains(str) then (insnsAcc, currentCtx) else val strRef = currentCtx.nextResultId - val strInsns = List( - Instruction(Op.OpString, List(ResultRef(strRef), Text(str))), - ) + val strInsns = List(Instruction(Op.OpString, List(ResultRef(strRef), Text(str)))) val newCtx = currentCtx.copy(stringLiterals = currentCtx.stringLiterals + (str -> strRef), nextResultId = currentCtx.nextResultId + 1) (insnsAcc ::: strInsns, newCtx) - def createAndInitUniformBlocks(schemas: List[(GUniform[?], Int)], ctx: Context): (List[Words], List[Words], Context) = { var decoratedOffsets = Set[Int]() @@ -212,16 +202,18 @@ private[cyfra] object SpirvProgramCompiler: if decoratedOffsets.contains(uniformStructTypeRef) then Nil else decoratedOffsets += uniformStructTypeRef - schema.fields.zipWithIndex.foldLeft[(List[Words], Int)](List.empty[Words], 0): - case ((acc, offset), ((name, fromExpr, tag), idx)) => - val stride = - if tag <:< schema.gStructTag then - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - else typeStride(tag) - val offsetDecoration = Instruction(Op.OpMemberDecorate, List(ResultRef(uniformStructTypeRef), IntWord(idx), Decoration.Offset, IntWord(offset))) - (acc :+ offsetDecoration, offset + stride) - ._1 ::: List(Instruction(Op.OpDecorate, List(ResultRef(uniformStructTypeRef), Decoration.Block))) + schema.fields.zipWithIndex + .foldLeft[(List[Words], Int)](List.empty[Words], 0): + case ((acc, offset), ((name, fromExpr, tag), idx)) => + val stride = + if tag <:< schema.gStructTag then + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + else typeStride(tag) + val offsetDecoration = + Instruction(Op.OpMemberDecorate, List(ResultRef(uniformStructTypeRef), IntWord(idx), Decoration.Offset, IntWord(offset))) + (acc :+ offsetDecoration, offset + stride) + ._1 ::: List(Instruction(Op.OpDecorate, List(ResultRef(uniformStructTypeRef), Decoration.Block))) val uniformPointerUniformRef = currentCtx.nextResultId val uniformPointerUniform = @@ -239,7 +231,7 @@ private[cyfra] object SpirvProgramCompiler: nextResultId = currentCtx.nextResultId + 2, uniformVarRefs = currentCtx.uniformVarRefs + (uniform -> uniformVarRef), uniformPointerMap = currentCtx.uniformPointerMap + (uniformStructTypeRef -> uniformPointerUniformRef), - bindingToStructType = currentCtx.bindingToStructType + (binding -> uniformStructTypeRef) + bindingToStructType = currentCtx.bindingToStructType + (binding -> uniformStructTypeRef), ) (newDecorations, newDefinitions, newCtx) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index 5d613a56..cfc041cf 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -36,7 +36,7 @@ object GBufferRegion: // noinspection ScalaRedundantCast val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): - case AllocRegion() => None + case AllocRegion() => None case MapRegion(req, f) => Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala index a686e9ff..9d4d4bb9 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GCodec.scala @@ -19,13 +19,14 @@ trait GCodec[CyfraType <: Value: {FromExpr, Tag}, ScalaType: ClassTag]: object GCodec: - def totalStride(gs: GStructSchema[?]): Int = gs.fields.map: - case (_, fromExpr, t) if t <:< gs.gStructTag => - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - case (_, _, t) => - typeStride(t) - .sum + def totalStride(gs: GStructSchema[?]): Int = gs.fields + .map: + case (_, fromExpr, t) if t <:< gs.gStructTag => + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + case (_, _, t) => + typeStride(t) + .sum given GCodec[Int32, Int]: def toByteBuffer(inBuf: ByteBuffer, chunk: Array[Int]): ByteBuffer = @@ -74,15 +75,14 @@ object GCodec: def fromByteBuffer(outBuf: ByteBuffer, arr: Array[Boolean]): Array[Boolean] = outBuf.get(arr.asInstanceOf[Array[Byte]]).flip() arr - + given [T <: GStruct[T]: {GStructSchema as schema, Tag, ClassTag}]: GCodec[T, T] with def toByteBuffer(inBuf: ByteBuffer, arr: Array[T]): ByteBuffer = inBuf.clear().order(ByteOrder.nativeOrder()) for struct <- arr field <- struct.productIterator - do - writeConstPrimitive(inBuf, field.asInstanceOf[Value]) + do writeConstPrimitive(inBuf, field.asInstanceOf[Value]) inBuf.flip() inBuf def fromByteBuffer(outBuf: ByteBuffer, arr: Array[T]): Array[T] = @@ -104,31 +104,37 @@ object GCodec: arr.appended(newStruct) outBuf.rewind() arr - - private def readPrimitive(buffer: ByteBuffer, value: Tag[_]): Value = + + private def readPrimitive(buffer: ByteBuffer, value: Tag[?]): Value = value.tag match - case t if t =:= summon[Tag[Int]].tag => Int32(ConstInt32(buffer.getInt())) - case t if t =:= summon[Tag[Float]].tag => Float32(ConstFloat32(buffer.getFloat())) - case t if t =:= summon[Tag[Boolean]].tag => GBoolean(ConstGB(buffer.get() != 0)) + case t if t =:= summon[Tag[Int]].tag => Int32(ConstInt32(buffer.getInt())) + case t if t =:= summon[Tag[Float]].tag => Float32(ConstFloat32(buffer.getFloat())) + case t if t =:= summon[Tag[Boolean]].tag => GBoolean(ConstGB(buffer.get() != 0)) case t if t =:= summon[Tag[(Float, Float, Float, Float)]].tag => // todo other tuples - Vec4(ComposeVec4(Float32(ConstFloat32(buffer.getFloat())), Float32(ConstFloat32(buffer.getFloat())), Float32(ConstFloat32(buffer.getFloat())), Float32(ConstFloat32(buffer.getFloat())))) + Vec4( + ComposeVec4( + Float32(ConstFloat32(buffer.getFloat())), + Float32(ConstFloat32(buffer.getFloat())), + Float32(ConstFloat32(buffer.getFloat())), + Float32(ConstFloat32(buffer.getFloat())), + ), + ) case illegal => throw new IllegalArgumentException(s"Unable to deserialize value of type $illegal") private def writeConstPrimitive(buff: ByteBuffer, value: Value): Unit = value.tree match - case c: Const[?] => writePrimitive(buff, c.value) - case compose: ComposeVec[_] => + case c: Const[?] => writePrimitive(buff, c.value) + case compose: ComposeVec[?] => compose.productIterator.foreach: v => - writeConstPrimitive(buff, v.asInstanceOf[Value]) + writeConstPrimitive(buff, v.asInstanceOf[Value]) case illegal => throw new IllegalArgumentException(s"Only constant Cyfra values can be serialized (got $illegal)") private def writePrimitive(buff: ByteBuffer, value: Any): Unit = value match - case i: Int => buff.putInt(i) - case f: Float => buff.putFloat(f) + case i: Int => buff.putInt(i) + case f: Float => buff.putFloat(f) case b: Boolean => buff.put(if b then 1.toByte else 0.toByte) - case t: Tuple => + case t: Tuple => t.productIterator.foreach(writePrimitive(buff, _)) case illegal => throw new IllegalArgumentException(s"Unable to serialize value $illegal of type ${illegal.getClass}") - diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 7154e47f..fe111bfd 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -36,10 +36,10 @@ object GProgram: )(body: L => GIO[?]): GProgram[Params, L] = new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) - def fromSpirvFile[Params, L <: Layout : {LayoutBinding, LayoutStruct}]( + def fromSpirvFile[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( layout: InitProgramLayout ?=> Params => L, dispatch: (L, Params) => ProgramDispatch, - path: Path + path: Path, ): SpirvProgram[Params, L] = Using.resource(new FileInputStream(path.toFile)): fis => val fc = fis.getChannel diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index f60c02e3..0cfacd43 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -30,17 +30,18 @@ case class SpirvProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] priv shaderBindings: L => ShaderLayout, ) extends GProgram[Params, L]: - /** - * A hash of the shader code, entry point, workgroup size, and layout bindings. - * Layout and dispatch are not taken into account. - */ + /** A hash of the shader code, entry point, workgroup size, and layout bindings. Layout and dispatch are not taken into account. + */ lazy val shaderHash: (Long, Long) = val md = MessageDigest.getInstance("SHA-256") md.update(code) code.rewind() md.update(entryPoint.getBytes) - md.update(workgroupSize.toList - .flatMap(BigInt(_).toByteArray).toArray) + md.update( + workgroupSize.toList + .flatMap(BigInt(_).toByteArray) + .toArray, + ) val layout = shaderBindings(summon[LayoutStruct[L]].layoutRef) layout.flatten.foreach: binding => md.update(binding.binding.tag.toString.getBytes) @@ -60,7 +61,7 @@ object SpirvProgram: def apply[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( layout: InitProgramLayout ?=> Params => L, dispatch: (L, Params) => ProgramDispatch, - code: ByteBuffer + code: ByteBuffer, ): SpirvProgram[Params, L] = val workgroupSize = (128, 1, 1) // TODO Extract form shader val main = "main" diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala index 0d559284..b124bed6 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/archive/GFunction.scala @@ -22,78 +22,65 @@ import io.computenode.cyfra.core.GCodec.{*, given} import io.computenode.cyfra.dsl.struct.GStruct.Empty case class GFunction[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( - underlying: GProgram[GFunctionParams, GFunctionLayout[G, H, R]] + underlying: GProgram[GFunctionParams, GFunctionLayout[G, H, R]], ): - def run[GS : ClassTag, HS, RS : ClassTag] (input: Array[HS], g: GS)( - using gCodec: GCodec[G, GS], + def run[GS: ClassTag, HS, RS: ClassTag](input: Array[HS], g: GS)(using + gCodec: GCodec[G, GS], hCodec: GCodec[H, HS], rCodec: GCodec[R, RS], - runtime: CyfraRuntime + runtime: CyfraRuntime, ): Array[RS] = - + val inTypeSize = typeStride(Tag.apply[H]) val outTypeSize = typeStride(Tag.apply[R]) val uniformStride = totalStride(summon[GStructSchema[G]]) val params = GFunctionParams(size = input.size) - + val in = BufferUtils.createByteBuffer(inTypeSize * input.size) hCodec.toByteBuffer(in, input) val out = BufferUtils.createByteBuffer(outTypeSize * input.size) val uniform = BufferUtils.createByteBuffer(uniformStride) gCodec.toByteBuffer(uniform, Array(g)) - - GBufferRegion.allocate[GFunctionLayout[G, H, R]] + + GBufferRegion + .allocate[GFunctionLayout[G, H, R]] .map: layout => underlying.execute(params, layout) .runUnsafe( - init = GFunctionLayout( - in = GBuffer[H](in), - out = GBuffer[R](input.size), - uniform = GUniform[G](uniform), - ), - onDone = layout => - layout.out.read(out) + init = GFunctionLayout(in = GBuffer[H](in), out = GBuffer[R](input.size), uniform = GUniform[G](uniform)), + onDone = layout => layout.out.read(out), ) val resultArray = Array.ofDim[RS](input.size) rCodec.fromByteBuffer(out, resultArray) object GFunction: - case class GFunctionParams( - size: Int - ) - - case class GFunctionLayout[G <: GStruct[G], H <: Value, R <: Value]( - in: GBuffer[H], - out: GBuffer[R], - uniform: GUniform[G] - ) extends Layout - - def forEachIndex[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: (G, Int32, GBuffer[H]) => R): GFunction[G, H, R] = - val body = (layout: GFunctionLayout[G, H, R]) => + case class GFunctionParams(size: Int) + + case class GFunctionLayout[G <: GStruct[G], H <: Value, R <: Value](in: GBuffer[H], out: GBuffer[R], uniform: GUniform[G]) extends Layout + + def forEachIndex[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( + fn: (G, Int32, GBuffer[H]) => R, + ): GFunction[G, H, R] = + val body = (layout: GFunctionLayout[G, H, R]) => val g = layout.uniform.read val result = fn(g, GIO.invocationId, layout.in) - for - _ <- layout.out.write(GIO.invocationId, result) + for _ <- layout.out.write(GIO.invocationId, result) yield Empty() val inTypeSize = typeStride(Tag.apply[H]) val outTypeSize = typeStride(Tag.apply[R]) - - GFunction( - underlying = GProgram.apply[GFunctionParams, GFunctionLayout[G, H, R]]( - layout = (p: GFunctionParams) => GFunctionLayout[G, H, R]( - in = GBuffer[H](p.size), - out = GBuffer[R](p.size), - uniform = GUniform[G](), - ), + + GFunction(underlying = + GProgram.apply[GFunctionParams, GFunctionLayout[G, H, R]]( + layout = (p: GFunctionParams) => GFunctionLayout[G, H, R](in = GBuffer[H](p.size), out = GBuffer[R](p.size), uniform = GUniform[G]()), dispatch = (l, p) => StaticDispatch((p.size + 255) / 256, 1, 1), workgroupSize = (256, 1, 1), - )(body) + )(body), ) - + def apply[H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](fn: H => R): GFunction[GStruct.Empty, H, R] = GFunction.forEachIndex[GStruct.Empty, H, R]((g: GStruct.Empty, index: Int32, a: GBuffer[H]) => fn(a.read(index))) - + def from2D[G <: GStruct[G]: {GStructSchema, Tag}, H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}]( width: Int, )(fn: (G, (Int32, Int32), GArray2D[H]) => R): GFunction[G, H, R] = @@ -105,9 +92,5 @@ object GFunction: ) extension [H <: Value: {Tag, FromExpr}, R <: Value: {Tag, FromExpr}](gf: GFunction[GStruct.Empty, H, R]) - def run[HS, RS : ClassTag](input: Array[HS])( - using hCodec: GCodec[H, HS], - rCodec: GCodec[R, RS], - runtime: CyfraRuntime - ): Array[RS] = - gf.run(input, GStruct.Empty()) \ No newline at end of file + def run[HS, RS: ClassTag](input: Array[HS])(using hCodec: GCodec[H, HS], rCodec: GCodec[R, RS], runtime: CyfraRuntime): Array[RS] = + gf.run(input, GStruct.Empty()) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala index 69327f73..1b460121 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutStruct.scala @@ -77,7 +77,11 @@ object LayoutStruct: case Some(s) => s case None => report.errorAndAbort(s"Cannot summon GStructSchema for type") '{ - UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using ${ tag.asExprOf[Tag[t]] }, ${ fromExpr.asExprOf[FromExpr[t]] }, ${ structSchema }) + UniformRef[t](${ Expr(i) }, ${ tag.asExprOf[Tag[t]] })(using + ${ tag.asExprOf[Tag[t]] }, + ${ fromExpr.asExprOf[FromExpr[t]] }, + ${ structSchema }, + ) } val constructor = sym.primaryConstructor @@ -86,16 +90,8 @@ object LayoutStruct: val typeArgs = tpe.typeArgs val layoutInstance = - if (typeArgs.isEmpty) then - Apply(Select(New(TypeIdent(sym)), constructor), buffers.map(_.asTerm)) - else - Apply( - TypeApply( - Select(New(TypeIdent(sym)), constructor), - typeArgs.map(arg => TypeTree.of(using arg.asType)) - ), - buffers.map(_.asTerm) - ) + if typeArgs.isEmpty then Apply(Select(New(TypeIdent(sym)), constructor), buffers.map(_.asTerm)) + else Apply(TypeApply(Select(New(TypeIdent(sym)), constructor), typeArgs.map(arg => TypeTree.of(using arg.asType))), buffers.map(_.asTerm)) val layoutRef = layoutInstance.asExprOf[T] diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala index 4ffc51c3..dfca871b 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/collections/GArray.scala @@ -10,4 +10,3 @@ import izumi.reflect.Tag case class GArray[T <: Value: {Tag, FromExpr}](underlying: GBuffer[T]): def at(i: Int32)(using Source): T = summon[FromExpr[T]].fromExpr(ReadBuffer(underlying, i)) - diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala index 0448fcf0..09373068 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/gio/GIO.scala @@ -29,7 +29,7 @@ object GIO: // TODO repeat that collects results case class Repeat(n: Int32, f: GIO[?]) extends GIO[Empty]: override def underlying: Empty = Empty() - + case class Printf(format: String, args: Value*) extends GIO[Empty]: override def underlying: Empty = Empty() @@ -45,7 +45,7 @@ object GIO: def write[T <: Value](buffer: GBuffer[T], index: Int32, value: T): GIO[Empty] = WriteBuffer(buffer, index, value) - + def printf(format: String, args: Value*): GIO[Empty] = Printf(s"|$format", args*) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala index 2a36c7c2..d298a839 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/RuntimeEnduranceTest.scala @@ -20,7 +20,6 @@ import scala.concurrent.ExecutionContext.Implicits.global import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.{Await, Future} - class RuntimeEnduranceTest extends munit.FunSuite: test("Endurance test for GExecution with multiple programs"): @@ -61,7 +60,7 @@ class RuntimeEnduranceTest extends munit.FunSuite: case class FilterProgramUniform(filterValue: Int32) extends GStruct[FilterProgramUniform] case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) - extends Layout + extends Layout val filterProgram = GProgram[FilterProgramParams, FilterProgramLayout]( layout = params => @@ -174,12 +173,14 @@ class RuntimeEnduranceTest extends munit.FunSuite: ) def runEnduranceTest(nRuns: Int): Unit = - logger.info(s"Starting endurance test with ${nRuns} runs...") + logger.info(s"Starting endurance test with $nRuns runs...") - given runtime: VkCyfraRuntime = VkCyfraRuntime( - spirvToolsRunner = SpirvToolsRunner( + given runtime: VkCyfraRuntime = VkCyfraRuntime(spirvToolsRunner = + SpirvToolsRunner( crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), - disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/dis.spvdis"))))) + disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/dis.spvdis"))), + ), + ) val bufferSize = 1280 val params = AddProgramParams(bufferSize, addA = 0, addB = 1) @@ -188,8 +189,8 @@ class RuntimeEnduranceTest extends munit.FunSuite: .map: region => execution.execute(params, region) val aInt = new AtomicInteger(0) - val runs = (1 to nRuns).map: - i => Future: + val runs = (1 to nRuns).map: i => + Future: val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) val rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala index 3a69f09f..cca59242 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/SpirvRuntimeEnduranceTest.scala @@ -20,7 +20,6 @@ import scala.concurrent.ExecutionContext.Implicits.global import java.util.concurrent.atomic.AtomicInteger import scala.concurrent.{Await, Future} - class SpirvRuntimeEnduranceTest extends munit.FunSuite: test("Endurance test for GExecution with multiple SPIRV programs loaded from files"): @@ -46,7 +45,7 @@ class SpirvRuntimeEnduranceTest extends munit.FunSuite: args = GUniform(EmitProgramUniform(params.emitN)), ), dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), - Paths.get(getClass.getResource("/emit.spv").toURI) + Paths.get(getClass.getResource("/emit.spv").toURI), ) // === Filter program === @@ -56,7 +55,7 @@ class SpirvRuntimeEnduranceTest extends munit.FunSuite: case class FilterProgramUniform(filterValue: Int32) extends GStruct[FilterProgramUniform] case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) - extends Layout + extends Layout val filterProgram = GProgram.fromSpirvFile[FilterProgramParams, FilterProgramLayout]( layout = params => @@ -66,7 +65,7 @@ class SpirvRuntimeEnduranceTest extends munit.FunSuite: params = GUniform(FilterProgramUniform(params.filterValue)), ), dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), - Paths.get(getClass.getResource("/filter.spv").toURI) + Paths.get(getClass.getResource("/filter.spv").toURI), ) // === GExecution === @@ -136,7 +135,7 @@ class SpirvRuntimeEnduranceTest extends munit.FunSuite: u2 = GUniform(AddProgramUniform(params.addB)), ), dispatch = (layout, args) => GProgram.StaticDispatch((args.bufferSize / 128, 1, 1)), - Paths.get(getClass.getResource("/addOne.spv").toURI) + Paths.get(getClass.getResource("/addOne.spv").toURI), ) def swap(l: AddProgramLayout): AddProgramLayout = @@ -155,12 +154,14 @@ class SpirvRuntimeEnduranceTest extends munit.FunSuite: ) def runEnduranceTest(nRuns: Int): Unit = - logger.info(s"Starting endurance test with ${nRuns} runs...") + logger.info(s"Starting endurance test with $nRuns runs...") - given runtime: VkCyfraRuntime = VkCyfraRuntime( - spirvToolsRunner = SpirvToolsRunner( + given runtime: VkCyfraRuntime = VkCyfraRuntime(spirvToolsRunner = + SpirvToolsRunner( crossCompilation = SpirvCross.Enable(toolOutput = ToFile(Paths.get("output/optimized.glsl"))), - disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/dis.spvdis"))))) + disassembler = SpirvDisassembler.Enable(toolOutput = ToFile(Paths.get("output/dis.spvdis"))), + ), + ) val bufferSize = 1280 val params = AddProgramParams(bufferSize, addA = 0, addB = 1) @@ -169,8 +170,8 @@ class SpirvRuntimeEnduranceTest extends munit.FunSuite: .map: region => execution.execute(params, region) val aInt = new AtomicInteger(0) - val runs = (1 to nRuns).map: - i => Future: + val runs = (1 to nRuns).map: i => + Future: val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) val rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala index 26ab573c..5a54d8ee 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/ArithmeticsE2eTest.scala @@ -10,7 +10,7 @@ import io.computenode.cyfra.core.GCodec.{*, given} class ArithmeticsE2eTest extends munit.FunSuite: given CyfraRuntime = VkCyfraRuntime() - + test("Float32 arithmetics"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: fl => (fl + 1.2f) * (fl - 3.4f) / 5.6f diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala index 057ecddc..4cbc71d8 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/FunctionsE2eTest.scala @@ -9,7 +9,7 @@ import io.computenode.cyfra.core.GCodec.{*, given} class FunctionsE2eTest extends munit.FunSuite: given CyfraRuntime = VkCyfraRuntime() - + test("Functions"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: f => val res1 = pow(sqrt(exp(sin(cos(tan(f))))), 2f) diff --git a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala index 9844c7f9..f63f077e 100644 --- a/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala +++ b/cyfra-e2e-test/src/test/scala/io/computenode/cyfra/e2e/dsl/GseqE2eTest.scala @@ -10,7 +10,7 @@ import io.computenode.cyfra.core.GCodec.{*, given} class GseqE2eTest extends munit.FunSuite: given CyfraRuntime = VkCyfraRuntime() - + test("GSeq gen limit map fold"): val gf: GFunction[GStruct.Empty, Float32, Float32] = GFunction: f => GSeq diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala index 8a77c384..3ad661dc 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/ImageRtRenderer.scala @@ -17,7 +17,7 @@ import java.nio.file.Path class ImageRtRenderer(params: ImageRtRenderer.Parameters) extends RtRenderer(params): given CyfraRuntime = VkCyfraRuntime() - + def renderToFile(scene: Scene, destinationPath: Path): Unit = val images = render(scene) for image <- images do ImageUtility.renderToImage(image, params.width, params.height, destinationPath) diff --git a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala index ee6e77ac..19ee393b 100644 --- a/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala +++ b/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/animation/AnimationRtRenderer.scala @@ -14,7 +14,7 @@ import io.computenode.cyfra.runtime.VkCyfraRuntime class AnimationRtRenderer(params: AnimationRtRenderer.Parameters) extends RtRenderer(params) with AnimationRenderer[AnimatedScene, AnimationRtRenderer.RenderFn](params): - + given CyfraRuntime = VkCyfraRuntime() protected def renderFrame(scene: AnimatedScene, time: Float32, fn: GFunction[RaytracingIteration, Vec4[Float32], Vec4[Float32]]): Array[fRGBA] = @@ -24,7 +24,6 @@ class AnimationRtRenderer(params: AnimationRtRenderer.Parameters) case (mem, render) => val result: Array[fRGBA] = fn.run(mem, RaytracingIteration(render, time)) (result, render + 1) - .map(_._1) .last diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index b8926666..59001acd 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -9,7 +9,15 @@ import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} import io.computenode.cyfra.dsl.struct.{GStruct, GStructSchema} -import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionBinding, ExecutionStep, PipelineBarrier, ShaderCall} +import io.computenode.cyfra.runtime.ExecutionHandler.{ + BindingLogicError, + Dispatch, + DispatchType, + ExecutionBinding, + ExecutionStep, + PipelineBarrier, + ShaderCall, +} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed @@ -150,7 +158,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte Binding(resolved, operation) val nextDispatch = dispatch match - case x: DispatchType.Direct => x + case x: DispatchType.Direct => x case DispatchType.Indirect(buffer, offset) => val base = bindingsAcc.getOrElse(buffer, mutable.Buffer.empty).toSeq val extras = callInits.getOrElse(buffer, Seq.empty) @@ -161,8 +169,10 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte ShaderCall(pipeline, nextLayout, nextDispatch, Map.empty) val mapper = summon[LayoutBinding[RL]] - val rlBindings = mapper.toBindings(rl).map: b => - finalBindingForRl.getOrElse(b, interpretBinding(b, bindingsAcc.getOrElse(b, mutable.Buffer.empty).toSeq)) + val rlBindings = mapper + .toBindings(rl) + .map: b => + finalBindingForRl.getOrElse(b, interpretBinding(b, bindingsAcc.getOrElse(b, mutable.Buffer.empty).toSeq)) val res = mapper.fromBindings(rlBindings) (res, nextSteps) @@ -250,17 +260,13 @@ object ExecutionHandler: pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType, - callInits: Map[GBinding[?], Seq[GBinding[?]]] // per-program contributions + callInits: Map[GBinding[?], Seq[GBinding[?]]], // per-program contributions ) sealed trait ExecutionStep - case class Dispatch( - pipeline: ComputePipeline, - layout: ShaderLayout, - descriptorSets: Seq[DescriptorSet], - dispatch: DispatchType - ) extends ExecutionStep + case class Dispatch(pipeline: ComputePipeline, layout: ShaderLayout, descriptorSets: Seq[DescriptorSet], dispatch: DispatchType) + extends ExecutionStep case object PipelineBarrier extends ExecutionStep sealed trait DispatchType @@ -275,12 +281,10 @@ object ExecutionHandler: def apply[T <: Value: {FromExpr as fe, Tag as t}](binding: GBinding[T]): ExecutionBinding[T] & GBinding[T] = binding match // todo types are a mess here - case u: GUniform[GStruct[?]] => new UniformBinding[GStruct[?]](using - fe.asInstanceOf[FromExpr[GStruct[?]]], - t.asInstanceOf[Tag[GStruct[?]]], - u.schema.asInstanceOf - ).asInstanceOf[UniformBinding[T]] - case _: GBuffer[T] => new BufferBinding() + case u: GUniform[GStruct[?]] => + new UniformBinding[GStruct[?]](using fe.asInstanceOf[FromExpr[GStruct[?]]], t.asInstanceOf[Tag[GStruct[?]]], u.schema.asInstanceOf) + .asInstanceOf[UniformBinding[T]] + case _: GBuffer[T] => new BufferBinding() case class BindingLogicError(bindings: Seq[GBinding[?]], message: String) extends RuntimeException(s"Error in binding logic for $bindings: $message") object BindingLogicError: diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index 4a3d3024..6f1dd91a 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -72,7 +72,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = VkBuffer[T](length).tap(bindings += _) - def apply[T <: Value : {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] = + def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] = val sizeOfT = typeStride(summon[Tag[T]]) val length = buff.capacity() / sizeOfT if buff.capacity() % sizeOfT != 0 then diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index 17b90fab..0d3ecd07 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -23,13 +23,15 @@ class VkCyfraRuntime(spirvToolsRunner: SpirvToolsRunner = SpirvToolsRunner()) ex val spirvProgram: SpirvProgram[Params, L] = program match case p: GioProgram[Params, L] if gProgramCache.contains(p) => gProgramCache(p).asInstanceOf - case p: GioProgram[Params, L] => compile(p) + case p: GioProgram[Params, L] => compile(p) case p: SpirvProgram[Params, L] => p - case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") shaderCache.getOrElseUpdate(spirvProgram.shaderHash, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] - private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = + private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}]( + program: GioProgram[Params, L], + ): SpirvProgram[Params, L] = val GioProgram(_, layout, dispatch, _) = program val bindings = lbinding.toBindings(lstruct.layoutRef).toList val compiled = DSLCompiler.compile(program.body(summon[LayoutStruct[L]].layoutRef), bindings) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index d2209fe1..492266e9 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -17,7 +17,7 @@ case class VkShader[L](underlying: ComputePipeline, shaderBindings: L => ShaderL object VkShader: def apply[P, L <: Layout: {LayoutBinding, LayoutStruct}](program: SpirvProgram[P, L])(using Device): VkShader[L] = - val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings) = program + val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings) = program val shaderLayout = shaderBindings(summon[LayoutStruct[L]].layoutRef) val sets = shaderLayout.map: set => @@ -31,4 +31,3 @@ object VkShader: val pipeline = ComputePipeline(code, entryPoint, LayoutInfo(sets)) VkShader(pipeline, shaderBindings) - diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala index 991fda38..4042b629 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvCross.scala @@ -19,7 +19,7 @@ object SpirvCross extends SpirvTool("spirv-cross"): None case Right(crossCompiledCode) => toolOutput match - case Ignore => + case Ignore => case toFile @ SpirvTool.ToFile(_, _) => toFile.write(crossCompiledCode) logger.debug(s"Saved cross compiled shader code in ${toFile.filePath}.") diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala index 67730961..4579db8a 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvDisassembler.scala @@ -18,7 +18,7 @@ object SpirvDisassembler extends SpirvTool("spirv-dis"): None case Right(disassembledShader) => toolOutput match - case Ignore => + case Ignore => case toFile @ SpirvTool.ToFile(_, _) => toFile.write(disassembledShader) logger.debug(s"Saved disassembled shader code in ${toFile.filePath}.") diff --git a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala index 509e763c..922d5346 100644 --- a/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala +++ b/cyfra-spirv-tools/src/main/scala/io/computenode/cyfra/spirvtools/SpirvOptimizer.scala @@ -19,7 +19,7 @@ object SpirvOptimizer extends SpirvTool("spirv-opt"): None case Right(optimizedShaderCode) => toolOutput match - case SpirvTool.Ignore => + case SpirvTool.Ignore => case toFile @ SpirvTool.ToFile(_, _) => toFile.write(optimizedShaderCode) logger.debug(s"Saved optimized shader code in ${toFile.filePath}.") diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala index 7664a3c6..630fa924 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala @@ -19,7 +19,7 @@ private[cyfra] class Fence(flags: Int = 0)(using device: Device) extends VulkanO val pFence = stack.callocLong(1) check(vkCreateFence(device.get, fenceInfo, null, pFence), "Failed to create fence") - pFence.get() + pFence.get(), ) override def close(): Unit = From 1f7a5267bd9d27e8719ef754f000516e91ce5f0a Mon Sep 17 00:00:00 2001 From: MarconZet <25779550+MarconZet@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:39:17 +0200 Subject: [PATCH 37/38] fixed filter^ --- .../io/computenode/cyfra/core/GProgram.scala | 2 +- .../computenode/cyfra/fs2interop/GPipe.scala | 15 +++-- .../cyfra/runtime/ExecutionHandler.scala | 64 ++++++------------- 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index fe111bfd..ffd87858 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -20,7 +20,7 @@ trait GProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] extends GExec val layout: InitProgramLayout => Params => L val dispatch: (L, Params) => ProgramDispatch val workgroupSize: WorkDimensions - def layoutStruct = summon[LayoutStruct[L]] + def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]] object GProgram: type WorkDimensions = (Int, Int, Int) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index 90c76906..a111a7a8 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -87,10 +87,11 @@ object GPipe: // Prefix sum (inclusive), upsweep/downsweep case class ScanParams(inSize: Int, intervalSize: Int) case class ScanArgs(intervalSize: Int32) extends GStruct[ScanArgs] - case class ScanLayout(ints: GBuffer[Int32], intervalSize: GUniform[ScanArgs] = GUniform.fromParams) extends Layout + case class ScanLayout(ints: GBuffer[Int32]) extends Layout + case class ScanProgramLayout(ints: GBuffer[Int32], intervalSize: GUniform[ScanArgs] = GUniform.fromParams) extends Layout - val upsweep = GProgram[ScanParams, ScanLayout]( - layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), + val upsweep = GProgram[ScanParams, ScanProgramLayout]( + layout = params => ScanProgramLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / params.intervalSize / 256).toInt, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read @@ -105,8 +106,8 @@ object GPipe: for _ <- GIO.write[Int32](layout.ints, end, newValue) yield Empty() - val downsweep = GProgram[ScanParams, ScanLayout]( - layout = params => ScanLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), + val downsweep = GProgram[ScanParams, ScanProgramLayout]( + layout = params => ScanProgramLayout(ints = GBuffer[Int32](params.inSize), intervalSize = GUniform(ScanArgs(params.intervalSize))), dispatch = (layout, params) => GProgram.StaticDispatch((Math.ceil(params.inSize.toFloat / params.intervalSize / 256).toInt, 1, 1)), ): layout => val ScanArgs(size) = layout.intervalSize.read @@ -129,7 +130,7 @@ object GPipe: ): GExecution[ScanParams, ScanLayout, ScanLayout] = if intervalSize > inSize then exec else - val newExec = exec.addProgram(upsweep)(params => ScanParams(inSize, intervalSize), layout => layout) + val newExec = exec.addProgram(upsweep)(params => ScanParams(inSize, intervalSize), layout => ScanProgramLayout(layout.ints)) upsweepPhases(newExec, inSize, intervalSize * 2) @annotation.tailrec @@ -140,7 +141,7 @@ object GPipe: ): GExecution[ScanParams, ScanLayout, ScanLayout] = if intervalSize < 2 then exec else - val newExec = exec.addProgram(downsweep)(params => ScanParams(inSize, intervalSize), layout => layout) + val newExec = exec.addProgram(downsweep)(params => ScanParams(inSize, intervalSize), layout => ScanProgramLayout(layout.ints)) downsweepPhases(newExec, inSize, intervalSize / 2) val initExec = GExecution[ScanParams, ScanLayout]() // no program diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 59001acd..7f2c6cff 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -46,7 +46,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val (result, shaderCalls) = interpret(execution, params, layout) val descriptorSets = shaderCalls.map: - case ShaderCall(pipeline, layout, _, _) => + case ShaderCall(pipeline, layout, _) => pipeline.pipelineLayout.sets .map(dsManager.allocate) .zip(layout) @@ -58,7 +58,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val dispatches: Seq[Dispatch] = shaderCalls .zip(descriptorSets) .map: - case (ShaderCall(pipeline, layout, dispatch, _), sets) => + case (ShaderCall(pipeline, layout, dispatch), sets) => Dispatch(pipeline, layout, sets, dispatch) val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): @@ -94,7 +94,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte case x: ExecutionBinding[?] => x case x: GBinding[?] => val e = ExecutionBinding(x)(using x.fromExpr, x.tag) - bindingsAcc.put(e, mutable.Buffer(x)) // store only base contribution here + bindingsAcc.put(e, mutable.Buffer(x)) e mapper.fromBindings(res) @@ -128,53 +128,33 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val layoutInit = val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout program.layout(initProgram)(params) - - val callInits: Map[GBinding[?], Seq[GBinding[?]]] = - lb - .toBindings(layout) - .zip(lb.toBindings(layoutInit)) - .groupMap(_._1)(_._2) - + lb.toBindings(layout) + .zip(lb.toBindings(layoutInit)) + .foreach: + case (binding, initBinding) => + bindingsAcc(binding).append(initBinding) val dispatch = program.dispatch(layout, params) match case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) // noinspection ScalaRedundantCast - (layout.asInstanceOf[RL], Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch, callInits))) + (layout.asInstanceOf[RL], Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) case _ => ??? val (rl, steps) = interpretImpl(execution, params, mockBindings(layout)) - - val finalBindingForRl: mutable.Map[GBinding[?], GBinding[?]] = mutable.Map.empty + val bingingToVk = bindingsAcc.map(x => (x._1, interpretBinding(x._1, x._2.toSeq))) val nextSteps = steps.map: - case ShaderCall(pipeline, layout, dispatch, callInits) => + case ShaderCall(pipeline, layout, dispatch) => val nextLayout = layout.map: _.map: - case Binding(binding, operation) => - val base = bindingsAcc.getOrElse(binding, mutable.Buffer.empty).toSeq - val extras = callInits.getOrElse(binding, Seq.empty) - val resolved = interpretBinding(binding, base ++ extras) - finalBindingForRl.update(binding, resolved) - Binding(resolved, operation) - + case Binding(binding, operation) => Binding(bingingToVk(binding), operation) val nextDispatch = dispatch match - case x: DispatchType.Direct => x - case DispatchType.Indirect(buffer, offset) => - val base = bindingsAcc.getOrElse(buffer, mutable.Buffer.empty).toSeq - val extras = callInits.getOrElse(buffer, Seq.empty) - val resolved = interpretBinding(buffer, base ++ extras) - finalBindingForRl.update(buffer, resolved) - DispatchType.Indirect(resolved, offset) - - ShaderCall(pipeline, nextLayout, nextDispatch, Map.empty) + case x: Direct => x + case Indirect(buffer, offset) => Indirect(bingingToVk(buffer), offset) + ShaderCall(pipeline, nextLayout, nextDispatch) val mapper = summon[LayoutBinding[RL]] - val rlBindings = mapper - .toBindings(rl) - .map: b => - finalBindingForRl.getOrElse(b, interpretBinding(b, bindingsAcc.getOrElse(b, mutable.Buffer.empty).toSeq)) - val res = mapper.fromBindings(rlBindings) - + val res = mapper.fromBindings(mapper.toBindings(rl).map(bingingToVk.apply)) (res, nextSteps) private def interpretBinding(binding: GBinding[?], bindings: Seq[GBinding[?]])(using VkAllocation): GBinding[?] = @@ -207,7 +187,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte case _: GUniform.ParamUniform[?] => false case x => throw BindingLogicError(x, "Unsupported binding type") if allocations.size > 1 then throw BindingLogicError(allocations, "Multiple allocations for uniform") - allocations.headOption.getOrElse(throw new IllegalStateException("Uniform never allocated")) + allocations.headOption.getOrElse(throw new BindingLogicError(Seq(), "Uniform never allocated")) case x => throw new IllegalArgumentException(s"Binding of type ${x.getClass.getName} should not be here") private def recordCommandBuffer(steps: Seq[ExecutionStep]): VkCommandBuffer = pushStack: stack => @@ -256,19 +236,13 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .distinct object ExecutionHandler: - case class ShaderCall( - pipeline: ComputePipeline, - layout: ShaderLayout, - dispatch: DispatchType, - callInits: Map[GBinding[?], Seq[GBinding[?]]], // per-program contributions - ) + case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) sealed trait ExecutionStep - case class Dispatch(pipeline: ComputePipeline, layout: ShaderLayout, descriptorSets: Seq[DescriptorSet], dispatch: DispatchType) extends ExecutionStep - case object PipelineBarrier extends ExecutionStep + sealed trait DispatchType object DispatchType: case class Direct(x: Int, y: Int, z: Int) extends DispatchType From 76ff1ebb71d72b6eff6ddb804dc6bcb1e37c5ee9 Mon Sep 17 00:00:00 2001 From: Szymon Date: Sun, 19 Oct 2025 18:16:52 +0200 Subject: [PATCH 38/38] Fix cache --- .../src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala | 1 - .../scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala index a111a7a8..0aef22d3 100644 --- a/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala +++ b/cyfra-fs2/src/main/scala/io/computenode/cyfra/fs2interop/GPipe.scala @@ -219,5 +219,4 @@ object GPipe: ) val filteredN = filteredCount.getInt(0) val arr = bridge.fromByteBuffer(compactBuf, new Array[S](filteredN)) - println(arr) Stream.emits(arr) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index 0d3ecd07..2e96e221 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -22,11 +22,12 @@ class VkCyfraRuntime(spirvToolsRunner: SpirvToolsRunner = SpirvToolsRunner()) ex val spirvProgram: SpirvProgram[Params, L] = program match case p: GioProgram[Params, L] if gProgramCache.contains(p) => - gProgramCache(p).asInstanceOf + gProgramCache(p).asInstanceOf[SpirvProgram[Params, L]] case p: GioProgram[Params, L] => compile(p) case p: SpirvProgram[Params, L] => p case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + gProgramCache.update(program, spirvProgram) shaderCache.getOrElseUpdate(spirvProgram.shaderHash, VkShader(spirvProgram)).asInstanceOf[VkShader[L]] private def compile[Params, L <: Layout: {LayoutBinding as lbinding, LayoutStruct as lstruct}](