Improve orx-shader-phrases
This commit is contained in:
@@ -1,20 +1,16 @@
|
|||||||
@file:ShaderPhrases(exports = ["hash22","hash21","valueNoise21"])
|
@file:ShaderPhrases(exports = ["hash22","hash21","valueNoise21"])
|
||||||
package org.openrndr.extra.noise.phrases
|
package org.openrndr.extra.noise.phrases
|
||||||
|
|
||||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrase
|
|
||||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||||
|
|
||||||
@ShaderPhrase(exports = ["hash22"])
|
|
||||||
val phraseHash22 = """vec2 hash22(vec2 p) {
|
val phraseHash22 = """vec2 hash22(vec2 p) {
|
||||||
float n = sin(dot(p, vec2(41, 289)));
|
float n = sin(dot(p, vec2(41, 289)));
|
||||||
return fract(vec2(262144, 32768)*n);
|
return fract(vec2(262144, 32768)*n);
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ShaderPhrase(exports = ["hash21"])
|
|
||||||
val phraseHash21 = "float hash21(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }"
|
val phraseHash21 = "float hash21(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }"
|
||||||
|
|
||||||
@ShaderPhrase(exports = ["valueNoise21"], imports = ["hash21"])
|
|
||||||
val phraseValueNoise21 = """
|
val phraseValueNoise21 = """
|
||||||
|
|
||||||
float noise(vec2 x) {
|
float noise(vec2 x) {
|
||||||
|
|||||||
47
orx-shader-phrases/src/README.md
Normal file
47
orx-shader-phrases/src/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# orx-shader-phrases
|
||||||
|
|
||||||
|
A library that provides a `#pragma import` statement for shaders by using the JVM class loader.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Given a shader source:
|
||||||
|
|
||||||
|
````glsl
|
||||||
|
#version 330
|
||||||
|
// -- this imports all phrases in Dummy
|
||||||
|
#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.*
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float a = dummy();
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
We can use the `preprocessShader()` function to resolve `#pragma import` statements.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val preprocessedSource = preprocessShader(originalSource)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively loading and preprocessing can be combined in a single function call.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val preprocessedSource = preprocessShaderFromUrl(resourceUrl("/some-shader.frag"))
|
||||||
|
```
|
||||||
|
|
||||||
|
To create importable shader phrases one creates a Kotlin class and adds the `ShaderPhrases` annotation.
|
||||||
|
For example the `dummy` phrase in our example is made available as follows:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// -- force the class name to be Dummy on the JVM
|
||||||
|
@file:JvmName("Dummy")
|
||||||
|
@file:ShaderPhrases
|
||||||
|
package org.openrndr.extra.shaderphrases.phrases
|
||||||
|
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||||
|
|
||||||
|
// -- the shader phrase
|
||||||
|
const val dummy = """
|
||||||
|
float dummy() {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
```
|
||||||
@@ -1,33 +1,58 @@
|
|||||||
package org.openrndr.extra.shaderphrases
|
package org.openrndr.extra.shaderphrases
|
||||||
|
|
||||||
|
import org.openrndr.draw.codeFromURL
|
||||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||||
|
|
||||||
fun preprocessShader(shader: String): String {
|
/**
|
||||||
val lines = shader.split("\n")
|
* Preprocess shader source.
|
||||||
val processed = lines.map {
|
* Looks for "#pragma import" statements and injects found phrases.
|
||||||
if (it.startsWith("import ")) {
|
* @param source GLSL source code encoded as string
|
||||||
|
* @return GLSL source code with injected shader phrases
|
||||||
|
*/
|
||||||
|
fun preprocessShader(source: String): String {
|
||||||
|
val lines = source.split("\n")
|
||||||
|
val processed = lines.mapIndexed { index, it ->
|
||||||
|
if (it.startsWith("#pragma import")) {
|
||||||
val tokens = it.split(" ")
|
val tokens = it.split(" ")
|
||||||
val full = tokens[1]
|
val full = tokens[2]
|
||||||
val fullTokens = full.split(".")
|
val fullTokens = full.split(".")
|
||||||
val fieldName = fullTokens.last().replace(";","").trim()
|
val fieldName = fullTokens.last().replace(";", "").trim()
|
||||||
val packageClassTokens = fullTokens.dropLast(1)
|
val packageClassTokens = fullTokens.dropLast(1)
|
||||||
val packageClass = packageClassTokens.joinToString(".")
|
val packageClass = packageClassTokens.joinToString(".")
|
||||||
|
|
||||||
val c = Class.forName(packageClass)
|
try {
|
||||||
if (c.annotations.any { it.annotationClass == ShaderPhrases::class }) {
|
val c = Class.forName(packageClass)
|
||||||
if (fieldName == "*") {
|
if (c.annotations.any { it.annotationClass == ShaderPhrases::class }) {
|
||||||
c.declaredFields.filter { it.type.name =="java.lang.String" }.map {
|
if (fieldName == "*") {
|
||||||
it.get(null)
|
c.declaredFields.filter { it.type.name == "java.lang.String" }.map {
|
||||||
}.joinToString("\n")
|
"/* imported from $packageClass.$it */\n ${it.get(null)}"
|
||||||
|
}.joinToString("\n")
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
c.getDeclaredField(fieldName).get(null)
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
error("field \"$fieldName\" not found in \"#pragma import $packageClass.$fieldName\" on line ${index + 1}")
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.getDeclaredField(fieldName).get(null)
|
throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation")
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e: ClassNotFoundException) {
|
||||||
throw IllegalArgumentException("class $packageClass has no ShaderPhrases annotation")
|
error("class \"$packageClass\" not found in \"#pragma import $packageClass\" on line ${index + 1}")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return processed.joinToString("\n")
|
return processed.joinToString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preprocess shader source from url
|
||||||
|
* Looks for "#pragma import" statements and injects found phrases.
|
||||||
|
* @param url url pointing to GLSL shader source
|
||||||
|
* @return GLSL source code with injected shader phrases
|
||||||
|
*/
|
||||||
|
fun preprocessShaderFromUrl(url: String): String {
|
||||||
|
return preprocessShader(codeFromURL(url))
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
package org.openrndr.extra.shaderphrases.annotations
|
package org.openrndr.extra.shaderphrases.annotations
|
||||||
|
|
||||||
enum class ShaderPhraseLanguage {
|
enum class ShaderPhraseLanguage {
|
||||||
GLSL
|
GLSL_330
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.FILE, AnnotationTarget.CLASS, AnnotationTarget.FIELD)
|
|
||||||
annotation class ShaderPhrase(val exports: Array<String>,
|
|
||||||
val imports: Array<String> = emptyArray(),
|
|
||||||
val language: ShaderPhraseLanguage = ShaderPhraseLanguage.GLSL)
|
|
||||||
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.FILE)
|
@Target(AnnotationTarget.FILE)
|
||||||
annotation class ShaderPhrases(val exports: Array<String>)
|
annotation class ShaderPhrases(val exports: Array<String> = emptyArray())
|
||||||
33
orx-shader-phrases/src/main/kotlin/phrases/Depth.kt
Normal file
33
orx-shader-phrases/src/main/kotlin/phrases/Depth.kt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@file:JvmName("Depth")
|
||||||
|
@file:ShaderPhrases([])
|
||||||
|
package org.openrndr.extra.shaderphrases.phrases
|
||||||
|
|
||||||
|
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||||
|
|
||||||
|
/**
|
||||||
|
* phrase for conversion from view to projection depth
|
||||||
|
* @param viewDepth depth in view space ([0.0 .. -far])
|
||||||
|
* @param projection projection matrix
|
||||||
|
* @return depth in projection space ([0.0 .. 1.0]]
|
||||||
|
*/
|
||||||
|
const val viewToProjectionDepth = """
|
||||||
|
float viewToProjectionDepth(float viewDepth, mat4 projection) {
|
||||||
|
float z = viewDepth * projection[2].z + projection[3].z;
|
||||||
|
float w = viewDepth * projection[2].w + projection[3].w;
|
||||||
|
return z / w;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* phrase for conversion from view to projection depth
|
||||||
|
* @param projectionDepth depth in projection space ([0.0 .. 1.0])
|
||||||
|
* @param projectionInversed inverse of the projection matrix
|
||||||
|
* @return depth in view space ([0.0 .. -far]]
|
||||||
|
*/
|
||||||
|
const val projectionToViewDepth = """
|
||||||
|
float projectionToViewDepth(float projectionDepth, mat4 projectionInverse) {
|
||||||
|
float z = projectionDepth * projectionInverse[2].z + projectionInverse[3].z;
|
||||||
|
float w = projectionDepth * projectionInverse[2].w + projectionInverse[3].w;
|
||||||
|
return z / w;
|
||||||
|
}
|
||||||
|
"""
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
@file:JvmName("Dummy")
|
@file:JvmName("Dummy")
|
||||||
@file:ShaderPhrases(["dummy"])
|
@file:ShaderPhrases
|
||||||
|
|
||||||
package org.openrndr.extra.shaderphrases.phrases
|
package org.openrndr.extra.shaderphrases.phrases
|
||||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrase
|
|
||||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||||
import org.openrndr.extra.shaderphrases.preprocessShader
|
import org.openrndr.extra.shaderphrases.preprocessShader
|
||||||
|
|
||||||
@ShaderPhrase(["dummy"])
|
|
||||||
const val phraseDummy = """
|
const val phraseDummy = """
|
||||||
float dummy() {
|
float dummy() {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val c = Class.forName("org.openrndr.extra.shaderphrases.phrases.Dummy")
|
val c = Class.forName("org.openrndr.extra.shaderphrases.phrases.Dummy")
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,53 @@
|
|||||||
|
import org.amshove.kluent.`should contain`
|
||||||
|
import org.amshove.kluent.`should throw`
|
||||||
|
import org.amshove.kluent.`with message`
|
||||||
|
import org.amshove.kluent.invoking
|
||||||
import org.openrndr.extra.shaderphrases.preprocessShader
|
import org.openrndr.extra.shaderphrases.preprocessShader
|
||||||
|
import org.openrndr.extra.shaderphrases.preprocessShaderFromUrl
|
||||||
|
import org.openrndr.resourceUrl
|
||||||
import org.spekframework.spek2.Spek
|
import org.spekframework.spek2.Spek
|
||||||
import org.spekframework.spek2.style.specification.describe
|
import org.spekframework.spek2.style.specification.describe
|
||||||
|
|
||||||
object TestPreprocessShader:Spek({
|
object TestPreprocessShader : Spek({
|
||||||
|
describe("An url pointing to a shader resource") {
|
||||||
|
val url = resourceUrl("/from-url-test.frag")
|
||||||
|
describe("results in injected dummy phrase when preprocessed") {
|
||||||
|
val processed = preprocessShaderFromUrl(url)
|
||||||
|
processed `should contain` "float dummy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("A shader with import statements") {
|
describe("A shader with import statements") {
|
||||||
val shader = """
|
val shader = """#version 330
|
||||||
#version 330
|
#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.*
|
||||||
import org.openrndr.extra.shaderphrases.phrases.Dummy.*
|
"""
|
||||||
|
describe("injects dummy phrase when preprocessed") {
|
||||||
|
|
||||||
""".trimIndent()
|
|
||||||
describe("when preprocessed") {
|
|
||||||
val processed = preprocessShader(shader)
|
val processed = preprocessShader(shader)
|
||||||
println(processed)
|
processed `should contain` "float dummy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("A shader with non-resolvable class statements") {
|
||||||
|
val shader = """#version 330
|
||||||
|
#pragma import invalid.Class.*
|
||||||
|
"""
|
||||||
|
describe("throws exception when preprocessed") {
|
||||||
|
invoking {
|
||||||
|
preprocessShader(shader)
|
||||||
|
} `should throw` RuntimeException::class `with message`
|
||||||
|
("class \"invalid.Class\" not found in \"#pragma import invalid.Class\" on line 2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("A shader with non-resolvable property statements") {
|
||||||
|
val shader = """#version 330
|
||||||
|
#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.invalid
|
||||||
|
"""
|
||||||
|
describe("throws exception when preprocessed") {
|
||||||
|
invoking {
|
||||||
|
preprocessShader(shader)
|
||||||
|
} `should throw` RuntimeException::class `with message`
|
||||||
|
("field \"invalid\" not found in \"#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.invalid\" on line 2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
2
orx-shader-phrases/src/test/resources/from-url-test.frag
Normal file
2
orx-shader-phrases/src/test/resources/from-url-test.frag
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#version 330
|
||||||
|
#pragma import org.openrndr.extra.shaderphrases.phrases.Dummy.*
|
||||||
Reference in New Issue
Block a user