From 65d99d77768f7d7ef2882ea82334d63819c27673 Mon Sep 17 00:00:00 2001 From: Christian Williams Date: Sun, 11 Oct 2020 13:09:48 -0700 Subject: [PATCH 1/2] Refactor GLSL generation spec. --- .../baaahs/gl/patch/GlslGenerationSpec.kt | 233 +++++++++++++++++ .../kotlin/baaahs/gl/patch/OpenPatchSpec.kt | 238 ------------------ 2 files changed, 233 insertions(+), 238 deletions(-) create mode 100644 src/commonTest/kotlin/baaahs/gl/patch/GlslGenerationSpec.kt delete mode 100644 src/commonTest/kotlin/baaahs/gl/patch/OpenPatchSpec.kt diff --git a/src/commonTest/kotlin/baaahs/gl/patch/GlslGenerationSpec.kt b/src/commonTest/kotlin/baaahs/gl/patch/GlslGenerationSpec.kt new file mode 100644 index 0000000000..42722d7939 --- /dev/null +++ b/src/commonTest/kotlin/baaahs/gl/patch/GlslGenerationSpec.kt @@ -0,0 +1,233 @@ +package baaahs.gl.patch + +import baaahs.glsl.Shaders.cylindricalProjection +import baaahs.plugin.CorePlugin +import baaahs.plugin.Plugins +import baaahs.show.ShaderChannel +import baaahs.show.mutable.MutablePatch +import baaahs.show.mutable.MutableShaderOutPort +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe +import kotlin.test.expect + +object GlslGenerationSpec : Spek({ + describe("Generation of GLSL from patches") { + val shaderText by value { + /**language=glsl*/ + """ + // This Shader's Name + // Other stuff. + + uniform float blueness; + uniform vec2 resolution; + uniform float time; + int someGlobalVar; + const int someConstVar = 123; + + int anotherFunc(int i) { return i; } + + void main( void ) { + vec2 uv = gl_FragCoord.xy / resolution.xy; + someGlobalVar = anotherFunc(someConstVar); + gl_FragColor = vec4(uv.xy, blueness, 1.); + } + """.trimIndent() + } + val autoWirer by value { AutoWirer(Plugins.safe()) } + val glslAnalyzer by value { autoWirer.glslAnalyzer } + val paintShader by value { glslAnalyzer.import(shaderText) } + val mutablePatch by value { MutablePatch { } } + val linkedPatch by value { mutablePatch.openForPreview(autoWirer)!! } + val glsl by value { linkedPatch.toGlsl().trim() } + + context("with screen coordinates for preview") { + beforeEachTest { + mutablePatch.addShaderInstance(paintShader) { + link("gl_FragCoord", CorePlugin.ScreenUvCoordDataSource()) + link("resolution", CorePlugin.ResolutionDataSource()) + link("time", CorePlugin.TimeDataSource()) + link( + "blueness", + CorePlugin.SliderDataSource( + "Blueness", + 0f, + 0f, + 1f, + null + ) + ) + shaderChannel = ShaderChannel.Main + } + } + + it("generates GLSL") { + expect( + """ + #ifdef GL_ES + precision mediump float; + #endif + + // SparkleMotion-generated GLSL + + layout(location = 0) out vec4 sm_result; + + uniform float in_bluenessSlider; + uniform vec2 in_resolution; + uniform float in_time; + + // Shader: This Shader's Name; namespace: p0 + // This Shader's Name + + vec4 p0_thisShaderSName_gl_FragColor = vec4(0., 0., 0., 1.); + + #line 7 + int p0_thisShaderSName_someGlobalVar; + + #line 8 + const int p0_thisShaderSName_someConstVar = 123; + + #line 10 + int p0_thisShaderSName_anotherFunc(int i) { return i; } + + #line 12 + void p0_thisShaderSName_main( void ) { + vec2 uv = gl_FragCoord.xy / in_resolution.xy; + p0_thisShaderSName_someGlobalVar = p0_thisShaderSName_anotherFunc(p0_thisShaderSName_someConstVar); + p0_thisShaderSName_gl_FragColor = vec4(uv.xy, in_bluenessSlider, 1.); + } + + + #line 10001 + void main() { + p0_thisShaderSName_main(); // This Shader's Name + sm_result = p0_thisShaderSName_gl_FragColor; + } + """.trimIndent() + ) { glsl } + } + } + + describe("with projection shader") { + beforeEachTest { + mutablePatch.apply { + addShaderInstance(cylindricalProjection) { + link( + "pixelCoordsTexture", + CorePlugin.PixelCoordsTextureDataSource() + ) + link( + "modelInfo", + CorePlugin.ModelInfoDataSource() + ) + shaderChannel = ShaderChannel.Main + } + + addShaderInstance(paintShader) { + link( + "gl_FragCoord", + MutableShaderOutPort( + findShaderInstanceFor(cylindricalProjection) + ) + ) + link("resolution", CorePlugin.ResolutionDataSource()) + link("time", CorePlugin.TimeDataSource()) + link( + "blueness", + CorePlugin.SliderDataSource( + "Blueness", + 0f, + 0f, + 1f, + null + ) + ) + shaderChannel = ShaderChannel.Main + } + } + } + + it("generates GLSL") { + expect( + /**language=glsl*/ + """ + #ifdef GL_ES + precision mediump float; + #endif + + // SparkleMotion-generated GLSL + + layout(location = 0) out vec4 sm_result; + + struct ModelInfo { + vec3 center; + vec3 extents; + }; + uniform float in_bluenessSlider; + uniform ModelInfo in_modelInfo; + uniform sampler2D in_pixelCoordsTexture; + uniform vec2 in_resolution; + uniform float in_time; + + // Shader: Cylindrical Projection; namespace: p0 + // Cylindrical Projection + + vec2 p0_cylindricalProjectioni_result = vec2(0.); + + #line 12 + const float p0_cylindricalProjection_PI = 3.141592654; + + #line 14 + vec2 p0_cylindricalProjection_project(vec3 pixelLocation) { + vec3 pixelOffset = pixelLocation - in_modelInfo.center; + vec3 normalDelta = normalize(pixelOffset); + float theta = atan(abs(normalDelta.z), normalDelta.x); // theta in range [-π,π] + if (theta < 0.0) theta += (2.0f * p0_cylindricalProjection_PI); // theta in range [0,2π) + float u = theta / (2.0f * p0_cylindricalProjection_PI); // u in range [0,1) + float v = (pixelOffset.y + in_modelInfo.extents.y / 2.0f) / in_modelInfo.extents.y; + return vec2(u, v); + } + + #line 24 + vec2 p0_cylindricalProjection_mainProjection(vec2 rasterCoord) { + int rasterX = int(rasterCoord.x); + int rasterY = int(rasterCoord.y); + + vec3 pixelCoord = texelFetch(in_pixelCoordsTexture, ivec2(rasterX, rasterY), 0).xyz; + return p0_cylindricalProjection_project(pixelCoord); + } + + // Shader: This Shader's Name; namespace: p1 + // This Shader's Name + + vec4 p1_thisShaderSName_gl_FragColor = vec4(0., 0., 0., 1.); + + #line 7 + int p1_thisShaderSName_someGlobalVar; + + #line 8 + const int p1_thisShaderSName_someConstVar = 123; + + #line 10 + int p1_thisShaderSName_anotherFunc(int i) { return i; } + + #line 12 + void p1_thisShaderSName_main( void ) { + vec2 uv = p0_cylindricalProjectioni_result.xy / in_resolution.xy; + p1_thisShaderSName_someGlobalVar = p1_thisShaderSName_anotherFunc(p1_thisShaderSName_someConstVar); + p1_thisShaderSName_gl_FragColor = vec4(uv.xy, in_bluenessSlider, 1.); + } + + + #line 10001 + void main() { + p0_cylindricalProjectioni_result = p0_cylindricalProjection_mainProjection(gl_FragCoord.xy); // Cylindrical Projection + p1_thisShaderSName_main(); // This Shader's Name + sm_result = p1_thisShaderSName_gl_FragColor; + } + """.trimIndent() + ) { glsl } + } + } + } +}) + diff --git a/src/commonTest/kotlin/baaahs/gl/patch/OpenPatchSpec.kt b/src/commonTest/kotlin/baaahs/gl/patch/OpenPatchSpec.kt deleted file mode 100644 index 41799d97c2..0000000000 --- a/src/commonTest/kotlin/baaahs/gl/patch/OpenPatchSpec.kt +++ /dev/null @@ -1,238 +0,0 @@ -package baaahs.gl.patch - -import baaahs.gl.override -import baaahs.glsl.Shaders.cylindricalProjection -import baaahs.plugin.CorePlugin -import baaahs.plugin.Plugins -import baaahs.show.ShaderChannel -import baaahs.show.mutable.MutablePatch -import baaahs.show.mutable.MutableShaderOutPort -import baaahs.toBeSpecified -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.specification.describe -import kotlin.test.expect - -object OpenPatchSpec : Spek({ - describe("GlslProgram") { - val shaderText by value { toBeSpecified() } - val autoWirer by value { AutoWirer(Plugins.safe()) } - val glslAnalyzer by value { autoWirer.glslAnalyzer } - val shader by value { glslAnalyzer.import(shaderText) } - val openShader by value { glslAnalyzer.openShader(shader) } - - context("GLSL generation") { - override(shaderText) { - /**language=glsl*/ - """ - // This Shader's Name - // Other stuff. - - uniform float blueness; - uniform vec2 resolution; - uniform float time; - int someGlobalVar; - const int someConstVar = 123; - - int anotherFunc(int i) { return i; } - - void main( void ) { - vec2 uv = gl_FragCoord.xy / resolution.xy; - someGlobalVar = anotherFunc(someConstVar); - gl_FragColor = vec4(uv.xy, blueness, 1.); - } - """.trimIndent() - } - - describe("#toGlsl") { - val linkedPatch by value { - MutablePatch { - addShaderInstance(shader) { - link("gl_FragCoord", CorePlugin.ScreenUvCoordDataSource()) - link("resolution", CorePlugin.ResolutionDataSource()) - link("time", CorePlugin.TimeDataSource()) - link("blueness", - CorePlugin.SliderDataSource( - "Blueness", - 0f, - 0f, - 1f, - null - ) - ) - shaderChannel = ShaderChannel.Main - } - }.openForPreview(autoWirer)!! - } - val glsl by value { - linkedPatch.toGlsl().trim() - } - - it("generates GLSL") { - expect( - """ - #ifdef GL_ES - precision mediump float; - #endif - - // SparkleMotion-generated GLSL - - layout(location = 0) out vec4 sm_result; - - uniform float in_bluenessSlider; - uniform vec2 in_resolution; - uniform float in_time; - - // Shader: This Shader's Name; namespace: p0 - // This Shader's Name - - vec4 p0_thisShaderSName_gl_FragColor = vec4(0., 0., 0., 1.); - - #line 7 - int p0_thisShaderSName_someGlobalVar; - - #line 8 - const int p0_thisShaderSName_someConstVar = 123; - - #line 10 - int p0_thisShaderSName_anotherFunc(int i) { return i; } - - #line 12 - void p0_thisShaderSName_main( void ) { - vec2 uv = gl_FragCoord.xy / in_resolution.xy; - p0_thisShaderSName_someGlobalVar = p0_thisShaderSName_anotherFunc(p0_thisShaderSName_someConstVar); - p0_thisShaderSName_gl_FragColor = vec4(uv.xy, in_bluenessSlider, 1.); - } - - - #line 10001 - void main() { - p0_thisShaderSName_main(); // This Shader's Name - sm_result = p0_thisShaderSName_gl_FragColor; - } - """.trimIndent() - ) { glsl } - } - - context("with UV translation shader") { - override(linkedPatch) { - MutablePatch { - addShaderInstance(cylindricalProjection) { - link("pixelCoordsTexture", - CorePlugin.PixelCoordsTextureDataSource() - ) - link("modelInfo", - CorePlugin.ModelInfoDataSource() - ) - shaderChannel = ShaderChannel.Main - } - - addShaderInstance(openShader.shader) { - link( - "gl_FragCoord", - MutableShaderOutPort( - findShaderInstanceFor(cylindricalProjection) - ) - ) - link("resolution", CorePlugin.ResolutionDataSource()) - link("time", CorePlugin.TimeDataSource()) - link("blueness", - CorePlugin.SliderDataSource( - "Blueness", - 0f, - 0f, - 1f, - null - ) - ) - shaderChannel = ShaderChannel.Main - } - }.openForPreview(autoWirer)!! - } - - it("generates GLSL") { - expect( - /**language=glsl*/ - """ - #ifdef GL_ES - precision mediump float; - #endif - - // SparkleMotion-generated GLSL - - layout(location = 0) out vec4 sm_result; - - struct ModelInfo { - vec3 center; - vec3 extents; - }; - uniform float in_bluenessSlider; - uniform ModelInfo in_modelInfo; - uniform sampler2D in_pixelCoordsTexture; - uniform vec2 in_resolution; - uniform float in_time; - - // Shader: Cylindrical Projection; namespace: p0 - // Cylindrical Projection - - vec2 p0_cylindricalProjectioni_result = vec2(0.); - - #line 12 - const float p0_cylindricalProjection_PI = 3.141592654; - - #line 14 - vec2 p0_cylindricalProjection_project(vec3 pixelLocation) { - vec3 pixelOffset = pixelLocation - in_modelInfo.center; - vec3 normalDelta = normalize(pixelOffset); - float theta = atan(abs(normalDelta.z), normalDelta.x); // theta in range [-π,π] - if (theta < 0.0) theta += (2.0f * p0_cylindricalProjection_PI); // theta in range [0,2π) - float u = theta / (2.0f * p0_cylindricalProjection_PI); // u in range [0,1) - float v = (pixelOffset.y + in_modelInfo.extents.y / 2.0f) / in_modelInfo.extents.y; - return vec2(u, v); - } - - #line 24 - vec2 p0_cylindricalProjection_mainProjection(vec2 rasterCoord) { - int rasterX = int(rasterCoord.x); - int rasterY = int(rasterCoord.y); - - vec3 pixelCoord = texelFetch(in_pixelCoordsTexture, ivec2(rasterX, rasterY), 0).xyz; - return p0_cylindricalProjection_project(pixelCoord); - } - - // Shader: This Shader's Name; namespace: p1 - // This Shader's Name - - vec4 p1_thisShaderSName_gl_FragColor = vec4(0., 0., 0., 1.); - - #line 7 - int p1_thisShaderSName_someGlobalVar; - - #line 8 - const int p1_thisShaderSName_someConstVar = 123; - - #line 10 - int p1_thisShaderSName_anotherFunc(int i) { return i; } - - #line 12 - void p1_thisShaderSName_main( void ) { - vec2 uv = p0_cylindricalProjectioni_result.xy / in_resolution.xy; - p1_thisShaderSName_someGlobalVar = p1_thisShaderSName_anotherFunc(p1_thisShaderSName_someConstVar); - p1_thisShaderSName_gl_FragColor = vec4(uv.xy, in_bluenessSlider, 1.); - } - - - #line 10001 - void main() { - p0_cylindricalProjectioni_result = p0_cylindricalProjection_mainProjection(gl_FragCoord.xy); // Cylindrical Projection - p1_thisShaderSName_main(); // This Shader's Name - sm_result = p1_thisShaderSName_gl_FragColor; - } - """.trimIndent() - ) { glsl } - } - } - } - } - } -}) - From f756a9809bc495194b90500e75e967677be24f04 Mon Sep 17 00:00:00 2001 From: Christian Williams Date: Sun, 11 Oct 2020 14:51:50 -0700 Subject: [PATCH 2/2] Fix crash when a shader instance has mappings for unknown ports (maybe e.g. because they were removed from the shader code). --- .../kotlin/baaahs/show/ShaderType.kt | 4 +- .../baaahs/show/live/LiveShaderInstance.kt | 30 +- .../kotlin/baaahs/show/mutable/MutableShow.kt | 12 +- .../kotlin/baaahs/show/MutableShowSpec.kt | 257 ++++++++++-------- .../kotlin/baaahs/show/live/OpenShowSpec.kt | 18 ++ .../baaahs/app/ui/editor/LinksEditor.kt | 16 +- 6 files changed, 197 insertions(+), 140 deletions(-) diff --git a/src/commonMain/kotlin/baaahs/show/ShaderType.kt b/src/commonMain/kotlin/baaahs/show/ShaderType.kt index e54b41a796..29479fad99 100644 --- a/src/commonMain/kotlin/baaahs/show/ShaderType.kt +++ b/src/commonMain/kotlin/baaahs/show/ShaderType.kt @@ -54,10 +54,10 @@ enum class ShaderType( mapOf(ContentType.UvCoordinateStream to ShaderChannel.Main), ContentType.UvCoordinateStream, CommonIcons.DistortionShader, """ - uniform float size; // @@Slider min=0.75 max=1.25 default=1 + uniform float scale; // @@Slider min=0.25 max=4 default=1 vec2 mainDistortion(vec2 uvIn) { - return (uvIn - .5) * size + .5; + return (uvIn - .5) / scale + .5; } """.trimIndent() ) { diff --git a/src/commonMain/kotlin/baaahs/show/live/LiveShaderInstance.kt b/src/commonMain/kotlin/baaahs/show/live/LiveShaderInstance.kt index ed58a465d6..70cd17fb52 100644 --- a/src/commonMain/kotlin/baaahs/show/live/LiveShaderInstance.kt +++ b/src/commonMain/kotlin/baaahs/show/live/LiveShaderInstance.kt @@ -1,5 +1,6 @@ package baaahs.show.live +import baaahs.Logger import baaahs.getBang import baaahs.gl.patch.PortDiagram import baaahs.gl.shader.InputPort @@ -72,19 +73,32 @@ class ShaderInstanceResolver( val shaderInstance = findShaderInstance(id) val shader = findShader(shaderInstance.shaderId) - val links = shaderInstance.incomingLinks.mapValues { (_, portRef) -> - when (portRef) { - is ShaderOutPortRef -> LiveShaderInstance.ShaderOutLink(resolve(portRef.shaderInstanceId)) - is DataSourceRef -> LiveShaderInstance.DataSourceLink(findDataSource(portRef.dataSourceId), portRef.dataSourceId) - is ShaderChannelRef -> LiveShaderInstance.ShaderChannelLink(portRef.shaderChannel) - is OutputPortRef -> TODO() - is ConstPortRef -> LiveShaderInstance.ConstLink(portRef.glsl) + val knownInputPorts = shader.inputPorts.associateBy { it.id } + + val links = shaderInstance.incomingLinks + .filterKeys { portId -> + knownInputPorts.contains(portId).also { containsKey -> + if (!containsKey) logger.warn { "Unknown port mapping \"$portId\" for shader \"${shader.title}\"" } + } } - } + .mapValues { (_, portRef) -> + when (portRef) { + is ShaderOutPortRef -> LiveShaderInstance.ShaderOutLink(resolve(portRef.shaderInstanceId)) + is DataSourceRef -> LiveShaderInstance.DataSourceLink(findDataSource(portRef.dataSourceId), portRef.dataSourceId) + is ShaderChannelRef -> LiveShaderInstance.ShaderChannelLink(portRef.shaderChannel) + is OutputPortRef -> TODO() + is ConstPortRef -> LiveShaderInstance.ConstLink(portRef.glsl) + } + } + return LiveShaderInstance(shader, links, shaderInstance.shaderChannel, shaderInstance.priority).also { liveShaderInstances[id] = it } } fun getResolvedShaderInstances() = liveShaderInstances + + companion object { + private val logger = Logger("ShaderInstanceResolver") + } } \ No newline at end of file diff --git a/src/commonMain/kotlin/baaahs/show/mutable/MutableShow.kt b/src/commonMain/kotlin/baaahs/show/mutable/MutableShow.kt index 41bd2ef3ff..60586125cf 100644 --- a/src/commonMain/kotlin/baaahs/show/mutable/MutableShow.kt +++ b/src/commonMain/kotlin/baaahs/show/mutable/MutableShow.kt @@ -162,12 +162,12 @@ class MutableShow( init { // Second pass required here since they might refer to each other. - baseShow.shaderInstances.values.zip(shaderInstances.values).forEach { (shaderInstance, editor) -> - editor.incomingLinks.putAll( - shaderInstance.incomingLinks.mapValues { (_, fromPortRef) -> - fromPortRef.dereference(this) - } - ) + baseShow.shaderInstances.forEach { (id, shaderInstance) -> + val editor = shaderInstances.getBang(id, "shader instance") + val resolvedIncomingLinks = shaderInstance.incomingLinks.mapValues { (_, fromPortRef) -> + fromPortRef.dereference(this) + } + editor.incomingLinks.putAll(resolvedIncomingLinks) } } diff --git a/src/commonTest/kotlin/baaahs/show/MutableShowSpec.kt b/src/commonTest/kotlin/baaahs/show/MutableShowSpec.kt index eb0550a1fc..8f64ff59ae 100644 --- a/src/commonTest/kotlin/baaahs/show/MutableShowSpec.kt +++ b/src/commonTest/kotlin/baaahs/show/MutableShowSpec.kt @@ -1,79 +1,93 @@ -//package baaahs.show -// -//import baaahs.ShowState -//import baaahs.gl.override -//import baaahs.gl.patch.AutoWirer -//import baaahs.glsl.Shaders.cylindricalProjection -//import baaahs.plugin.Plugins -//import baaahs.show.mutable.MutableButtonGroupControl -//import baaahs.show.mutable.MutablePatch -//import baaahs.show.mutable.MutableShow -//import baaahs.show.mutable.ShowBuilder -//import org.spekframework.spek2.Spek -//import org.spekframework.spek2.style.specification.describe -//import kotlin.test.expect -// -//object MutableShowSpec : Spek({ -// describe("MutableShow") { -// val autoWirer by value { AutoWirer(Plugins.safe()) } -// -// val shader1a by value { autoWirer.testPatch("shader 1a") } -// val shader2a by value { autoWirer.testPatch("shader 2a") } -// -// val baseMutableShow by value { -// MutableShow("test show").apply { -// addScene("scene 1") { -// addPatchSet("patchset 1a") { addPatch(shader1a) } -// } -// addScene("scene 2") { -// addPatchSet("patchset 2a") { addPatch(shader2a) } -// addPatchSet("patchset 2b") { addPatch(autoWirer.testPatch("shader 2b")) } -// addPatchSet("patchset 2c") { addPatch(autoWirer.testPatch("shader 2c")) } -// } -// addControl("Scenes", MutableButtonGroupControl("Scenes")) -// } -// } -// val baseShow by value { baseMutableShow.build(ShowBuilder()) } +package baaahs.show + +import baaahs.gl.patch.AutoWirer +import baaahs.only +import baaahs.plugin.Plugins +import baaahs.show.mutable.MutableConstPort +import baaahs.show.mutable.MutablePatch +import baaahs.show.mutable.MutableShow +import baaahs.show.mutable.ShowBuilder +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe +import kotlin.test.expect + +object MutableShowSpec : Spek({ + describe("MutableShow") { + val autoWirer by value { AutoWirer(Plugins.safe()) } + + val shader0 by value { autoWirer.testPatch("shader 0") } + val shader1a by value { autoWirer.testPatch("shader 1a") } + val shader2a by value { autoWirer.testPatch("shader 2a") } + + val baseMutableShow by value { + MutableShow("test show").apply { + addPatch(shader0) + + addButtonGroup("main", "scene 1") { + addButton("patchset 1a") { addPatch(shader1a) } + } + addButtonGroup("main", "scene 2") { + addButton("patchset 2a") { addPatch(shader2a) } + addButton("patchset 2b") { addPatch(autoWirer.testPatch("shader 2b")) } + addButton("patchset 2c") { addPatch(autoWirer.testPatch("shader 2c")) } + } +// addControl("Scenes", MutableButtonGroupControl("Scenes", ButtonGroupControl.Direction.Horizontal, this)) + } + } + val baseShow by value { baseMutableShow.build(ShowBuilder()) } // fun Show.showState() = ShowState.from(this).selectScene(1).selectPatchSet(1) // val baseShowState by value { baseShow.showState() } -// val mutableShow by value { MutableShow(baseShow, baseShowState) } -// val show by value { mutableShow.build(ShowBuilder()) } + val mutableShow by value { MutableShow(baseShow) } + val show by value { mutableShow.build(ShowBuilder()) } // val showState by value { mutableShow.getShowState() } -// -// context("base show") { + + context("base show") { // it("has the expected initial scenes and patchsets") { // expect( // listOf("scene 1 (patchset 1a)", "scene 2 (patchset 2a, patchset 2b, patchset 2c)") // ) { show.desc() } // } -// -// it("has the expected initial shaders") { -// expect( -// setOf("Cylindrical Projection", "shader 1a", "shader 2a", "shader 2b", "shader 2c") -// ) { show.shaders.values.map { it.title }.toSet() } -// } -// -// it("has the expected initial datasources") { -// expect( -// setOf("Pixel Coordinates Texture", "Model Info", "Time", "Resolution", "Blueness Slider") -// ) { show.dataSources.values.map { it.dataSourceName }.toSet() } -// } -// -// it("has the expected initial state") { -// expect(ShowState(1, listOf(0, 1))) { showState } -// } -// } -// -// it("leaves everything as-is if no changes are made") { -// expect(show) { baseShow } -// expect(showState) { baseShowState } -// } -// + + it("has the expected initial shaders") { + expect( + setOf("shader 0", "shader 1a", "shader 2a", "shader 2b", "shader 2c") + ) { show.shaders.values.map { it.title }.toSet() } + } + + it("has the expected initial datasources") { + expect( + setOf("Time", "Resolution", "Blueness Slider") + ) { show.dataSources.values.map { it.dataSourceName }.toSet() } + } + } + + it("leaves everything as-is if no changes are made") { + expect(show) { baseShow } + } + + context("editing a shader instance") { + val editor by value { mutableShow.patches.only().mutableShaderInstances.only() } + + context("when weird port mappings are added") { + beforeEachTest { + editor.incomingLinks["nonsense"] = MutableConstPort("invalid") + } + + it("should retain them, I guess?") { + val id = show.patches.only().shaderInstanceIds.only() + val shaderInstance = show.shaderInstances[id]!! + expect( + setOf("nonsense", "time", "blueness", "resolution", "gl_FragCoord") + ) { shaderInstance.incomingLinks.keys } + } + } + } + // context("adding a patchset") { // beforeEachTest { // mutableShow.apply { // editScene(1) { -// addPatchSet("patchset 2b") { addPatch(autoWirer.testPatch("shader 2b")) } +// addButton("patchset 2b") { addPatch(autoWirer.testPatch("shader 2b")) } // } // } // } @@ -84,7 +98,7 @@ // ) { show.shaders.values.map { it.title }.toSet() } // } // } -// + // context("editing a patchset") { // beforeEachTest { // mutableShow.editScene(1) { @@ -98,15 +112,15 @@ // ) { show.desc() } // } // } -// + // context("reordering scenes") { // val fromIndex = 1 // val toIndex = 0 // // beforeEachTest { // baseMutableShow.apply { -// addScene("scene 3") { -// addPatchSet("patchset 3a") { addPatch(autoWirer.testPatch("shader 3a")) } +// addButtonGroup("scene 3") { +// addButton("patchset 3a") { addPatch(autoWirer.testPatch("shader 3a")) } // } // } // @@ -149,7 +163,7 @@ // } // // } -// + // context("reordering patchsets") { // val fromIndex = 1 // val toIndex = 2 @@ -189,56 +203,59 @@ // } // } // } -// -// context("editing MutablePatchHolders") { -// it("adds to existing patch for the given surface, if it exists") { -// mutableShow.addPatch(autoWirer.testPatch("show shader 1a")) -// mutableShow.addPatch(autoWirer.testPatch("show shader 1b")) -// -// expect( -// mapOf(Surfaces.AllSurfaces to listOf( -// "Cylindrical Projection", "show shader 1a", -// "Cylindrical Projection", "show shader 1b" -// )) -// ) { -// show.patches.map { patch -> -// patch.surfaces to -// patch.shaderInstanceIds.map { -// show.shaderInstances[it]?.shaderId?.let { shaderId -> -// show.shaders[shaderId]?.title -// } ?: "?!?" -// } -// }.associate { it } -// } -// } -// } -// } -//}) -// -//private fun AutoWirer.testPatch(title: String): MutablePatch { -// val shader = Shader( -// title, ShaderType.Paint, """ -// // $title -// uniform float time; -// uniform vec2 resolution; -// uniform float blueness; -// int someGlobalVar; -// const int someConstVar = 123; -// -// int anotherFunc(int i) { return i; } -// -// void main( void ) { -// vec2 uv = gl_FragCoord.xy / resolution.xy; -// someGlobalVar = anotherFunc(someConstVar); -// gl_FragColor = vec4(uv.xy, blueness, 1.); -// } -// """.trimIndent() -// ) -// -// return autoWire(cylindricalProjection, shader) -// .acceptSymbolicChannelLinks() -// .resolve() -//} -// + + context("editing MutablePatchHolders") { + it("adds to existing patch for the given surface, if it exists") { + mutableShow.addPatch(autoWirer.testPatch("show shader 1a")) + mutableShow.addPatch(autoWirer.testPatch("show shader 1b")) + + expect( + mapOf( + Surfaces.AllSurfaces to listOf( + "shader 0", + "show shader 1a", + "show shader 1b" + ) + ) + ) { + show.patches.map { patch -> + patch.surfaces to + patch.shaderInstanceIds.map { + show.shaderInstances[it]?.shaderId?.let { shaderId -> + show.shaders[shaderId]?.title + } ?: "?!?" + } + }.associate { it } + } + } + } + } +}) + +private fun AutoWirer.testPatch(title: String): MutablePatch { + val shader = Shader( + title, ShaderType.Paint, """ + // $title + uniform float time; + uniform vec2 resolution; + uniform float blueness; + int someGlobalVar; + const int someConstVar = 123; + + int anotherFunc(int i) { return i; } + + void main( void ) { + vec2 uv = gl_FragCoord.xy / resolution.xy; + someGlobalVar = anotherFunc(someConstVar); + gl_FragColor = vec4(uv.xy, blueness, 1.); + } + """.trimIndent() + ) + + return autoWire(shader) + .acceptSymbolicChannelLinks() + .resolve() +} + //private fun Show.desc(): List = // scenes.map { "${it.title} (${it.patchSets.joinToString(", ") { it.title }})" } \ No newline at end of file diff --git a/src/commonTest/kotlin/baaahs/show/live/OpenShowSpec.kt b/src/commonTest/kotlin/baaahs/show/live/OpenShowSpec.kt index b013030f04..3f8762b89b 100644 --- a/src/commonTest/kotlin/baaahs/show/live/OpenShowSpec.kt +++ b/src/commonTest/kotlin/baaahs/show/live/OpenShowSpec.kt @@ -1,9 +1,11 @@ package baaahs.show.live import baaahs.gl.patch.AutoWirer +import baaahs.only import baaahs.plugin.Plugins import baaahs.show.Layout import baaahs.show.Layouts +import baaahs.show.mutable.MutableConstPort import baaahs.show.mutable.MutableShow import baaahs.show.mutable.ShowBuilder import baaahs.shows.FakeGlContext @@ -74,5 +76,21 @@ object OpenShowSpec : Spek({ } } } + + context("when a shader instance has weird incoming links") { + beforeEachTest { + mutableShow.addPatch( + autoWirer.wireUp(fakeShader("Weird Shader")).apply { + mutableShaderInstances.only().incomingLinks["nonsense"] = MutableConstPort("invalid") + } + ) + } + + it("ignores links to unknown ports") { + expect( + setOf("gl_FragCoord", "time") + ) { openShow.patches.only().shaderInstances.only().incomingLinks.keys } + } + } } }) \ No newline at end of file diff --git a/src/jsMain/kotlin/baaahs/app/ui/editor/LinksEditor.kt b/src/jsMain/kotlin/baaahs/app/ui/editor/LinksEditor.kt index 404befb13b..65f9ec1179 100644 --- a/src/jsMain/kotlin/baaahs/app/ui/editor/LinksEditor.kt +++ b/src/jsMain/kotlin/baaahs/app/ui/editor/LinksEditor.kt @@ -47,9 +47,10 @@ val LinksEditor = xComponent("LinksEditor") { props -> val shaderInstance = props.mutableShaderInstance val shader = shaderInstance.mutableShader val openShader = appContext.showPlayer.openShaderOrNull(shader.build()) - val inputPorts = openShader?.inputPorts - ?.sortedBy { it.title } - ?.associateWith { inputPort -> + val inputPorts = (openShader?.inputPorts ?: emptyList()).associateBy { it.id } + val inputPortChangeHandlers = inputPorts.values + .sortedBy { it.title } + .associateWith { inputPort -> handler( "change to ${inputPort.id}", props.mutableShaderInstance, props.editableManager ) { sourcePortOption: SourcePortOption? -> @@ -59,6 +60,13 @@ val LinksEditor = xComponent("LinksEditor") { props -> } else { incomingLinks[inputPort.id] = sourcePortOption.portEditor } + + // Prune any unknown port mappings (e.g. if a uniform was removed). + incomingLinks.keys.minus(inputPorts.keys).forEach { unknownKey -> + logger.debug { "Removing unknown mapping for unknown port \"$unknownKey\"" } + incomingLinks.remove(unknownKey) + } + props.editableManager.onChange() } } @@ -74,7 +82,7 @@ val LinksEditor = xComponent("LinksEditor") { props -> } tableBody { - inputPorts?.forEach { (inputPort, handleSourceChange) -> + inputPortChangeHandlers?.forEach { (inputPort, handleSourceChange) -> val currentSourcePort = shaderInstance.incomingLinks[inputPort.id] tableRow {