From 328e9c94f3795506b9d428d9227fc6a0521e282a Mon Sep 17 00:00:00 2001 From: Abe Pazos Date: Tue, 19 Oct 2021 11:58:08 +0200 Subject: [PATCH] [orx-shader-phrases] Add shader phrases tweaks (#196) --- orx-shader-phrases/build.gradle.kts | 1 + .../commonMain/kotlin/ShaderPreprocessor.kt | 40 ++++++++------ .../src/jvmTest/kotlin/TestFunctionNameRx.kt | 52 +++++++++++++++++++ .../src/jvmTest/kotlin/TestShaderPhrase.kt | 6 ++- .../jvmTest/kotlin/TestShaderPhraseBook.kt | 18 ++++--- .../kotlin/TestShaderPhrasePreprocessing.kt | 42 +++++++++++++++ 6 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 orx-shader-phrases/src/jvmTest/kotlin/TestFunctionNameRx.kt create mode 100644 orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrasePreprocessing.kt diff --git a/orx-shader-phrases/build.gradle.kts b/orx-shader-phrases/build.gradle.kts index de0abbcd..116c64ca 100644 --- a/orx-shader-phrases/build.gradle.kts +++ b/orx-shader-phrases/build.gradle.kts @@ -88,6 +88,7 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion") runtimeOnly("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") runtimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") + runtimeOnly("org.slf4j:slf4j-simple:1.7.30") implementation("org.spekframework.spek2:spek-dsl-jvm:$spekVersion") implementation("org.amshove.kluent:kluent:$kluentVersion") } diff --git a/orx-shader-phrases/src/commonMain/kotlin/ShaderPreprocessor.kt b/orx-shader-phrases/src/commonMain/kotlin/ShaderPreprocessor.kt index 91544469..15f351e3 100644 --- a/orx-shader-phrases/src/commonMain/kotlin/ShaderPreprocessor.kt +++ b/orx-shader-phrases/src/commonMain/kotlin/ShaderPreprocessor.kt @@ -2,6 +2,7 @@ package org.openrndr.extra.shaderphrases import mu.KotlinLogging import org.openrndr.draw.Shader +import org.openrndr.extra.shaderphrases.ShaderPhraseRegistry.getGLSLFunctionName //import org.openrndr.extra.shaderphrases.phrases.phraseTbnMatrix import org.openrndr.utils.url.textFromURL @@ -16,16 +17,9 @@ class ShaderPhrase(val phrase: String) { * 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) + val id = getGLSLFunctionName(phrase) + val prefix = bookId?.let { "$it." } ?: "" + ShaderPhraseRegistry.registerPhrase("$prefix$id", this) } } /** @@ -61,6 +55,22 @@ object ShaderPhraseRegistry { } return phrase } + + /** + * Gets the first GLSL function name out of GLSL source code + */ + fun getGLSLFunctionName(glsl: String): String { + val functionRex = + Regex("""\s*(float|int|[bi]?vec[234]|mat[34])\s+(\w+)\s*\(.*\).*""") + val defs = glsl.split("\n").filter { + functionRex.matches(it) + }.take(1).mapNotNull { + val m = functionRex.find(it) + m?.groupValues?.getOrNull(2) + } + return defs.firstOrNull() + ?: error("no function body found in phrase") + } } /** @@ -74,10 +84,10 @@ fun preprocessShader(source: String, symbols: Set = emptySet()): String newSymbols.addAll(symbols) val lines = source.split("\n") - val processed = lines.mapIndexed { index, it -> - if (it.startsWith("#pragma import")) { - val tokens = it.split(" ") - val symbol = tokens[2].trim().replace(";", "") + val funcName = Regex("""^\s*#pragma\s+import\s+([a-zA-Z0-9_.]+)""") + val processed = lines.map { line -> + if (line.contains("#pragma")) { + val symbol = funcName.find(line)?.groupValues?.get(1) ?: return@map line val fullTokens = symbol.split(".") val fieldName = fullTokens.last().replace(";", "").trim() val packageClassTokens = fullTokens.dropLast(1) @@ -90,7 +100,7 @@ fun preprocessShader(source: String, symbols: Set = emptySet()): String "" } } else { - it + line } } return processed.joinToString("\n") diff --git a/orx-shader-phrases/src/jvmTest/kotlin/TestFunctionNameRx.kt b/orx-shader-phrases/src/jvmTest/kotlin/TestFunctionNameRx.kt new file mode 100644 index 00000000..75544cf3 --- /dev/null +++ b/orx-shader-phrases/src/jvmTest/kotlin/TestFunctionNameRx.kt @@ -0,0 +1,52 @@ +import org.amshove.kluent.`should be equal to` +import org.amshove.kluent.internal.assertFails +import org.openrndr.extra.shaderphrases.ShaderPhraseRegistry.getGLSLFunctionName +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe + +object TestFunctionNameRx : Spek({ + describe("A function name") { + + mapOf( + "ivec4 aaa() {" to "aaa", + "ivec3 bbb() {" to "bbb", + "ivec2 ccc() {" to "ccc", + "bvec4 ddd() {" to "ddd", + "bvec3 eee() {" to "eee", + "bvec2 fff() {" to "fff", + "vec4 ggg() {" to "ggg", + "vec3 hhh() {" to "hhh", + "vec2 iii() {" to "iii", + "mat3 jjj() {" to "jjj", + "mat4 kkk() {" to "kkk", + "float lll() {" to "lll", + " float mmm( ) { " to "mmm", + "int nnn() {" to "nnn", + "vec2 limit(vec2 a, float b) {" to "limit", + """ + vec4 white() { + return vec4(1.0); + } + """.trimMargin() to "white" + ).forEach { (goodGLSL, expectedFuncName) -> + it("can be extracted from valid GLSL") { + getGLSLFunctionName(goodGLSL) `should be equal to` + expectedFuncName + } + } + } + + describe("A function name") { + listOf( + "float int mat4", + "float rnd {", + ).forEach { badGLSL -> + it("is not extracted if GLSL function not declared") { + assertFails { + val funcName = getGLSLFunctionName(badGLSL) + } + } + } + } + +}) diff --git a/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrase.kt b/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrase.kt index f45c3aad..d8bb56e7 100644 --- a/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrase.kt +++ b/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrase.kt @@ -6,10 +6,12 @@ import org.spekframework.spek2.style.specification.describe object TestShaderPhrase : Spek({ describe("A shader phrase") { - val phrase = ShaderPhrase(""" + val phrase = ShaderPhrase( + """ |vec4 test_phrase() { |} - """.trimMargin() ) + """.trimMargin() + ) it("can be registered") { phrase.register() } diff --git a/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhraseBook.kt b/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhraseBook.kt index 695d7625..8949bc47 100644 --- a/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhraseBook.kt +++ b/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhraseBook.kt @@ -5,19 +5,23 @@ import org.openrndr.extra.shaderphrases.ShaderPhraseRegistry import org.spekframework.spek2.Spek import org.spekframework.spek2.style.specification.describe -class TestShaderPhraseBookobject : Spek({ +class TestShaderPhraseBook : Spek({ describe("A shader phrase book") { - val book = object:ShaderPhraseBook("testBook") { - val phrase = ShaderPhrase(""" - |vec4 test_phrase() { - |} - """.trimMargin() ) + 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 + ShaderPhraseRegistry.findPhrase( + "testBook.test_phrase" + ) `should be` book.phrase } } }) \ No newline at end of file diff --git a/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrasePreprocessing.kt b/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrasePreprocessing.kt new file mode 100644 index 00000000..28f0465b --- /dev/null +++ b/orx-shader-phrases/src/jvmTest/kotlin/TestShaderPhrasePreprocessing.kt @@ -0,0 +1,42 @@ +import org.amshove.kluent.`should contain` +import org.amshove.kluent.`should not contain` +import org.openrndr.extra.shaderphrases.ShaderPhrase +import org.openrndr.extra.shaderphrases.ShaderPhraseBook +import org.openrndr.extra.shaderphrases.preprocessShader +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe + +class TestShaderPhrasePreprocessing : Spek({ + describe("the glsl code") { + val book = object : ShaderPhraseBook("testBook") { + val phrase1 = ShaderPhrase("vec3 phrase1() { }") + val phrase2 = ShaderPhrase("vec4 phrase2() { }") + } + book.register() + + val glsl = """ + // test expected usage + #pragma import testBook.phrase1 + // test odd spacing and line termination + #pragma import testBook.phrase2; + """.trimIndent() + + val glslProcessed = preprocessShader(glsl) + + it("should not contain phrase1 before preprocessing") { + glsl `should not contain` book.phrase1.phrase + } + + it("should not contain phrase2 before preprocessing") { + glsl `should not contain` book.phrase2.phrase + } + + it("should contain phrase1 after preprocessing") { + glslProcessed `should contain` book.phrase1.phrase + } + + it("should contain phrase2 after preprocessing") { + glslProcessed `should contain` book.phrase2.phrase + } + } +}) \ No newline at end of file