Add orx-glslify

This commit is contained in:
Ricardo Matias
2020-02-28 15:52:09 +01:00
committed by Edwin Jakobs
parent b6c16f0831
commit 75d6802ea8
6 changed files with 283 additions and 0 deletions

61
orx-glslify/README.md Normal file
View 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
View 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")
}

View 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)
}

View 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
}

View 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
}
}

View File

@@ -7,6 +7,7 @@ include 'orx-camera',
'orx-parameters',
'orx-filter-extension',
'orx-fx',
'orx-glslify',
'orx-gradient-descent',
'orx-integral-image',
'orx-interval-tree',
@@ -35,4 +36,5 @@ include 'orx-camera',
'orx-kinect-v1-natives-macos',
'orx-kinect-v1-natives-windows',
'orx-kinect-v1-demo'
include 'orx-glslify'