diff --git a/indigo/indigo/src/main/scala/indigo/IndigoShader.scala b/indigo/indigo/src/main/scala/indigo/IndigoShader.scala index 627955518..767146e29 100644 --- a/indigo/indigo/src/main/scala/indigo/IndigoShader.scala +++ b/indigo/indigo/src/main/scala/indigo/IndigoShader.scala @@ -2,6 +2,7 @@ package indigo import indigo.entry.StandardFrameProcessor import indigo.gameengine.GameEngine +import indigo.shared.shader.UniformBlock import indigo.shared.shader.library import indigo.shared.shader.library.IndigoUV.BlendFragmentEnvReference import indigo.shared.subsystems.SubSystemsRegister @@ -11,18 +12,12 @@ import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._ import scala.concurrent.Future /** A trait representing a shader that fills the available window. - * - * You can override a number of the details in this trait using launch flags, including: - * - * - width - starting width of the shader - * - height - starting height of the shader - * - channel0 - path to an image - * - channel1 - path to an image - * - channel2 - path to an image - * - channel3 - path to an image */ trait IndigoShader extends GameLauncher[IndigoShaderModel, IndigoShaderModel, Unit] { + given [A](using toUBO: ToUniformBlock[A]): Conversion[A, UniformBlock] with + def apply(value: A): UniformBlock = toUBO.toUniformBlock(value) + private val Channel0Name: String = "channel0" private val Channel1Name: String = "channel1" private val Channel2Name: String = "channel2" @@ -52,6 +47,33 @@ trait IndigoShader extends GameLauncher[IndigoShaderModel, IndigoShaderModel, Un */ def channel3: Option[AssetPath] + /** The uniform blocks (data) you want to pass to your shader. Example: + * + * ```scala + * import indigo.* + * import indigo.syntax.shaders.* + * import ultraviolet.syntax.* + * + * final case class CustomData(color: vec4, customTime: Float) extends FragmentEnvReference derives ToUniformBlock + * def uniformBlocks: Batch[UniformBlock] = Batch(CustomData(RGBA.Magenta.toUVVec4, 0.seconds.toFloat)) + * ``` + * + * As long as the field types in your case class are ultraviolet types, you can pass them to your shader, see + * Ultraviolet docs for more info. + * + * Many standard Indigo types are supported for the data fields, but you will need a separate case class for the + * Shader side of the data contract definition, i.e. This is valid too: + * + * ```scala + * // For use with Indigo's shader setup. Note: derives ToUniformBlock, but doesn't need to extend FragmentEnvReference + * final case class CustomDataIndigo(color: RGBA, customTime: Seconds) derives ToUniformBlock + * + * // For use with Ultraviolet's UBO definitions. Note extends FragmentEnvReference, but doesn't derive ToUniformBlock + * final case class CustomDataUV(color: vec4, customTime: Float) extends FragmentEnvReference + * ``` + */ + def uniformBlocks: Batch[UniformBlock] + /** The shader you want to render */ def shader: ShaderProgram @@ -139,7 +161,7 @@ trait IndigoShader extends GameLauncher[IndigoShaderModel, IndigoShaderModel, Un model.viewport, ShaderData( shader.id, - Batch.empty, + uniformBlocks, model.channel0, model.channel1, model.channel2, diff --git a/indigo/indigo/src/main/scala/indigo/package.scala b/indigo/indigo/src/main/scala/indigo/package.scala index a9eae7887..981afe3de 100644 --- a/indigo/indigo/src/main/scala/indigo/package.scala +++ b/indigo/indigo/src/main/scala/indigo/package.scala @@ -119,6 +119,59 @@ object syntax: export SignalFunction.multiply end animations + object shaders: + + extension (c: RGBA) + def toUVVec4: ultraviolet.syntax.vec4 = + ultraviolet.syntax.vec4(c.r.toFloat, c.g.toFloat, c.b.toFloat, c.a.toFloat) + extension (c: RGB) + def toUVVec3: ultraviolet.syntax.vec3 = + ultraviolet.syntax.vec3(c.r.toFloat, c.g.toFloat, c.b.toFloat) + extension (p: Point) + def toUVVec2: ultraviolet.syntax.vec2 = + ultraviolet.syntax.vec2(p.x.toFloat, p.y.toFloat) + extension (s: Size) + def toUVVec2: ultraviolet.syntax.vec2 = + ultraviolet.syntax.vec2(s.width.toFloat, s.height.toFloat) + extension (v: Vector2) + def toUVVec2: ultraviolet.syntax.vec2 = + ultraviolet.syntax.vec2(v.x.toFloat, v.y.toFloat) + extension (v: Vector3) + def toUVVec3: ultraviolet.syntax.vec3 = + ultraviolet.syntax.vec3(v.x.toFloat, v.y.toFloat, v.z.toFloat) + extension (v: Vector4) + def toUVVec4: ultraviolet.syntax.vec4 = + ultraviolet.syntax.vec4(v.x.toFloat, v.y.toFloat, v.z.toFloat, v.w.toFloat) + extension (r: Rectangle) + def toUVVec4: ultraviolet.syntax.vec4 = + ultraviolet.syntax.vec4(r.x.toFloat, r.y.toFloat, r.width.toFloat, r.height.toFloat) + extension (m: Matrix4) + def toUVMat4: ultraviolet.syntax.mat4 = + ultraviolet.syntax.mat4(m.toArray.map(_.toFloat)) + extension (d: Depth) def toUVFloat: Float = d.toFloat + extension (m: Millis) def toUVFloat: Float = m.toFloat + extension (r: Radians) def toUVFloat: Float = r.toFloat + extension (s: Seconds) + @targetName("ext_Seconds_toUVFloat") + def toUVFloat: Float = s.toFloat + extension (d: Double) + @targetName("ext_Double_toUVFloat") + def toUVFloat: Float = d.toFloat + extension (i: Int) + @targetName("ext_Int_toUVFloat") + def toUVFloat: Float = i.toFloat + extension (l: Long) + @targetName("ext_Long_toUVFloat") + def toUVFloat: Float = l.toFloat + extension (a: Array[Float]) + def toUVArray: ultraviolet.syntax.array[Singleton & Int, Float] = + ultraviolet.syntax.array(a) + extension (a: scalajs.js.Array[Float]) + def toUVArray: ultraviolet.syntax.array[Singleton & Int, Float] = + ultraviolet.syntax.array(a.toArray) + + end shaders + end syntax object mutable: @@ -218,6 +271,9 @@ val ShaderId: shared.shader.ShaderId.type = shared.shader.ShaderId type ToUniformBlock[A] = shared.shader.ToUniformBlock[A] val ToUniformBlock: shared.shader.ToUniformBlock.type = shared.shader.ToUniformBlock +type UniformBlock = shared.shader.UniformBlock +val UniformBlock: shared.shader.UniformBlock.type = shared.shader.UniformBlock + val StandardShaders: shared.shader.StandardShaders.type = shared.shader.StandardShaders type Outcome[T] = shared.Outcome[T] diff --git a/indigo/indigo/src/main/scala/indigo/shared/datatypes/Depth.scala b/indigo/indigo/src/main/scala/indigo/shared/datatypes/Depth.scala index ffe645d4d..0b5a7db75 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/datatypes/Depth.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/datatypes/Depth.scala @@ -19,5 +19,6 @@ object Depth: @targetName("-_Int") def -(other: Int): Depth = Depth(d - other) - def toDouble: Double = d + def toDouble: Double = d.toDouble + def toFloat: Float = d.toFloat def toInt: Int = d.toInt diff --git a/indigo/indigo/src/main/scala/indigo/shared/shader/ToUniformBlock.scala b/indigo/indigo/src/main/scala/indigo/shared/shader/ToUniformBlock.scala index e11ccafff..443cc98a1 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/shader/ToUniformBlock.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/shader/ToUniformBlock.scala @@ -38,7 +38,7 @@ object ToUniformBlock: case given ShaderTypeOf[T] => summonInline[ShaderTypeOf[T]] case _ => error( - "Unsupported type. Only supported types in Indigo shaders are: Int, Long, Float, Double, RGBA, RGB, Point, Size, Vertex, Vector2, Vector3, Vector4, Rectangle, Matrix4, Depth, Radians, Millis, Seconds, Array[Float], js.Array[Float]" + "Unsupported shader uniform type. Supported types From Scala (Int, Long, Float, Double), Indigo [RGBA, RGB, Point, Size, Vertex, Vector2, Vector3, Vector4, Rectangle, Matrix4, Depth, Radians, Millis, Seconds, Array[Float], js.Array[Float]], and UltraViolet [vec2, vec3, vec4, mat4]. However, if you intend to use the same case class for both Indigo and UltraViolet, you should stick to Float + the UltraViolet types." ) } @@ -62,6 +62,22 @@ object ToUniformBlock: object ShaderTypeOf: + given ShaderTypeOf[ultraviolet.syntax.vec2] with + def toShaderPrimitive(value: ultraviolet.syntax.vec2): ShaderPrimitive = + ShaderPrimitive.vec2(value.x, value.y) + + given ShaderTypeOf[ultraviolet.syntax.vec3] with + def toShaderPrimitive(value: ultraviolet.syntax.vec3): ShaderPrimitive = + ShaderPrimitive.vec3(value.x, value.y, value.z) + + given ShaderTypeOf[ultraviolet.syntax.vec4] with + def toShaderPrimitive(value: ultraviolet.syntax.vec4): ShaderPrimitive = + ShaderPrimitive.vec4(value.x, value.y, value.z, value.w) + + given ShaderTypeOf[ultraviolet.syntax.mat4] with + def toShaderPrimitive(value: ultraviolet.syntax.mat4): ShaderPrimitive = + ShaderPrimitive.mat4(value.mat) + given ShaderTypeOf[Int] with def toShaderPrimitive(value: Int): ShaderPrimitive = ShaderPrimitive.float(value) diff --git a/indigo/shader/src/main/scala/com/example/shader/ShaderGame.scala b/indigo/shader/src/main/scala/com/example/shader/ShaderGame.scala index 7a7f3c85c..e426de43e 100644 --- a/indigo/shader/src/main/scala/com/example/shader/ShaderGame.scala +++ b/indigo/shader/src/main/scala/com/example/shader/ShaderGame.scala @@ -1,4 +1,6 @@ import indigo.* +import indigo.syntax.shaders.* +import ultraviolet.syntax.* import scala.scalajs.js.annotation._ @@ -12,9 +14,34 @@ object ShaderGame extends IndigoShader: val channel2: Option[AssetPath] = None val channel3: Option[AssetPath] = None + val uniformBlocks: Batch[UniformBlock] = + Batch(CustomData(RGBA.Magenta.toUVVec4)) + val shader: ShaderProgram = - // ShowImage.shader - SeascapeShader.shader + ShaderWithData.shader +// ShowImage.shader +// SeascapeShader.shader + +final case class CustomData(CUSTOM_COLOR: vec4) extends FragmentEnvReference derives ToUniformBlock +object CustomData: + val reference = + CustomData(vec4(0.0f)) + +object ShaderWithData: + + val shader: UltravioletShader = + UltravioletShader.entityFragment( + ShaderId("shader with data"), + EntityShader.fragment[CustomData](shaderWithData, CustomData.reference) + ) + + inline def shaderWithData: Shader[CustomData, Unit] = + Shader[CustomData] { env => + ubo[CustomData] + + def fragment(color: vec4): vec4 = + env.CUSTOM_COLOR + } object ShowImage: @@ -24,8 +51,6 @@ object ShowImage: EntityShader.fragment[FragmentEnv](showImage, FragmentEnv.reference) ) - import ultraviolet.syntax.* - inline def showImage: Shader[FragmentEnv, Unit] = Shader[FragmentEnv] { env => def fragment(color: vec4): vec4 = @@ -55,8 +80,6 @@ object VoronoiShader: EntityShader.fragment[FragmentEnv](voronoi, FragmentEnv.reference) ) - import ultraviolet.syntax.* - // Ported from: https://www.youtube.com/watch?v=l-07BXzNdPw&feature=youtu.be @SuppressWarnings(Array("scalafix:DisableSyntax.var")) inline def voronoi: Shader[FragmentEnv, Unit] =