From 596c94f6f0e2ee8287bd82b40daedc072042873d Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Thu, 20 Sep 2018 11:09:15 -0700 Subject: [PATCH] Tungsten: normal mapping capabilities (#274) * Ability to add code prior to prepareMaterial * Add normal input on shading node * Add constant float node * Add multiply and subtract nodes * Split math nodes out into MathNodes.kt --- .../filament/tungsten/compiler/Expression.kt | 4 +- .../tungsten/compiler/GraphCompiler.java | 29 +++- .../tungsten/compiler/GraphFormatter.java | 11 +- .../tungsten/compiler/NodeRegistry.kt | 12 +- .../android/filament/tungsten/model/Graph.kt | 3 +- .../filament/tungsten/model/MathNodes.kt | 136 ++++++++++++++++++ .../android/filament/tungsten/model/Nodes.kt | 76 +++++----- .../filament/tungsten/model/Properties.kt | 14 ++ .../tungsten/properties/PropertyEditors.kt | 69 +++++++++ .../filament/tungsten/model/NodeTest.kt | 59 ++++++++ 10 files changed, 367 insertions(+), 46 deletions(-) create mode 100644 tools/tungsten/core/src/com/google/android/filament/tungsten/model/MathNodes.kt create mode 100644 tools/tungsten/core/test/com/google/android/filament/tungsten/model/NodeTest.kt diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/Expression.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/Expression.kt index e9b6951773..2e2acf8d47 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/Expression.kt +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/Expression.kt @@ -56,9 +56,11 @@ open class Expression( } } +fun floatTypeName(dimensions: Int) = if (dimensions > 1) "float$dimensions" else "float" + private fun createFloatLiteral(dimensions: Int): String { val zeroes = "0.0, ".repeat(maxOf(dimensions - 1, 0)) + "0.0" - val typeName = if (dimensions > 1) "float$dimensions" else "float" + val typeName = floatTypeName(dimensions) return "$typeName($zeroes)" } diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphCompiler.java b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphCompiler.java index d5288067c8..c39ccc4031 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphCompiler.java +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphCompiler.java @@ -36,7 +36,19 @@ import org.jetbrains.annotations.Nullable; */ public final class GraphCompiler { + public enum CodeSection { + AFTER_PREPARE_MATERIAL, + BEFORE_PREPARE_MATERIAL + } + + // Material function source text before the "prepareMaterial" call + private StringBuilder mMaterialFunctionPrologue = new StringBuilder(); + + // Material function source text after the "prepareMaterial" call private StringBuilder mMaterialFunctionBodyBuilder = new StringBuilder(); + + private CodeSection mCurrentCodeSection = CodeSection.AFTER_PREPARE_MATERIAL; + private final List mRequiredAttributes = new ArrayList<>(); private String mShadingModel = "unlit"; private final List mParameters = new ArrayList<>(); @@ -73,7 +85,7 @@ public final class GraphCompiler { } String fragmentSection = GraphFormatter.formatFragmentSection(mGlobalFunctions.values(), - mMaterialFunctionBodyBuilder.toString()); + mMaterialFunctionPrologue.toString(), mMaterialFunctionBodyBuilder.toString()); String materialDefinition = GraphFormatter.formatMaterialSection(mRequiredAttributes, mParameters, mShadingModel) + fragmentSection; @@ -181,7 +193,20 @@ public final class GraphCompiler { if (!mShouldAppendCode) { return; } - mMaterialFunctionBodyBuilder.append(code); + if (mCurrentCodeSection == CodeSection.AFTER_PREPARE_MATERIAL) { + mMaterialFunctionBodyBuilder.append(code); + } else if (mCurrentCodeSection == CodeSection.BEFORE_PREPARE_MATERIAL) { + mMaterialFunctionPrologue.append(code); + } + } + + /** + * Set the state of this GraphCompiler to output code to a specific section of the material + * function body: either before the call to prepareMaterial(), or after. + * The state persists until setCurrentCodeSection is called again. + */ + public void setCurrentCodeSection(CodeSection section) { + mCurrentCodeSection = section; } /** diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphFormatter.java b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphFormatter.java index a1d6311a38..b541860b27 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphFormatter.java +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/GraphFormatter.java @@ -30,7 +30,7 @@ final class GraphFormatter { private GraphFormatter() { } static String formatFragmentSection(Collection globalFunctions, - String materialFunctionBody) { + String materialFunctionBodyPrologue, String materialFunctionBody) { StringBuilder functions = new StringBuilder(); for (String functionBody : globalFunctions) { functions.append(indent(functionBody, FUNCTION_INDENT_AMOUNT)); @@ -39,7 +39,7 @@ final class GraphFormatter { return "fragment {\n" + functions.toString() + " void material(inout MaterialInputs material) {\n" - + formatMaterialFunctionBody(materialFunctionBody) + + formatMaterialFunctionBody(materialFunctionBodyPrologue ,materialFunctionBody) + " }\n" + "}"; } @@ -75,9 +75,10 @@ final class GraphFormatter { return t.replaceAll("\n(?!$)", "\n" + spaces); // indent remaining non-empty lines } - private static String formatMaterialFunctionBody(String code) { - return indent("prepareMaterial(material);\n", BODY_CODE_INDENT_AMOUNT) - + indent(code, BODY_CODE_INDENT_AMOUNT); + private static String formatMaterialFunctionBody(String prologue, String epilogue) { + return indent(prologue, BODY_CODE_INDENT_AMOUNT) + + indent("prepareMaterial(material);\n", BODY_CODE_INDENT_AMOUNT) + + indent(epilogue, BODY_CODE_INDENT_AMOUNT); } private static String formatParameters(Collection parameters) { diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/NodeRegistry.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/NodeRegistry.kt index 686cb880fa..223b01da2c 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/NodeRegistry.kt +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/compiler/NodeRegistry.kt @@ -18,11 +18,15 @@ package com.google.android.filament.tungsten.compiler import com.google.android.filament.tungsten.model.Node import com.google.android.filament.tungsten.model.NodeId -import com.google.android.filament.tungsten.model.createAdderNode +import com.google.android.filament.tungsten.model.createAddNode +import com.google.android.filament.tungsten.model.createDivideNode import com.google.android.filament.tungsten.model.createFloat2ConstantNode import com.google.android.filament.tungsten.model.createFloat3ConstantNode import com.google.android.filament.tungsten.model.createFloat3ParameterNode +import com.google.android.filament.tungsten.model.createFloatConstantNode +import com.google.android.filament.tungsten.model.createMultiplyNode import com.google.android.filament.tungsten.model.createShaderNode +import com.google.android.filament.tungsten.model.createSubtractNode import com.google.android.filament.tungsten.model.createTextureSampleNode import com.google.android.filament.tungsten.model.serialization.INodeFactory @@ -44,7 +48,11 @@ class NodeRegistry : INodeFactory { init { mNodes = listOf( - NodeEntry("Add", "adder", createAdderNode), + NodeEntry("Add", "add", createAddNode), + NodeEntry("Subtract", "subtract", createSubtractNode), + NodeEntry("Multiply", "multiply", createMultiplyNode), + NodeEntry("Divide", "divide", createDivideNode), + NodeEntry("Constant float", "floatConstant", createFloatConstantNode), NodeEntry("Constant float2", "float2Constant", createFloat2ConstantNode), NodeEntry("Constant float3", "float3Constant", createFloat3ConstantNode), NodeEntry("Float3 parameter", "float3Parameter", createFloat3ParameterNode), diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Graph.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Graph.kt index 773547e90f..5b1afc480a 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Graph.kt +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Graph.kt @@ -28,6 +28,7 @@ data class Connection( abstract class Slot typealias NodeId = Int +typealias CompileFunction = (Node, GraphCompiler) -> Node /** * An immutable Node in the graph. @@ -42,7 +43,7 @@ data class Node( // A node's compile function generates code by calling methods on a GraphCompiler. The function // can return a new node if compilation has changed properties of the node itself. - val compileFunction: (Node, GraphCompiler) -> Node = { n, _ -> n }, + val compileFunction: CompileFunction = { n, _ -> n }, // Input and output slots of a node are represented by a String name. val outputSlots: List = emptyList(), diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/MathNodes.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/MathNodes.kt new file mode 100644 index 0000000000..46ed6198b2 --- /dev/null +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/MathNodes.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament.tungsten.model + +import com.google.android.filament.tungsten.compiler.Expression +import com.google.android.filament.tungsten.compiler.Literal +import com.google.android.filament.tungsten.compiler.floatTypeName +import com.google.android.filament.tungsten.compiler.trim + +data class ResolvedExpressions( + val a: Expression, + val b: Expression, + val resultDimensionality: Int +) + +// A function that takes two expressions and prepares them for a math operation. +typealias ExpressionResolver = (Expression?, Expression?) -> ResolvedExpressions + +typealias Operation = (Expression, Expression) -> String + +/** + * Create a math node compile function with a given operation. + * Assumes two input slots, "a" and "b", and a single output, "out". + */ +private fun createMathCompileFunction( + expressionResolver: ExpressionResolver, + operation: Operation, + temporaryVariable: String +): CompileFunction = { node, compiler -> + val a = compiler.compileAndRetrieveExpression(node.getInputSlot("a")) + val b = compiler.compileAndRetrieveExpression(node.getInputSlot("b")) + + val (aExpr, bExpr, resultDimensionality) = expressionResolver(a, b) + + compiler.setExpressionForSlot(node.getInputSlot("a"), aExpr) + compiler.setExpressionForSlot(node.getInputSlot("b"), bExpr) + + val temp = compiler.getNewTemporaryVariableName(temporaryVariable) + compiler.addCodeToMaterialFunctionBody( + "${floatTypeName(resultDimensionality)} $temp = ${operation(aExpr, bExpr)};\n") + + val output = node.getOutputSlot("out") + compiler.setExpressionForSlot(output, Expression(temp, resultDimensionality)) + + node + } + +/** + * Trims two expressions to be the same dimensionality, preferring the one with fewer dimensions. + * If one of the inputs is a single float, neither input is modified, allowing for a scalar + * operations. + */ +internal fun resolveInputExpressions(a: Expression?, b: Expression?): ResolvedExpressions { + val aExpr = a ?: Literal(4) + val bExpr = b ?: Literal(4) + val (trimmedA, trimmedB) = if (aExpr.dimensions > 1 && bExpr.dimensions > 1) { + trim(aExpr, bExpr) + } else { + Pair(aExpr, bExpr) + } + return ResolvedExpressions(trimmedA, trimmedB, maxOf(trimmedA.dimensions, trimmedB.dimensions)) +} + +private val subtractNodeCompile = createMathCompileFunction( + ::resolveInputExpressions, + { a, b -> "$a - $b" }, + "subtract" +) + +private val addNodeCompile = createMathCompileFunction( + ::resolveInputExpressions, + { a, b -> "$a + $b" }, + "add" +) + +private val multiplyNodeCompile = createMathCompileFunction( + ::resolveInputExpressions, + { a, b -> "$a * $b" }, + "multiply" +) + +private val divideNodeCompile = createMathCompileFunction( + ::resolveInputExpressions, + { a, b -> "$a / $b" }, + "divide" +) + +val createAddNode = fun(id: NodeId) = + Node( + id = id, + type = "add", + compileFunction = addNodeCompile, + inputSlots = listOf("a", "b"), + outputSlots = listOf("out") + ) + +val createSubtractNode = fun(id: NodeId) = + Node( + id = id, + type = "subtract", + compileFunction = subtractNodeCompile, + inputSlots = listOf("a", "b"), + outputSlots = listOf("out") + ) + +val createMultiplyNode = fun(id: NodeId) = + Node( + id = id, + type = "multiply", + compileFunction = multiplyNodeCompile, + inputSlots = listOf("a", "b"), + outputSlots = listOf("out") + ) + +val createDivideNode = fun(id: NodeId) = + Node( + id = id, + type = "divide", + compileFunction = divideNodeCompile, + inputSlots = listOf("a", "b"), + outputSlots = listOf("out") + ) diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Nodes.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Nodes.kt index b956b47d63..1a99647e07 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Nodes.kt +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Nodes.kt @@ -19,36 +19,15 @@ package com.google.android.filament.tungsten.model import com.google.android.filament.tungsten.compiler.GraphCompiler import com.google.android.filament.tungsten.compiler.Expression import com.google.android.filament.tungsten.compiler.Literal -import com.google.android.filament.tungsten.compiler.trim import com.google.android.filament.tungsten.properties.ColorChooser +import com.google.android.filament.tungsten.properties.FloatSlider import com.google.android.filament.tungsten.properties.MultipleChoice import com.google.android.filament.tungsten.properties.TextureFileChooser -private val adderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node { - val outputSlot = node.getOutputSlot("result") - - val a = compiler.compileAndRetrieveExpression(node.getInputSlot("a")) - val b = compiler.compileAndRetrieveExpression(node.getInputSlot("b")) - - // Trim the expressions so that they're the same dimension. - val (aExpression, bExpression) = trim(a ?: Literal(4), b ?: Literal(4)) - - compiler.setExpressionForSlot(node.getInputSlot("a"), aExpression) - compiler.setExpressionForSlot(node.getInputSlot("b"), bExpression) - - val temp = compiler.getNewTemporaryVariableName("adder") - val resultDimensions = aExpression.dimensions - compiler.addCodeToMaterialFunctionBody( - "float$resultDimensions $temp = $aExpression + $bExpression;\n") - - compiler.setExpressionForSlot(outputSlot, Expression(temp, resultDimensions)) - - return node -} - private val UNLIT_INPUTS = listOf("baseColor", "emissive") -private val LIT_INPUTS = listOf("baseColor", "metallic", "roughness", "reflectance", "clearCoat", - "clearCoatRoughness", "anisotropy", "anisotropyDirection", "ambientOcclusion", "emissive") +private val LIT_INPUTS = listOf("normal", "baseColor", "metallic", "roughness", "reflectance", + "clearCoat", "clearCoatRoughness", "anisotropy", "anisotropyDirection", "ambientOcclusion", + "emissive") private val inputSlotsForShadingModel = { shadingModel: String -> when (shadingModel) { @@ -63,6 +42,11 @@ private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node { compiler.setShadingModel(shadingModel) val compileMaterialInput = { name: String, dimensions: Int -> + // The "normal" input is special, and its code must go before the call to prepareMaterial(). + if (name == "normal") { + compiler.setCurrentCodeSection(GraphCompiler.CodeSection.BEFORE_PREPARE_MATERIAL) + } + val inputSlot = node.getInputSlot(name) val connectedExpression = compiler.compileAndRetrieveExpression(inputSlot) val isConnected = connectedExpression != null @@ -75,6 +59,8 @@ private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node { compiler.addCodeToMaterialFunctionBody("material.$name = $conformedExpression;\n") } compiler.setExpressionForSlot(inputSlot, conformedExpression) + + compiler.setCurrentCodeSection(GraphCompiler.CodeSection.AFTER_PREPARE_MATERIAL) } // Compile inputs common to all shading models. @@ -95,6 +81,11 @@ private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node { compileMaterialInput("ambientOcclusion", 1) } + if (shadingModel == "lit") { + // "normal" must be compiled first. If it has any dependencies that are connected to other + // inputs, we need to ensure their code comes before prepareMaterial() + compileMaterialInput("normal", 3) + } compileCommonInputs() when (shadingModel) { "lit" -> compileLitInputs() @@ -127,6 +118,18 @@ private val float3ParameterNodeCompile = fun(node: Node, compiler: GraphCompiler return node } +private val constantFloatNodeCompile = fun(node: Node, compiler: GraphCompiler): Node { + val value = (node.properties[0].value as FloatValue) + + val outputVariable = compiler.getNewTemporaryVariableName("floatConstant") + compiler.addCodeToMaterialFunctionBody("float $outputVariable = ${value.v};\n") + + val outputSlot = node.getOutputSlot("out") + compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 1)) + + return node +} + private val constantFloat2NodeCompile = fun(node: Node, compiler: GraphCompiler): Node { val outputSlot = node.getOutputSlot("result") @@ -175,16 +178,6 @@ val createTextureSampleNode = fun(id: NodeId) = )) ) -val createAdderNode = fun(id: NodeId): Node { - return Node( - id = id, - type = "adder", - compileFunction = adderNodeCompile, - inputSlots = listOf("a", "b"), - outputSlots = listOf("result") - ) -} - val createFloat3ConstantNode = fun(id: NodeId): Node { return Node( id = id, @@ -212,6 +205,19 @@ val createFloat3ParameterNode = fun(id: NodeId): Node { ) } +val createFloatConstantNode = fun(id: NodeId) = + Node( + id = id, + type = "floatConstant", + compileFunction = constantFloatNodeCompile, + outputSlots = listOf("out"), + properties = listOf(Property( + name = "value", + value = FloatValue(), + editorFactory = ::FloatSlider + )) + ) + val createFloat2ConstantNode = fun(id: NodeId): Node { return Node( id = id, diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Properties.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Properties.kt index 3a6cdde86a..41cb7b89b7 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Properties.kt +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/model/Properties.kt @@ -39,6 +39,20 @@ sealed class PropertyValue { abstract fun deserialize(value: Any): PropertyValue } +data class FloatValue(val v: Float = 0.0f) : PropertyValue() { + + override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) { + materialInstance.setParameter(name, v) + } + + override fun serialize() = v + + override fun deserialize(value: Any): PropertyValue { + if (value !is Number) return this + return FloatValue(value.toFloat()) + } +} + data class Float3(val x: Float = 0.0f, val y: Float = 0.0f, val z: Float = 0.0f) : PropertyValue() { override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) { diff --git a/tools/tungsten/core/src/com/google/android/filament/tungsten/properties/PropertyEditors.kt b/tools/tungsten/core/src/com/google/android/filament/tungsten/properties/PropertyEditors.kt index 483fbcae39..6416c50302 100644 --- a/tools/tungsten/core/src/com/google/android/filament/tungsten/properties/PropertyEditors.kt +++ b/tools/tungsten/core/src/com/google/android/filament/tungsten/properties/PropertyEditors.kt @@ -17,17 +17,21 @@ package com.google.android.filament.tungsten.properties import com.google.android.filament.tungsten.model.Float3 +import com.google.android.filament.tungsten.model.FloatValue import com.google.android.filament.tungsten.model.PropertyValue import com.google.android.filament.tungsten.model.StringValue import com.google.android.filament.tungsten.model.TextureFile import com.google.android.filament.tungsten.texture.TextureUtils import java.awt.Color +import java.text.NumberFormat import javax.swing.JButton import javax.swing.JColorChooser import javax.swing.JComboBox import javax.swing.JComponent import javax.swing.JFileChooser +import javax.swing.JFormattedTextField import javax.swing.JPanel +import javax.swing.JSlider import kotlin.math.roundToInt abstract class PropertyEditor { @@ -38,6 +42,71 @@ abstract class PropertyEditor { abstract fun setValue(v: PropertyValue) } +/** + * Set the value of a JSlider without causing any changeListeners to trigger. + */ +fun JSlider.quietlySetValue(v: Int) { + val changeListeners = this.changeListeners + for (changeListener in changeListeners) { + this.removeChangeListener(changeListener) + } + this.value = v + for (changeListener in changeListeners) { + this.addChangeListener(changeListener) + } +} + +internal class FloatSlider(initialValue: FloatValue) : PropertyEditor() { + + private val scaleFactor = 1000 + + override val component: JPanel = JPanel() + private val slider: JSlider + private val field: JFormattedTextField + + private var currentValue: Float = initialValue.v + + override fun setValue(v: PropertyValue) { + val value = v as FloatValue + currentValue = value.v + field.value = currentValue + slider.quietlySetValue((currentValue * scaleFactor).roundToInt()) + } + + init { + slider = JSlider(0, scaleFactor, (initialValue.v * scaleFactor).roundToInt()) + slider.addChangeListener { + if (!slider.valueIsAdjusting) { + currentValue = slider.value.toFloat() / scaleFactor + updateValue() + } + } + + val format = NumberFormat.getNumberInstance() + format.maximumFractionDigits = 5 + field = JFormattedTextField(format) + field.columns = 10 + field.value = initialValue.v + + field.addActionListener { + val newValue = field.value as? Number + newValue?.let { v -> + currentValue = v.toFloat() + updateValue() + } + } + + component.add(slider) + component.add(field) + } + + private fun updateValue() { + slider.quietlySetValue((currentValue * scaleFactor).roundToInt()) + field.value = currentValue + valueChanged(FloatValue(currentValue)) + } +} + internal class ColorChooser(value: Float3) : PropertyEditor() { override val component: JColorChooser diff --git a/tools/tungsten/core/test/com/google/android/filament/tungsten/model/NodeTest.kt b/tools/tungsten/core/test/com/google/android/filament/tungsten/model/NodeTest.kt new file mode 100644 index 0000000000..df98568956 --- /dev/null +++ b/tools/tungsten/core/test/com/google/android/filament/tungsten/model/NodeTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament.tungsten.model + +import com.google.android.filament.tungsten.compiler.Literal +import org.junit.Assert.assertEquals +import org.junit.Test + +class NodeTest { + + @Test + fun `resolveInputExpressions keeps two float4s the same length`() { + val (a, b) = resolveInputExpressions(Literal(4), Literal(4)) + assertEquals(4, a.dimensions) + assertEquals(4, b.dimensions) + } + + @Test + fun `resolveInputExpressions keeps two floats the same length`() { + val (a, b) = resolveInputExpressions(Literal(1), Literal(1)) + assertEquals(1, a.dimensions) + assertEquals(1, b.dimensions) + } + + @Test + fun `resolveInputExpressions trims both a float3 and a float2 to a float2`() { + val (a, b) = resolveInputExpressions(Literal(3), Literal(2)) + assertEquals(2, a.dimensions) + assertEquals(2, b.dimensions) + } + + @Test + fun `resolveInputExpressions allows a float as input for scalar operations`() { + val (a, b) = resolveInputExpressions(Literal(3), Literal(1)) + assertEquals(3, a.dimensions) + assertEquals(1, b.dimensions) + } + + @Test + fun `resolveInputExpressions defaults to float4 when a single input is not present`() { + val (a, b) = resolveInputExpressions(null, Literal(1)) + assertEquals(4, a.dimensions) + assertEquals(1, b.dimensions) + } +} \ No newline at end of file