Add orx-glslify
This commit is contained in:
committed by
Edwin Jakobs
parent
b6c16f0831
commit
75d6802ea8
61
orx-glslify/README.md
Normal file
61
orx-glslify/README.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# orx-glslify
|
||||||
|
|
||||||
|
Easily use glslify compatible shaders found on [npm](https://www.npmjs.com/search?q=glslify).
|
||||||
|
|
||||||
|
### Caveats
|
||||||
|
|
||||||
|
Some glslify shaders have their own imports. When this happens we print a message to the console,
|
||||||
|
so you can proceed to import them. These need to be imported in the shader file on top of the main import.
|
||||||
|
|
||||||
|
There's also a mapping functionality that glslify provides that we don't support. This can be easily solved
|
||||||
|
by doing as the following example (based on `glsl-raytrace` package):
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
const int steps = 50;
|
||||||
|
vec2 map(vec3 p);
|
||||||
|
|
||||||
|
#pragma import shaders.RayMarching.*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Shader Phrases file:
|
||||||
|
```kotlin
|
||||||
|
@file:JvmName("Checkers")
|
||||||
|
@file:ShaderPhrases
|
||||||
|
|
||||||
|
package shaders
|
||||||
|
|
||||||
|
import org.openrndr.extra.glslify.glslify
|
||||||
|
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||||
|
|
||||||
|
val periodic by lazy { glslify("glsl-noise/classic/3d", "perlin") }
|
||||||
|
val checker by lazy { glslify("glsl-checker") }
|
||||||
|
val easings by lazy { glslify("glsl-easings/cubic-in-out", "easing")}
|
||||||
|
```
|
||||||
|
|
||||||
|
Shader file:
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 v_texCoord0;
|
||||||
|
|
||||||
|
uniform sampler2D tex0;
|
||||||
|
uniform float uTime;
|
||||||
|
|
||||||
|
out vec4 o_color;
|
||||||
|
|
||||||
|
#pragma import shaders.Checkers.*
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_texCoord0;
|
||||||
|
float n = perlin(vec3(uv * 2.5 + uTime * 0.01, uTime * 0.2)) * 0.5 + 0.5;
|
||||||
|
|
||||||
|
float patt = checker(uv * easing(n), 6.0);
|
||||||
|
|
||||||
|
vec3 col = mix(vec3(0.173, 0.216, 0.278),vec3(0.792, 0.282, 0.478), vec3(patt)) * (n + 0.1);
|
||||||
|
|
||||||
|
o_color = vec4(col, 1.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
13
orx-glslify/build.gradle
Normal file
13
orx-glslify/build.gradle
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url = uri("https://jitpack.io")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "com.google.code.gson:gson:$gsonVersion"
|
||||||
|
implementation "com.github.jkcclemens:khttp:-SNAPSHOT"
|
||||||
|
implementation "org.rauschig:jarchivelib:1.0.0"
|
||||||
|
implementation project(":orx-noise")
|
||||||
|
}
|
||||||
|
|
||||||
42
orx-glslify/src/main/kotlin/FileSystemUtils.kt
Normal file
42
orx-glslify/src/main/kotlin/FileSystemUtils.kt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package org.openrndr.extra.glslify
|
||||||
|
|
||||||
|
import org.rauschig.jarchivelib.Archiver
|
||||||
|
import org.rauschig.jarchivelib.ArchiverFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
internal fun extractGzipStream(tarStream: InputStream, dest: File) {
|
||||||
|
val archiver: Archiver = ArchiverFactory.createArchiver("tar", "gz")
|
||||||
|
|
||||||
|
archiver.extract(tarStream, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun moveFilesUp(moduleDir: File) {
|
||||||
|
val modulePath = Paths.get(moduleDir.path)
|
||||||
|
val packageFolder = modulePath.resolve("package")
|
||||||
|
|
||||||
|
val packageFiles = Files.list(packageFolder).filter {
|
||||||
|
Files.isDirectory(it) || shaderExtensions.contains(it.fileName.toString().substringAfterLast("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
packageFiles.forEach {
|
||||||
|
// fileName also retrieves folders ¯\_(ツ)_/¯
|
||||||
|
val dest = modulePath.resolve(modulePath.relativize(it).fileName)
|
||||||
|
Files.move(it, dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun parseModule(module: String): Pair<String, String> {
|
||||||
|
val path = Paths.get(module)
|
||||||
|
val pathLen = path.nameCount
|
||||||
|
val moduleName = path.subpath(0, 1).toString()
|
||||||
|
var shaderPath = "index"
|
||||||
|
|
||||||
|
if (pathLen > 1) {
|
||||||
|
shaderPath = path.subpath(1, pathLen).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(moduleName, shaderPath)
|
||||||
|
}
|
||||||
113
orx-glslify/src/main/kotlin/Glslify.kt
Normal file
113
orx-glslify/src/main/kotlin/Glslify.kt
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package org.openrndr.extra.glslify
|
||||||
|
|
||||||
|
import khttp.responses.Response
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
const val BASE_URL = "https://registry.npmjs.org"
|
||||||
|
const val GLSLIFY_PATH = "src/main/resources/glslify"
|
||||||
|
const val IMPORT_PATT = """#pragma\sglslify:\s*(.*)\s=\s*require\('([\w\-]+)'\)"""
|
||||||
|
const val EXPORT_PATT = """#pragma\sglslify:\s*export\(([\w\-]+)\)"""
|
||||||
|
|
||||||
|
internal val shaderExtensions = arrayOf("glsl", "frag")
|
||||||
|
|
||||||
|
data class GlslifyImport(
|
||||||
|
val functionName: String,
|
||||||
|
val pkgName: String,
|
||||||
|
var exists: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
fun glslify(module: String, renameFunctionTo: String? = null): String {
|
||||||
|
val (moduleName: String, shaderPath: String) = parseModule(module)
|
||||||
|
val moduleDir = File("$GLSLIFY_PATH/$moduleName")
|
||||||
|
|
||||||
|
val packageUrl = getPackageUrl(moduleName)
|
||||||
|
|
||||||
|
if (packageUrl.isNullOrEmpty()) {
|
||||||
|
throw error("[glslify] $moduleName not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moduleDir.exists()) {
|
||||||
|
try {
|
||||||
|
moduleDir.mkdirs()
|
||||||
|
|
||||||
|
val response: Response = khttp.get(packageUrl, stream = true)
|
||||||
|
|
||||||
|
val tarStream: InputStream = ByteArrayInputStream(response.content)
|
||||||
|
|
||||||
|
extractGzipStream(tarStream, moduleDir)
|
||||||
|
|
||||||
|
moveFilesUp(moduleDir)
|
||||||
|
|
||||||
|
File("$moduleDir/package").deleteRecursively()
|
||||||
|
|
||||||
|
logger.trace("[glslify] $moduleName downloaded")
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
logger.error(ex) { "[glslify]: There was an error getting $moduleName" }
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.trace("[glslify] $moduleName already exists.. Skipping download")
|
||||||
|
}
|
||||||
|
|
||||||
|
val shaderFile: File
|
||||||
|
|
||||||
|
try {
|
||||||
|
shaderFile = shaderExtensions.map {
|
||||||
|
File("$GLSLIFY_PATH/$moduleName/$shaderPath.$it")
|
||||||
|
}.first { it.exists() }
|
||||||
|
} catch (ex: NoSuchElementException) {
|
||||||
|
logger.trace("[glslify] $moduleName: $shaderPath doesn't lead to any shader file")
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val shader = mutableListOf<String>()
|
||||||
|
val imports = mutableListOf<GlslifyImport>()
|
||||||
|
var exportName: String? = null
|
||||||
|
|
||||||
|
shaderFile.useLines { sequence ->
|
||||||
|
for (line in sequence.iterator()) {
|
||||||
|
if (line.contains("#pragma")) {
|
||||||
|
Regex(IMPORT_PATT).find(line)?.let {
|
||||||
|
if (it.groupValues.size > 1) {
|
||||||
|
val importExists = File("$GLSLIFY_PATH/${it.groupValues[2]}").exists()
|
||||||
|
|
||||||
|
imports.add(GlslifyImport(it.groupValues[1], it.groupValues[2], importExists))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Regex(EXPORT_PATT).find(line)?.let {
|
||||||
|
if (it.groupValues.size > 1) {
|
||||||
|
exportName = it.groupValues[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shader.add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val missingImports = imports.filter { !it.exists }
|
||||||
|
|
||||||
|
if (missingImports.isNotEmpty()) {
|
||||||
|
missingImports.forEach {
|
||||||
|
logger.info("Missing package: ${it.pkgName} - Import name: ${it.functionName}")
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error("[glslify] Please declare the missing imports")
|
||||||
|
}
|
||||||
|
|
||||||
|
var shaderString = shader.joinToString("\n")
|
||||||
|
|
||||||
|
if (renameFunctionTo != null && exportName != null) {
|
||||||
|
shaderString = shaderString.replace( exportName!!, renameFunctionTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return shaderString
|
||||||
|
}
|
||||||
52
orx-glslify/src/main/kotlin/NpmUtils.kt
Normal file
52
orx-glslify/src/main/kotlin/NpmUtils.kt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package org.openrndr.extra.glslify
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import khttp.responses.Response
|
||||||
|
|
||||||
|
|
||||||
|
internal data class NPMPackageDist(
|
||||||
|
val shasum: String,
|
||||||
|
val tarball: String
|
||||||
|
)
|
||||||
|
|
||||||
|
internal data class NPMPackageVersion(
|
||||||
|
val name: String,
|
||||||
|
val version: String,
|
||||||
|
val dist: NPMPackageDist
|
||||||
|
)
|
||||||
|
|
||||||
|
internal data class NPMResponse(
|
||||||
|
val name: String?,
|
||||||
|
val error: String?,
|
||||||
|
@SerializedName("dist-tags")
|
||||||
|
val distTags: MutableMap<String, String>?,
|
||||||
|
val versions: Map<String, NPMPackageVersion>?
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun getPackageUrl(module: String): String? {
|
||||||
|
val url = "$BASE_URL/$module"
|
||||||
|
|
||||||
|
val response : Response = khttp.get(
|
||||||
|
url = url,
|
||||||
|
headers = mapOf(
|
||||||
|
"Accept" to "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val gson = GsonBuilder().create()
|
||||||
|
|
||||||
|
val npmResponse = gson.fromJson(
|
||||||
|
response.text, NPMResponse::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
if (npmResponse.error != null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return npmResponse.distTags?.let {
|
||||||
|
val latest = it["latest"]
|
||||||
|
|
||||||
|
npmResponse.versions?.get(latest)?.dist?.tarball
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ include 'orx-camera',
|
|||||||
'orx-parameters',
|
'orx-parameters',
|
||||||
'orx-filter-extension',
|
'orx-filter-extension',
|
||||||
'orx-fx',
|
'orx-fx',
|
||||||
|
'orx-glslify',
|
||||||
'orx-gradient-descent',
|
'orx-gradient-descent',
|
||||||
'orx-integral-image',
|
'orx-integral-image',
|
||||||
'orx-interval-tree',
|
'orx-interval-tree',
|
||||||
@@ -35,4 +36,5 @@ include 'orx-camera',
|
|||||||
'orx-kinect-v1-natives-macos',
|
'orx-kinect-v1-natives-macos',
|
||||||
'orx-kinect-v1-natives-windows',
|
'orx-kinect-v1-natives-windows',
|
||||||
'orx-kinect-v1-demo'
|
'orx-kinect-v1-demo'
|
||||||
|
include 'orx-glslify'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user