re-re-land of skslc now automatically turns on derivatives support
authorethannicholas <ethannicholas@google.com>
Thu, 20 Oct 2016 16:54:00 +0000 (09:54 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 20 Oct 2016 16:54:00 +0000 (09:54 -0700)
Only change from last attempt is putting the call to shaderDerivativeExtensionString behind a check for shaderDerivativeSupport to avoid a spurious assertion failure.

TBR=benjaminwagner@google.com

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2437063002

Review-Url: https://chromiumcodereview.appspot.com/2437063002

18 files changed:
fuzz/fuzz.cpp
src/gpu/GrOvalRenderer.cpp
src/gpu/batches/GrAAConvexPathRenderer.cpp
src/gpu/batches/GrPLSPathRenderer.cpp
src/gpu/effects/GrBezierEffect.cpp
src/gpu/effects/GrDistanceFieldGeoProc.cpp
src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
src/gpu/instanced/InstanceProcessor.cpp
src/sksl/SkSLCompiler.cpp
src/sksl/SkSLGLSLCodeGenerator.cpp
src/sksl/SkSLGLSLCodeGenerator.h
src/sksl/SkSLMain.cpp
src/sksl/ir/SkSLFunctionDeclaration.h
src/sksl/ir/SkSLSymbolTable.cpp
src/sksl/ir/SkSLSymbolTable.h
tests/SkSLGLSLTest.cpp

index cbfb744..a415135 100644 (file)
@@ -406,23 +406,11 @@ int fuzz_color_deserialize(sk_sp<SkData> bytes) {
     return 0;
 }
 
-static SkSL::GLCaps default_caps() {
-    return {
-             400,
-             SkSL::GLCaps::kGL_Standard,
-             false, // isCoreProfile
-             false, // usesPrecisionModifiers;
-             false, // mustDeclareFragmentShaderOutput
-             true,   // canUseMinAndAbsTogether
-             false  // mustForceNegatedAtanParamToFloat
-           };
-}
-
 int fuzz_sksl2glsl(sk_sp<SkData> bytes) {
     SkSL::Compiler compiler;
     std::string output;
     bool result = compiler.toGLSL(SkSL::Program::kFragment_Kind,
-        (const char*)bytes->data(), default_caps(), &output);
+        (const char*)bytes->data(), SkSL::GLCaps(), &output);
 
     if (!result) {
         SkDebugf("[terminated] Couldn't compile input.\n");
index 39221a6..d04502e 100644 (file)
@@ -463,8 +463,6 @@ private:
                                  diegp.fInPosition->fName,
                                  args.fFPCoordTransformHandler);
 
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             // for outer curve
             fragBuilder->codeAppendf("vec2 scaledOffset = %s.xy;", offsets0.fsIn());
             fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;");
index c71f46d..f02be4c 100644 (file)
@@ -579,8 +579,6 @@ public:
                                  qe.localMatrix(),
                                  args.fFPCoordTransformHandler);
 
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("float edgeAlpha;");
 
             // keep the derivative instructions outside the conditional
index e8711c0..f20fb13 100644 (file)
@@ -339,8 +339,6 @@ public:
             GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder;
             SkAssertResult(fsBuilder->enableFeature(
                            GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature));
-            SkAssertResult(fsBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL);
             // Compute four subsamples, each shifted a quarter pixel along x and y from
             // gl_FragCoord. The oriented box positioning of the subsamples is of course not
@@ -522,8 +520,6 @@ public:
             GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder;
             SkAssertResult(fsBuilder->enableFeature(
                            GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature));
-            SkAssertResult(fsBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             static const int QUAD_ARGS = 2;
             GrGLSLShaderVar inQuadArgs[QUAD_ARGS] = {
                 GrGLSLShaderVar("dot", kFloat_GrSLType, 0, kHigh_GrSLPrecision),
index 798695d..7f39b92 100644 (file)
@@ -136,8 +136,6 @@ void GrGLConicEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
 
     switch (fEdgeType) {
         case kHairlineAA_GrProcessorEdgeType: {
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("%s = dFdx(%s.xyz);", dklmdx.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s = dFdy(%s.xyz);", dklmdy.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s = 2.0 * %s.x * %s.x - %s.y * %s.z - %s.z * %s.y;",
@@ -165,8 +163,6 @@ void GrGLConicEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
             break;
         }
         case kFillAA_GrProcessorEdgeType: {
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("%s = dFdx(%s.xyz);", dklmdx.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s = dFdy(%s.xyz);", dklmdy.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s ="
@@ -374,8 +370,6 @@ void GrGLQuadEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
 
     switch (fEdgeType) {
         case kHairlineAA_GrProcessorEdgeType: {
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("vec2 duvdx = dFdx(%s.xy);", v.fsIn());
             fragBuilder->codeAppendf("vec2 duvdy = dFdy(%s.xy);", v.fsIn());
             fragBuilder->codeAppendf("vec2 gF = vec2(2.0 * %s.x * duvdx.x - duvdx.y,"
@@ -390,8 +384,6 @@ void GrGLQuadEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
             break;
         }
         case kFillAA_GrProcessorEdgeType: {
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("vec2 duvdx = dFdx(%s.xy);", v.fsIn());
             fragBuilder->codeAppendf("vec2 duvdy = dFdy(%s.xy);", v.fsIn());
             fragBuilder->codeAppendf("vec2 gF = vec2(2.0 * %s.x * duvdx.x - duvdx.y,"
@@ -592,8 +584,6 @@ void GrGLCubicEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
 
     switch (fEdgeType) {
         case kHairlineAA_GrProcessorEdgeType: {
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("%s = dFdx(%s.xyz);", dklmdx.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s = dFdy(%s.xyz);", dklmdy.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s = 3.0 * %s.x * %s.x * %s.x - %s.y * %s.z - %s.z * %s.y;",
@@ -620,8 +610,6 @@ void GrGLCubicEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
             break;
         }
         case kFillAA_GrProcessorEdgeType: {
-            SkAssertResult(fragBuilder->enableFeature(
-                    GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
             fragBuilder->codeAppendf("%s = dFdx(%s.xyz);", dklmdx.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s = dFdy(%s.xyz);", dklmdy.c_str(), v.fsIn());
             fragBuilder->codeAppendf("%s ="
index 5404b0c..7f9fd85 100644 (file)
@@ -35,8 +35,6 @@ public:
         const GrDistanceFieldA8TextGeoProc& dfTexEffect =
                 args.fGP.cast<GrDistanceFieldA8TextGeoProc>();
         GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        SkAssertResult(fragBuilder->enableFeature(
-                GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
 
         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
@@ -313,8 +311,6 @@ public:
         const GrDistanceFieldPathGeoProc& dfTexEffect = args.fGP.cast<GrDistanceFieldPathGeoProc>();
 
         GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        SkAssertResult(fragBuilder->enableFeature(
-                                     GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
 
         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
@@ -610,9 +606,6 @@ public:
 
         // add frag shader code
 
-        SkAssertResult(fragBuilder->enableFeature(
-                GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
-
         // create LCD offset adjusted by inverse of transform
         // Use highp to work around aliasing issues
         fragBuilder->appendPrecisionModifier(kHigh_GrSLPrecision);
index 8e8bb9a..8a5b700 100644 (file)
@@ -85,6 +85,10 @@ SkSL::GLCaps GrGLSkSLCapsForContext(const GrGLContext& context) {
     result.fIsCoreProfile = caps->isCoreProfile();
     result.fUsesPrecisionModifiers = glslCaps->usesPrecisionModifiers();
     result.fMustDeclareFragmentShaderOutput = glslCaps->mustDeclareFragmentShaderOutput();
+    result.fShaderDerivativeSupport = glslCaps->shaderDerivativeSupport();
+    if (result.fShaderDerivativeSupport && glslCaps->shaderDerivativeExtensionString()) {
+        result.fShaderDerivativeExtensionString = glslCaps->shaderDerivativeExtensionString();
+    }
     result.fCanUseMinAndAbsTogether = glslCaps->canUseMinAndAbsTogether();
     result.fMustForceNegatedAtanParamToFloat = glslCaps->mustForceNegatedAtanParamToFloat();
     return result;
index eb744a9..db420c8 100644 (file)
@@ -96,14 +96,6 @@ GrGLSLFragmentShaderBuilder::GrGLSLFragmentShaderBuilder(GrGLSLProgramBuilder* p
 bool GrGLSLFragmentShaderBuilder::enableFeature(GLSLFeature feature) {
     const GrGLSLCaps& glslCaps = *fProgramBuilder->glslCaps();
     switch (feature) {
-        case kStandardDerivatives_GLSLFeature:
-            if (!glslCaps.shaderDerivativeSupport()) {
-                return false;
-            }
-            if (const char* extension = glslCaps.shaderDerivativeExtensionString()) {
-                this->addFeature(1 << kStandardDerivatives_GLSLFeature, extension);
-            }
-            return true;
         case kPixelLocalStorage_GLSLFeature:
             if (glslCaps.pixelLocalStorageSize() <= 0) {
                 return false;
index 6a4a184..bf8569c 100644 (file)
@@ -29,8 +29,7 @@ public:
      * if code is added that uses one of these features without calling enableFeature()
      */
     enum GLSLFeature {
-        kStandardDerivatives_GLSLFeature = kLastGLSLPrivateFeature + 1,
-        kPixelLocalStorage_GLSLFeature,
+        kPixelLocalStorage_GLSLFeature = kLastGLSLPrivateFeature + 1,
         kMultisampleInterpolation_GLSLFeature
     };
 
index 82116c4..55be89e 100644 (file)
@@ -1408,7 +1408,6 @@ void GLSLInstanceProcessor::BackendMultisample::onEmitCode(GrGLSLVertexBuilder*,
         if (arcTest && fBatchInfo.fHasPerspective) {
             // The non-perspective version accounts for fwidth() in the vertex shader.
             // We make sure to take the derivative here, before a neighbor pixel may early accept.
-            f->enableFeature(GrGLSLPPFragmentBuilder::kStandardDerivatives_GLSLFeature);
             f->appendPrecisionModifier(kHigh_GrSLPrecision);
             f->codeAppendf("vec2 arcTest = %s - 0.5 * fwidth(%s);",
                            fArcTest.fsIn(), fArcTest.fsIn());
index d4fbc95..5b502dc 100644 (file)
@@ -19,6 +19,7 @@
 #include "ir/SkSLIntLiteral.h"
 #include "ir/SkSLModifiersDeclaration.h"
 #include "ir/SkSLSymbolTable.h"
+#include "ir/SkSLUnresolvedFunction.h"
 #include "ir/SkSLVarDeclarations.h"
 #include "SkMutex.h"
 
@@ -135,6 +136,7 @@ Compiler::Compiler()
     Modifiers::Flag ignored1;
     std::vector<std::unique_ptr<ProgramElement>> ignored2;
     this->internalConvertProgram(SKSL_INCLUDE, &ignored1, &ignored2);
+    fIRGenerator->fSymbolTable->markAllFunctionsBuiltin();
     ASSERT(!fErrorCount);
 }
 
@@ -393,10 +395,11 @@ std::unique_ptr<Program> Compiler::convertProgram(Program::Kind kind, std::strin
             this->internalConvertProgram(SKSL_FRAG_INCLUDE, &ignored, &elements);
             break;
     }
+    fIRGenerator->fSymbolTable->markAllFunctionsBuiltin();
     Modifiers::Flag defaultPrecision;
     this->internalConvertProgram(text, &defaultPrecision, &elements);
     auto result = std::unique_ptr<Program>(new Program(kind, defaultPrecision, std::move(elements), 
-                                                       fIRGenerator->fSymbolTable));;
+                                                       fIRGenerator->fSymbolTable));
     fIRGenerator->popSymbolTable();
     this->writeErrorCount();
     return result;
index 45644e8..8a26f6a 100644 (file)
@@ -150,7 +150,7 @@ void GLSLCodeGenerator::writeMinAbsHack(Expression& absExpr, Expression& otherEx
 }
 
 void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
-    if (!fCaps.fCanUseMinAndAbsTogether && c.fFunction.fName == "min") {
+    if (!fCaps.fCanUseMinAndAbsTogether && c.fFunction.fName == "min" && c.fFunction.fBuiltin) {
         ASSERT(c.fArguments.size() == 2);
         if (is_abs(*c.fArguments[0])) {
             this->writeMinAbsHack(*c.fArguments[0], *c.fArguments[1]);
@@ -164,7 +164,8 @@ void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
         }
     }
     if (fCaps.fMustForceNegatedAtanParamToFloat && c.fFunction.fName == "atan" && 
-        c.fArguments.size() == 2 && c.fArguments[1]->fKind == Expression::kPrefix_Kind) {
+        c.fFunction.fBuiltin && c.fArguments.size() == 2 && 
+        c.fArguments[1]->fKind == Expression::kPrefix_Kind) {
         const PrefixExpression& p = (PrefixExpression&) *c.fArguments[1];
         if (p.fOperator == Token::MINUS) {
             this->write("atan(");
@@ -175,6 +176,12 @@ void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
             return;
         }
     }
+    if (!fFoundDerivatives && fCaps.fShaderDerivativeExtensionString != "" && 
+        (c.fFunction.fName == "dFdx" || c.fFunction.fName == "dFdy") && c.fFunction.fBuiltin) {
+        ASSERT(fCaps.fShaderDerivativeSupport);
+        fHeader << "#extension " << fCaps.fShaderDerivativeExtensionString << " : require\n";
+        fFoundDerivatives = true;
+    }
     this->write(c.fFunction.fName + "(");
     const char* separator = "";
     for (const auto& arg : c.fArguments) {
@@ -578,7 +585,7 @@ void GLSLCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
 
 void GLSLCodeGenerator::generateCode(const Program& program, std::ostream& out) {
     ASSERT(fOut == nullptr);
-    fOut = &out;
+    fOut = &fHeader;
     fProgramKind = program.fKind;
     this->write("#version " + to_string(fCaps.fVersion));
     if (fCaps.fStandard == GLCaps::kGLES_Standard && fCaps.fVersion >= 300) {
@@ -592,6 +599,8 @@ void GLSLCodeGenerator::generateCode(const Program& program, std::ostream& out)
             this->writeExtension((Extension&) *e);
         }
     }
+    std::stringstream body;
+    fOut = &body;
     if (fCaps.fStandard == GLCaps::kGLES_Standard) {
         this->write("precision ");
         switch (program.fDefaultPrecision) {
@@ -649,6 +658,9 @@ void GLSLCodeGenerator::generateCode(const Program& program, std::ostream& out)
         }
     }
     fOut = nullptr;
+
+    out << fHeader.str();
+    out << body.str();
 }
 
 }
index 17ac90e..97e6038 100644 (file)
@@ -45,20 +45,25 @@ namespace SkSL {
 #define kLast_Capability SpvCapabilityMultiViewport
 
 struct GLCaps {
-    int fVersion;
+    GLCaps() {}
+
+    int fVersion = 400;
     enum {
         kGL_Standard,
         kGLES_Standard
-    } fStandard;
-    bool fIsCoreProfile;
-    bool fUsesPrecisionModifiers;
-    bool fMustDeclareFragmentShaderOutput;
+    } fStandard = kGL_Standard;
+    bool fIsCoreProfile = false;
+    bool fUsesPrecisionModifiers = false;
+    bool fMustDeclareFragmentShaderOutput = false;
+    bool fShaderDerivativeSupport = true;
+    // extension string to enable derivative support, or null if unnecessary
+    std::string fShaderDerivativeExtensionString;
     // The Tegra3 compiler will sometimes never return if we have min(abs(x), y)
-    bool fCanUseMinAndAbsTogether;
+    bool fCanUseMinAndAbsTogether = true;
     // On Intel GPU there is an issue where it misinterprets an atan argument (second argument only,
     // apparently) of the form "-<expr>" as an int, so we rewrite it as "-1.0 * <expr>" to avoid
     // this problem
-    bool fMustForceNegatedAtanParamToFloat;
+    bool fMustForceNegatedAtanParamToFloat = false;
 };
 
 /**
@@ -89,11 +94,7 @@ public:
 
     GLSLCodeGenerator(const Context* context, GLCaps caps)
     : fContext(*context)
-    , fCaps(caps)
-    , fOut(nullptr)
-    , fVarCount(0)
-    , fIndentation(0)
-    , fAtLineStart(true) {}
+    , fCaps(caps) {}
 
     void generateCode(const Program& program, std::ostream& out) override;
 
@@ -176,16 +177,19 @@ private:
 
     const Context& fContext;
     const GLCaps fCaps;
-    std::ostream* fOut;
+    std::ostream* fOut = nullptr;
+    std::stringstream fHeader;
     std::string fFunctionHeader;
     Program::Kind fProgramKind;
-    int fVarCount;
-    int fIndentation;
-    bool fAtLineStart;
+    int fVarCount = 0;
+    int fIndentation = 0;
+    bool fAtLineStart = false;
     // Keeps track of which struct types we have written. Given that we are unlikely to ever write 
     // more than one or two structs per shader, a simple linear search will be faster than anything 
     // fancier.
     std::vector<const Type*> fWrittenStructs;
+    // true if we have run into usages of dFdx / dFdy
+    bool fFoundDerivatives = false;
 };
 
 }
index fe925e0..eb07b4d 100644 (file)
@@ -16,18 +16,6 @@ bool endsWith(const std::string& s, const std::string& ending) {
     return false;
 }
 
-static SkSL::GLCaps default_caps() {
-    return { 
-             400, 
-             SkSL::GLCaps::kGL_Standard,
-             false, // isCoreProfile
-             false, // usesPrecisionModifiers;
-             false, // mustDeclareFragmentShaderOutput
-             true,  // canUseMinAndAbsTogether
-             false  // mustForceNegatedAtanParamToFloat
-           };
-}
-
 /**
  * Very simple standalone executable to facilitate testing.
  */
@@ -69,7 +57,7 @@ int main(int argc, const char** argv) {
     } else if (endsWith(name, ".glsl")) {
         std::ofstream out(argv[2], std::ofstream::binary);
         SkSL::Compiler compiler;
-        if (!compiler.toGLSL(kind, text, default_caps(), out)) {
+        if (!compiler.toGLSL(kind, text, SkSL::GLCaps(), out)) {
             printf("%s", compiler.errorText().c_str());
             exit(3);
         }
index 16a184a..ffde0c6 100644 (file)
@@ -24,6 +24,7 @@ struct FunctionDeclaration : public Symbol {
                         std::vector<const Variable*> parameters, const Type& returnType)
     : INHERITED(position, kFunctionDeclaration_Kind, std::move(name))
     , fDefined(false)
+    , fBuiltin(false)
     , fParameters(std::move(parameters))
     , fReturnType(returnType) {}
 
@@ -55,6 +56,7 @@ struct FunctionDeclaration : public Symbol {
     }
 
     mutable bool fDefined;
+    bool fBuiltin;
     const std::vector<const Variable*> fParameters;
     const Type& fReturnType;
 
index 9d8c006..6d8e9a7 100644 (file)
@@ -97,4 +97,22 @@ void SymbolTable::addWithoutOwnership(const std::string& name, const Symbol* sym
     }
 }
 
+
+void SymbolTable::markAllFunctionsBuiltin() {
+    for (const auto& pair : fSymbols) {
+        switch (pair.second->fKind) {
+            case Symbol::kFunctionDeclaration_Kind:
+                ((FunctionDeclaration&) *pair.second).fBuiltin = true;
+                break;
+            case Symbol::kUnresolvedFunction_Kind:
+                for (auto& f : ((UnresolvedFunction&) *pair.second).fFunctions) {
+                    ((FunctionDeclaration*) f)->fBuiltin = true;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+}
+
 } // namespace
index d732023..be2b49c 100644 (file)
@@ -39,6 +39,8 @@ public:
 
     Symbol* takeOwnership(Symbol* s);
 
+    void markAllFunctionsBuiltin();
+
     const std::shared_ptr<SymbolTable> fParent;
 
 private:
index b615f67..ad1fe0d 100644 (file)
@@ -27,15 +27,7 @@ static void test(skiatest::Reporter* r, const char* src, SkSL::GLCaps caps, cons
 }
 
 static SkSL::GLCaps default_caps() {
-    return { 
-             400, 
-             SkSL::GLCaps::kGL_Standard,
-             false, // isCoreProfile
-             false, // usesPrecisionModifiers;
-             false, // mustDeclareFragmentShaderOutput
-             true,  // canUseMinAndAbsTogether
-             false  // mustForceNegatedAtanParamToFloat
-           };
+    return SkSL::GLCaps();
 }
 
 DEF_TEST(SkSLHelloWorld, r) {
@@ -387,3 +379,30 @@ DEF_TEST(SkSLArrayConstructors, r) {
          "vec2 test2[] = vec2[](vec2(1.0, 2.0), vec2(3.0, 4.0));\n"
          "mat4 test3[] = mat4[]();\n");
 }
+
+DEF_TEST(SkSLDerivatives, r) {
+    test(r,
+         "void main() { float x = dFdx(1); }",
+         default_caps(),
+         "#version 400\n"
+         "void main() {\n"
+         "    float x = dFdx(1.0);\n"
+         "}\n");
+    SkSL::GLCaps caps = default_caps();
+    caps.fShaderDerivativeExtensionString = "GL_OES_standard_derivatives";
+    test(r,
+         "void main() { float x = 1; }",
+         caps,
+         "#version 400\n"
+         "void main() {\n"
+         "    float x = 1.0;\n"
+         "}\n");
+    test(r,
+         "void main() { float x = dFdx(1); }",
+         caps,
+         "#version 400\n"
+         "#extension GL_OES_standard_derivatives : require\n"
+         "void main() {\n"
+         "    float x = dFdx(1.0);\n"
+         "}\n");
+}