Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
Library provides a way to compile Scala 3 DSL to SPIR-V and to run it with Vulkan runtime on GPUs.

It is multiplatform. It works on:
- Linux, Windows, and Mac (for Mac requires installation of moltenvk).
- Any dedicated or integrated GPUs that support Vulkan. In practice, it means almost all moderately modern devices from most manufacturers including Nvidia, AMD, Intel, Apple.

- Linux, Windows, and Mac (for Mac requires installation of moltenvk).
- Any dedicated or integrated GPUs that support Vulkan. In practice, it means almost all moderately modern devices from
most manufacturers including Nvidia, AMD, Intel, Apple.

Library is in an early stage - alpha release and proper documentation are coming.

Expand All @@ -15,22 +17,27 @@ Included Foton library provides a clean and fun way to animate functions and ray
## Examples

### Ray traced animation

![output](https://github.com/user-attachments/assets/3eac9f7f-72df-4a5d-b768-9117d651c78d)

[code](https://github.com/ComputeNode/cyfra/blob/50aecea/cyfra-examples/src/main/scala/io/computenode/samples/cyfra/foton/AnimatedRaytrace.scala)
(this is API usage, to see ray tracing implementation look at [RtRenderer](https://github.com/ComputeNode/cyfra/blob/50aecea132188776021afe0b407817665676a021/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala))
(this is API usage, to see ray tracing implementation look
at [RtRenderer](https://github.com/ComputeNode/cyfra/blob/50aecea132188776021afe0b407817665676a021/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala))

### Animated Julia set

<img src="assets/julia.gif" width="360">

[code](https://github.com/ComputeNode/cyfra/blob/50aecea132188776021afe0b407817665676a021/cyfra-examples/src/main/scala/io/computenode/samples/cyfra/foton/AnimatedJulia.scala)

## Animation features examples

### Custom animated functions

<img src="https://github.com/user-attachments/assets/1030d968-014a-4c2c-8f21-26b999fe57fc" width="650">

### Animated ray traced scene

<img src="https://github.com/user-attachments/assets/a4189bc3-e2a9-4e52-9363-93f83b530595" width="750">

## Coding features examples
Expand All @@ -48,9 +55,15 @@ Included Foton library provides a clean and fun way to animate functions and ray

## Development

To enable validation layers for vulkan, you need to install vulkan SKD. After installing, set the following VM options:
To enable validation layers for vulkan, you need to install vulkan SKD. After installing, set the following VM option:

```
-Dorg.lwjgl.vulkan.libname=libvulkan.1.dylib
-Dio.computenode.cyfra.vulkan.validation=true
```

If you are on MacOs, then also add:

```
-Dorg.lwjgl.vulkan.libname=libvulkan.1.dylib
-Djava.library.path=$VULKAN_SDK/lib
```
7 changes: 4 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
ThisBuild / organization := "com.computenode.cyfra"
ThisBuild / scalaVersion := "3.6.4"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / version := "0.2.0-SNAPSHOT"

val lwjglVersion = "3.3.6"
val lwjglVersion = "3.4.0-SNAPSHOT"
val jomlVersion = "1.10.0"

lazy val osName = System.getProperty("os.name").toLowerCase
Expand Down Expand Up @@ -37,8 +37,9 @@ lazy val vulkanNatives =

lazy val commonSettings = Seq(
scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked", "-language:implicitConversions"),
resolvers += "maven snapshots" at "https://central.sonatype.com/repository/maven-snapshots/",
libraryDependencies ++= Seq(
"dev.zio" % "izumi-reflect_3" % "2.3.10",
"dev.zio" % "izumi-reflect_3" % "3.0.5",
"com.lihaoyi" % "pprint_3" % "0.9.0",
"com.diogonunes" % "JColor" % "5.5.1",
"org.lwjgl" % "lwjgl" % lwjglVersion,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.computenode.cyfra.core

import io.computenode.cyfra.core.layout.{Layout, LayoutStruct}
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}
Expand All @@ -11,14 +11,15 @@ import java.nio.ByteBuffer

trait Allocation:
extension (buffer: GBinding[?])
def read(bb: ByteBuffer, offset: Int = 0, length: Int = -1): Unit
def read(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit

def write(bb: ByteBuffer, offset: Int = 0, length: Int = -1): Unit
def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit

extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) def execute(params: Params, layout: L): RL
extension [Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](execution: GExecution[Params, EL, RL])
def execute(params: Params, layout: EL): RL

extension (buffers: GBuffer.type)
def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T]
def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T]

def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import io.computenode.cyfra.core.Allocation

trait CyfraRuntime:

def allocation(): Allocation
def withAllocation(f: Allocation => Unit): Unit
Original file line number Diff line number Diff line change
@@ -1,47 +1,43 @@
package io.computenode.cyfra.core

import io.computenode.cyfra.core.Allocation
import io.computenode.cyfra.core.GProgram.BufferSizeSpec
import io.computenode.cyfra.core.layout.{Layout, LayoutStruct}
import io.computenode.cyfra.core.GProgram.BufferLengthSpec
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.GBuffer
import izumi.reflect.Tag

import java.nio.ByteBuffer

sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct]:
val initAlloc: ReqAlloc
sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]

object GBufferRegion:

def allocate[Alloc <: Layout: LayoutStruct]: GBufferRegion[Alloc, Alloc] =
AllocRegion(summon[LayoutStruct[Alloc]].layoutRef)
def allocate[Alloc <: Layout: LayoutBinding]: GBufferRegion[Alloc, Alloc] = AllocRegion()

case class AllocRegion[Alloc <: Layout: LayoutStruct](l: Alloc) extends GBufferRegion[Alloc, Alloc]:
val initAlloc: Alloc = l
case class AllocRegion[Alloc <: Layout: LayoutBinding]() extends GBufferRegion[Alloc, Alloc]

case class MapRegion[ReqAlloc <: Layout: LayoutStruct, BodyAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct](
case class MapRegion[ReqAlloc <: Layout: LayoutBinding, BodyAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding](
reqRegion: GBufferRegion[ReqAlloc, BodyAlloc],
f: Allocation => BodyAlloc => ResAlloc,
) extends GBufferRegion[ReqAlloc, ResAlloc]:
val initAlloc: ReqAlloc = reqRegion.initAlloc
) extends GBufferRegion[ReqAlloc, ResAlloc]

extension [ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct](region: GBufferRegion[ReqAlloc, ResAlloc])
def map[NewAlloc <: Layout: LayoutStruct](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] =
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 =
val allocation = cyfraRuntime.allocation()
init(using allocation)
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))
// 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 bodyAlloc = steps.foldLeft[Layout](region.initAlloc): (acc, step) =>
step(allocation)(acc)
val initAlloc = init(using allocation)
val bodyAlloc = steps.foldLeft[Layout](initAlloc): (acc, step) =>
step(allocation)(acc)

onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc])
onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc])
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import io.computenode.cyfra.spirv.compilers.ExpressionCompiler.UniformStructRef
import izumi.reflect.Tag
import GExecution.*

trait GExecution[-Params, ExecLayout <: Layout, +ResLayout <: Layout]:
trait GExecution[-Params, ExecLayout <: Layout: LayoutBinding, ResLayout <: Layout: LayoutBinding]:

def flatMap[NRL <: Layout, NP <: Params](f: ResLayout => GExecution[NP, ExecLayout, NRL]): GExecution[NP, ExecLayout, NRL] =
def layoutBinding: LayoutBinding[ExecLayout] = summon[LayoutBinding[ExecLayout]]
def resLayoutBinding: LayoutBinding[ResLayout] = summon[LayoutBinding[ResLayout]]

def flatMap[NRL <: Layout: LayoutBinding, NP <: Params](f: ResLayout => GExecution[NP, ExecLayout, NRL]): GExecution[NP, ExecLayout, NRL] =
FlatMap(this, (p, r) => f(r))

def map[NRL <: Layout](f: ResLayout => NRL): GExecution[Params, ExecLayout, NRL] =
def map[NRL <: Layout: LayoutBinding](f: ResLayout => NRL): GExecution[Params, ExecLayout, NRL] =
Map(this, f, identity, identity)

def contramap[NL <: Layout](f: NL => ExecLayout): GExecution[Params, NL, ResLayout] =
def contramap[NEL <: Layout: LayoutBinding](f: NEL => ExecLayout): GExecution[Params, NEL, ResLayout] =
Map(this, identity, f, identity)

def contramapParams[NP](f: NP => Params): GExecution[NP, ExecLayout, ResLayout] =
Expand All @@ -32,31 +35,33 @@ trait GExecution[-Params, ExecLayout <: Layout, +ResLayout <: Layout]:

object GExecution:

def apply[Params, L <: Layout]() =
Pure[Params, L, L]()
def apply[Params, L <: Layout: LayoutBinding]() =
Pure[Params, L]()

def forParams[Params, L <: Layout, RL <: Layout](f: Params => GExecution[Params, L, RL]): GExecution[Params, L, RL] =
FlatMap[Params, L, RL, RL](Pure[Params, L, RL](), (params: Params, _: RL) => f(params))
def forParams[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](
f: Params => GExecution[Params, EL, RL],
): GExecution[Params, EL, RL] =
FlatMap[Params, EL, EL, RL](Pure[Params, EL](), (params: Params, _: EL) => f(params))

case class Pure[Params, L <: Layout, RL <: Layout]() extends GExecution[Params, L, RL]
case class Pure[Params, L <: Layout: LayoutBinding]() extends GExecution[Params, L, L]

case class FlatMap[Params, L <: Layout, RL <: Layout, NRL <: Layout](
execution: GExecution[Params, L, RL],
f: (Params, RL) => GExecution[Params, L, NRL],
) extends GExecution[Params, L, NRL]
case class FlatMap[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding, NRL <: Layout: LayoutBinding](
execution: GExecution[Params, EL, RL],
f: (Params, RL) => GExecution[Params, EL, NRL],
) extends GExecution[Params, EL, NRL]

case class Map[P, NP, L <: Layout, NL <: Layout, RL <: Layout, NRL <: Layout](
execution: GExecution[P, L, RL],
case class Map[P, NP, EL <: Layout: LayoutBinding, NEL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding, NRL <: Layout: LayoutBinding](
execution: GExecution[P, EL, RL],
mapResult: RL => NRL,
contramapLayout: NL => L,
contramapLayout: NEL => EL,
contramapParams: NP => P,
) extends GExecution[NP, NL, NRL]:
) extends GExecution[NP, NEL, NRL]:

override def map[NNRL <: Layout](f: NRL => NNRL): GExecution[NP, NL, NNRL] =
override def map[NNRL <: Layout: LayoutBinding](f: NRL => NNRL): GExecution[NP, NEL, NNRL] =
Map(execution, mapResult andThen f, contramapLayout, contramapParams)

override def contramapParams[NNP](f: NNP => NP): GExecution[NNP, NL, NRL] =
override def contramapParams[NNP](f: NNP => NP): GExecution[NNP, NEL, NRL] =
Map(execution, mapResult, contramapLayout, f andThen contramapParams)

override def contramap[NNL <: Layout](f: NNL => NL): GExecution[NP, NNL, NRL] =
override def contramap[NNL <: Layout: LayoutBinding](f: NNL => NEL): GExecution[NP, NNL, NRL] =
Map(execution, mapResult, f andThen contramapLayout, contramapParams)
60 changes: 19 additions & 41 deletions cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala
Original file line number Diff line number Diff line change
@@ -1,70 +1,48 @@
package io.computenode.cyfra.core

import io.computenode.cyfra.core.layout.LayoutStruct
import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct}
import io.computenode.cyfra.dsl.gio.GIO
import io.computenode.cyfra.core.layout.Layout

import java.nio.ByteBuffer
import GProgram.*
import io.computenode.cyfra.dsl.{Expression, Value}
import io.computenode.cyfra.dsl.Value.{FromExpr, Int32}
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.Empty
import izumi.reflect.Tag

sealed trait GProgram[Params, L <: Layout: LayoutStruct] extends GExecution[Params, L, L]:
trait GProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] extends GExecution[Params, L, L]:
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:

class GioProgram[Params, L <: Layout: LayoutStruct](
val body: L => GIO[?],
val layout: InitProgramLayout => Params => L,
val dispatch: (L, Params) => ProgramDispatch,
val workgroupSize: WorkDimensions,
) extends GProgram[Params, L]:
private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]]

class SpirvProgram[Params, L <: Layout: LayoutStruct](
val code: ByteBuffer,
val layout: InitProgramLayout => Params => L,
val dispatch: (L, Params) => ProgramDispatch,
val workgroupSize: WorkDimensions,
) extends GProgram[Params, L]:
private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]]

type WorkDimensions = (Int, Int, Int)

sealed trait ProgramDispatch

case class DynamicDispatch[L <: Layout](buffer: GBinding[?], offset: Int) extends ProgramDispatch

case class StaticDispatch(size: WorkDimensions) extends ProgramDispatch

private[cyfra] case class BufferSizeSpec[T <: Value: {Tag, FromExpr}](size: Int) extends GBuffer[T]

private[cyfra] case class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}](value: T) extends GUniform[T]
def apply[Params, L <: Layout: {LayoutBinding, LayoutStruct}](
layout: InitProgramLayout ?=> Params => L,
dispatch: (L, Params) => ProgramDispatch,
workgroupSize: WorkDimensions = (128, 1, 1),
)(body: L => GIO[?]): GProgram[Params, L] =
new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize)

private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T]
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]

trait InitProgramLayout:
extension (buffers: GBuffer.type)
def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] =
BufferSizeSpec[T](size)

extension (uniforms: GUniform.type)
def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] =
ParamUniform[T](value)
extension (_buffers: GBuffer.type)
def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] =
BufferLengthSpec[T](length)

extension (_uniforms: GUniform.type)
def apply[T <: GStruct[T]: {Tag, FromExpr}](): GUniform[T] =
DynamicUniform[T]()

def apply[Params, L <: Layout: LayoutStruct](
layout: InitProgramLayout ?=> Params => L,
dispatch: (L, Params) => ProgramDispatch,
workgroupSize: WorkDimensions = (128, 1, 1),
)(body: L => GIO[?]): GProgram[Params, L] =
new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize)
def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.computenode.cyfra.core

import io.computenode.cyfra.core.GProgram.*
import io.computenode.cyfra.core.layout.*
import io.computenode.cyfra.dsl.Value.GBoolean
import io.computenode.cyfra.dsl.gio.GIO
import izumi.reflect.Tag

case class GioProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}](
body: L => GIO[?],
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 _ => ???
Loading