Add "sharpen" option to SkSL, to LOD bias all textures

This adds a fixed bias (-0.5) to the computed LOD of all
mip-mapped texture fetches. (Technically, to all texture
fetches, but that only matters for mip-mapped ones).

Clients can opt-in with a new GrContextOption.

Bug: skia:7541
Bug: chromium:562162
Change-Id: Ie3cd0679c4ab66f62d2dc32e7e68e5c99355115e
Reviewed-on: https://skia-review.googlesource.com/106322
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index aac0d8d..4d937c7 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -370,6 +370,7 @@
     std::unique_ptr<GrTextBlobCache>        fTextBlobCache;
 
     bool                                    fDisableGpuYUVConversion;
+    bool                                    fSharpenMipmappedTextures;
     bool                                    fDidTestPMConversions;
     // true if the PM/UPM conversion succeeded; false otherwise
     bool                                    fPMUPMConversionsRoundTrip;
diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h
index 338c538..84863f5 100644
--- a/include/gpu/GrContextOptions.h
+++ b/include/gpu/GrContextOptions.h
@@ -135,6 +135,13 @@
     bool fAvoidStencilBuffers = false;
 
     /**
+     * If true, texture fetches from mip-mapped textures will be biased to read larger MIP levels.
+     * This has the effect of sharpening those textures, at the cost of some aliasing, and possible
+     * performance impact.
+     */
+    bool fSharpenMipmappedTextures = false;
+
+    /**
      * Enables driver workaround to use draws instead of glClear. This only applies to
      * kOpenGL_GrBackend.
      */
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 8a5417e..09985e6 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -255,6 +255,7 @@
                                                         options));
 
     fDisableGpuYUVConversion = options.fDisableGpuYUVConversion;
+    fSharpenMipmappedTextures = options.fSharpenMipmappedTextures;
     fDidTestPMConversions = false;
 
     GrPathRendererChain::Options prcOptions;
diff --git a/src/gpu/GrContextPriv.h b/src/gpu/GrContextPriv.h
index e007425..93c1c17 100644
--- a/src/gpu/GrContextPriv.h
+++ b/src/gpu/GrContextPriv.h
@@ -67,6 +67,7 @@
                                                                  const SkSurfaceProps* = nullptr);
 
     bool disableGpuYUVConversion() const { return fContext->fDisableGpuYUVConversion; }
+    bool sharpenMipmappedTextures() const { return fContext->fSharpenMipmappedTextures; }
 
     /**
      * Call to ensure all drawing to the context has been issued to the
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index 7ae63a2..ed6386b 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -152,6 +152,7 @@
     SkSL::Program::Settings settings;
     settings.fCaps = this->gpu()->glCaps().shaderCaps();
     settings.fFlipY = this->pipeline().proxy()->origin() != kTopLeft_GrSurfaceOrigin;
+    settings.fSharpenTextures = this->gpu()->getContext()->contextPriv().sharpenMipmappedTextures();
     SkSL::Program::Inputs inputs;
     SkTDArray<GrGLuint> shadersToDelete;
     bool cached = nullptr != fCached.get();
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 9b9b070..a263b77 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -7,6 +7,8 @@
 
 #include "vk/GrVkPipelineStateBuilder.h"
 
+#include "GrContext.h"
+#include "GrContextPriv.h"
 #include "GrShaderCaps.h"
 #include "vk/GrVkDescriptorSetManager.h"
 #include "vk/GrVkGpu.h"
@@ -141,6 +143,7 @@
     SkSL::Program::Settings settings;
     settings.fCaps = this->caps()->shaderCaps();
     settings.fFlipY = this->pipeline().proxy()->origin() != kTopLeft_GrSurfaceOrigin;
+    settings.fSharpenTextures = this->gpu()->getContext()->contextPriv().sharpenMipmappedTextures();
     SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT,
                                               fVS,
                                               &vertShaderModule,
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index d02948f..f94cc4c 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -505,12 +505,14 @@
         fHeader.writeText(" : require\n");
         fFoundDerivatives = true;
     }
+    bool isTextureFunctionWithBias = false;
     if (c.fFunction.fName == "texture" && c.fFunction.fBuiltin) {
         const char* dim = "";
         bool proj = false;
         switch (c.fArguments[0]->fType.dimensions()) {
             case SpvDim1D:
                 dim = "1D";
+                isTextureFunctionWithBias = true;
                 if (c.fArguments[1]->fType == *fContext.fFloat_Type) {
                     proj = false;
                 } else {
@@ -520,6 +522,7 @@
                 break;
             case SpvDim2D:
                 dim = "2D";
+                isTextureFunctionWithBias = true;
                 if (c.fArguments[1]->fType == *fContext.fFloat2_Type) {
                     proj = false;
                 } else {
@@ -529,6 +532,7 @@
                 break;
             case SpvDim3D:
                 dim = "3D";
+                isTextureFunctionWithBias = true;
                 if (c.fArguments[1]->fType == *fContext.fFloat3_Type) {
                     proj = false;
                 } else {
@@ -538,6 +542,7 @@
                 break;
             case SpvDimCube:
                 dim = "Cube";
+                isTextureFunctionWithBias = true;
                 proj = false;
                 break;
             case SpvDimRect:
@@ -573,6 +578,9 @@
         separator = ", ";
         this->writeExpression(*arg, kSequence_Precedence);
     }
+    if (fProgram.fSettings.fSharpenTextures && isTextureFunctionWithBias) {
+        this->write(", -0.5");
+    }
     this->write(")");
 }
 
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index cb8b388..d01a82d 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -824,8 +824,16 @@
                                        out);
             } else {
                 ASSERT(c.fArguments.size() == 2);
-                this->writeInstruction(op, type, result, sampler, uv,
-                                       out);
+                if (fProgram.fSettings.fSharpenTextures) {
+                    FloatLiteral lodBias(fContext, -1, -0.5);
+                    this->writeInstruction(op, type, result, sampler, uv,
+                                           SpvImageOperandsBiasMask,
+                                           this->writeFloatLiteral(lodBias),
+                                           out);
+                } else {
+                    this->writeInstruction(op, type, result, sampler, uv,
+                                           out);
+                }
             }
             break;
         }
diff --git a/src/sksl/ir/SkSLProgram.h b/src/sksl/ir/SkSLProgram.h
index 3184547..a63cd23 100644
--- a/src/sksl/ir/SkSLProgram.h
+++ b/src/sksl/ir/SkSLProgram.h
@@ -76,6 +76,8 @@
         bool fReplaceSettings = true;
         // if true, all halfs are forced to be floats
         bool fForceHighPrecision = false;
+        // if true, add -0.5 bias to LOD of all texture lookups
+        bool fSharpenTextures = false;
         std::unordered_map<String, Value> fArgs;
     };
 
diff --git a/tests/SkSLGLSLTest.cpp b/tests/SkSLGLSLTest.cpp
index 62c5cd6..e637197 100644
--- a/tests/SkSLGLSLTest.cpp
+++ b/tests/SkSLGLSLTest.cpp
@@ -983,6 +983,62 @@
          "}\n");
 }
 
+DEF_TEST(SkSLSharpen, r) {
+    SkSL::Program::Settings settings;
+    settings.fSharpenTextures = true;
+    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
+    settings.fCaps = caps.get();
+    SkSL::Program::Inputs inputs;
+    test(r,
+         "uniform sampler1D one;"
+         "uniform sampler2D two;"
+         "void main() {"
+         "float4 a = texture(one, 0);"
+         "float4 b = texture(two, float2(0));"
+         "float4 c = texture(one, float2(0));"
+         "float4 d = texture(two, float3(0));"
+         "sk_FragColor = half4(a.x, b.x, c.x, d.x);"
+         "}",
+         settings,
+         "#version 400\n"
+         "out vec4 sk_FragColor;\n"
+         "uniform sampler1D one;\n"
+         "uniform sampler2D two;\n"
+         "void main() {\n"
+         "    vec4 a = texture(one, 0.0, -0.5);\n"
+         "    vec4 b = texture(two, vec2(0.0), -0.5);\n"
+         "    vec4 c = textureProj(one, vec2(0.0), -0.5);\n"
+         "    vec4 d = textureProj(two, vec3(0.0), -0.5);\n"
+         "    sk_FragColor = vec4(a.x, b.x, c.x, d.x);\n"
+         "}\n",
+         &inputs);
+
+    caps = SkSL::ShaderCapsFactory::Version110();
+    settings.fCaps = caps.get();
+    test(r,
+         "uniform sampler1D one;"
+         "uniform sampler2D two;"
+         "void main() {"
+         "float4 a = texture(one, 0);"
+         "float4 b = texture(two, float2(0));"
+         "float4 c = texture(one, float2(0));"
+         "float4 d = texture(two, float3(0));"
+         "sk_FragColor = half4(a.x, b.x, c.x, d.x);"
+         "}",
+         settings,
+         "#version 110\n"
+         "uniform sampler1D one;\n"
+         "uniform sampler2D two;\n"
+         "void main() {\n"
+         "    vec4 a = texture1D(one, 0.0, -0.5);\n"
+         "    vec4 b = texture2D(two, vec2(0.0), -0.5);\n"
+         "    vec4 c = texture1DProj(one, vec2(0.0), -0.5);\n"
+         "    vec4 d = texture2DProj(two, vec3(0.0), -0.5);\n"
+         "    gl_FragColor = vec4(a.x, b.x, c.x, d.x);\n"
+         "}\n",
+         &inputs);
+}
+
 DEF_TEST(SkSLOffset, r) {
     test(r,
          "struct Test {"