skia2/gm/lighting.cpp

179 lines
8.2 KiB
C++
Raw Normal View History

/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/core/SkPoint3.h"
#include "include/effects/SkLightingImageFilter.h"
#include "include/effects/SkOffsetImageFilter.h"
#include "tools/ToolUtils.h"
#include "tools/timer/AnimTimer.h"
Revert of Update feSpotLight to match spec (patchset #2 id:20001 of https://codereview.chromium.org/1403403003/ ) Reason for revert: re-land once layout test have been disabled (so they can be rebased) Original issue's description: > Update feSpotLight to match spec > > This change updates feSpotLight to match the spec via two changes: > > 1) specularExponent is ignored if the spotlight has no coneAngle (GPU > bug only). This change updates the GPU path so that it matches the > CPU path and the spec in this regard. > > 2) specularExponent is clamped to the 1-128 range. The spec does not > specify a clamp for the specularExponent attribute of feSpotLight. > Note that the spec *does* specify this clamp for the > specularExponent attribute of feSpecularLighting. It looks like we > incorrectly applied this to both specularExponent attributes. > > This change (along with a parallel change in Blink) allows us to pass > the SVG filter effects conformance test here: > http://www.w3.org/Graphics/SVG/Test/20110816/harness/htmlObject/filters-light-01-f.html > > Additionally, this brings our behavior in line with Safari and Edge’s > behavior on this filter. > > Two new cases were added to gm/lighting.cpp to catch these issues: > - The existing spotlight case exercised the path where our specular > exponent was between 1-128 and had a limiting cone angle. > - The first new spotlight case exercises the path where our specular > exponent is between 1-128 and we do not have a limiting cone angle. > - The second new spotlight case exercises the path where the specular > exponent is not within the 1-128 range, to ensure that we don’t > incorrectly clip to this range. > > BUG=472849 > > Committed: https://skia.googlesource.com/skia/+/c84ccb070258db2803a9e8f532bfe7239a737063 TBR=senorblanco@google.com,senorblanco@chromium.org,bsalomon@google.com,ericrk@chromium.org NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=472849 Review URL: https://codereview.chromium.org/1417463006
2015-10-20 17:04:03 +00:00
#define WIDTH 330
Implement approx-match support in image filter saveLayer() offscreen. Currently, the GPU-side image filter implementation creates exact-match textures for the offscreen backing stores for saveLayer(). This is because several filters have GPU implementations which depend on the texture coordinates being 0..1. The fix is three-fold: 1) Store the actual requested size in the SkGpuDevice, so that when wrapping it in an SkBitmap for passing to filterImage(), we can give it the original size. 2) Fix the filters (SkMagnifierImageFilter, SkLightingImageFilter, SkMatrixConvolutionImageFilter, SkMatrixImageFilter) whose GPU implementation depends on 0..1 texture coordinates. 3) Remove the exception for GPU-side image filters in SkCanvas::internalSaveLayer(). For the lighting filters, there were two bugs which were cancelling each other out: the sobel filter matrix was being computed upside down, but then we'd negate the resulting normal. This worked fine in the exact-match case, but in the approx-match case we'd sample garbage along the edge pixels. Also, we never implemented the edge pixels according to spec in the GPU case. It requires a different fragment shader for each edge of the nine-patch, which meant we couldn't use asFragmentProcessor(), and had to implement the drawing via a filterImageGPU() override. In order to avoid polluting the public API, I inserted a new base class, SkLightingImageFilterInternal above Sk[Diffuse|Specular]LightingImageFilter to handle the implementation. For the SkMatrixConvolutionImageFilter, it seems the GLSL clamp() function occasionally returns values outside the clamped range, resulting in access of garbage texels even in GL_NEAREST. The fix here is to clamp to a rect inset by half a texel. There was also a bug in the unpremultiply step when fConvolveAlpha is false. For SkMatrixImageFilter, the fix was to make the generic draw path be more careful about when to use texture domain. If the bitmap already has a texture, use texture domain if the srcRect is smaller than the entire texture (not the entire bitmap). N.B.: this change will cause some minor pixel diffs in the GPU results of the following GMs (and possibly more): matriximagefilter, matrixconvolution, imagefiltersscaled, lighting, imagemagnifier, filterfastbounds, complexclip_aa_Layer_invert, complexclip_aa_layer, complexclip_bw_layer_invert, complexclip_bw_layer. BUG=skia:3532 Committed: https://skia.googlesource.com/skia/+/b97dafefe63ea0a1bbce8e8b209f4920983fb8b9 Committed: https://skia.googlesource.com/skia/+/f5f8518fe0bbd2703e4ffc1b11ad7b4312ff7641 Committed: https://skia.googlesource.com/skia/+/46112cf2a7c7307f1c9eebb5f881cbda15aa460c Review URL: https://codereview.chromium.org/1034733002
2015-04-02 11:54:56 +00:00
#define HEIGHT 660
namespace skiagm {
class ImageLightingGM : public GM {
public:
ImageLightingGM()
: fAzimuth(SkIntToScalar(kStartAzimuth)) {
this->setBGColor(0xFF000000);
}
protected:
SkString onShortName() override {
return SkString("lighting");
}
SkISize onISize() override {
return SkISize::Make(WIDTH, HEIGHT);
}
void drawClippedBitmap(SkCanvas* canvas, const SkPaint& paint, int x, int y) {
canvas->save();
canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
canvas->clipRect(SkRect::MakeWH(
SkIntToScalar(fBitmap.width()), SkIntToScalar(fBitmap.height())));
canvas->drawBitmap(fBitmap, 0, 0, &paint);
canvas->restore();
}
void onOnceBeforeDraw() override {
fBitmap = ToolUtils::create_string_bitmap(100, 100, 0xFFFFFFFF, 20, 70, 96, "e");
}
void onDraw(SkCanvas* canvas) override {
canvas->clear(0xFF101010);
SkPaint checkPaint;
checkPaint.setColor(0xFF202020);
for (int y = 0; y < HEIGHT; y += 16) {
for (int x = 0; x < WIDTH; x += 16) {
canvas->save();
canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
canvas->drawRect(SkRect::MakeXYWH(8, 0, 8, 8), checkPaint);
canvas->drawRect(SkRect::MakeXYWH(0, 8, 8, 8), checkPaint);
canvas->restore();
}
}
SkScalar sinAzimuth = SkScalarSin(SkDegreesToRadians(fAzimuth)),
cosAzimuth = SkScalarCos(SkDegreesToRadians(fAzimuth));
SkPoint3 spotTarget = SkPoint3::Make(SkIntToScalar(40), SkIntToScalar(40), 0);
SkPoint3 spotLocation = SkPoint3::Make(spotTarget.fX + 70.7214f * cosAzimuth,
spotTarget.fY + 70.7214f * sinAzimuth,
spotTarget.fZ + SkIntToScalar(20));
SkScalar spotExponent = SK_Scalar1;
SkPoint3 pointLocation = SkPoint3::Make(spotTarget.fX + 50 * cosAzimuth,
spotTarget.fY + 50 * sinAzimuth,
SkIntToScalar(10));
SkScalar elevationRad = SkDegreesToRadians(SkIntToScalar(5));
SkPoint3 distantDirection = SkPoint3::Make(cosAzimuth * SkScalarCos(elevationRad),
sinAzimuth * SkScalarCos(elevationRad),
SkScalarSin(elevationRad));
Revert of Update feSpotLight to match spec (patchset #2 id:20001 of https://codereview.chromium.org/1403403003/ ) Reason for revert: re-land once layout test have been disabled (so they can be rebased) Original issue's description: > Update feSpotLight to match spec > > This change updates feSpotLight to match the spec via two changes: > > 1) specularExponent is ignored if the spotlight has no coneAngle (GPU > bug only). This change updates the GPU path so that it matches the > CPU path and the spec in this regard. > > 2) specularExponent is clamped to the 1-128 range. The spec does not > specify a clamp for the specularExponent attribute of feSpotLight. > Note that the spec *does* specify this clamp for the > specularExponent attribute of feSpecularLighting. It looks like we > incorrectly applied this to both specularExponent attributes. > > This change (along with a parallel change in Blink) allows us to pass > the SVG filter effects conformance test here: > http://www.w3.org/Graphics/SVG/Test/20110816/harness/htmlObject/filters-light-01-f.html > > Additionally, this brings our behavior in line with Safari and Edge’s > behavior on this filter. > > Two new cases were added to gm/lighting.cpp to catch these issues: > - The existing spotlight case exercised the path where our specular > exponent was between 1-128 and had a limiting cone angle. > - The first new spotlight case exercises the path where our specular > exponent is between 1-128 and we do not have a limiting cone angle. > - The second new spotlight case exercises the path where the specular > exponent is not within the 1-128 range, to ensure that we don’t > incorrectly clip to this range. > > BUG=472849 > > Committed: https://skia.googlesource.com/skia/+/c84ccb070258db2803a9e8f532bfe7239a737063 TBR=senorblanco@google.com,senorblanco@chromium.org,bsalomon@google.com,ericrk@chromium.org NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=472849 Review URL: https://codereview.chromium.org/1417463006
2015-10-20 17:04:03 +00:00
SkScalar cutoffAngle = SkIntToScalar(15);
SkScalar kd = SkIntToScalar(2);
SkScalar ks = SkIntToScalar(1);
SkScalar shininess = SkIntToScalar(8);
SkScalar surfaceScale = SkIntToScalar(1);
SkColor white(0xFFFFFFFF);
SkPaint paint;
SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(20, 10, 60, 65));
Implement approx-match support in image filter saveLayer() offscreen. Currently, the GPU-side image filter implementation creates exact-match textures for the offscreen backing stores for saveLayer(). This is because several filters have GPU implementations which depend on the texture coordinates being 0..1. The fix is three-fold: 1) Store the actual requested size in the SkGpuDevice, so that when wrapping it in an SkBitmap for passing to filterImage(), we can give it the original size. 2) Fix the filters (SkMagnifierImageFilter, SkLightingImageFilter, SkMatrixConvolutionImageFilter, SkMatrixImageFilter) whose GPU implementation depends on 0..1 texture coordinates. 3) Remove the exception for GPU-side image filters in SkCanvas::internalSaveLayer(). For the lighting filters, there were two bugs which were cancelling each other out: the sobel filter matrix was being computed upside down, but then we'd negate the resulting normal. This worked fine in the exact-match case, but in the approx-match case we'd sample garbage along the edge pixels. Also, we never implemented the edge pixels according to spec in the GPU case. It requires a different fragment shader for each edge of the nine-patch, which meant we couldn't use asFragmentProcessor(), and had to implement the drawing via a filterImageGPU() override. In order to avoid polluting the public API, I inserted a new base class, SkLightingImageFilterInternal above Sk[Diffuse|Specular]LightingImageFilter to handle the implementation. For the SkMatrixConvolutionImageFilter, it seems the GLSL clamp() function occasionally returns values outside the clamped range, resulting in access of garbage texels even in GL_NEAREST. The fix here is to clamp to a rect inset by half a texel. There was also a bug in the unpremultiply step when fConvolveAlpha is false. For SkMatrixImageFilter, the fix was to make the generic draw path be more careful about when to use texture domain. If the bitmap already has a texture, use texture domain if the srcRect is smaller than the entire texture (not the entire bitmap). N.B.: this change will cause some minor pixel diffs in the GPU results of the following GMs (and possibly more): matriximagefilter, matrixconvolution, imagefiltersscaled, lighting, imagemagnifier, filterfastbounds, complexclip_aa_Layer_invert, complexclip_aa_layer, complexclip_bw_layer_invert, complexclip_bw_layer. BUG=skia:3532 Committed: https://skia.googlesource.com/skia/+/b97dafefe63ea0a1bbce8e8b209f4920983fb8b9 Committed: https://skia.googlesource.com/skia/+/f5f8518fe0bbd2703e4ffc1b11ad7b4312ff7641 Committed: https://skia.googlesource.com/skia/+/46112cf2a7c7307f1c9eebb5f881cbda15aa460c Review URL: https://codereview.chromium.org/1034733002
2015-04-02 11:54:56 +00:00
SkImageFilter::CropRect fullSizeCropRect(SkRect::MakeXYWH(0, 0, 100, 100));
sk_sp<SkImageFilter> noopCropped(SkOffsetImageFilter::Make(0, 0, nullptr, &cropRect));
int y = 0;
Implement approx-match support in image filter saveLayer() offscreen. Currently, the GPU-side image filter implementation creates exact-match textures for the offscreen backing stores for saveLayer(). This is because several filters have GPU implementations which depend on the texture coordinates being 0..1. The fix is three-fold: 1) Store the actual requested size in the SkGpuDevice, so that when wrapping it in an SkBitmap for passing to filterImage(), we can give it the original size. 2) Fix the filters (SkMagnifierImageFilter, SkLightingImageFilter, SkMatrixConvolutionImageFilter, SkMatrixImageFilter) whose GPU implementation depends on 0..1 texture coordinates. 3) Remove the exception for GPU-side image filters in SkCanvas::internalSaveLayer(). For the lighting filters, there were two bugs which were cancelling each other out: the sobel filter matrix was being computed upside down, but then we'd negate the resulting normal. This worked fine in the exact-match case, but in the approx-match case we'd sample garbage along the edge pixels. Also, we never implemented the edge pixels according to spec in the GPU case. It requires a different fragment shader for each edge of the nine-patch, which meant we couldn't use asFragmentProcessor(), and had to implement the drawing via a filterImageGPU() override. In order to avoid polluting the public API, I inserted a new base class, SkLightingImageFilterInternal above Sk[Diffuse|Specular]LightingImageFilter to handle the implementation. For the SkMatrixConvolutionImageFilter, it seems the GLSL clamp() function occasionally returns values outside the clamped range, resulting in access of garbage texels even in GL_NEAREST. The fix here is to clamp to a rect inset by half a texel. There was also a bug in the unpremultiply step when fConvolveAlpha is false. For SkMatrixImageFilter, the fix was to make the generic draw path be more careful about when to use texture domain. If the bitmap already has a texture, use texture domain if the srcRect is smaller than the entire texture (not the entire bitmap). N.B.: this change will cause some minor pixel diffs in the GPU results of the following GMs (and possibly more): matriximagefilter, matrixconvolution, imagefiltersscaled, lighting, imagemagnifier, filterfastbounds, complexclip_aa_Layer_invert, complexclip_aa_layer, complexclip_bw_layer_invert, complexclip_bw_layer. BUG=skia:3532 Committed: https://skia.googlesource.com/skia/+/b97dafefe63ea0a1bbce8e8b209f4920983fb8b9 Committed: https://skia.googlesource.com/skia/+/f5f8518fe0bbd2703e4ffc1b11ad7b4312ff7641 Committed: https://skia.googlesource.com/skia/+/46112cf2a7c7307f1c9eebb5f881cbda15aa460c Review URL: https://codereview.chromium.org/1034733002
2015-04-02 11:54:56 +00:00
for (int i = 0; i < 3; i++) {
const SkImageFilter::CropRect* cr = (i == 1) ? &cropRect : (i == 2) ? &fullSizeCropRect : nullptr;
sk_sp<SkImageFilter> input = (i == 2) ? noopCropped : nullptr;
paint.setImageFilter(SkLightingImageFilter::MakePointLitDiffuse(pointLocation,
white,
surfaceScale,
kd,
input,
cr));
drawClippedBitmap(canvas, paint, 0, y);
paint.setImageFilter(SkLightingImageFilter::MakeDistantLitDiffuse(distantDirection,
Implement approx-match support in image filter saveLayer() offscreen. Currently, the GPU-side image filter implementation creates exact-match textures for the offscreen backing stores for saveLayer(). This is because several filters have GPU implementations which depend on the texture coordinates being 0..1. The fix is three-fold: 1) Store the actual requested size in the SkGpuDevice, so that when wrapping it in an SkBitmap for passing to filterImage(), we can give it the original size. 2) Fix the filters (SkMagnifierImageFilter, SkLightingImageFilter, SkMatrixConvolutionImageFilter, SkMatrixImageFilter) whose GPU implementation depends on 0..1 texture coordinates. 3) Remove the exception for GPU-side image filters in SkCanvas::internalSaveLayer(). For the lighting filters, there were two bugs which were cancelling each other out: the sobel filter matrix was being computed upside down, but then we'd negate the resulting normal. This worked fine in the exact-match case, but in the approx-match case we'd sample garbage along the edge pixels. Also, we never implemented the edge pixels according to spec in the GPU case. It requires a different fragment shader for each edge of the nine-patch, which meant we couldn't use asFragmentProcessor(), and had to implement the drawing via a filterImageGPU() override. In order to avoid polluting the public API, I inserted a new base class, SkLightingImageFilterInternal above Sk[Diffuse|Specular]LightingImageFilter to handle the implementation. For the SkMatrixConvolutionImageFilter, it seems the GLSL clamp() function occasionally returns values outside the clamped range, resulting in access of garbage texels even in GL_NEAREST. The fix here is to clamp to a rect inset by half a texel. There was also a bug in the unpremultiply step when fConvolveAlpha is false. For SkMatrixImageFilter, the fix was to make the generic draw path be more careful about when to use texture domain. If the bitmap already has a texture, use texture domain if the srcRect is smaller than the entire texture (not the entire bitmap). N.B.: this change will cause some minor pixel diffs in the GPU results of the following GMs (and possibly more): matriximagefilter, matrixconvolution, imagefiltersscaled, lighting, imagemagnifier, filterfastbounds, complexclip_aa_Layer_invert, complexclip_aa_layer, complexclip_bw_layer_invert, complexclip_bw_layer. BUG=skia:3532 Committed: https://skia.googlesource.com/skia/+/b97dafefe63ea0a1bbce8e8b209f4920983fb8b9 Committed: https://skia.googlesource.com/skia/+/f5f8518fe0bbd2703e4ffc1b11ad7b4312ff7641 Committed: https://skia.googlesource.com/skia/+/46112cf2a7c7307f1c9eebb5f881cbda15aa460c Review URL: https://codereview.chromium.org/1034733002
2015-04-02 11:54:56 +00:00
white,
surfaceScale,
kd,
input,
cr));
drawClippedBitmap(canvas, paint, 110, y);
paint.setImageFilter(SkLightingImageFilter::MakeSpotLitDiffuse(spotLocation,
spotTarget,
spotExponent,
cutoffAngle,
white,
surfaceScale,
kd,
input,
cr));
drawClippedBitmap(canvas, paint, 220, y);
y += 110;
paint.setImageFilter(SkLightingImageFilter::MakePointLitSpecular(pointLocation,
white,
surfaceScale,
ks,
shininess,
input,
cr));
drawClippedBitmap(canvas, paint, 0, y);
paint.setImageFilter(SkLightingImageFilter::MakeDistantLitSpecular(distantDirection,
Implement approx-match support in image filter saveLayer() offscreen. Currently, the GPU-side image filter implementation creates exact-match textures for the offscreen backing stores for saveLayer(). This is because several filters have GPU implementations which depend on the texture coordinates being 0..1. The fix is three-fold: 1) Store the actual requested size in the SkGpuDevice, so that when wrapping it in an SkBitmap for passing to filterImage(), we can give it the original size. 2) Fix the filters (SkMagnifierImageFilter, SkLightingImageFilter, SkMatrixConvolutionImageFilter, SkMatrixImageFilter) whose GPU implementation depends on 0..1 texture coordinates. 3) Remove the exception for GPU-side image filters in SkCanvas::internalSaveLayer(). For the lighting filters, there were two bugs which were cancelling each other out: the sobel filter matrix was being computed upside down, but then we'd negate the resulting normal. This worked fine in the exact-match case, but in the approx-match case we'd sample garbage along the edge pixels. Also, we never implemented the edge pixels according to spec in the GPU case. It requires a different fragment shader for each edge of the nine-patch, which meant we couldn't use asFragmentProcessor(), and had to implement the drawing via a filterImageGPU() override. In order to avoid polluting the public API, I inserted a new base class, SkLightingImageFilterInternal above Sk[Diffuse|Specular]LightingImageFilter to handle the implementation. For the SkMatrixConvolutionImageFilter, it seems the GLSL clamp() function occasionally returns values outside the clamped range, resulting in access of garbage texels even in GL_NEAREST. The fix here is to clamp to a rect inset by half a texel. There was also a bug in the unpremultiply step when fConvolveAlpha is false. For SkMatrixImageFilter, the fix was to make the generic draw path be more careful about when to use texture domain. If the bitmap already has a texture, use texture domain if the srcRect is smaller than the entire texture (not the entire bitmap). N.B.: this change will cause some minor pixel diffs in the GPU results of the following GMs (and possibly more): matriximagefilter, matrixconvolution, imagefiltersscaled, lighting, imagemagnifier, filterfastbounds, complexclip_aa_Layer_invert, complexclip_aa_layer, complexclip_bw_layer_invert, complexclip_bw_layer. BUG=skia:3532 Committed: https://skia.googlesource.com/skia/+/b97dafefe63ea0a1bbce8e8b209f4920983fb8b9 Committed: https://skia.googlesource.com/skia/+/f5f8518fe0bbd2703e4ffc1b11ad7b4312ff7641 Committed: https://skia.googlesource.com/skia/+/46112cf2a7c7307f1c9eebb5f881cbda15aa460c Review URL: https://codereview.chromium.org/1034733002
2015-04-02 11:54:56 +00:00
white,
surfaceScale,
ks,
shininess,
input,
cr));
drawClippedBitmap(canvas, paint, 110, y);
paint.setImageFilter(SkLightingImageFilter::MakeSpotLitSpecular(spotLocation,
spotTarget,
spotExponent,
cutoffAngle,
white,
surfaceScale,
ks,
shininess,
input,
cr));
drawClippedBitmap(canvas, paint, 220, y);
y += 110;
}
}
bool onAnimate(const AnimTimer& timer) override {
constexpr SkScalar kDesiredDurationSecs = 15.0f;
fAzimuth = kStartAzimuth + timer.scaled(360.0f/kDesiredDurationSecs, 360.0f);
return true;
}
private:
static constexpr int kStartAzimuth = 225;
SkBitmap fBitmap;
SkScalar fAzimuth;
typedef GM INHERITED;
};
//////////////////////////////////////////////////////////////////////////////
DEF_GM(return new ImageLightingGM;)
}