2D kernel initial wiring for Guassian

BUG=skia:
R=senorblanco@chromium.org, bsalomon@chromium.org, bsalomon@google.com

Author: joshualitt@chromium.org

Review URL: https://codereview.chromium.org/418223009
diff --git a/bench/BlurBench.cpp b/bench/BlurBench.cpp
index cafc6f3..4133adc 100644
--- a/bench/BlurBench.cpp
+++ b/bench/BlurBench.cpp
@@ -14,6 +14,7 @@
 #include "SkShader.h"
 #include "SkString.h"
 
+#define MINI   0.01f
 #define SMALL   SkIntToScalar(2)
 #define REAL    1.5f
 #define BIG     SkIntToScalar(10)
@@ -78,6 +79,11 @@
     typedef Benchmark INHERITED;
 };
 
+DEF_BENCH(return new BlurBench(MINI, kNormal_SkBlurStyle);)
+DEF_BENCH(return new BlurBench(MINI, kSolid_SkBlurStyle);)
+DEF_BENCH(return new BlurBench(MINI, kOuter_SkBlurStyle);)
+DEF_BENCH(return new BlurBench(MINI, kInner_SkBlurStyle);)
+
 DEF_BENCH(return new BlurBench(SMALL, kNormal_SkBlurStyle);)
 DEF_BENCH(return new BlurBench(SMALL, kSolid_SkBlurStyle);)
 DEF_BENCH(return new BlurBench(SMALL, kOuter_SkBlurStyle);)
@@ -98,6 +104,8 @@
 DEF_BENCH(return new BlurBench(REAL, kOuter_SkBlurStyle);)
 DEF_BENCH(return new BlurBench(REAL, kInner_SkBlurStyle);)
 
+DEF_BENCH(return new BlurBench(MINI, kNormal_SkBlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
+
 DEF_BENCH(return new BlurBench(SMALL, kNormal_SkBlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
 
 DEF_BENCH(return new BlurBench(BIG, kNormal_SkBlurStyle, SkBlurMaskFilter::kHighQuality_BlurFlag);)
diff --git a/bench/BlurImageFilterBench.cpp b/bench/BlurImageFilterBench.cpp
index 4e8688d..9021d1b 100644
--- a/bench/BlurImageFilterBench.cpp
+++ b/bench/BlurImageFilterBench.cpp
@@ -17,6 +17,7 @@
 #define FILTER_HEIGHT_SMALL 32
 #define FILTER_WIDTH_LARGE  256
 #define FILTER_HEIGHT_LARGE 256
+#define BLUR_SIGMA_MINI     0.5f
 #define BLUR_SIGMA_SMALL    1.0f
 #define BLUR_SIGMA_LARGE    10.0f
 #define BLUR_SIGMA_HUGE     80.0f
@@ -86,6 +87,8 @@
 DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_SMALL, 0, false);)
 DEF_BENCH(return new BlurImageFilterBench(0, BLUR_SIGMA_LARGE, false);)
 DEF_BENCH(return new BlurImageFilterBench(0, BLUR_SIGMA_SMALL, false);)
+DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_MINI, BLUR_SIGMA_MINI, true);)
+DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_MINI, BLUR_SIGMA_MINI, false);)
 DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_SMALL, BLUR_SIGMA_SMALL, true);)
 DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_SMALL, BLUR_SIGMA_SMALL, false);)
 DEF_BENCH(return new BlurImageFilterBench(BLUR_SIGMA_LARGE, BLUR_SIGMA_LARGE, true);)
diff --git a/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt
index 226482c..241ce18 100644
--- a/expectations/gm/ignored-tests.txt
+++ b/expectations/gm/ignored-tests.txt
@@ -59,3 +59,5 @@
 simpleaaclip_rect
 complexclip2_rect_aa
 
+#joshualitt
+matrixconvolution
\ No newline at end of file
diff --git a/gm/imageblur2.cpp b/gm/imageblur2.cpp
new file mode 100644
index 0000000..b7c9f9d
--- /dev/null
+++ b/gm/imageblur2.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBlurImageFilter.h"
+#include "SkRandom.h"
+
+// TODO deprecate imageblur
+
+#define WIDTH 500
+#define HEIGHT 500
+
+static const float kBlurSigmas[] = {
+        0.0, 0.3f, 0.5f, 2.0f, 32.0f, 80.0f };
+
+const char* kTestStrings[] = {
+        "The quick`~",
+        "brown fox[]",
+        "jumped over",
+        "the lazy@#$",
+        "dog.{}!%^&",
+        "*()+=-\\'\"/",
+};
+
+namespace skiagm {
+
+class BlurImageFilter : public GM {
+public:
+    BlurImageFilter() {
+        this->setBGColor(0xFFFFFFFF);
+        fName.printf("imageblur2");
+    }
+
+protected:
+    virtual uint32_t onGetFlags() const SK_OVERRIDE {
+        return kSkipTiled_Flag;
+    }
+
+    virtual SkString onShortName() {
+        return fName;
+    }
+
+    virtual SkISize onISize() {
+        return SkISize::Make(WIDTH, HEIGHT);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        const int sigmaCount = SK_ARRAY_COUNT(kBlurSigmas);
+        const int testStringCount = SK_ARRAY_COUNT(kTestStrings);
+        SkScalar dx = WIDTH / sigmaCount;
+        SkScalar dy = HEIGHT / sigmaCount;
+        const SkScalar textSize = 12;
+
+        for (int x = 0; x < sigmaCount; x++) {
+            SkScalar sigmaX = kBlurSigmas[x];
+            for (int y = 0; y < sigmaCount; y++) {
+                SkScalar sigmaY = kBlurSigmas[y];
+
+                SkPaint paint;
+                paint.setImageFilter(SkBlurImageFilter::Create(sigmaX, sigmaY))->unref();
+                canvas->saveLayer(NULL, &paint);
+
+                SkRandom rand;
+                SkPaint textPaint;
+                textPaint.setAntiAlias(false);
+                textPaint.setColor(rand.nextBits(24) | 0xFF000000);
+                textPaint.setTextSize(textSize);
+
+                for (int i = 0; i < testStringCount; i++) {
+                    canvas->drawText(kTestStrings[i],
+                                     strlen(kTestStrings[i]),
+                                     SkIntToScalar(x * dx),
+                                     SkIntToScalar(y * dy + textSize * i + textSize),
+                                     textPaint);
+                }
+                canvas->restore();
+            }
+        }
+    }
+
+private:
+    SkString fName;
+
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new BlurImageFilter; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index fbd2462..e1cb1a6 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -96,6 +96,7 @@
         '../gm/hittestpath.cpp',
         '../gm/imagealphathreshold.cpp',
         '../gm/imageblur.cpp',
+        '../gm/imageblur2.cpp',
         '../gm/imageblurtiled.cpp',
         '../gm/imagemagnifier.cpp',
         '../gm/imageresizetiled.cpp',
diff --git a/src/effects/SkGpuBlurUtils.cpp b/src/effects/SkGpuBlurUtils.cpp
index 5888108..aa7f995 100644
--- a/src/effects/SkGpuBlurUtils.cpp
+++ b/src/effects/SkGpuBlurUtils.cpp
@@ -11,7 +11,7 @@
 
 #if SK_SUPPORT_GPU
 #include "effects/GrConvolutionEffect.h"
-#include "effects/GrTextureDomain.h"
+#include "effects/GrMatrixConvolutionEffect.h"
 #include "GrContext.h"
 #endif
 
@@ -43,15 +43,15 @@
     return sigma;
 }
 
-static void convolve_gaussian_pass(GrContext* context,
-                                   const SkRect& srcRect,
-                                   const SkRect& dstRect,
-                                   GrTexture* texture,
-                                   Gr1DKernelEffect::Direction direction,
-                                   int radius,
-                                   float sigma,
-                                   bool useBounds,
-                                   float bounds[2]) {
+static void convolve_gaussian_1d(GrContext* context,
+                                 const SkRect& srcRect,
+                                 const SkRect& dstRect,
+                                 GrTexture* texture,
+                                 Gr1DKernelEffect::Direction direction,
+                                 int radius,
+                                 float sigma,
+                                 bool useBounds,
+                                 float bounds[2]) {
     GrPaint paint;
     paint.reset();
     SkAutoTUnref<GrEffect> conv(GrConvolutionEffect::CreateGaussian(
@@ -61,6 +61,29 @@
     context->drawRectToRect(paint, dstRect, srcRect);
 }
 
+static void convolve_gaussian_2d(GrContext* context,
+                                 const SkRect& srcRect,
+                                 const SkRect& dstRect,
+                                 GrTexture* texture,
+                                 int radiusX,
+                                 int radiusY,
+                                 SkScalar sigmaX,
+                                 SkScalar sigmaY,
+                                 bool useBounds,
+                                 SkIRect bounds) {
+    SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
+    SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
+    GrPaint paint;
+    paint.reset();
+    SkAutoTUnref<GrEffect> conv(GrMatrixConvolutionEffect::CreateGaussian(
+            texture, bounds, size, 1.0, 0.0, kernelOffset,
+            useBounds ? GrTextureDomain::kClamp_Mode : GrTextureDomain::kIgnore_Mode,
+            true, sigmaX, sigmaY));
+    paint.reset();
+    paint.addColorEffect(conv);
+    context->drawRectToRect(paint, dstRect, srcRect);
+}
+
 static void convolve_gaussian(GrContext* context,
                               const SkRect& srcRect,
                               const SkRect& dstRect,
@@ -71,8 +94,8 @@
                               bool cropToSrcRect) {
     float bounds[2] = { 0.0f, 1.0f };
     if (!cropToSrcRect) {
-        convolve_gaussian_pass(context, srcRect, dstRect, texture,
-                          direction, radius, sigma, false, bounds);
+        convolve_gaussian_1d(context, srcRect, dstRect, texture,
+                             direction, radius, sigma, false, bounds);
         return;
     }
     SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
@@ -103,16 +126,16 @@
     }
     if (radius >= size * SK_ScalarHalf) {
         // Blur radius covers srcRect; use bounds over entire draw
-        convolve_gaussian_pass(context, srcRect, dstRect, texture,
-                          direction, radius, sigma, true, bounds);
+        convolve_gaussian_1d(context, srcRect, dstRect, texture,
+                            direction, radius, sigma, true, bounds);
     } else {
         // Draw upper and lower margins with bounds; middle without.
-        convolve_gaussian_pass(context, lowerSrcRect, lowerDstRect, texture,
-                          direction, radius, sigma, true, bounds);
-        convolve_gaussian_pass(context, upperSrcRect, upperDstRect, texture,
-                          direction, radius, sigma, true, bounds);
-        convolve_gaussian_pass(context, middleSrcRect, middleDstRect, texture,
-                          direction, radius, sigma, false, bounds);
+        convolve_gaussian_1d(context, lowerSrcRect, lowerDstRect, texture,
+                             direction, radius, sigma, true, bounds);
+        convolve_gaussian_1d(context, upperSrcRect, upperDstRect, texture,
+                             direction, radius, sigma, true, bounds);
+        convolve_gaussian_1d(context, middleSrcRect, middleDstRect, texture,
+                             direction, radius, sigma, false, bounds);
     }
 }
 
@@ -196,39 +219,55 @@
     SkIRect srcIRect;
     srcRect.roundOut(&srcIRect);
 
-    if (sigmaX > 0.0f) {
-        if (scaleFactorX > 1) {
-            // Clear out a radius to the right of the srcRect to prevent the
-            // X convolution from reading garbage.
-            clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
-                                          radiusX, srcIRect.height());
-            context->clear(&clearRect, 0x0, false);
-        }
+    // For really small blurs(Certainly no wider than 5x5 on desktop gpus) it is faster to just
+    // launch a single non separable kernel vs two launches
+    if (sigmaX > 0.0f && sigmaY > 0 &&
+            (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
+        // We shouldn't be scaling because this is a small size blur
+        SkASSERT((scaleFactorX == scaleFactorY) == 1);
         context->setRenderTarget(dstTexture->asRenderTarget());
         SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
-        convolve_gaussian(context, srcRect, dstRect, srcTexture,
-                          Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
+        convolve_gaussian_2d(context, srcRect, dstRect, srcTexture,
+                radiusX, radiusY, sigmaX, sigmaY, cropToRect, srcIRect);
         srcTexture = dstTexture;
         srcRect = dstRect;
         SkTSwap(dstTexture, tempTexture);
-    }
 
-    if (sigmaY > 0.0f) {
-        if (scaleFactorY > 1 || sigmaX > 0.0f) {
-            // Clear out a radius below the srcRect to prevent the Y
-            // convolution from reading garbage.
-            clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
-                                          srcIRect.width(), radiusY);
-            context->clear(&clearRect, 0x0, false);
+    } else {
+        if (sigmaX > 0.0f) {
+            if (scaleFactorX > 1) {
+                // Clear out a radius to the right of the srcRect to prevent the
+                // X convolution from reading garbage.
+                clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
+                                              radiusX, srcIRect.height());
+                context->clear(&clearRect, 0x0, false);
+            }
+            context->setRenderTarget(dstTexture->asRenderTarget());
+            SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+            convolve_gaussian(context, srcRect, dstRect, srcTexture,
+                              Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
+            srcTexture = dstTexture;
+            srcRect = dstRect;
+            SkTSwap(dstTexture, tempTexture);
         }
 
-        context->setRenderTarget(dstTexture->asRenderTarget());
-        SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
-        convolve_gaussian(context, srcRect, dstRect, srcTexture,
-                          Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
-        srcTexture = dstTexture;
-        srcRect = dstRect;
-        SkTSwap(dstTexture, tempTexture);
+        if (sigmaY > 0.0f) {
+            if (scaleFactorY > 1 || sigmaX > 0.0f) {
+                // Clear out a radius below the srcRect to prevent the Y
+                // convolution from reading garbage.
+                clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
+                                              srcIRect.width(), radiusY);
+                context->clear(&clearRect, 0x0, false);
+            }
+
+            context->setRenderTarget(dstTexture->asRenderTarget());
+            SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+            convolve_gaussian(context, srcRect, dstRect, srcTexture,
+                              Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
+            srcTexture = dstTexture;
+            srcRect = dstRect;
+            SkTSwap(dstTexture, tempTexture);
+        }
     }
 
     if (scaleFactorX > 1 || scaleFactorY > 1) {
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index 242aba8..01cf944 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -84,34 +84,38 @@
     int kWidth = fKernelSize.width();
     int kHeight = fKernelSize.height();
 
-    builder->fsCodeAppend("\t\tvec4 sum = vec4(0, 0, 0, 0);\n");
-    builder->fsCodeAppendf("\t\tvec2 coord = %s - %s * %s;\n", coords2D.c_str(), kernelOffset,
+    builder->fsCodeAppend("vec4 sum = vec4(0, 0, 0, 0);");
+    builder->fsCodeAppendf("vec2 coord = %s - %s * %s;", coords2D.c_str(), kernelOffset,
                            imgInc);
-    builder->fsCodeAppend("\t\tvec4 c;\n");
+    builder->fsCodeAppend("vec4 c;");
 
     for (int y = 0; y < kHeight; y++) {
         for (int x = 0; x < kWidth; x++) {
             GrGLShaderBuilder::FSBlock block(builder);
-            builder->fsCodeAppendf("\t\tfloat k = %s[%d * %d + %d];\n", kernel, y, kWidth, x);
+            builder->fsCodeAppendf("float k = %s[%d * %d + %d];", kernel, y, kWidth, x);
             SkString coord;
             coord.printf("coord + vec2(%d, %d) * %s", x, y, imgInc);
             fDomain.sampleTexture(builder, domain, "c", coord, samplers[0]);
             if (!fConvolveAlpha) {
-                builder->fsCodeAppend("\t\tc.rgb /= c.a;\n");
+                builder->fsCodeAppend("c.rgb /= c.a;");
             }
-            builder->fsCodeAppend("\t\tsum += c * k;\n");
+            builder->fsCodeAppend("sum += c * k;");
         }
     }
     if (fConvolveAlpha) {
-        builder->fsCodeAppendf("\t\t%s = sum * %s + %s;\n", outputColor, gain, bias);
-        builder->fsCodeAppendf("\t\t%s.rgb = clamp(%s.rgb, 0.0, %s.a);\n",
+        builder->fsCodeAppendf("%s = sum * %s + %s;", outputColor, gain, bias);
+        builder->fsCodeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);",
                                outputColor, outputColor, outputColor);
     } else {
         fDomain.sampleTexture(builder, domain, "c", coords2D, samplers[0]);
-        builder->fsCodeAppendf("\t\t%s.a = c.a;\n", outputColor);
-        builder->fsCodeAppendf("\t\t%s.rgb = sum.rgb * %s + %s;\n", outputColor, gain, bias);
-        builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+        builder->fsCodeAppendf("%s.a = c.a;", outputColor);
+        builder->fsCodeAppendf("%s.rgb = sum.rgb * %s + %s;", outputColor, gain, bias);
+        builder->fsCodeAppendf("%s.rgb *= %s.a;", outputColor, outputColor);
     }
+
+    SkString modulate;
+    GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+    builder->fsCodeAppend(modulate.c_str());
 }
 
 void GrGLMatrixConvolutionEffect::GenKey(const GrDrawEffect& drawEffect,
@@ -157,17 +161,14 @@
     fBias(SkScalarToFloat(bias) / 255.0f),
     fConvolveAlpha(convolveAlpha),
     fDomain(GrTextureDomain::MakeTexelDomain(texture, bounds), tileMode) {
-    fKernel = new float[kernelSize.width() * kernelSize.height()];
     for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
         fKernel[i] = SkScalarToFloat(kernel[i]);
     }
     fKernelOffset[0] = static_cast<float>(kernelOffset.x());
     fKernelOffset[1] = static_cast<float>(kernelOffset.y());
-    this->setWillNotUseInputColor();
 }
 
 GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() {
-    delete[] fKernel;
 }
 
 const GrBackendEffectFactory& GrMatrixConvolutionEffect::getFactory() const {
@@ -187,6 +188,54 @@
            fDomain == s.domain();
 }
 
+// Static function to create a 2D convolution
+GrEffect* GrMatrixConvolutionEffect::CreateGaussian(GrTexture* texture,
+                                                    const SkIRect& bounds,
+                                                    const SkISize& kernelSize,
+                                                    SkScalar gain,
+                                                    SkScalar bias,
+                                                    const SkIPoint& kernelOffset,
+                                                    GrTextureDomain::Mode tileMode,
+                                                    bool convolveAlpha,
+                                                    SkScalar sigmaX,
+                                                    SkScalar sigmaY) {
+    float kernel[MAX_KERNEL_SIZE];
+    int width = kernelSize.width();
+    int height = kernelSize.height();
+    SkASSERT(width * height <= MAX_KERNEL_SIZE);
+    float sum = 0.0f;
+    float sigmaXDenom = 1.0f / (2.0f * SkScalarToFloat(SkScalarSquare(sigmaX)));
+    float sigmaYDenom = 1.0f / (2.0f * SkScalarToFloat(SkScalarSquare(sigmaY)));
+    int xRadius = width / 2;
+    int yRadius = height / 2;
+    for (int x = 0; x < width; x++) {
+      float xTerm = static_cast<float>(x - xRadius);
+      xTerm = xTerm * xTerm * sigmaXDenom;
+      for (int y = 0; y < height; y++) {
+        float yTerm = static_cast<float>(y - yRadius);
+        float xyTerm = sk_float_exp(-(xTerm + yTerm * yTerm * sigmaYDenom));
+        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
+       // is dropped here, since we renormalize the kernel below.
+        kernel[y * width + x] = xyTerm;
+        sum += xyTerm;
+      }
+    }
+    // Normalize the kernel
+    float scale = 1.0f / sum;
+    for (int i = 0; i < width * height; ++i) {
+        kernel[i] *= scale;
+    }
+    return SkNEW_ARGS(GrMatrixConvolutionEffect, (texture,
+                                                  bounds,
+                                                  kernelSize,
+                                                  kernel,
+                                                  gain,
+                                                  bias,
+                                                  kernelOffset,
+                                                  tileMode,
+                                                  convolveAlpha));
+}
+
 GR_DEFINE_EFFECT_TEST(GrMatrixConvolutionEffect);
 
 GrEffect* GrMatrixConvolutionEffect::TestCreate(SkRandom* random,
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.h b/src/gpu/effects/GrMatrixConvolutionEffect.h
index 24c3bdb..814299f 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.h
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.h
@@ -38,6 +38,18 @@
                                                       tileMode,
                                                       convolveAlpha));
     }
+
+    static GrEffect* CreateGaussian(GrTexture* texture,
+                                    const SkIRect& bounds,
+                                    const SkISize& kernelSize,
+                                    SkScalar gain,
+                                    SkScalar bias,
+                                    const SkIPoint& kernelOffset,
+                                    GrTextureDomain::Mode tileMode,
+                                    bool convolveAlpha,
+                                    SkScalar sigmaX,
+                                    SkScalar sigmaY);
+
     virtual ~GrMatrixConvolutionEffect();
 
     virtual void getConstantColorComponents(GrColor* color,
@@ -75,7 +87,7 @@
 
     SkIRect         fBounds;
     SkISize         fKernelSize;
-    float*          fKernel;
+    float           fKernel[MAX_KERNEL_SIZE];
     float           fGain;
     float           fBias;
     float           fKernelOffset[2];