Skip to content

Commit

Permalink
Fixes #29,#3: Basic ShaderToy test passes
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed Nov 22, 2022
1 parent 03e0760 commit 51a3835
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 276 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package ultraviolet.datatypes
final case class GLSLHeader(glsl: String)
object GLSLHeader:

inline def Version300ES: GLSLHeader = GLSLHeader("#version 300 es\n")
inline def Version300ES: GLSLHeader = GLSLHeader("#version 300 es")

inline def PrecisionHighPFloat: GLSLHeader = GLSLHeader("precision highp float;\n")
inline def PrecisionMediumPFloat: GLSLHeader = GLSLHeader("precision mediump float;\n")
inline def PrecisionLowPFloat: GLSLHeader = GLSLHeader("precision lowp float;\n")
inline def PrecisionHighPFloat: GLSLHeader = GLSLHeader("precision highp float;")
inline def PrecisionMediumPFloat: GLSLHeader = GLSLHeader("precision mediump float;")
inline def PrecisionLowPFloat: GLSLHeader = GLSLHeader("precision lowp float;")
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object ProceduralShader:
}

extension (p: ProceduralShader)
inline def render(using template: ShaderTemplate): String =
inline def render(using template: ShaderTemplate, printer: ShaderPrinter): String =
import ShaderAST.*
def envName(ast: ShaderAST): Option[String] =
ast
Expand All @@ -31,11 +31,11 @@ object ProceduralShader:
case None => content
case Some(name) => content.replace(name + ".", "").replace(name, "")

val renderedHeaders = stripOutEnvName(p.main.renderHeaders)
val renderedDefs = p.defs.map(_.render).map(stripOutEnvName)
val renderedBody = stripOutEnvName(p.main.render)
val renderedHeaders = p.main.headers.flatMap(printer.print).map(stripOutEnvName)
val renderedDefs = p.defs.map(d => printer.print(d).mkString("\n")).map(stripOutEnvName)
val renderedBody = printer.print(p.main).map(stripOutEnvName)

template.render(renderedHeaders, renderedDefs, renderedBody)
template.print(renderedHeaders, renderedDefs, renderedBody)

def exists(q: ShaderAST => Boolean): Boolean =
p.main.exists(q) || p.defs.exists(_.exists(q))
220 changes: 6 additions & 214 deletions ultraviolet/src/main/scala/ultraviolet/datatypes/ShaderAST.scala
Original file line number Diff line number Diff line change
Expand Up @@ -351,218 +351,10 @@ object ShaderAST:
case DataTypes.vec4(_) => Option(ShaderAST.DataTypes.ident("vec4"))
case DataTypes.swizzle(v, _, _) => v.typeIdent

def renderStatements(statements: List[ShaderAST]): String =
statements
.map(_.prune)
.filterNot(_.isEmpty) // Empty()
.map {
case ShaderAST.RawLiteral(raw) =>
raw

case f: ShaderAST.Function =>
"\n" + f.render + "\n"

case ShaderAST.Block(ss) =>
renderStatements(ss)

case x =>
val r = x.render
if r.isEmpty then r else r + ";"
}
.filterNot(_.isEmpty) // empty String
.mkString
.trim
.replace("\n\n", "\n")
.replace(";;", ";")

@SuppressWarnings(Array("scalafix:DisableSyntax.throw"))
def render: String =
def rf(f: Float): String =
val s = f.toString
if s.contains(".") then s else s + ".0"

def decideType(a: ShaderAST): Option[String] =
a match
case Empty() => None
case Block(_) => None
case NamedBlock(_, _, _) => None
case ShaderBlock(_, _, _) => None
case Function(_, _, _, rt) => rt.map(_.render)
case CallFunction(_, _, _, rt) => rt.map(_.render)
case FunctionRef(_, rt) => rt.map(_.render)
case Cast(_, as) => Option(as)
case Infix(_, _, _, rt) => rt.map(_.render)
case Assign(_, _) => None
case If(_, _, _) => None
case While(_, _) => None
case Switch(_, _) => None
case Val(id, value, typeOf) => typeOf
case Annotated(id, value) => decideType(value)
case RawLiteral(_) => None
case n @ DataTypes.ident(_) => None
case DataTypes.closure(_, typeOf) => typeOf
case DataTypes.float(v) => Option("float")
case DataTypes.int(v) => Option("int")
case DataTypes.vec2(args) => Option("vec2")
case DataTypes.vec3(args) => Option("vec3")
case DataTypes.vec4(args) => Option("vec4")
case DataTypes.swizzle(v, _, rt) => rt.map(_.render)

def processFunctionStatements(statements: List[ShaderAST], maybeReturnType: Option[String]): (String, String) =
val nonEmpty = statements
.map(_.prune)
.filterNot(_.isEmpty)

val (init, last) =
if nonEmpty.length > 1 then (nonEmpty.dropRight(1), nonEmpty.takeRight(1))
else (Nil, nonEmpty)

val returnType =
maybeReturnType match
case None => last.headOption.flatMap(decideType).getOrElse("void")
case Some(value) => value

val body =
renderStatements(init) +
last.headOption
.map { ss =>
(if returnType != "void" then "return " else "") + renderStatements(List(ss))
}
.getOrElse("")

(
body,
returnType
)

val res =
ast match
case Empty() =>
""

case Block(statements) =>
renderStatements(statements)

case ShaderBlock(envVarName, headers, statements) =>
envVarName match
case None =>
renderStatements(statements)

case Some(value) =>
val (body, _) = processFunctionStatements(statements, None)
body

case NamedBlock(namespace, id, statements) =>
s"""$namespace$id {${renderStatements(statements)}}"""

case Function(id, args, body, returnType) if id.isEmpty =>
throw new Exception("Failed to render shader, unnamed function definition found.")

case Function(id, args, Block(statements), returnType) =>
val (body, rt) = processFunctionStatements(statements, returnType.map(_.render))
s"""$rt $id(${args.map(s => s"in ${s._1.render} ${s._2}").mkString(",")}){$body}"""

case Function(id, args, NamedBlock(_, _, statements), returnType) =>
val (body, rt) = processFunctionStatements(statements, returnType.map(_.render))
s"""$rt $id(${args.map(s => s"in ${s._1.render} ${s._2}").mkString(",")}){$body}"""

case Function(id, args, statement, returnType) =>
val (body, rt) = processFunctionStatements(List(statement), returnType.map(_.render))
s"""$rt $id(${args.map(s => s"in ${s._1.render} ${s._2}").mkString(",")}){$body}"""

case CallFunction(id, args, _, _) =>
s"""$id(${args.map(_.render).mkString(",")})"""

case FunctionRef(_, _) =>
""

case Cast(value, as) =>
s"""$as(${value.render})"""

case Infix(op, left, right, returnType) =>
s"""(${left.render})$op(${right.render})"""

case Assign(left, right) =>
s"""${left.render}=${right.render}"""

case If(cond, thenTerm, elseTerm) =>
elseTerm match
case None =>
s"""if(${cond.render}){${thenTerm.render};}"""

case Some(els) =>
s"""if(${cond.render}){${thenTerm.render};}else{${els.render};}"""

case While(cond, body) =>
s"""while(${cond.render}){${body.render}}"""

case Switch(on, cases) =>
val cs =
cases.map {
case (Some(i), body) =>
s"case $i:${body.render};break;"

case (None, body) =>
s"default:${body.render};break;"
}

s"""switch(${on.render}){${cs.mkString}}"""

case DataTypes.closure(body, typeOf) =>
s"[closure $body $typeOf]"

case DataTypes.ident(id) =>
s"$id"

case DataTypes.float(v) =>
s"${rf(v)}"

case DataTypes.int(v) =>
s"${v.toString}"

case DataTypes.vec2(args) =>
s"vec2(${args.map(_.render).mkString(",")})"

case DataTypes.vec3(args) =>
s"vec3(${args.map(_.render).mkString(",")})"

case DataTypes.vec4(args) =>
s"vec4(${args.map(_.render).mkString(",")})"

case DataTypes.swizzle(genType, swizzle, returnType) =>
s"${genType.render}.$swizzle"

case Val(id, value, typeOf) =>
val tOf = typeOf.getOrElse("void")
value match
case Empty() =>
s"""$tOf $id"""

case _ =>
s"""$tOf $id=${value.render}"""

case Annotated(label, value) =>
value match
case Val(id, value, typeOf) =>
s"""${label.render} ${Val(id, Empty(), typeOf).render}"""

case _ =>
""

case RawLiteral(body) =>
body

res

def renderHeaders: String =
val res =
ast match
case ShaderBlock(envVarName, headers, statements) =>
renderStatements(headers)

case _ =>
""

res
def headers: List[ShaderAST] =
ast match
case ShaderBlock(_, headers, _) =>
headers

final case class RenderedAST(headers: String, body: String)
case _ =>
Nil
Loading

0 comments on commit 51a3835

Please sign in to comment.