From 45e3838006df8bc393fe6f4ca1cff999b41167f4 Mon Sep 17 00:00:00 2001 From: John Stiles Date: Thu, 23 Sep 2021 16:26:53 -0400 Subject: [PATCH] Rewrite switch statements in GLSL strict-ES2 mode. Once this lands, switch statements will work everywhere--Metal, SPIR-V, GLSL, and SkVM. Change-Id: I2797d0a872de8be77bb9f7aa6acb93421d571d70 Bug: skia:12450 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/452356 Commit-Queue: John Stiles Auto-Submit: John Stiles Reviewed-by: Brian Osman --- gn/sksl_tests.gni | 4 ++ src/sksl/codegen/SkSLGLSLCodeGenerator.cpp | 62 ++++++++++++++++++- tests/SkSLTest.cpp | 8 +-- tests/sksl/runtime/Switch.glsl | 22 +++++++ tests/sksl/runtime/SwitchDefaultOnly.glsl | 9 +++ tests/sksl/runtime/SwitchWithFallthrough.glsl | 53 ++++++++++++++++ tests/sksl/runtime/SwitchWithLoops.glsl | 52 ++++++++++++++++ 7 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 tests/sksl/runtime/Switch.glsl create mode 100644 tests/sksl/runtime/SwitchDefaultOnly.glsl create mode 100644 tests/sksl/runtime/SwitchWithFallthrough.glsl create mode 100644 tests/sksl/runtime/SwitchWithLoops.glsl diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni index daeb765ef8..c85c46e010 100644 --- a/gn/sksl_tests.gni +++ b/gn/sksl_tests.gni @@ -176,6 +176,10 @@ sksl_glsl_tests = [ "/sksl/glsl/UsesPrecisionModifiers.sksl", "/sksl/glsl/Version110.sksl", "/sksl/glsl/Version450Core.sksl", + "/sksl/runtime/Switch.rts", + "/sksl/runtime/SwitchDefaultOnly.rts", + "/sksl/runtime/SwitchWithFallthrough.rts", + "/sksl/runtime/SwitchWithLoops.rts", ] sksl_metal_tests = [ diff --git a/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp b/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp index d4479db19b..5c86c9415e 100644 --- a/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp +++ b/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp @@ -1355,8 +1355,66 @@ void GLSLCodeGenerator::writeDoStatement(const DoStatement& d) { void GLSLCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { if (fProgram.fConfig->strictES2Mode()) { - // TODO(skia:12450): write switch compatibility code - fContext.fErrors->error(s.fOffset, "switch statements are not supported"); + String fallthroughVar = "_tmpSwitchFallthrough" + to_string(fVarCount++); + String valueVar = "_tmpSwitchValue" + to_string(fVarCount++); + String loopVar = "_tmpSwitchLoop" + to_string(fVarCount++); + this->write("int "); + this->write(valueVar); + this->write(" = "); + this->writeExpression(*s.value(), Precedence::kAssignment); + this->write(", "); + this->write(fallthroughVar); + this->writeLine(" = 0; "); + this->write("for (int "); + this->write(loopVar); + this->write(" = 0; "); + this->write(loopVar); + this->write(" < 1; "); + this->write(loopVar); + this->writeLine("++) {"); + fIndentation++; + + bool firstCase = true; + for (const std::unique_ptr& stmt : s.cases()) { + const SwitchCase& c = stmt->as(); + if (c.value()) { + this->write("if (("); + if (firstCase) { + firstCase = false; + } else { + this->write(fallthroughVar); + this->write(" > 0) || ("); + } + this->write(valueVar); + this->write(" == "); + this->writeExpression(*c.value(), Precedence::kEquality); + this->writeLine(")) {"); + fIndentation++; + + // We write the entire case-block statement here, and then set `switchFallthrough` + // to 1. If the case-block had a break statement in it, we break out of the outer + // for-loop entirely, meaning the `switchFallthrough` assignment never occurs, nor + // does any code after it inside the switch. We've forbidden `continue` statements + // inside switch case-blocks entirely, so we don't need to consider their effect on + // control flow; see the Finalizer in FunctionDefinition::Convert. + this->writeStatement(*c.statement()); + this->finishLine(); + this->write(fallthroughVar); + this->write(" = 1;"); + this->writeLine(); + + fIndentation--; + this->writeLine("}"); + } else { + // This is the default case. Since it's always last, we can just dump in the code. + this->writeStatement(*c.statement()); + this->finishLine(); + } + } + + fIndentation--; + this->writeLine("}"); + return; } this->write("switch ("); diff --git a/tests/SkSLTest.cpp b/tests/SkSLTest.cpp index 2ce704d198..cfc0fccb68 100644 --- a/tests/SkSLTest.cpp +++ b/tests/SkSLTest.cpp @@ -323,10 +323,10 @@ SKSL_TEST(SkSLStaticIf, "shared/StaticIf.sksl") SKSL_TEST_ES3(SkSLStaticSwitch, "shared/StaticSwitch.sksl") SKSL_TEST(SkSLStructArrayFollowedByScalar, "shared/StructArrayFollowedByScalar.sksl") SKSL_TEST(SkSLStructsInFunctions, "shared/StructsInFunctions.sksl") -SKSL_TEST_ES3(SkSLSwitch, "shared/Switch.sksl") -SKSL_TEST_ES3(SkSLSwitchDefaultOnly, "shared/SwitchDefaultOnly.sksl") -SKSL_TEST_ES3(SkSLSwitchWithFallthrough, "shared/SwitchWithFallthrough.sksl") -SKSL_TEST_ES3(SkSLSwitchWithLoops, "shared/SwitchWithLoops.sksl") +SKSL_TEST(SkSLSwitch, "shared/Switch.sksl") +SKSL_TEST(SkSLSwitchDefaultOnly, "shared/SwitchDefaultOnly.sksl") +SKSL_TEST(SkSLSwitchWithFallthrough, "shared/SwitchWithFallthrough.sksl") +SKSL_TEST(SkSLSwitchWithLoops, "shared/SwitchWithLoops.sksl") SKSL_TEST(SkSLSwizzleBoolConstants, "shared/SwizzleBoolConstants.sksl") SKSL_TEST(SkSLSwizzleByConstantIndex, "shared/SwizzleByConstantIndex.sksl") SKSL_TEST(SkSLSwizzleConstants, "shared/SwizzleConstants.sksl") diff --git a/tests/sksl/runtime/Switch.glsl b/tests/sksl/runtime/Switch.glsl new file mode 100644 index 0000000000..42607dad72 --- /dev/null +++ b/tests/sksl/runtime/Switch.glsl @@ -0,0 +1,22 @@ + +uniform vec4 colorGreen; +uniform vec4 colorRed; +vec4 main() { + vec4 color; + int _tmpSwitchValue1 = int(colorGreen.y), _tmpSwitchFallthrough0 = 0; + for (int _tmpSwitchLoop2 = 0; _tmpSwitchLoop2 < 1; _tmpSwitchLoop2++) { + if ((_tmpSwitchValue1 == 0)) { + color = colorRed; + break; + _tmpSwitchFallthrough0 = 1; + } + if ((_tmpSwitchFallthrough0 > 0) || (_tmpSwitchValue1 == 1)) { + color = colorGreen; + break; + _tmpSwitchFallthrough0 = 1; + } + color = colorRed; + break; + } + return color; +} diff --git a/tests/sksl/runtime/SwitchDefaultOnly.glsl b/tests/sksl/runtime/SwitchDefaultOnly.glsl new file mode 100644 index 0000000000..2728d5f111 --- /dev/null +++ b/tests/sksl/runtime/SwitchDefaultOnly.glsl @@ -0,0 +1,9 @@ + +uniform vec4 colorGreen; +uniform vec4 colorRed; +vec4 main() { + int _tmpSwitchValue1 = int(colorGreen.y), _tmpSwitchFallthrough0 = 0; + for (int _tmpSwitchLoop2 = 0; _tmpSwitchLoop2 < 1; _tmpSwitchLoop2++) { + return colorGreen; + } +} diff --git a/tests/sksl/runtime/SwitchWithFallthrough.glsl b/tests/sksl/runtime/SwitchWithFallthrough.glsl new file mode 100644 index 0000000000..30b25d9a0b --- /dev/null +++ b/tests/sksl/runtime/SwitchWithFallthrough.glsl @@ -0,0 +1,53 @@ + +uniform vec4 colorGreen; +uniform vec4 colorRed; +bool switch_fallthrough_twice_bi(int value) { + bool ok = false; + int _tmpSwitchValue1 = value, _tmpSwitchFallthrough0 = 0; + for (int _tmpSwitchLoop2 = 0; _tmpSwitchLoop2 < 1; _tmpSwitchLoop2++) { + if ((_tmpSwitchValue1 == 0)) { + break; + _tmpSwitchFallthrough0 = 1; + } + if ((_tmpSwitchFallthrough0 > 0) || (_tmpSwitchValue1 == 1)) { + { + } + _tmpSwitchFallthrough0 = 1; + } + if ((_tmpSwitchFallthrough0 > 0) || (_tmpSwitchValue1 == 2)) { + { + } + _tmpSwitchFallthrough0 = 1; + } + if ((_tmpSwitchFallthrough0 > 0) || (_tmpSwitchValue1 == 3)) { + ok = true; + break; + _tmpSwitchFallthrough0 = 1; + } + break; + } + return ok; +} +vec4 main() { + int x = int(colorGreen.y); + bool _0_ok = false; + int _tmpSwitchValue4 = x, _tmpSwitchFallthrough3 = 0; + for (int _tmpSwitchLoop5 = 0; _tmpSwitchLoop5 < 1; _tmpSwitchLoop5++) { + if ((_tmpSwitchValue4 == 2)) { + break; + _tmpSwitchFallthrough3 = 1; + } + if ((_tmpSwitchFallthrough3 > 0) || (_tmpSwitchValue4 == 1)) { + { + } + _tmpSwitchFallthrough3 = 1; + } + if ((_tmpSwitchFallthrough3 > 0) || (_tmpSwitchValue4 == 0)) { + _0_ok = true; + break; + _tmpSwitchFallthrough3 = 1; + } + break; + } + return _0_ok && switch_fallthrough_twice_bi(x) ? colorGreen : colorRed; +} diff --git a/tests/sksl/runtime/SwitchWithLoops.glsl b/tests/sksl/runtime/SwitchWithLoops.glsl new file mode 100644 index 0000000000..075e459f97 --- /dev/null +++ b/tests/sksl/runtime/SwitchWithLoops.glsl @@ -0,0 +1,52 @@ + +uniform vec4 colorGreen; +uniform vec4 colorRed; +bool switch_with_continue_in_loop_bi(int x) { + int val = 0; + int _tmpSwitchValue1 = x, _tmpSwitchFallthrough0 = 0; + for (int _tmpSwitchLoop2 = 0; _tmpSwitchLoop2 < 1; _tmpSwitchLoop2++) { + if ((_tmpSwitchValue1 == 1)) { + for (int i = 0;i < 10; ++i) { + ++val; + continue; + ++val; + } + _tmpSwitchFallthrough0 = 1; + } + ++val; + } + return val == 11; +} +bool loop_with_break_in_switch_bi(int x) { + int val = 0; + for (int i = 0;i < 10; ++i) { + int _tmpSwitchValue4 = x, _tmpSwitchFallthrough3 = 0; + for (int _tmpSwitchLoop5 = 0; _tmpSwitchLoop5 < 1; _tmpSwitchLoop5++) { + if ((_tmpSwitchValue4 == 1)) { + ++val; + break; + _tmpSwitchFallthrough3 = 1; + } + return false; + } + ++val; + } + return val == 20; +} +vec4 main() { + int x = int(colorGreen.y); + int _0_val = 0; + int _tmpSwitchValue7 = x, _tmpSwitchFallthrough6 = 0; + for (int _tmpSwitchLoop8 = 0; _tmpSwitchLoop8 < 1; _tmpSwitchLoop8++) { + if ((_tmpSwitchValue7 == 1)) { + for (int _1_i = 0;_1_i < 10; ++_1_i) { + ++_0_val; + break; + ++_0_val; + } + _tmpSwitchFallthrough6 = 1; + } + ++_0_val; + } + return (_0_val == 2 && switch_with_continue_in_loop_bi(x)) && loop_with_break_in_switch_bi(x) ? colorGreen : colorRed; +}