commonMain.dsl.ShaderGenerator.kt Maven / Gradle / Ivy
The newest version!
@file:Suppress("PackageDirectoryMismatch")
package org.openrndr.orsl.shadergenerator.dsl
import org.openrndr.draw.Struct
import org.openrndr.draw.typeDef
import org.openrndr.orsl.shadergenerator.dsl.functions.*
import org.openrndr.orsl.shadergenerator.dsl.functions.BooleanFunctions
import org.openrndr.orsl.shadergenerator.dsl.functions.Matrix33Functions
import org.openrndr.orsl.shadergenerator.dsl.functions.SamplerFunctions
import org.openrndr.orsl.shadergenerator.dsl.functions.ColorRGBaFunctions
import org.openrndr.orsl.shadergenerator.phrases.dsl.functions.AtomicCounterBufferFunctions
import org.openrndr.orsl.shadergenerator.phrases.dsl.functions.BarrierFunctions
import org.openrndr.math.*
import kotlin.reflect.KProperty
open class ShaderBuilder(declaredSymbols: Set) : Generator, Functions, BooleanFunctions, DoubleFunctions, ArrayFunctions, SamplerFunctions,
Vector3Functions,
IntFunctions, ColorRGBaFunctions, UIntFunctions,
Vector2Functions, Vector4Functions, Matrix33Functions, Matrix44Functions,
IntVector2Functions, IntVector3unctions, UIntVector2Functions,
UIntVector3Functions, AtomicCounterBufferFunctions, IntRImage2DFunctions, RImage3DFunctions,
IntRImage3DFunctions,
BarrierFunctions {
override var code = ""
override var preamble = ""
override val declaredSymbols= declaredSymbols.map { it }.toMutableSet()
infix fun Int.until(to: Int): Range {
return Range(startV = this, endV = to)
}
infix fun Symbol.until(to: Int): Range {
return Range(startP = this, endV = to)
}
infix fun Symbol.until(to: Symbol): Range = Range(startP = this, endP = to)
operator fun Symbol.rangeTo(to: Symbol): Range = Range(startP = this, endP = to + symbol("1"))
operator fun Int.rangeTo(to: Symbol): Range = Range(startV = this, endP = to + symbol("1"))
operator fun Symbol.rangeTo(to: Int): Range = Range(startP = this, endV = to - 1)
operator fun Int.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("int ${property.name} = $this;")
return ConstantProperty("int")
}
operator fun Boolean.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("${staticType()} ${property.name} = $this;")
return ConstantProperty(staticType())
}
operator fun Vector4.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("${staticType()} ${property.name} = ${glsl(this)};")
return ConstantProperty(staticType())
}
operator fun Vector3.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("${staticType()} ${property.name} = ${glsl(this)};")
return ConstantProperty(staticType())
}
inline operator fun > T.provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ConstantProperty {
emit("${staticType()} ${property.name} = ${glsl(this)};")
return ConstantProperty(staticType())
}
operator fun Matrix44.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("mat4 ${property.name} = ${glsl(this)};")
return ConstantProperty("mat4")
}
operator fun Matrix33.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("mat3 ${property.name} = $this;")
return ConstantProperty("mat3")
}
operator fun Double.provideDelegate(thisRef: Any?, property: KProperty<*>): ConstantProperty {
emit("float ${property.name} = $this;")
return ConstantProperty("float")
}
inline operator fun ArraySymbol.provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ArrayValueProperty {
if (name.isNotEmpty()) {
emit("${staticType()} ${property.name}[$length] = ${name};")
} else {
emit("${staticType()} ${property.name}[$length];")
}
return ArrayValueProperty(this@ShaderBuilder, this.length, this.type)
}
inline operator fun Symbol.provideDelegate(thisRef: Any?, property: KProperty<*>): ValueProperty {
emit("$type ${property.name} = ${name};")
return ValueProperty(type)
}
// inline operator fun > Symbol
private val emitted = mutableSetOf()
override fun emitPreamble(code: String) {
if (code.isNotBlank()) {
if (code !in emitted) {
this.preamble += code.trimEnd() + "\n"
if (code.split("\n").size > 1) {
emitted.add(code)
}
}
}
}
inline fun global(): GlobalProperty {
val glslType = staticType()
return GlobalProperty(this@ShaderBuilder, glslType)
}
inline fun variable(): VariableProperty {
val glslType = staticType()
return VariableProperty(this@ShaderBuilder, glslType, null, null)
}
inline fun variable(initialValue: T): VariableProperty {
val glslType = staticType()
return VariableProperty(this@ShaderBuilder, glslType, initialValue, null)
}
inline fun variable(initialValue: Symbol): VariableProperty {
val glslType = staticType()
return VariableProperty(this@ShaderBuilder, glslType, null, initialValue)
}
inline fun arrayVariable(length: Int): ArrayVariableProperty {
val glslType = staticType()
return ArrayVariableProperty(this@ShaderBuilder, length, glslType)
}
inline fun array(length: Int): ArraySymbol = arraySymbol("", length)
inline fun constant(): ConstantProperty {
return ConstantProperty(staticType())
}
inline fun output(): OutputProperty {
return OutputProperty(this@ShaderBuilder, staticType())
}
inline fun fragmentOutput(): FragmentOutputProperty {
return FragmentOutputProperty(this@ShaderBuilder, staticType())
}
@PublishedApi
internal var tempId = 1
inline fun Symbol.elseIf(
precondition: Symbol,
noinline f: ShaderBuilder.() -> Symbol
): Symbol {
val sb = ShaderBuilder(declaredSymbols)
sb.push()
val result = sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""${staticType()} temp_$tempId;
if (${precondition.name}) {
${sb.code.prependIndent(" ").trimEnd()}
temp_$tempId = ${result.name};
} else {
temp_$tempId = ${this@elseIf.name};
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
val s = symbol("temp_$tempId")
tempId++
return s
}
@Suppress("FunctionName")
fun Symbol.for_(range: Range, f: ShaderBuilder.() -> Unit) {
val sb = ShaderBuilder(declaredSymbols)
sb.push()
sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""for (${this.name} = ${range.startV}; ${this.name} < ${range.endV}; ++${this.name}) {
${sb.code.prependIndent(" ").trimEnd()}
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
}
@Suppress("FunctionName")
fun break_() {
emit("break;")
}
@Suppress("FunctionName")
fun continue_() {
emit("continue;")
}
inline fun run(noinline f: ShaderBuilder.() -> Symbol): Symbol {
val sb = ShaderBuilder(declaredSymbols)
sb.tempId = tempId * 31
sb.push()
val result = sb.f()
sb.pop()
val hash = hash(this.code, this.preamble)
emitPreamble(sb.preamble)
emit(
"""${staticType()} temp_${hash}_$tempId;
{
${sb.code.prependIndent(" ").trimEnd()}
temp_${hash}_$tempId = ${result.name};
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
val s = ifSymbol("temp_${hash}_$tempId")
tempId++
return s
}
@Suppress("FunctionName")
inline fun if_(precondition: Symbol, noinline f: ShaderBuilder.() -> Symbol): IfSymbol {
val sb = ShaderBuilder(declaredSymbols)
sb.push()
sb.tempId = tempId * 31
val result = sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""${staticType()} temp_$tempId;
if (${precondition.name}) {
${sb.code.prependIndent(" ").trimEnd()}
temp_$tempId = ${result.name};
}"""
)
val s = ifSymbol("temp_$tempId")
declaredSymbols.addAll(sb.declaredSymbols)
tempId++
return s
}
fun doIf(precondition: Symbol, f: ShaderBuilder.() -> Unit): DoIfSymbol {
val sb = ShaderBuilder(declaredSymbols)
sb.push()
sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""if (${precondition.name}) {
${sb.code.prependIndent(" ").trimEnd()}
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
return object: DoIfSymbol {
override val name: String
get() = ""
override val type: String
get() = "void"
}
}
@Suppress("FunctionName")
inline infix fun IfSymbol.else_(noinline f: ShaderBuilder.() -> Symbol): Symbol {
val sb = ShaderBuilder(declaredSymbols)
sb.tempId = tempId * 31
sb.push()
val result = sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""else {
${sb.code.prependIndent(" ").trimEnd()}
temp_${tempId - 1} = ${result.name};
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
return symbol("temp_${tempId - 1}")
}
/**
* non-expression else
*/
@Suppress("FunctionName")
inline infix fun DoIfSymbol.else_(noinline f: ShaderBuilder.() -> Unit) {
val sb = ShaderBuilder(declaredSymbols)
sb.tempId = tempId * 31
sb.push()
sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""else {
${sb.code.prependIndent(" ").trimEnd()}
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
}
@Suppress("UnusedReceiverParameter")
inline fun IfSymbol.elseIf(
precondition: Symbol,
noinline f: ShaderBuilder.() -> Symbol
): IfSymbol {
val sb = ShaderBuilder(declaredSymbols)
sb.tempId = tempId * 31
sb.push()
val result = sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""else if (${precondition.name}) {
${sb.code.prependIndent(" ").trimEnd()}
temp_${tempId - 1} = ${result.name};
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
return ifSymbol("temp_${tempId - 1}")
}
@Suppress("UnusedReceiverParameter")
inline fun DoIfSymbol.elseIf(
precondition: Symbol,
noinline f: ShaderBuilder.() -> Unit
): DoIfSymbol {
val sb = ShaderBuilder(declaredSymbols)
sb.push()
sb.f()
sb.pop()
emitPreamble(sb.preamble)
emit(
"""else if (${precondition.name}) {
${sb.code.prependIndent(" ").trimEnd()}
}"""
)
declaredSymbols.addAll(sb.declaredSymbols)
return object: DoIfSymbol {
override val name: String
get() = ""
override val type: String
get() = "void"
}
}
inline fun BoxRange2.weightedAverageBy(
noinline itemFunction: (x: Symbol) -> FunctionSymbol1,
noinline weightFunction: (x: Symbol) -> FunctionSymbol1
): Symbol {
val id = symbol("$0")
val itemFunctionId = itemFunction(id)
val weightFunctionId = weightFunction(id)
val returnType = staticType()
val hash = hash(itemFunctionId.name, weightFunctionId.name, returnType)
emitPreamble(
"""$returnType weightedAverageBy_${hash}(int startX, int endX, int startY, int endY) {
$returnType sum = ${zero()};
float weight = 0.0;
for (int j = startY; j < endY; ++j) {
for (int i = startX; i < endX; ++i) {
sum += ${itemFunctionId.function.replace("$0", "ivec2(i, j)")};
weight += ${weightFunctionId.function.replace("$0", "ivec2(i, j)")};
}
}
return sum / weight;
}"""
)
val startX = xrange.startP?.name ?: xrange.startV?.toString() ?: error("no startX")
val startY = yrange.startP?.name ?: yrange.startV?.toString() ?: error("no startY")
val endX = xrange.endP?.name ?: xrange.endV?.toString() ?: error("no endX")
val endY = yrange.endP?.name ?: yrange.endV?.toString() ?: error("no endY")
return symbol("weightedAverageBy_${hash}($startX, $endX, $startY, $endY)")
}
inline fun BoxRange2.sumBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val hash = hash(functionId.name, this, returnType)
emit(
"""$returnType sumBy_${hash}(int startX, int endX, int startY, int endY) {
$returnType sum = ${zero()};
for (int j = startY; j < endY; ++j) {
for (int i = startX; i < endX; ++i) {
sum += ${functionId.function.replace("$0", "ivec2(i, j)")};
}
}
return sum;
}"""
)
val startX = xrange.startP?.name ?: xrange.startV?.toString() ?: error("no startX")
val startY = yrange.startP?.name ?: yrange.startV?.toString() ?: error("no startY")
val endX = xrange.endP?.name ?: xrange.endV?.toString() ?: error("no endX")
val endY = yrange.endP?.name ?: yrange.endV?.toString() ?: error("no endY")
return symbol("sumBy_${hash}($startX, $endX, $startY, $endY)")
}
inline fun BoxRange2.minBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val hash = hash(functionId.name, this, returnType)
emitPreamble(
"""$returnType minBy_${hash}(int startX, int endX, int startY, int endY) {
$returnType minV = ${functionId.function.replace("$0", "ivec2(startX, startY)")};
for (int j = startY; j < endY; ++j) {
for (int i = startX; i < endX; ++i) {
minV = min(minV, ${functionId.function.replace("$0", "ivec2(i, j)")});
}
}
return minV;
}"""
)
val startX = xrange.startP?.name ?: xrange.startV?.toString() ?: error("no startX")
val startY = yrange.startP?.name ?: yrange.startV?.toString() ?: error("no startY")
val endX = xrange.endP?.name ?: xrange.endV?.toString() ?: error("no endX")
val endY = yrange.endP?.name ?: yrange.endV?.toString() ?: error("no endY")
return symbol("minBy_${hash}($startX, $endX, $startY, $endY)")
}
inline fun BoxRange2.foldBy(
init: Symbol,
accumulate: (acc: Symbol, elem: Symbol) -> Function2Symbol,
noinline function: (x: Symbol) -> FunctionSymbol1
): Symbol {
val id = symbol("$0")
val accId = symbol("$0")
val elemId = symbol("$1")
val accFunctionId = accumulate(accId, elemId)
val functionId = function(id)
val returnType = staticType()
val hash = hash(functionId.name, this, returnType)
emitPreamble(
"""$returnType foldBy_${hash}(${staticType()} init, int startX, int endX, int startY, int endY) {
$returnType acc = init;
for (int j = startY; j < endY; ++j) {
for (int i = startX; i < endX; ++i) {
$returnType elem = ${functionId.function.replace("$0", "ivec2(i, j)")};
acc = ${accFunctionId.function.replace("$0", "acc").replace("$1", "elem")};
}
}
return acc;
}"""
)
val startX = xrange.startP?.name ?: xrange.startV?.toString() ?: error("no startX")
val startY = yrange.startP?.name ?: yrange.startV?.toString() ?: error("no startY")
val endX = xrange.endP?.name ?: xrange.endV?.toString() ?: error("no endX")
val endY = yrange.endP?.name ?: yrange.endV?.toString() ?: error("no endY")
return symbol("foldBy_${hash}(${init.name}, $startX, $endX, $startY, $endY)")
}
inline fun Range.sumBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val hash = hash(functionId.name, returnType)
emitPreamble(
"""$returnType sumBy_${hash}(int start, int end) {
$returnType sum = ${zero()};
for (int i = start; i < end; ++i) {
sum += ${functionId.function.replace("$0", "i")};
}
return sum;
}"""
)
val start = startP?.name ?: startV?.toString() ?: error("no start")
val end = endP?.name ?: endV?.toString() ?: error("no end")
return symbol("sumBy_${hash}($start, $end)")
}
inline fun ArraySymbol.map(noinline function: (x: Symbol) -> FunctionSymbol1): ArraySymbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val inputType = staticType()
val hash = hash(functionId.name, returnType, inputType)
emitPreamble(
"""$returnType[${length}] map_${hash}($inputType x[${length}]) {
$returnType[$length] y;
for (int i = 0; i < $length; ++i) {
y[i] = ${functionId.function.replace("$0", "x[i]")};
}
return y;
}"""
)
return object : ArraySymbol {
override val name = "map_${hash}(${this@map.name})"
override val length = this@map.length
override val type = "float"
}
}
inline fun ArraySymbol.minBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val inputType = staticType()
val hash = hash(functionId.name, returnType, inputType)
emitPreamble(
"""$returnType minBy_${hash}($inputType x__[${length}]) {
float minValue = ${functionId.function.replace("$0", "x__[0]")};
int index = 0;
for (int i = 1; i < $length; ++i) {
float candidateValue = ${functionId.function.replace("$0", "x__[i]")};
if (candidateValue < minValue) {
minValue = candidateValue;
index = i;
}
}
return x__[index];
}"""
)
return symbol(name = "minBy_${hash}(${this@minBy.name})", type = this@minBy.type)
}
inline fun ArraySymbol.argMinBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val inputType = staticType()
val hash = hash(functionId.name, returnType, inputType)
emitPreamble(
"""$returnType argMinBy_${hash}($inputType x__[${length}]) {
float minValue = ${functionId.function.replace("$0", "x__[0]")};
int index = 0;
for (int i = 1; i < $length; ++i) {
float candidateValue = ${functionId.function.replace("$0", "x__[i]")};
if (candidateValue < minValue) {
minValue = candidateValue;
index = i;
}
}
return index;
}"""
)
return symbol(name = "argMinBy_${hash}(${this@argMinBy.name})", type = "int")
}
inline fun ArraySymbol.maxBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val inputType = staticType()
val hash = hash(functionId.name, returnType, inputType)
emitPreamble(
"""$returnType maxBy_${hash}($inputType x__[${length}]) {
float maxValue = ${functionId.function.replace("$0", "x__[0]")};
int index = 0;
for (int i = 1; i < $length; ++i) {
float candidateValue = ${functionId.function.replace("$0", "x__[i]")};
if (candidateValue > maxValue) {
maxValue = candidateValue;
index = i;
}
}
return x__[index];
}"""
)
return symbol(name = "maxBy_${hash}(${this@maxBy.name})", type = this@maxBy.type)
}
inline fun ArraySymbol.argMaxBy(noinline function: (x: Symbol) -> FunctionSymbol1): Symbol {
val id = symbol("$0")
val functionId = function(id)
val returnType = staticType()
val inputType = staticType()
val hash = hash(functionId.name, returnType, inputType)
emitPreamble(
"""$returnType argMaxBy_${hash}($inputType x__[${length}]) {
float maxValue = ${functionId.function.replace("$0", "x__[0]")};
int index = 0;
for (int i = 1; i < $length; ++i) {
float candidateValue = ${functionId.function.replace("$0", "x__[i]")};
if (candidateValue > maxValue) {
minValue = candidateValue;
index = i;
}
}
return index;
}"""
)
return symbol(name = "argMaxBy_${hash}(${this@argMaxBy.name})", type = "int")
}
inline operator fun > T.provideDelegate(any: Any?, property: KProperty<*>): T {
emitPreamble(this.typeDef())
emit("${this::class.simpleName} ${property.name};")
return this
}
}