Skip to content

Commit

Permalink
Fixed #57: Shader.fromFile (load ext GLSL)
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed Dec 18, 2022
1 parent 840c8c5 commit 629a88b
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 0 deletions.
11 changes: 11 additions & 0 deletions test-resources/shader.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;

// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));

// Output to screen
fragColor = vec4(col,1.0);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import scala.deriving.Mirror
/** A `Shader` is a program that can be run on a graphics card as part of the rendering pipeline.
*/
final case class Shader[In, Out](headers: List[GLSLHeader], body: In => Out):
/** `run` is useful for testing shaders as it allows you to treat the shader like an ordinary function call.
*/
def run(in: In): Out = body(in)

object Shader:
inline def apply[In, Out](f: In => Out): Shader[In, Out] =
new Shader[In, Out](Nil, f)
Expand All @@ -24,6 +27,13 @@ object Shader:
inline def apply(headers: GLSLHeader*)(body: => Any): Shader[Unit, Unit] =
new Shader[Unit, Unit](headers.toList, (_: Unit) => body)

/** `fromFile` allows you to load raw GLSL code from a file at compile time to produce a shader.
*/
inline def fromFile(inline projectRelativePath: String): Shader[Unit, Unit] =
Shader {
ShaderMacros.fromFile(projectRelativePath)
}

extension [In, Out](inline ctx: Shader[In, Out])
inline def toGLSL[T](using ShaderPrinter[T]): ShaderOutput =
ShaderMacros.toAST(ctx).render
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ object ShaderError:
final case class Unsupported(msg: String) extends Exception(makeMsg(msg)) with NoStackTrace
final case class Validation(msg: String) extends Exception(makeMsg(msg)) with NoStackTrace
final case class Metadata(msg: String) extends Exception(makeMsg(msg)) with NoStackTrace
final case class OnFileLoad(msg: String) extends Exception(makeMsg(msg)) with NoStackTrace
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,41 @@ class CreateShaderAST[Q <: Quotes](using val qq: Q) extends ShaderMacroUtils:

// Specific hooks we care about

// Entry point 'from file'
case Inlined(
Some(
Apply(Select(Ident("Shader"), "fromFile"), List(Literal(StringConstant(_))))
),
Nil,
Typed(
Inlined(
Some(
Apply(
Select(Ident("Shader"), "apply"),
List(
Inlined(
Some(_),
Nil,
Typed(
Inlined(
Some(TypeIdent(_)),
Nil,
term
),
_
)
)
)
)
),
_,
_
),
_
)
) =>
walkTree(term, None)

// Entry point (with type params, no headers)
case Apply(
TypeApply(Select(Ident("Shader"), "apply"), types),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package ultraviolet.macros

import ultraviolet.datatypes.ProceduralShader
import ultraviolet.datatypes.ShaderAST
import ultraviolet.datatypes.ShaderError
import ultraviolet.datatypes.UBODef
import ultraviolet.datatypes.UBOField
import ultraviolet.syntax.*

import java.io.File
import scala.annotation.tailrec
import scala.io.Source
import scala.quoted.*

object ShaderMacros:
Expand Down Expand Up @@ -36,3 +39,23 @@ object ShaderMacros:

Expr(ProceduralShader(shaderDefList.filterNot(_.userDefined).map(_.fn), main))
}

inline def fromFile(inline expr: String): RawGLSL = ${ fromFileImpl('{ expr }) }

@SuppressWarnings(Array("scalafix:DisableSyntax.throw"))
private[macros] def fromFileImpl[In, Out: Type](expr: Expr[String])(using q: Quotes): Expr[RawGLSL] =
expr.value match
case None =>
throw ShaderError.OnFileLoad("Unexpected error loading a shader from a file.")

case Some(path) =>
val f = File(path)
if f.exists() then
val glsl = Source.fromFile(f).getLines().toList.mkString("\n")
Expr(RawGLSL(glsl))
else throw ShaderError.OnFileLoad("Could not find shader file on given path: " + f.getAbsolutePath())

given ToExpr[RawGLSL] with {
def apply(x: RawGLSL)(using Quotes): Expr[RawGLSL] =
'{ RawGLSL(${ Expr(x.glsl) }) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ultraviolet.macros

import ultraviolet.DebugAST
import ultraviolet.syntax.*

class ShaderMacrosTests extends munit.FunSuite {

// ...and from resource?
test("Loading a shader from a file") {

val code: String =
"""
|void mainImage( out vec4 fragColor, in vec2 fragCoord )
|{
| // Normalized pixel coordinates (from 0 to 1)
| vec2 uv = fragCoord/iResolution.xy;
|
| // Time varying pixel color
| vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
|
| // Output to screen
| fragColor = vec4(col,1.0);
|}
|""".stripMargin.trim

inline def shader: Shader[Unit, Unit] =
Shader.fromFile("./test-resources/shader.frag")

// DebugAST.toAST(shader)

val actualAst: ShaderAST =
ShaderMacros.toAST(shader).main

val expectedAst: ShaderAST =
ShaderAST.RawLiteral(code)

val actualCode: String =
shader.toGLSL[WebGL2].code

val expectedCode: String =
code

assertEquals(actualAst, expectedAst)
assertEquals(actualCode, expectedCode)

}

}

0 comments on commit 629a88b

Please sign in to comment.