skia2/tests/SkSLFPTest.cpp

780 lines
31 KiB
C++
Raw Normal View History

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLStringStream.h"
#include "tests/Test.h"
static void test(skiatest::Reporter* r, const char* src, const GrShaderCaps& caps,
std::vector<const char*> expectedH, std::vector<const char*> expectedCPP) {
SkSL::Program::Settings settings;
settings.fCaps = &caps;
SkSL::Compiler compiler;
SkSL::StringStream output;
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
SkSL::Program::kFragmentProcessor_Kind,
SkSL::String(src),
settings);
if (!program) {
SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
return;
}
REPORTER_ASSERT(r, program);
bool success = compiler.toH(*program, "Test", output);
if (!success) {
SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
}
REPORTER_ASSERT(r, success);
if (success) {
for (const char* expected : expectedH) {
bool found = strstr(output.str().c_str(), expected);
if (!found) {
SkDebugf("HEADER MISMATCH:\nsource:\n%s\n\nexpected:\n'%s'\n\nreceived:\n'%s'", src,
expected, output.str().c_str());
}
REPORTER_ASSERT(r, found);
}
}
output.reset();
success = compiler.toCPP(*program, "Test", output);
if (!success) {
SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
}
REPORTER_ASSERT(r, success);
if (success) {
for (const char* expected : expectedCPP) {
bool found = strstr(output.str().c_str(), expected);
if (!found) {
SkDebugf("CPP MISMATCH:\nsource:\n%s\n\nexpected:\n'%s'\n\nreceived:\n'%s'", src,
expected, output.str().c_str());
}
REPORTER_ASSERT(r, found);
}
}
}
static void test_failure(skiatest::Reporter* r, const char* src, const char* error) {
SkSL::Compiler compiler;
SkSL::Program::Settings settings;
sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
settings.fCaps = caps.get();
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
SkSL::Program::kFragmentProcessor_Kind,
SkSL::String(src),
settings);
if (!compiler.errorCount()) {
compiler.optimize(*program);
}
SkSL::String skError(error);
if (compiler.errorText() != skError) {
SkDebugf("SKSL ERROR:\n source: %s\n expected: %s received: %s", src, error,
compiler.errorText().c_str());
}
REPORTER_ASSERT(r, compiler.errorText() == skError);
}
DEF_TEST(SkSLFPHelloWorld, r) {
test(r,
"/* HEADER */"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"/* HEADER */\n"
"\n"
"/**************************************************************************************************\n"
" *** This file was autogenerated from GrTest.fp; do not modify.\n"
" **************************************************************************************************/\n"
"#ifndef GrTest_DEFINED\n"
"#define GrTest_DEFINED\n"
"#include \"include/core/SkTypes.h\"\n"
"#include \"include/core/SkM44.h\"\n\n"
"#include \"src/gpu/GrCoordTransform.h\"\n"
"#include \"src/gpu/GrFragmentProcessor.h\"\n"
"class GrTest : public GrFragmentProcessor {\n"
"public:\n"
" static std::unique_ptr<GrFragmentProcessor> Make() {\n"
" return std::unique_ptr<GrFragmentProcessor>(new GrTest());\n"
" }\n"
" GrTest(const GrTest& src);\n"
" std::unique_ptr<GrFragmentProcessor> clone() const override;\n"
" const char* name() const override { return \"Test\"; }\n"
"private:\n"
" GrTest()\n"
" : INHERITED(kGrTest_ClassID, kNone_OptimizationFlags) {\n"
" }\n"
" GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;\n"
" void onGetGLSLProcessorKey(const GrShaderCaps&,GrProcessorKeyBuilder*) const "
"override;\n"
" bool onIsEqual(const GrFragmentProcessor&) const override;\n"
" GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n"
" typedef GrFragmentProcessor INHERITED;\n"
"};\n"
"#endif\n"
},
{
"/**************************************************************************************************\n"
" *** This file was autogenerated from GrTest.fp; do not modify.\n"
" **************************************************************************************************/\n"
"#include \"GrTest.h\"\n\n"
Reland "Move GrGpuResource GrSurface and GrTexture into src." This reverts commit f6ed96d1c23b79130ca7344c984b07ef9d94fb7b. Reason for revert: google3 change landed Original change's description: > Revert "Move GrGpuResource GrSurface and GrTexture into src." > > This reverts commit e5a06ce678aad7640411f99f70f220f82ad49908. > > Reason for revert: Need to make change in google3 first > > Original change's description: > > Move GrGpuResource GrSurface and GrTexture into src. > > > > Must land https://chromium-review.googlesource.com/c/chromium/src/+/2087980 > > before this can land. > > > > Bug: skia:7966 > > Change-Id: I60bbb1765bfbb2c96b2bc0c9826b6b9d57eb2a03 > > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/275077 > > Commit-Queue: Greg Daniel <egdaniel@google.com> > > Reviewed-by: Robert Phillips <robertphillips@google.com> > > TBR=egdaniel@google.com,bsalomon@google.com,robertphillips@google.com > > Change-Id: Id39e0a351e49a87209de88a6ad9fadb0219db72c > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Bug: skia:7966 > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/275216 > Reviewed-by: Greg Daniel <egdaniel@google.com> > Commit-Queue: Greg Daniel <egdaniel@google.com> TBR=egdaniel@google.com,bsalomon@google.com,robertphillips@google.com Change-Id: I746ce739cb084cefc46f9dab24ef773e7c3cc621 No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: skia:7966 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/275436 Reviewed-by: Greg Daniel <egdaniel@google.com> Commit-Queue: Greg Daniel <egdaniel@google.com>
2020-03-05 19:14:18 +00:00
"#include \"src/gpu/GrTexture.h\"\n"
"#include \"src/gpu/glsl/GrGLSLFragmentProcessor.h\"\n"
"#include \"src/gpu/glsl/GrGLSLFragmentShaderBuilder.h\"\n"
"#include \"src/gpu/glsl/GrGLSLProgramBuilder.h\"\n"
"#include \"src/sksl/SkSLCPP.h\"\n"
"#include \"src/sksl/SkSLUtil.h\"\n"
"class GrGLSLTest : public GrGLSLFragmentProcessor {\n"
"public:\n"
" GrGLSLTest() {}\n"
" void emitCode(EmitArgs& args) override {\n"
" GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;\n"
" const GrTest& _outer = args.fFp.cast<GrTest>();\n"
" (void) _outer;\n"
" fragBuilder->codeAppendf(\"%s = half4(1.0);\\n\", args.fOutputColor);\n"
" }\n"
"private:\n"
" void onSetData(const GrGLSLProgramDataManager& pdman, "
"const GrFragmentProcessor& _proc) override {\n"
" }\n"
"};\n"
"GrGLSLFragmentProcessor* GrTest::onCreateGLSLInstance() const {\n"
" return new GrGLSLTest();\n"
"}\n"
"void GrTest::onGetGLSLProcessorKey(const GrShaderCaps& caps, "
"GrProcessorKeyBuilder* b) const {\n"
"}\n"
"bool GrTest::onIsEqual(const GrFragmentProcessor& other) const {\n"
" const GrTest& that = other.cast<GrTest>();\n"
" (void) that;\n"
" return true;\n"
"}\n"
"GrTest::GrTest(const GrTest& src)\n"
": INHERITED(kGrTest_ClassID, src.optimizationFlags()) {\n"
"}\n"
"std::unique_ptr<GrFragmentProcessor> GrTest::clone() const {\n"
" return std::unique_ptr<GrFragmentProcessor>(new GrTest(*this));\n"
"}\n"
});
}
DEF_TEST(SkSLFPInput, r) {
test(r,
"layout(key) in half2 point;"
"void main() {"
"sk_OutColor = half4(point, point);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
"return std::unique_ptr<GrFragmentProcessor>(new GrTest(point));",
"GrTest(SkPoint point)",
", point(point)"
},
{
"fragBuilder->codeAppendf(\"%s = half4(half2(%f, %f), half2(%f, %f));\\n\", "
"args.fOutputColor, _outer.point.fX, _outer.point.fY, "
"_outer.point.fX, _outer.point.fY);",
"if (point != that.point) return false;"
});
}
DEF_TEST(SkSLFPUniform, r) {
test(r,
"uniform half4 color;"
"void main() {"
"sk_OutColor = color;"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make()"
},
{
"colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
"kHalf4_GrSLType, \"color\");",
});
}
// SkSLFPInUniform tests the simplest plumbing case, default type, no tracking
// with a setUniform template that supports inlining the value call with no
// local variable.
DEF_TEST(SkSLFPInUniform, r) {
test(r,
"in uniform half4 color;"
"void main() {"
"sk_OutColor = color;"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
},
{
"colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
"kHalf4_GrSLType, \"color\");",
"pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&(_outer.color)));"
});
}
// As above, but tests in uniform's ability to override the default ctype.
DEF_TEST(SkSLFPInUniformCType, r) {
test(r,
"layout(ctype=SkPMColor4f) in uniform half4 color;"
"void main() {"
"sk_OutColor = color;"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f color) {",
},
{
"colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
"kHalf4_GrSLType, \"color\");",
"pdman.set4fv(colorVar, 1, (_outer.color).vec());"
});
}
// Add state tracking to the default typed SkRect <-> half4 uniform. But since
// it now has to track state, the value inlining previously done for the
// setUniform call is removed in favor of a local variable.
DEF_TEST(SkSLFPTrackedInUniform, r) {
test(r,
"layout(tracked) in uniform half4 color;"
"void main() {"
"sk_OutColor = color;"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
},
{
"SkRect colorPrev = SkRect::MakeEmpty();",
"colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
"kHalf4_GrSLType, \"color\");",
"const SkRect& colorValue = _outer.color;",
"if (colorPrev.isEmpty() || colorPrev != colorValue) {",
"colorPrev = colorValue;",
"pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&colorValue));"
});
}
// Test the case where the template does not support variable inlining in
// setUniform (i.e. it references the value multiple times).
DEF_TEST(SkSLFPNonInlinedInUniform, r) {
test(r,
"in uniform half2 point;"
"void main() {"
"sk_OutColor = half4(point, point);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
},
{
"pointVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
"kHalf2_GrSLType, \"point\");",
"const SkPoint& pointValue = _outer.point;",
"pdman.set2f(pointVar, pointValue.fX, pointValue.fY);"
});
}
// Test handling conditional uniforms (that use when= in layout), combined with
// state tracking and custom ctypes to really put the code generation through its paces.
DEF_TEST(SkSLFPConditionalInUniform, r) {
test(r,
"layout(key) in bool test;"
"layout(ctype=SkPMColor4f, tracked, when=test) in uniform half4 color;"
"void main() {"
" if (test) {"
" sk_OutColor = color;"
" } else {"
" sk_OutColor = half4(1);"
" }"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"static std::unique_ptr<GrFragmentProcessor> Make(bool test, SkPMColor4f color) {",
},
{
"SkPMColor4f colorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}",
"auto test = _outer.test;",
"if (test) {",
"colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
"kHalf4_GrSLType, \"color\");",
"if (colorVar.isValid()) {",
"const SkPMColor4f& colorValue = _outer.color;",
"if (colorPrev != colorValue) {",
"colorPrev = colorValue;",
"pdman.set4fv(colorVar, 1, colorValue.vec());"
});
}
DEF_TEST(SkSLFPSections, r) {
test(r,
"@header { header section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"header section"
},
{});
test(r,
"@class { class section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"class GrTest : public GrFragmentProcessor {\n"
"public:\n"
" class section"
},
{});
test(r,
"@cpp { cpp section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{"cpp section"});
test(r,
"@constructorParams { int x, float y, std::vector<float> z }"
"in float w;"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"Make(float w, int x, float y, std::vector<float> z )",
"return std::unique_ptr<GrFragmentProcessor>(new GrTest(w, x, y, z));",
"GrTest(float w, int x, float y, std::vector<float> z )",
", w(w) {"
},
{});
test(r,
"@constructor { constructor section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"private:\n constructor section"
},
{});
test(r,
"@initializers { initializers section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
": INHERITED(kGrTest_ClassID, kNone_OptimizationFlags)\n , initializers section"
},
{});
test(r,
"half x = 10;"
"@emitCode { fragBuilder->codeAppendf(\"half y = %d\\n\", x * 2); }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"x = 10.0;\n"
" fragBuilder->codeAppendf(\"half y = %d\\n\", x * 2);"
});
test(r,
"@fields { fields section }"
"@clone { }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"const char* name() const override { return \"Test\"; }\n"
" fields section private:"
},
{});
test(r,
"@make { make section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"public:\n"
" make section"
},
{});
test(r,
"uniform half calculated;"
"layout(key) in half provided;"
"@setData(varName) { varName.set1f(calculated, provided * 2); }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"void onSetData(const GrGLSLProgramDataManager& varName, "
"const GrFragmentProcessor& _proc) override {\n",
"UniformHandle& calculated = calculatedVar;",
"auto provided = _outer.provided;",
"varName.set1f(calculated, provided * 2);"
});
test(r,
"@test(testDataName) { testDataName section }"
"void main() {"
"sk_OutColor = half4(1);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"#if GR_TEST_UTILS\n"
"std::unique_ptr<GrFragmentProcessor> GrTest::TestCreate(GrProcessorTestData* testDataName) {\n"
" testDataName section }\n"
"#endif"
});
}
DEF_TEST(SkSLFPTransformedCoords, r) {
test(r,
"void main() {"
"sk_OutColor = half4(sk_TransformedCoords2D[0], sk_TransformedCoords2D[0]);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"SkString sk_TransformedCoords2D_0 = "
"fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);",
"fragBuilder->codeAppendf(\"%s = half4(%s, %s);\\n\", args.fOutputColor, "
"sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str());"
});
}
DEF_TEST(SkSLFPLayoutWhen, r) {
test(r,
"layout(when=someExpression(someOtherExpression())) uniform half sometimes;"
"void main() {"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"if (someExpression(someOtherExpression())) {\n"
" sometimesVar = args.fUniformHandler->addUniform"
});
}
DEF_TEST(SkSLFPChildProcessors, r) {
test(r,
"in fragmentProcessor child1;"
"in fragmentProcessor child2;"
"void main() {"
" sk_OutColor = sample(child1) * sample(child2);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child1));",
"this->registerChildProcessor(std::move(child2));"
},
{
"SkString _sample93;\n",
"_sample93 = this->invokeChild(_outer.child1_index, args);\n",
"SkString _sample110;\n",
"_sample110 = this->invokeChild(_outer.child2_index, args);\n",
"fragBuilder->codeAppendf(\"%s = %s * %s;\\n\", args.fOutputColor, _sample93.c_str(), "
"_sample110.c_str());\n",
"{",
" auto clone = src.childProcessor(child1_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child1_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
"{",
" auto clone = src.childProcessor(child2_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child2_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
});
}
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
DEF_TEST(SkSLFPChildProcessorsWithInput, r) {
test(r,
"in fragmentProcessor child1;"
"in fragmentProcessor child2;"
"void main() {"
" half4 childIn = sk_InColor;"
" half4 childOut1 = sample(child1, childIn);"
" half4 childOut2 = sample(child2, childOut1);"
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
" sk_OutColor = childOut2;"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child1));",
"this->registerChildProcessor(std::move(child2));"
},
{
"SkString _input128(\"childIn\");",
"SkString _sample128;",
"_sample128 = this->invokeChild(_outer.child1_index, _input128.c_str(), args);",
"fragBuilder->codeAppendf(\"\\nhalf4 childOut1 = %s;\", _sample128.c_str());",
"SkString _input174(\"childOut1\");",
"SkString _sample174;",
"_sample174 = this->invokeChild(_outer.child2_index, _input174.c_str(), args);",
"{",
" auto clone = src.childProcessor(child1_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child1_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
"{",
" auto clone = src.childProcessor(child2_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child2_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}"
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
});
}
DEF_TEST(SkSLFPChildProcessorWithInputExpression, r) {
test(r,
"in fragmentProcessor child;"
"void main() {"
" sk_OutColor = sample(child, sk_InColor * half4(0.5));"
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child));",
},
{
"SkString _input64 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
"SkString _sample64;",
"_sample64 = this->invokeChild(_outer.child_index, _input64.c_str(), args);",
"fragBuilder->codeAppendf(\"%s = %s;\\n\", args.fOutputColor, _sample64.c_str());",
"{",
" auto clone = src.childProcessor(child_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
});
}
DEF_TEST(SkSLFPNestedChildProcessors, r) {
test(r,
"in fragmentProcessor child1;"
"in fragmentProcessor child2;"
"void main() {"
" sk_OutColor = sample(child2, sk_InColor * sample(child1, sk_InColor * half4(0.5)));"
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child1));",
"this->registerChildProcessor(std::move(child2));"
},
{
"SkString _input121 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
"SkString _sample121;",
"_sample121 = this->invokeChild(_outer.child1_index, _input121.c_str(), args);",
"SkString _input93 = SkStringPrintf(\"%s * %s\", args.fInputColor, _sample121.c_str());",
"SkString _sample93;",
"_sample93 = this->invokeChild(_outer.child2_index, _input93.c_str(), args);",
"fragBuilder->codeAppendf(\"%s = %s;\\n\", args.fOutputColor, _sample93.c_str());",
"{",
" auto clone = src.childProcessor(child1_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child1_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
"{",
" auto clone = src.childProcessor(child2_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child2_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
});
Support input color argument to process() function in sksl .fp files -- This expands sksl's capabilities with .fp files. Previously, it was possible to declare "in fragmentProcessor foo" and emit it automatically when "process(foo);" was called. This adds a variant of process that takes a second argument, which must be a half4 expression. This argument specifies the value, or dynamic expression calculated earlier in the parent shader, to use as sk_InColor by the child. The CL is longer than anticipated because of properly handling dependencies between previous sksl statements and the input to the child. The original writeEmitCode() collected all extra emission code (the calls to build->emitChild) and put them before any call to codeAppendf. This makes it impossible to use a parent's variable, or the output of another child, as the input for process. To solve this, there is now a flushEmittedCode() function that takes over the logic of outputting the extra emission code and the necessary codeAppendf calls. When invoked, it (by default) only appends completed sksl statements, and places any current expression back at the beginning of the output stream. It now updates fFormatArgs and fExtraEmitCodeCode as it consumes their contents. This allows writeFunctionCall() for a call to "process" to flush all previous statements before it adds its emit child code. Bug: skia: Change-Id: I63c41af6f3e0620aa890d10d14436ee6244f0051 Reviewed-on: https://skia-review.googlesource.com/148395 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
2018-08-30 20:08:18 +00:00
}
Reland "Redo how extra emit code flushing operates" with type fix. This reverts commit d0440195d5cc049062238d02b8a962ae49c7f4ff. Reason for revert: Fixes size_t -> int that was triggering ASAN failures. Original change's description: > Revert "Redo how extra emit code flushing operates" > > This reverts commit 9b8181b05a84e7dd24234c46c87d0bb2c73a7c08. > > Reason for revert: <INSERT REASONING HERE> > > Original change's description: > > Redo how extra emit code flushing operates > > > > The previous implementation of flushEmittedCode(), that flushed on > > demand when a process() was encountered, was brittle and susceptible to > > mangling the expected sksl when fOut was modified outside of its > > control. Given that writeFunction() and generateCode() in the parent > > class all do this, it's possible to generate a simple SkSL snippet that > > would generate a CPP file that builds invalid final SkSL: > > > > ``` > > in fragmentProcessor child; > > bool someGlobalVar = ...; > > void main() { > > if (someGlobalVar) { > > sk_OutColor = process(child, sk_InColor); > > } else { > > sk_OutColor = half4(1); > > } > > } > > ``` > > > > The CPP generated code *should* insert 'bool someGlobalVar' at the start > > but because of the early flush from the child process and because of > > how fOut was overwritten, someGlobalVar's declaration is put into a > > stream that is not visible to the flush and ends up being inserted into > > the output sksl in an incorrect location (namely after the if condition > > that depends on it). > > > > This CL updates the extra emitted code logic to support multiple blocks > > of extra CPP code. When a flush point occurs in SkSL writing, a special > > token is inserted into the SkSL and a new CPP code buffer is associated > > with that token. Then once all of the SkSL is accumulated into the root > > output stream, it is processed into sections for each extra CPP block. > > Special logic is done so that the SkSL that is emitted before the next > > CPP block terminates at the end of the last valid statement before the > > special token. > > > > A unit test demonstrating this failure condition is added to SkSLFPTest > > and the CL properly passes. Since this bug did not trigger on existing > > .fp files, the updated generator does not modify the generated FPs. > > > > Bug: skia: > > Change-Id: Ib74911942080f1b964159807a06805bc52898789 > > Reviewed-on: https://skia-review.googlesource.com/152321 > > Commit-Queue: Michael Ludwig <michaelludwig@google.com> > > Reviewed-by: Ethan Nicholas <ethannicholas@google.com> > > TBR=ethannicholas@google.com,michaelludwig@google.com > > Change-Id: Id0f908453b596873f43b86a1c14eed48b2474a76 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Bug: skia: > Reviewed-on: https://skia-review.googlesource.com/152660 > Reviewed-by: Michael Ludwig <michaelludwig@google.com> > Commit-Queue: Michael Ludwig <michaelludwig@google.com> TBR=ethannicholas@google.com,michaelludwig@google.com Change-Id: I3ccf2fee6ef96c6102dbe1c2c2ef6c14a701b8fd No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: skia: Reviewed-on: https://skia-review.googlesource.com/152663 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
2018-09-07 17:13:06 +00:00
DEF_TEST(SkSLFPChildFPAndGlobal, r) {
test(r,
"in fragmentProcessor child;"
"bool hasCap = sk_Caps.externalTextureSupport;"
"void main() {"
" if (hasCap) {"
" sk_OutColor = sample(child, sk_InColor);"
Reland "Redo how extra emit code flushing operates" with type fix. This reverts commit d0440195d5cc049062238d02b8a962ae49c7f4ff. Reason for revert: Fixes size_t -> int that was triggering ASAN failures. Original change's description: > Revert "Redo how extra emit code flushing operates" > > This reverts commit 9b8181b05a84e7dd24234c46c87d0bb2c73a7c08. > > Reason for revert: <INSERT REASONING HERE> > > Original change's description: > > Redo how extra emit code flushing operates > > > > The previous implementation of flushEmittedCode(), that flushed on > > demand when a process() was encountered, was brittle and susceptible to > > mangling the expected sksl when fOut was modified outside of its > > control. Given that writeFunction() and generateCode() in the parent > > class all do this, it's possible to generate a simple SkSL snippet that > > would generate a CPP file that builds invalid final SkSL: > > > > ``` > > in fragmentProcessor child; > > bool someGlobalVar = ...; > > void main() { > > if (someGlobalVar) { > > sk_OutColor = process(child, sk_InColor); > > } else { > > sk_OutColor = half4(1); > > } > > } > > ``` > > > > The CPP generated code *should* insert 'bool someGlobalVar' at the start > > but because of the early flush from the child process and because of > > how fOut was overwritten, someGlobalVar's declaration is put into a > > stream that is not visible to the flush and ends up being inserted into > > the output sksl in an incorrect location (namely after the if condition > > that depends on it). > > > > This CL updates the extra emitted code logic to support multiple blocks > > of extra CPP code. When a flush point occurs in SkSL writing, a special > > token is inserted into the SkSL and a new CPP code buffer is associated > > with that token. Then once all of the SkSL is accumulated into the root > > output stream, it is processed into sections for each extra CPP block. > > Special logic is done so that the SkSL that is emitted before the next > > CPP block terminates at the end of the last valid statement before the > > special token. > > > > A unit test demonstrating this failure condition is added to SkSLFPTest > > and the CL properly passes. Since this bug did not trigger on existing > > .fp files, the updated generator does not modify the generated FPs. > > > > Bug: skia: > > Change-Id: Ib74911942080f1b964159807a06805bc52898789 > > Reviewed-on: https://skia-review.googlesource.com/152321 > > Commit-Queue: Michael Ludwig <michaelludwig@google.com> > > Reviewed-by: Ethan Nicholas <ethannicholas@google.com> > > TBR=ethannicholas@google.com,michaelludwig@google.com > > Change-Id: Id0f908453b596873f43b86a1c14eed48b2474a76 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Bug: skia: > Reviewed-on: https://skia-review.googlesource.com/152660 > Reviewed-by: Michael Ludwig <michaelludwig@google.com> > Commit-Queue: Michael Ludwig <michaelludwig@google.com> TBR=ethannicholas@google.com,michaelludwig@google.com Change-Id: I3ccf2fee6ef96c6102dbe1c2c2ef6c14a701b8fd No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: skia: Reviewed-on: https://skia-review.googlesource.com/152663 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
2018-09-07 17:13:06 +00:00
" } else {"
" sk_OutColor = half4(1);"
" }"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child));"
},
{
"hasCap = sk_Caps.externalTextureSupport;",
"fragBuilder->codeAppendf(\"bool hasCap = %s;\\nif (hasCap) {\", (hasCap ? \"true\" : "
"\"false\"));",
"SkString _input130 = SkStringPrintf(\"%s\", args.fInputColor);",
"SkString _sample130;",
"_sample130 = this->invokeChild(_outer.child_index, _input130.c_str(), args);",
"fragBuilder->codeAppendf(\"\\n %s = %s;\\n} else {\\n %s = half4(1.0);\\n}\\n\","
" args.fOutputColor, _sample130.c_str(), args.fOutputColor);",
"{",
" auto clone = src.childProcessor(child_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
});
Reland "Redo how extra emit code flushing operates" with type fix. This reverts commit d0440195d5cc049062238d02b8a962ae49c7f4ff. Reason for revert: Fixes size_t -> int that was triggering ASAN failures. Original change's description: > Revert "Redo how extra emit code flushing operates" > > This reverts commit 9b8181b05a84e7dd24234c46c87d0bb2c73a7c08. > > Reason for revert: <INSERT REASONING HERE> > > Original change's description: > > Redo how extra emit code flushing operates > > > > The previous implementation of flushEmittedCode(), that flushed on > > demand when a process() was encountered, was brittle and susceptible to > > mangling the expected sksl when fOut was modified outside of its > > control. Given that writeFunction() and generateCode() in the parent > > class all do this, it's possible to generate a simple SkSL snippet that > > would generate a CPP file that builds invalid final SkSL: > > > > ``` > > in fragmentProcessor child; > > bool someGlobalVar = ...; > > void main() { > > if (someGlobalVar) { > > sk_OutColor = process(child, sk_InColor); > > } else { > > sk_OutColor = half4(1); > > } > > } > > ``` > > > > The CPP generated code *should* insert 'bool someGlobalVar' at the start > > but because of the early flush from the child process and because of > > how fOut was overwritten, someGlobalVar's declaration is put into a > > stream that is not visible to the flush and ends up being inserted into > > the output sksl in an incorrect location (namely after the if condition > > that depends on it). > > > > This CL updates the extra emitted code logic to support multiple blocks > > of extra CPP code. When a flush point occurs in SkSL writing, a special > > token is inserted into the SkSL and a new CPP code buffer is associated > > with that token. Then once all of the SkSL is accumulated into the root > > output stream, it is processed into sections for each extra CPP block. > > Special logic is done so that the SkSL that is emitted before the next > > CPP block terminates at the end of the last valid statement before the > > special token. > > > > A unit test demonstrating this failure condition is added to SkSLFPTest > > and the CL properly passes. Since this bug did not trigger on existing > > .fp files, the updated generator does not modify the generated FPs. > > > > Bug: skia: > > Change-Id: Ib74911942080f1b964159807a06805bc52898789 > > Reviewed-on: https://skia-review.googlesource.com/152321 > > Commit-Queue: Michael Ludwig <michaelludwig@google.com> > > Reviewed-by: Ethan Nicholas <ethannicholas@google.com> > > TBR=ethannicholas@google.com,michaelludwig@google.com > > Change-Id: Id0f908453b596873f43b86a1c14eed48b2474a76 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Bug: skia: > Reviewed-on: https://skia-review.googlesource.com/152660 > Reviewed-by: Michael Ludwig <michaelludwig@google.com> > Commit-Queue: Michael Ludwig <michaelludwig@google.com> TBR=ethannicholas@google.com,michaelludwig@google.com Change-Id: I3ccf2fee6ef96c6102dbe1c2c2ef6c14a701b8fd No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: skia: Reviewed-on: https://skia-review.googlesource.com/152663 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
2018-09-07 17:13:06 +00:00
}
DEF_TEST(SkSLFPChildProcessorInlineFieldAccess, r) {
test(r,
"in fragmentProcessor child;"
"void main() {"
" if (child.preservesOpaqueInput) {"
" sk_OutColor = sample(child, sk_InColor);"
" } else {"
" sk_OutColor = half4(1);"
" }"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child));"
},
{
"fragBuilder->codeAppendf(\"if (%s) {\", "
"(_outer.childProcessor(_outer.child_index).preservesOpaqueInput() ? ",
"SkString _input105 = SkStringPrintf(\"%s\", args.fInputColor);",
"SkString _sample105;",
"_sample105 = this->invokeChild(_outer.child_index, _input105.c_str(), args);",
"fragBuilder->codeAppendf(\"\\n %s = %s;\\n} else {\\n %s = half4(1.0);\\n}\\n\","
" args.fOutputColor, _sample105.c_str(), args.fOutputColor);",
"{",
" auto clone = src.childProcessor(child_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
});
}
DEF_TEST(SkSLFPChildProcessorFieldAccess, r) {
test(r,
"in fragmentProcessor child;"
"bool opaque = child.preservesOpaqueInput;"
"void main() {"
" if (opaque) {"
" sk_OutColor = sample(child);"
" } else {"
" sk_OutColor = half4(0.5);"
" }"
"}",
*SkSL::ShaderCapsFactory::Default(),
{
"this->registerChildProcessor(std::move(child));"
},
{
"opaque = _outer.childProcessor(_outer.child_index).preservesOpaqueInput();",
"fragBuilder->codeAppendf(\"bool opaque = %s;\\nif (opaque) {\", (opaque ? \"true\" : "
"\"false\"));",
"SkString _sample126;",
"_sample126 = this->invokeChild(_outer.child_index, args);",
"fragBuilder->codeAppendf(\"\\n %s = %s;\\n} else {\\n %s = half4(0.5);\\n}\\n\","
" args.fOutputColor, _sample126.c_str(), args.fOutputColor);",
"{",
" auto clone = src.childProcessor(child_index).clone();",
" clone->setSampledWithExplicitCoords(",
" src.childProcessor(child_index).isSampledWithExplicitCoords());",
" this->registerChildProcessor(std::move(clone));",
"}",
});
}
DEF_TEST(SkSLFPNullableChildProcessor, r) {
test(r,
"in fragmentProcessor? child;"
"void main() {"
" if (child != null) {"
" sk_OutColor = sample(child);"
" } else {"
" sk_OutColor = half4(0.5);"
" }"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"fragBuilder->codeAppendf(\"if (%s) {\", _outer.child_index >= 0 ? \"true\" : "
"\"false\");",
"SkString _sample93;",
"if (_outer.child_index >= 0) {",
"_sample93 = this->invokeChild(_outer.child_index, args);",
"}",
"fragBuilder->codeAppendf(\"\\n %s = %s;\\n} else {\\n %s = half4(0.5);\\n}\\n\","
" args.fOutputColor, _sample93.c_str(), args.fOutputColor);"
});
}
DEF_TEST(SkSLFPBadIn, r) {
test_failure(r,
"in half4 c;"
"void main() {"
" sk_OutColor = c;"
"}",
"error: 1: 'in' variable must be either 'uniform' or 'layout(key)', or there must be a "
"custom @setData function\n1 error\n");
}
DEF_TEST(SkSLFPSampleCoords, r) {
test(r,
"in fragmentProcessor child;"
"@coordTransform { SkMatrix() }"
"void main() {"
" sk_OutColor = sample(child) + sample(child, sk_TransformedCoords2D[0] / 2);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"SkString _sample94;\n",
"_sample94 = this->invokeChild(_outer.child_index, args);\n",
"SkString _sample110;\n",
"SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D("
"args.fTransformedCoords[0].fVaryingPoint);\n",
"SkString _coords110 = SkStringPrintf(\"%s / 2.0\", "
"sk_TransformedCoords2D_0.c_str());\n",
"_sample110 = this->invokeChild(_outer.child_index, args, _coords110.c_str());\n",
"fragBuilder->codeAppendf(\"%s = %s + %s;\\n\", args.fOutputColor, _sample94.c_str(), "
"_sample110.c_str());\n"
});
}
DEF_TEST(SkSLFPFunction, r) {
test(r,
"in fragmentProcessor? child;"
"half4 flip(half4 c) { return c.abgr; }"
"void main() {"
" sk_OutColor = flip(sk_InColor);"
"}",
*SkSL::ShaderCapsFactory::Default(),
{},
{
"SkString flip_name;",
"const GrShaderVar flip_args[] = { GrShaderVar(\"c\", kHalf4_GrSLType)};",
"fragBuilder->emitFunction(kHalf4_GrSLType, \"flip\", 1, flip_args, "
"\"return c.wzyx;\\n\", &flip_name);",
"fragBuilder->codeAppendf(\"%s = %s(%s);\\n\", args.fOutputColor, flip_name.c_str(), "
"args.fInputColor);"
});
}