[orx-shader-phrases] Add ShaderPhraseRegistry
This commit is contained in:
3
orx-shader-phrases/build.gradle
Normal file
3
orx-shader-phrases/build.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||
}
|
||||
@@ -1,9 +1,81 @@
|
||||
package org.openrndr.extra.shaderphrases
|
||||
|
||||
import mu.KotlinLogging
|
||||
import org.openrndr.draw.Shader
|
||||
import org.openrndr.draw.codeFromURL
|
||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||
import org.openrndr.internal.Driver
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* A single shader phrase.
|
||||
*/
|
||||
class ShaderPhrase(val phrase: String) {
|
||||
/**
|
||||
* Register this shader phrase in the [ShaderPhraseRegistry]
|
||||
* This will likely be called by [ShaderPhraseBook]
|
||||
*/
|
||||
fun register(bookId: String? = null) {
|
||||
val functionRex =
|
||||
Regex("(float|int|[bi]?vec2|[bi]?vec3|[bi]?vec4|mat3|mat4)[ ]+([a-zA-Z0-9_]+)[ ]*\\(.*\\).*")
|
||||
val defs = phrase.split("\n").filter {
|
||||
functionRex.matches(it)
|
||||
}.take(1).mapNotNull {
|
||||
val m = functionRex.find(it)
|
||||
m?.groupValues?.getOrNull(2)
|
||||
}
|
||||
val id = defs.firstOrNull() ?: error("no function body found in phrase")
|
||||
ShaderPhraseRegistry.registerPhrase("${bookId?.let { "$it." } ?: ""}$id", this)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A book of shader phrases.
|
||||
*/
|
||||
open class ShaderPhraseBook(val bookId: String) {
|
||||
private var registered = false
|
||||
|
||||
/**
|
||||
* Registers all known shader phrases
|
||||
*/
|
||||
fun register() {
|
||||
if (!registered) {
|
||||
this::class.declaredMemberProperties.filter {
|
||||
it.returnType.toString() == "org.openrndr.extra.shaderphrases.ShaderPhrase"
|
||||
}.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val m = it as? KProperty1<ShaderPhraseBook, ShaderPhrase>
|
||||
m?.get(this)?.register(bookId)
|
||||
}
|
||||
registered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The global, application-wide, shader phrase registry
|
||||
*/
|
||||
object ShaderPhraseRegistry {
|
||||
private val phrases = mutableMapOf<String, ShaderPhrase>()
|
||||
/**
|
||||
* Registers a [phrase] with [id]
|
||||
*/
|
||||
fun registerPhrase(id: String, phrase: ShaderPhrase) {
|
||||
phrases[id] = phrase
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a phrase for [id], returns null when no phrase found
|
||||
*/
|
||||
fun findPhrase(id: String): ShaderPhrase? {
|
||||
val phrase = phrases[id]
|
||||
if (phrase == null) {
|
||||
logger.warn { "no phrase found for id: \"$id\"" }
|
||||
}
|
||||
return phrase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess shader source.
|
||||
@@ -27,38 +99,46 @@ fun preprocessShader(source: String, symbols: Set<String> = emptySet()): String
|
||||
|
||||
if (symbol !in newSymbols) {
|
||||
newSymbols.add(symbol)
|
||||
try {
|
||||
/* Note that JVM-style reflection is used here because of short-comings in the Kotlin reflection
|
||||
library (as of 1.3.61), most notably reflection support for file facades is missing. */
|
||||
val c = Class.forName(packageClass)
|
||||
if (c.annotations.any { it.annotationClass == ShaderPhrases::class }) {
|
||||
if (fieldName == "*") {
|
||||
c.declaredMethods.filter { it.returnType.name == "java.lang.String" }.map {
|
||||
"/* imported from $packageClass.$it */\n${it.invoke(null)}\n"
|
||||
}.joinToString("\n") +
|
||||
c.declaredFields.filter { it.type.name == "java.lang.String" }.map {
|
||||
"/* imported from $packageClass.$it */\n${it.get(null)}\n"
|
||||
}.joinToString("\n")
|
||||
} else {
|
||||
var result: String?
|
||||
try {
|
||||
val methodName = "get${fieldName.take(1).toUpperCase() + fieldName.drop(1)}"
|
||||
result = preprocessShader(c.getMethod(methodName).invoke(null) as String, newSymbols)
|
||||
} catch (e: NoSuchMethodException) {
|
||||
val registryPhrase = ShaderPhraseRegistry.findPhrase(symbol)
|
||||
|
||||
registryPhrase?.let { preprocessShader(it.phrase, newSymbols) }
|
||||
?: try {
|
||||
/* Note that JVM-style reflection is used here because of short-comings in the Kotlin reflection
|
||||
library (as of 1.3.61), most notably reflection support for file facades is missing. */
|
||||
val c = Class.forName(packageClass)
|
||||
if (c.annotations.any { it.annotationClass == ShaderPhrases::class }) {
|
||||
if (fieldName == "*") {
|
||||
c.declaredMethods.filter { it.returnType.name == "java.lang.String" }.map {
|
||||
"/* imported from $packageClass.$it */\n${it.invoke(null)}\n"
|
||||
}.joinToString("\n") +
|
||||
c.declaredFields.filter { it.type.name == "java.lang.String" }.map {
|
||||
"/* imported from $packageClass.$it */\n${it.get(null)}\n"
|
||||
}.joinToString("\n")
|
||||
} else {
|
||||
var result: String?
|
||||
try {
|
||||
result = preprocessShader(c.getDeclaredField(fieldName).get(null) as String, newSymbols)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
error("field \"$fieldName\" not found in \"#pragma import $packageClass.$fieldName\" on line ${index + 1}")
|
||||
val methodName = "get${fieldName.take(1).toUpperCase() + fieldName.drop(1)}"
|
||||
result =
|
||||
preprocessShader(c.getMethod(methodName).invoke(null) as String, newSymbols)
|
||||
} catch (e: NoSuchMethodException) {
|
||||
try {
|
||||
result =
|
||||
preprocessShader(
|
||||
c.getDeclaredField(fieldName).get(null) as String,
|
||||
newSymbols
|
||||
)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
error("field \"$fieldName\" not found in \"#pragma import $packageClass.$fieldName\" on line ${index + 1}")
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
result
|
||||
} else {
|
||||
throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation")
|
||||
}
|
||||
} else {
|
||||
throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation")
|
||||
} catch (e: ClassNotFoundException) {
|
||||
error("class \"$packageClass\" not found in \"#pragma import $packageClass\" on line ${index + 1}")
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
error("class \"$packageClass\" not found in \"#pragma import $packageClass\" on line ${index + 1}")
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -83,11 +163,12 @@ fun preprocessShaderFromUrl(url: String, symbols: Set<String> = emptySet()): Str
|
||||
}
|
||||
|
||||
fun Shader.Companion.preprocessedFromUrls(
|
||||
vsUrl: String,
|
||||
tcsUrl: String? = null,
|
||||
tesUrl: String? = null,
|
||||
gsUrl: String? = null,
|
||||
fsUrl: String): Shader {
|
||||
vsUrl: String,
|
||||
tcsUrl: String? = null,
|
||||
tesUrl: String? = null,
|
||||
gsUrl: String? = null,
|
||||
fsUrl: String
|
||||
): Shader {
|
||||
|
||||
val vsCode = codeFromURL(vsUrl).preprocess()
|
||||
val tcsCode = tcsUrl?.let { codeFromURL(it) }?.preprocess()
|
||||
|
||||
@@ -18,9 +18,9 @@ object TestPreprocessShader : Spek({
|
||||
}
|
||||
|
||||
describe("A shader with import statements") {
|
||||
val shader = """#version 330
|
||||
#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.*
|
||||
"""
|
||||
val shader = """
|
||||
|#version 330
|
||||
|#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.*""".trimMargin()
|
||||
describe("injects dummy phrase when preprocessed") {
|
||||
val processed = preprocessShader(shader)
|
||||
processed `should contain` "float dummy"
|
||||
|
||||
20
orx-shader-phrases/src/test/kotlin/TestShaderPhrase.kt
Normal file
20
orx-shader-phrases/src/test/kotlin/TestShaderPhrase.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
import org.amshove.kluent.`should be`
|
||||
import org.openrndr.extra.shaderphrases.ShaderPhrase
|
||||
import org.openrndr.extra.shaderphrases.ShaderPhraseRegistry
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
|
||||
object TestShaderPhrase : Spek({
|
||||
describe("A shader phrase") {
|
||||
val phrase = ShaderPhrase("""
|
||||
|vec4 test_phrase() {
|
||||
|}
|
||||
""".trimMargin() )
|
||||
it("can be registered") {
|
||||
phrase.register()
|
||||
}
|
||||
it("can be found") {
|
||||
ShaderPhraseRegistry.findPhrase("test_phrase") `should be` phrase
|
||||
}
|
||||
}
|
||||
})
|
||||
23
orx-shader-phrases/src/test/kotlin/TestShaderPhraseBook.kt
Normal file
23
orx-shader-phrases/src/test/kotlin/TestShaderPhraseBook.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
import org.amshove.kluent.`should be`
|
||||
import org.openrndr.extra.shaderphrases.ShaderPhrase
|
||||
import org.openrndr.extra.shaderphrases.ShaderPhraseBook
|
||||
import org.openrndr.extra.shaderphrases.ShaderPhraseRegistry
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
|
||||
class TestShaderPhraseBookobject : Spek({
|
||||
describe("A shader phrase book") {
|
||||
val book = object:ShaderPhraseBook("testBook") {
|
||||
val phrase = ShaderPhrase("""
|
||||
|vec4 test_phrase() {
|
||||
|}
|
||||
""".trimMargin() )
|
||||
}
|
||||
it("can be registered") {
|
||||
book.register()
|
||||
}
|
||||
it("can be found") {
|
||||
ShaderPhraseRegistry.findPhrase("testBook.test_phrase") `should be` book.phrase
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user