From a8cdbd7431b6a27d28db1bc80d7557cedf7e66d0 Mon Sep 17 00:00:00 2001 From: Robert Phillips Date: Tue, 17 Jul 2018 12:30:40 -0400 Subject: [PATCH] Restore SkLightingShader and associated classes This reverts https://skia-review.googlesource.com/c/skia/+/31140 (Remove SkLightingShader and associated classes) and updates the classes to ToT Change-Id: I3b1df1704cca8907aa00f081a7e93339b65ad4fa Reviewed-on: https://skia-review.googlesource.com/141545 Reviewed-by: Mike Reed Commit-Queue: Robert Phillips --- gm/lightingshader.cpp | 160 ++++++ gm/lightingshader2.cpp | 278 ++++++++++ gn/core.gni | 10 + gn/gm.gni | 2 + gn/samples.gni | 2 + include/core/SkFlattenable.h | 2 +- include/core/SkLights.h | 195 +++++++ samplecode/SampleLighting.cpp | 109 ++++ samplecode/SampleLitAtlas.cpp | 503 +++++++++++++++++++ src/core/SkLights.cpp | 91 ++++ src/core/SkNormalFlatSource.cpp | 100 ++++ src/core/SkNormalFlatSource.h | 47 ++ src/core/SkNormalMapSource.cpp | 253 ++++++++++ src/core/SkNormalMapSource.h | 57 +++ src/core/SkNormalSource.cpp | 22 + src/core/SkNormalSource.h | 75 +++ src/gpu/GrProcessor.h | 3 + src/ports/SkGlobalInitialization_default.cpp | 4 + src/shaders/SkLightingShader.cpp | 497 ++++++++++++++++++ src/shaders/SkLightingShader.h | 40 ++ tests/SerializationTest.cpp | 60 +++ tools/sk_tool_utils.cpp | 110 +++- tools/sk_tool_utils.h | 6 + 23 files changed, 2624 insertions(+), 2 deletions(-) create mode 100644 gm/lightingshader.cpp create mode 100644 gm/lightingshader2.cpp create mode 100644 include/core/SkLights.h create mode 100644 samplecode/SampleLighting.cpp create mode 100644 samplecode/SampleLitAtlas.cpp create mode 100644 src/core/SkLights.cpp create mode 100644 src/core/SkNormalFlatSource.cpp create mode 100644 src/core/SkNormalFlatSource.h create mode 100644 src/core/SkNormalMapSource.cpp create mode 100644 src/core/SkNormalMapSource.h create mode 100644 src/core/SkNormalSource.cpp create mode 100644 src/core/SkNormalSource.h create mode 100644 src/shaders/SkLightingShader.cpp create mode 100644 src/shaders/SkLightingShader.h diff --git a/gm/lightingshader.cpp b/gm/lightingshader.cpp new file mode 100644 index 0000000000..303a4e233a --- /dev/null +++ b/gm/lightingshader.cpp @@ -0,0 +1,160 @@ +/* + * Copyright 2015 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 "sk_tool_utils.h" +#include "SkLightingShader.h" +#include "SkNormalSource.h" +#include "SkPoint3.h" +#include "SkShader.h" + +// Create a hemispherical normal map +static SkBitmap make_hemi_normalmap(int texSize) { + SkBitmap hemi; + hemi.allocN32Pixels(texSize, texSize); + + sk_tool_utils::create_hemi_normal_map(&hemi, SkIRect::MakeWH(texSize, texSize)); + return hemi; +} + +// Create a truncated pyramid normal map +static SkBitmap make_frustum_normalmap(int texSize) { + SkBitmap frustum; + frustum.allocN32Pixels(texSize, texSize); + + sk_tool_utils::create_frustum_normal_map(&frustum, SkIRect::MakeWH(texSize, texSize)); + return frustum; +} + +// Create a tetrahedral normal map +static SkBitmap make_tetra_normalmap(int texSize) { + SkBitmap tetra; + tetra.allocN32Pixels(texSize, texSize); + + sk_tool_utils::create_tetra_normal_map(&tetra, SkIRect::MakeWH(texSize, texSize)); + return tetra; +} + +namespace skiagm { + +// This GM exercises lighting shaders by drawing rotated and non-rotated normal mapped rects with +// a directional light off to the viewers right. +class LightingShaderGM : public GM { +public: + LightingShaderGM() { + this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC)); + } + +protected: + enum NormalMap { + kHemi_NormalMap, + kFrustum_NormalMap, + kTetra_NormalMap, + + kLast_NormalMap = kTetra_NormalMap + }; + + static constexpr int kNormalMapCount = kLast_NormalMap+1; + + SkString onShortName() override { return SkString("lightingshader"); } + + SkISize onISize() override { return SkISize::Make(kGMSize, kGMSize); } + + void onOnceBeforeDraw() override { + { + SkLights::Builder builder; + + // The direction vector is towards the light w/ +Z coming out of the screen + builder.add(SkLights::Light::MakeDirectional(SkColor3f::Make(1.0f, 1.0f, 1.0f), + SkVector3::Make(SK_ScalarRoot2Over2, + 0.0f, + SK_ScalarRoot2Over2))); + builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f)); + + fLights = builder.finish(); + } + + fDiffuse = sk_tool_utils::create_checkerboard_bitmap( + kTexSize, kTexSize, + sk_tool_utils::color_to_565(0x0), + sk_tool_utils::color_to_565(0xFF804020), + 8); + + fNormalMaps[kHemi_NormalMap] = make_hemi_normalmap(kTexSize); + fNormalMaps[kFrustum_NormalMap] = make_frustum_normalmap(kTexSize); + fNormalMaps[kTetra_NormalMap] = make_tetra_normalmap(kTexSize); + } + + void drawRect(SkCanvas* canvas, const SkRect& r, NormalMap mapType) { + + SkRect bitmapBounds = SkRect::MakeIWH(fDiffuse.width(), fDiffuse.height()); + + SkMatrix matrix; + matrix.setRectToRect(bitmapBounds, r, SkMatrix::kFill_ScaleToFit); + + const SkMatrix& ctm = canvas->getTotalMatrix(); + + SkPaint paint; + sk_sp diffuseShader = SkShader::MakeBitmapShader(fDiffuse, + SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix); + sk_sp normalMap = SkShader::MakeBitmapShader(fNormalMaps[mapType], + SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix); + sk_sp normalSource = SkNormalSource::MakeFromNormalMap(std::move(normalMap), + ctm); + paint.setShader(SkLightingShader::Make(std::move(diffuseShader), std::move(normalSource), + fLights)); + + canvas->drawRect(r, paint); + } + + // Draw an axis-aligned and rotated version of the normal mapped rect + void drawPair(SkCanvas* canvas, const SkRect& r, NormalMap mapType, const SkVector& v) { + SkMatrix m; + m.setRotate(45.0f, r.centerX(), r.centerY()); + m.postTranslate(kScale * v.fX, kScale * v.fY); + + this->drawRect(canvas, r, mapType); + + canvas->save(); + canvas->setMatrix(m); + this->drawRect(canvas, r, mapType); + canvas->restore(); + } + + void onDraw(SkCanvas* canvas) override { + SkRect r; + + r = SkRect::MakeWH(SkIntToScalar(kTexSize), SkIntToScalar(kTexSize)); + this->drawPair(canvas, r, kHemi_NormalMap, SkVector::Make(1.0f, 0.0f)); + + r.offset(kGMSize - kTexSize, 0); + this->drawPair(canvas, r, kFrustum_NormalMap, SkVector::Make(0.0f, 1.0f)); + + r.offset(0, kGMSize - kTexSize); + this->drawPair(canvas, r, kTetra_NormalMap, SkVector::Make(-1.0, 0.0f)); + + r.offset(kTexSize - kGMSize, 0); + this->drawPair(canvas, r, kHemi_NormalMap, SkVector::Make(0.0f, -1)); + } + +private: + static constexpr int kTexSize = 128; + static constexpr int kGMSize = 512; + static constexpr SkScalar kScale = kGMSize/2.0f - kTexSize/2.0f; + + SkBitmap fDiffuse; + SkBitmap fNormalMaps[kNormalMapCount]; + + sk_sp fLights; + + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +DEF_GM(return new LightingShaderGM;) +} diff --git a/gm/lightingshader2.cpp b/gm/lightingshader2.cpp new file mode 100644 index 0000000000..a3454deb4b --- /dev/null +++ b/gm/lightingshader2.cpp @@ -0,0 +1,278 @@ +/* + * Copyright 2016 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 "sk_tool_utils.h" +#include "SkLightingShader.h" +#include "SkNormalSource.h" +#include "SkPoint3.h" +#include "SkShader.h" +#include "SkTypeface.h" + +// Create a truncated pyramid normal map +static SkBitmap make_frustum_normalmap(int texSize) { + SkBitmap frustum; + frustum.allocN32Pixels(texSize, texSize); + + sk_tool_utils::create_frustum_normal_map(&frustum, SkIRect::MakeWH(texSize, texSize)); + return frustum; +} + +namespace skiagm { + +// This GM exercises lighting shaders. Specifically, nullptr arguments, scaling when using +// normal maps, paint transparency, zero directional lights, multiple directional lights. +class LightingShader2GM : public GM { +public: + LightingShader2GM() : fRect(SkRect::MakeIWH(kTexSize, kTexSize)) { + this->setBGColor(sk_tool_utils::color_to_565(0xFF0000CC)); + } + +protected: + SkString onShortName() override { + return SkString("lightingshader2"); + } + + SkISize onISize() override { + return SkISize::Make(600, 740); + } + + void onOnceBeforeDraw() override { + // The light direction is towards the light with +Z coming out of the screen + const SkVector3 kLightFromUpperRight = SkVector3::Make(0.788f, 0.394f, 0.473f); + const SkVector3 kLightFromUpperLeft = SkVector3::Make(-0.788f, 0.394f, 0.473f); + + // Standard set of lights + { + SkLights::Builder builder; + builder.add(SkLights::Light::MakeDirectional(SkColor3f::Make(1.0f, 1.0f, 1.0f), + kLightFromUpperRight)); + builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f)); + fLights = builder.finish(); + } + + // No directional lights + { + SkLights::Builder builder; + builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f)); + fLightsNoDir = builder.finish(); + } + + // Two directional lights + { + SkLights::Builder builder; + builder.add(SkLights::Light::MakeDirectional(SkColor3f::Make(1.0f, 0.0f, 0.0f), + kLightFromUpperRight)); + builder.add(SkLights::Light::MakeDirectional(SkColor3f::Make(0.0f, 1.0f, 0.0f), + kLightFromUpperLeft)); + builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f)); + fLightsTwoDir = builder.finish(); + } + + SkMatrix matrix; + SkRect bitmapBounds = SkRect::MakeIWH(kTexSize, kTexSize); + matrix.setRectToRect(bitmapBounds, fRect, SkMatrix::kFill_ScaleToFit); + + SkBitmap opaqueDiffuseMap = sk_tool_utils::create_checkerboard_bitmap( + kTexSize, kTexSize, SK_ColorBLACK, + sk_tool_utils::color_to_565(0xFF808080), + 8); + fOpaqueDiffuse = SkShader::MakeBitmapShader(opaqueDiffuseMap, SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, &matrix); + + SkBitmap translucentDiffuseMap = sk_tool_utils::create_checkerboard_bitmap( + kTexSize, kTexSize, + SkColorSetARGB(0x55, 0x00, 0x00, 0x00), + SkColorSetARGB(0x55, 0x80, 0x80, 0x80), + 8); + fTranslucentDiffuse = SkShader::MakeBitmapShader(translucentDiffuseMap, + SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, &matrix); + + SkBitmap normalMap = make_frustum_normalmap(kTexSize); + fNormalMapShader = SkShader::MakeBitmapShader(normalMap, SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, &matrix); + + } + + // Scales shape around origin, rotates shape around origin, then translates shape to origin + void positionCTM(SkCanvas *canvas, SkScalar scaleX, SkScalar scaleY, SkScalar rotate) const { + canvas->translate(kTexSize/2.0f, kTexSize/2.0f); + canvas->scale(scaleX, scaleY); + canvas->rotate(rotate); + canvas->translate(-kTexSize/2.0f, -kTexSize/2.0f); + } + + void drawRect(SkCanvas* canvas, SkScalar scaleX, SkScalar scaleY, + SkScalar rotate, bool useNormalSource, bool useDiffuseShader, + bool useTranslucentPaint, bool useTranslucentShader, sk_sp lights) { + canvas->save(); + + this->positionCTM(canvas, scaleX, scaleY, rotate); + + const SkMatrix& ctm = canvas->getTotalMatrix(); + + SkPaint paint; + sk_sp normalSource = nullptr; + sk_sp diffuseShader = nullptr; + + if (useNormalSource) { + normalSource = SkNormalSource::MakeFromNormalMap(fNormalMapShader, ctm); + } + + if (useDiffuseShader) { + diffuseShader = (useTranslucentShader) ? fTranslucentDiffuse : fOpaqueDiffuse; + } else { + paint.setColor(SK_ColorGREEN); + } + + if (useTranslucentPaint) { + paint.setAlpha(0x99); + } + + paint.setShader(SkLightingShader::Make(std::move(diffuseShader), std::move(normalSource), + std::move(lights))); + canvas->drawRect(fRect, paint); + + canvas->restore(); + } + + void onDraw(SkCanvas* canvas) override { + SkPaint labelPaint; + labelPaint.setTypeface(sk_tool_utils::create_portable_typeface("sans-serif",SkFontStyle())); + labelPaint.setAntiAlias(true); + labelPaint.setTextSize(kLabelSize); + + int gridNum = 0; + + // Running through all possible bool parameter combinations + for (bool useNormalSource : {true, false}) { + for (bool useDiffuseShader : {true, false}) { + for (bool useTranslucentPaint : {true, false}) { + for (bool useTranslucentShader : {true, false}) { + + // Determining position + SkScalar xPos = (gridNum % kGridColumnNum) * kGridCellWidth; + SkScalar yPos = (gridNum / kGridColumnNum) * kGridCellWidth; + + canvas->save(); + + canvas->translate(xPos, yPos); + this->drawRect(canvas, 1.0f, 1.0f, 0.f, useNormalSource, useDiffuseShader, + useTranslucentPaint, useTranslucentShader, fLights); + // Drawing labels + canvas->translate(0.0f, SkIntToScalar(kTexSize)); + { + canvas->translate(0.0f, kLabelSize); + SkString label; + label.appendf("useNormalSource: %d", useNormalSource); + canvas->drawString(label, 0.0f, 0.0f, labelPaint); + } + { + canvas->translate(0.0f, kLabelSize); + SkString label; + label.appendf("useDiffuseShader: %d", useDiffuseShader); + canvas->drawString(label, 0.0f, 0.0f, labelPaint); + } + { + canvas->translate(0.0f, kLabelSize); + SkString label; + label.appendf("useTranslucentPaint: %d", useTranslucentPaint); + canvas->drawString(label, 0.0f, 0.0f, labelPaint); + } + { + canvas->translate(0.0f, kLabelSize); + SkString label; + label.appendf("useTranslucentShader: %d", useTranslucentShader); + canvas->drawString(label, 0.0f, 0.0f, labelPaint); + } + + canvas->restore(); + + gridNum++; + } + } + } + } + + + // Rotation/scale test + { + SkScalar xPos = (gridNum % kGridColumnNum) * kGridCellWidth; + SkScalar yPos = (gridNum / kGridColumnNum) * kGridCellWidth; + + canvas->save(); + canvas->translate(xPos, yPos); + this->drawRect(canvas, 0.6f, 0.6f, 45.0f, true, true, true, true, fLights); + canvas->restore(); + + gridNum++; + } + + // Anisotropic scale test + { + SkScalar xPos = (gridNum % kGridColumnNum) * kGridCellWidth; + SkScalar yPos = (gridNum / kGridColumnNum) * kGridCellWidth; + + canvas->save(); + canvas->translate(xPos, yPos); + this->drawRect(canvas, 0.6f, 0.4f, 30.0f, true, true, true, true, fLights); + canvas->restore(); + + gridNum++; + } + + // No directional lights test + { + SkScalar xPos = (gridNum % kGridColumnNum) * kGridCellWidth; + SkScalar yPos = (gridNum / kGridColumnNum) * kGridCellWidth; + + canvas->save(); + canvas->translate(xPos, yPos); + this->drawRect(canvas, 1.0f, 1.0f, 0.0f, true, true, false, false, fLightsNoDir); + canvas->restore(); + + gridNum++; + } + + // Two directional lights test + { + SkScalar xPos = (gridNum % kGridColumnNum) * kGridCellWidth; + SkScalar yPos = (gridNum / kGridColumnNum) * kGridCellWidth; + + canvas->save(); + canvas->translate(xPos, yPos); + this->drawRect(canvas, 1.0f, 1.0f, 0.0f, true, true, false, false, fLightsTwoDir); + canvas->restore(); + + gridNum++; + } + } + +private: + static constexpr int kTexSize = 96; + static constexpr int kNumBooleanParams = 4; + static constexpr SkScalar kLabelSize = 10.0f; + static constexpr int kGridColumnNum = 4; + static constexpr SkScalar kGridCellWidth = kTexSize + 20.0f + kNumBooleanParams * kLabelSize; + + sk_sp fOpaqueDiffuse; + sk_sp fTranslucentDiffuse; + sk_sp fNormalMapShader; + + const SkRect fRect; + sk_sp fLights; + sk_sp fLightsNoDir; + sk_sp fLightsTwoDir; + + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +DEF_GM(return new LightingShader2GM;) +} diff --git a/gn/core.gni b/gn/core.gni index 2f60ef8162..a226d37f47 100644 --- a/gn/core.gni +++ b/gn/core.gni @@ -165,6 +165,7 @@ skia_core_sources = [ "$_src/core/SkImageInfo.cpp", "$_src/core/SkImageCacherator.h", "$_src/core/SkImageGenerator.cpp", + "$_src/core/SkLights.cpp", "$_src/core/SkLineClipper.cpp", "$_src/core/SkLiteDL.cpp", "$_src/core/SkLiteRecorder.cpp", @@ -199,6 +200,12 @@ skia_core_sources = [ "$_src/core/SkNextID.h", "$_src/core/SkLatticeIter.cpp", "$_src/core/SkLatticeIter.h", + "$_src/core/SkNormalFlatSource.cpp", + "$_src/core/SkNormalFlatSource.h", + "$_src/core/SkNormalMapSource.cpp", + "$_src/core/SkNormalMapSource.h", + "$_src/core/SkNormalSource.cpp", + "$_src/core/SkNormalSource.h", "$_src/core/SkNx.h", "$_src/core/SkOpts.cpp", "$_src/core/SkOpts.h", @@ -374,6 +381,8 @@ skia_core_sources = [ "$_src/shaders/SkEmptyShader.h", "$_src/shaders/SkImageShader.cpp", "$_src/shaders/SkImageShader.h", + "$_src/shaders/SkLightingShader.cpp", + "$_src/shaders/SkLightingShader.h", "$_src/shaders/SkLocalMatrixShader.cpp", "$_src/shaders/SkLocalMatrixShader.h", "$_src/shaders/SkPictureShader.cpp", @@ -406,6 +415,7 @@ skia_core_sources = [ "$_include/core/SkImageFilter.h", "$_include/core/SkImageGenerator.h", "$_include/core/SkImageInfo.h", + "$_include/core/SkLights.h", "$_include/core/SkMallocPixelRef.h", "$_include/core/SkMaskFilter.h", "$_include/core/SkMath.h", diff --git a/gn/gm.gni b/gn/gm.gni index 111d68d075..587f83b0b8 100644 --- a/gn/gm.gni +++ b/gn/gm.gni @@ -198,6 +198,8 @@ gm_sources = [ "$_gm/lcdoverlap.cpp", "$_gm/lcdtext.cpp", "$_gm/lighting.cpp", + "$_gm/lightingshader.cpp", + "$_gm/lightingshader2.cpp", "$_gm/linepaths.cpp", "$_gm/localmatriximagefilter.cpp", "$_gm/localmatriximageshader.cpp", diff --git a/gn/samples.gni b/gn/samples.gni index c4678c6c60..588a0f1ca6 100644 --- a/gn/samples.gni +++ b/gn/samples.gni @@ -60,7 +60,9 @@ samples_sources = [ "$_samplecode/SampleLayerMask.cpp", "$_samplecode/SampleLayers.cpp", "$_samplecode/SampleLCD.cpp", + "$_samplecode/SampleLighting.cpp", "$_samplecode/SampleLines.cpp", + "$_samplecode/SampleLitAtlas.cpp", "$_samplecode/SampleManyRects.cpp", "$_samplecode/SampleMeasure.cpp", "$_samplecode/SampleMegaStroke.cpp", diff --git a/include/core/SkFlattenable.h b/include/core/SkFlattenable.h index 1bee00a403..af3d5870a8 100644 --- a/include/core/SkFlattenable.h +++ b/include/core/SkFlattenable.h @@ -37,7 +37,7 @@ public: kSkShaderBase_Type, kSkUnused_Type, // used to be SkUnitMapper kSkUnused_Type2, - kSkUnused_Type3, // used to be SkNormalSource + kSkNormalSource_Type, }; typedef sk_sp (*Factory)(SkReadBuffer&); diff --git a/include/core/SkLights.h b/include/core/SkLights.h new file mode 100644 index 0000000000..c4262a0fc0 --- /dev/null +++ b/include/core/SkLights.h @@ -0,0 +1,195 @@ + +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLights_DEFINED +#define SkLights_DEFINED + +#include "SkPoint3.h" +#include "SkRefCnt.h" +#include "../private/SkTArray.h" + +class SkColorSpaceXformer; +class SkReadBuffer; +class SkWriteBuffer; + +/** \class SkLights + SkLights encapsulates a set of directional, point and ambient lights for use with the + SkLightingShader. +*/ +class SK_API SkLights : public SkRefCnt { +public: + class Light { + public: + enum LightType { + kDirectional_LightType, + kPoint_LightType + }; + + Light(const Light& other) + : fType(other.fType) + , fColor(other.fColor) + , fDirOrPos(other.fDirOrPos) + , fIntensity(other.fIntensity) {} + + Light(Light&& other) + : fType(other.fType) + , fColor(other.fColor) + , fDirOrPos(other.fDirOrPos) + , fIntensity(other.fIntensity) {} + + static Light MakeDirectional(const SkColor3f& color, const SkVector3& dir) { + Light light(kDirectional_LightType, color, dir, 0.0f); + if (!light.fDirOrPos.normalize()) { + light.fDirOrPos.set(0.0f, 0.0f, 1.0f); + } + return light; + } + + static Light MakePoint(const SkColor3f& color, const SkPoint3& pos, SkScalar intensity) { + return Light(kPoint_LightType, color, pos, intensity); + } + + LightType type() const { return fType; } + const SkColor3f& color() const { return fColor; } + const SkVector3& dir() const { + SkASSERT(kDirectional_LightType == fType); + return fDirOrPos; + } + const SkPoint3& pos() const { + SkASSERT(kPoint_LightType == fType); + return fDirOrPos; + } + SkScalar intensity() const { + SkASSERT(kPoint_LightType == fType); + return fIntensity; + } + + Light& operator=(const Light& other) { + if (this == &other) { + return *this; + } + + fType = other.fType; + fColor = other.fColor; + fDirOrPos = other.fDirOrPos; + fIntensity = other.fIntensity; + return *this; + } + + bool operator==(const Light& other) { + return (fType == other.fType) && + (fColor == other.fColor) && + (fDirOrPos == other.fDirOrPos) && + (fIntensity == other.fIntensity); + } + + bool operator!=(const Light& other) { return !(this->operator==(other)); } + + private: + friend class SkLights; + + Light(LightType type, const SkColor3f& color, const SkVector3& dirOrPos, + SkScalar intensity) + : fType(type) + , fColor(color) + , fDirOrPos(dirOrPos) + , fIntensity(intensity) {} + + LightType fType; + SkColor3f fColor; // linear (unpremul) color. Range is 0..1 in each channel. + + SkVector3 fDirOrPos; // For directional lights, holds the direction towards the + // light (+Z is out of the screen). + // If degenerate, it will be replaced with (0, 0, 1). + // For point lights, holds location of point light + + SkScalar fIntensity; // For point lights, dictates the light intensity. + // Simply a multiplier to the final light output value. + }; + + class Builder { + public: + Builder() : fLights(new SkLights) {} + + void add(const Light& light) { + if (fLights) { + fLights->fLights.push_back(light); + } + } + + void add(Light&& light) { + if (fLights) { + fLights->fLights.push_back(std::move(light)); + } + } + + void setAmbientLightColor(const SkColor3f& color) { + if (fLights) { + fLights->fAmbientLightColor = color; + } + } + + sk_sp finish() { + return std::move(fLights); + } + + private: + sk_sp fLights; + }; + + /** Returns number of lights not including the ambient light. + + @return number of lights not including the ambient light + */ + int numLights() const { return fLights.count(); } + + /** Returns the index-th light. + + @param index the index of the desired light + @return the index-th light + */ + const Light& light(int index) const { return fLights[index]; } + + /** Returns the ambient light. + + @return the ambient light + */ + const SkColor3f& ambientLightColor() const { + return fAmbientLightColor; + } + + /** + * Recreate an SkLights object that was serialized into a buffer. + * + * @param SkReadBuffer Serialized blob data. + * @return A new SkLights representing the serialized data, or NULL if the buffer is + * invalid. + */ + static sk_sp MakeFromBuffer(SkReadBuffer& buf); + + /** + * Serialize to a buffer. + * + * @param buffer the write buffer to write out to + */ + void flatten(SkWriteBuffer& buf) const; + +private: + friend class SkLightingShaderImpl; + + SkLights() : fAmbientLightColor(SkColor3f::Make(0.0f, 0.0f, 0.0f)) {} + + sk_sp makeColorSpace(SkColorSpaceXformer* xformer) const; + + SkTArray fLights; + SkColor3f fAmbientLightColor; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/samplecode/SampleLighting.cpp b/samplecode/SampleLighting.cpp new file mode 100644 index 0000000000..0e68135b9d --- /dev/null +++ b/samplecode/SampleLighting.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "DecodeFile.h" +#include "SampleCode.h" +#include "Resources.h" +#include "SkCanvas.h" +#include "SkLightingShader.h" +#include "SkNormalSource.h" +#include "SkPoint3.h" + +static sk_sp create_lights(SkScalar angle, SkScalar blue) { + + const SkVector3 dir = SkVector3::Make(SkScalarSin(angle)*SkScalarSin(SK_ScalarPI*0.25f), + SkScalarCos(angle)*SkScalarSin(SK_ScalarPI*0.25f), + SkScalarCos(SK_ScalarPI*0.25f)); + + SkLights::Builder builder; + + builder.add(SkLights::Light::MakeDirectional(SkColor3f::Make(1.0f, 1.0f, blue), dir)); + builder.setAmbientLightColor(SkColor3f::Make(0.1f, 0.1f, 0.1f)); + + return builder.finish(); +} + +//////////////////////////////////////////////////////////////////////////// + +class LightingView : public SampleView { +public: + LightingView() : fLightAngle(0.0f) , fColorFactor(0.0f) { + { + SkBitmap diffuseBitmap; + SkAssertResult(GetResourceAsBitmap("images/brickwork-texture.jpg", &diffuseBitmap)); + + fRect = SkRect::MakeIWH(diffuseBitmap.width(), diffuseBitmap.height()); + + fDiffuseShader = SkShader::MakeBitmapShader(diffuseBitmap, + SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, + nullptr); + } + + { + SkBitmap normalBitmap; + SkAssertResult(GetResourceAsBitmap("images/brickwork_normal-map.jpg", &normalBitmap)); + + sk_sp normalMap = SkShader::MakeBitmapShader(normalBitmap, + SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, + nullptr); + fNormalSource = SkNormalSource::MakeFromNormalMap(std::move(normalMap), SkMatrix::I()); + } + } + +protected: + // overrides from SkEventSink + bool onQuery(SkEvent* evt) override { + if (SampleCode::TitleQ(*evt)) { + SampleCode::TitleR(evt, "Lighting"); + return true; + } + return this->INHERITED::onQuery(evt); + } + + void onDrawContent(SkCanvas* canvas) override { + sk_sp lights(create_lights(fLightAngle, fColorFactor)); + + SkPaint paint; + paint.setShader(SkLightingShader::Make(fDiffuseShader, + fNormalSource, + std::move(lights))); + paint.setColor(SK_ColorBLACK); + + canvas->drawRect(fRect, paint); + } + + SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override { + return this->INHERITED::onFindClickHandler(x, y, modi); + } + + bool onAnimate(const SkAnimTimer& timer) override { + fLightAngle += 0.015f; + fColorFactor += 0.01f; + if (fColorFactor > 1.0f) { + fColorFactor = 0.0f; + } + + return true; + } + +private: + SkRect fRect; + sk_sp fDiffuseShader; + sk_sp fNormalSource; + + SkScalar fLightAngle; + SkScalar fColorFactor; + + typedef SampleView INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static SkView* MyFactory() { return new LightingView; } +static SkViewRegister reg(MyFactory); diff --git a/samplecode/SampleLitAtlas.cpp b/samplecode/SampleLitAtlas.cpp new file mode 100644 index 0000000000..9ca6df2aa0 --- /dev/null +++ b/samplecode/SampleLitAtlas.cpp @@ -0,0 +1,503 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SampleCode.h" +#include "SkAnimTimer.h" +#include "SkBitmapProcShader.h" +#include "SkCanvas.h" +#include "SkDrawable.h" +#include "SkLightingShader.h" +#include "SkLights.h" +#include "SkNormalSource.h" +#include "SkRandom.h" +#include "SkRSXform.h" +#include "SkView.h" + +#include "sk_tool_utils.h" + +// A crude normal mapped asteroids-like sample +class DrawLitAtlasDrawable : public SkDrawable { +public: + DrawLitAtlasDrawable(const SkRect& r) + : fBounds(r) + , fUseColors(false) + , fLightDir(SkVector3::Make(1.0f, 0.0f, 0.0f)) { + fAtlas = MakeAtlas(); + + SkRandom rand; + for (int i = 0; i < kNumAsteroids; ++i) { + fAsteroids[i].initAsteroid(&rand, fBounds, &fDiffTex[i], &fNormTex[i]); + } + + fShip.initShip(fBounds, &fDiffTex[kNumAsteroids], &fNormTex[kNumAsteroids]); + + this->updateLights(); + } + + void toggleUseColors() { + fUseColors = !fUseColors; + } + + void rotateLight() { + SkScalar c; + SkScalar s = SkScalarSinCos(SK_ScalarPI/6.0f, &c); + + SkScalar newX = c * fLightDir.fX - s * fLightDir.fY; + SkScalar newY = s * fLightDir.fX + c * fLightDir.fY; + + fLightDir.set(newX, newY, 0.0f); + + this->updateLights(); + } + + void left() { + SkScalar newRot = SkScalarMod(fShip.rot() + (2*SK_ScalarPI - SK_ScalarPI/32.0f), + 2 * SK_ScalarPI); + fShip.setRot(newRot); + } + + void right() { + SkScalar newRot = SkScalarMod(fShip.rot() + SK_ScalarPI/32.0f, 2 * SK_ScalarPI); + fShip.setRot(newRot); + } + + void thrust() { + SkScalar c; + SkScalar s = SkScalarSinCos(fShip.rot(), &c); + + SkVector newVel = fShip.velocity(); + newVel.fX += s; + newVel.fY += -c; + + SkScalar len = newVel.length(); + if (len > kMaxShipSpeed) { + newVel.setLength(SkIntToScalar(kMaxShipSpeed)); + } + + fShip.setVelocity(newVel); + } + +protected: + void onDraw(SkCanvas* canvas) override { + SkRSXform xforms[kNumAsteroids+kNumShips]; + SkColor colors[kNumAsteroids+kNumShips]; + + for (int i = 0; i < kNumAsteroids; ++i) { + fAsteroids[i].advance(fBounds); + xforms[i] = fAsteroids[i].asRSXform(); + if (fUseColors) { + colors[i] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); + } + } + + fShip.advance(fBounds); + xforms[kNumAsteroids] = fShip.asRSXform(); + if (fUseColors) { + colors[kNumAsteroids] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); + } + +#ifdef SK_DEBUG + canvas->drawBitmap(fAtlas, 0, 0); // just to see the atlas + + this->drawLightDir(canvas, fBounds.centerX(), fBounds.centerY()); +#endif + +#if 0 + // TODO: revitalize when drawLitAtlas API lands + SkPaint paint; + paint.setFilterQuality(kLow_SkFilterQuality); + + const SkRect cull = this->getBounds(); + const SkColor* colorsPtr = fUseColors ? colors : NULL; + + canvas->drawLitAtlas(fAtlas, xforms, fDiffTex, fNormTex, colorsPtr, kNumAsteroids+1, + SkXfermode::kModulate_Mode, &cull, &paint, fLights); +#else + SkMatrix diffMat, normalMat; + + for (int i = 0; i < kNumAsteroids+1; ++i) { + colors[i] = colors[i] & 0xFF000000; // to silence compilers + SkPaint paint; + + SkRect r = fDiffTex[i]; + r.offsetTo(0, 0); + + diffMat.setRectToRect(fDiffTex[i], r, SkMatrix::kFill_ScaleToFit); + normalMat.setRectToRect(fNormTex[i], r, SkMatrix::kFill_ScaleToFit); + + SkMatrix m; + m.setRSXform(xforms[i]); + + sk_sp normalMap = SkShader::MakeBitmapShader(fAtlas, SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, &normalMat); + sk_sp normalSource = SkNormalSource::MakeFromNormalMap( + std::move(normalMap), m); + sk_sp diffuseShader = SkShader::MakeBitmapShader(fAtlas, + SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &diffMat); + paint.setShader(SkLightingShader::Make(std::move(diffuseShader), + std::move(normalSource), fLights)); + + canvas->save(); + canvas->setMatrix(m); + canvas->drawRect(r, paint); + canvas->restore(); + } +#endif + +#ifdef SK_DEBUG + { + SkPaint paint; + paint.setColor(SK_ColorRED); + + for (int i = 0; i < kNumAsteroids; ++i) { + canvas->drawCircle(fAsteroids[i].pos().x(), fAsteroids[i].pos().y(), 2, paint); + } + canvas->drawCircle(fShip.pos().x(), fShip.pos().y(), 2, paint); + + paint.setStyle(SkPaint::kStroke_Style); + canvas->drawRect(this->getBounds(), paint); + } +#endif + } + + SkRect onGetBounds() override { + return fBounds; + } + +private: + + enum ObjType { + kBigAsteroid_ObjType = 0, + kMedAsteroid_ObjType, + kSmAsteroid_ObjType, + kShip_ObjType, + + kLast_ObjType = kShip_ObjType + }; + + static const int kObjTypeCount = kLast_ObjType + 1; + + void updateLights() { + SkLights::Builder builder; + + builder.add(SkLights::Light::MakeDirectional( + SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir)); + builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f)); + + fLights = builder.finish(); + } + +#ifdef SK_DEBUG + // Draw a vector to the light + void drawLightDir(SkCanvas* canvas, SkScalar centerX, SkScalar centerY) { + static const int kBgLen = 30; + static const int kSmLen = 5; + + // TODO: change the lighting coordinate system to be right handed + SkPoint p1 = SkPoint::Make(centerX + kBgLen * fLightDir.fX, + centerY - kBgLen * fLightDir.fY); + SkPoint p2 = SkPoint::Make(centerX + (kBgLen-kSmLen) * fLightDir.fX, + centerY - (kBgLen-kSmLen) * fLightDir.fY); + + SkPaint p; + canvas->drawLine(centerX, centerY, p1.fX, p1.fY, p); + canvas->drawLine(p1.fX, p1.fY, + p2.fX - kSmLen * fLightDir.fY, p2.fY - kSmLen * fLightDir.fX, p); + canvas->drawLine(p1.fX, p1.fY, + p2.fX + kSmLen * fLightDir.fY, p2.fY + kSmLen * fLightDir.fX, p); + } +#endif + + // Create the mixed diffuse & normal atlas + // + // big color circle | big normal hemi + // ------------------------------------ + // med color circle | med normal pyra + // ------------------------------------ + // sm color circle | sm normal hemi + // ------------------------------------ + // big ship | big tetra normal + static SkBitmap MakeAtlas() { + + SkBitmap atlas; + atlas.allocN32Pixels(kAtlasWidth, kAtlasHeight); + + for (int y = 0; y < kAtlasHeight; ++y) { + int x = 0; + for ( ; x < kBigSize+kPad; ++x) { + *atlas.getAddr32(x, y) = SK_ColorTRANSPARENT; + } + for ( ; x < kAtlasWidth; ++x) { + *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0x88, 0x88, 0xFF); + } + } + + // big asteroid + { + SkPoint bigCenter = SkPoint::Make(kDiffXOff + kBigSize/2.0f, kBigYOff + kBigSize/2.0f); + + for (int y = kBigYOff; y < kBigYOff+kBigSize; ++y) { + for (int x = kDiffXOff; x < kDiffXOff+kBigSize; ++x) { + SkScalar distSq = (x - bigCenter.fX) * (x - bigCenter.fX) + + (y - bigCenter.fY) * (y - bigCenter.fY); + if (distSq > kBigSize*kBigSize/4.0f) { + *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0); + } else { + *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0xFF, 0, 0); + } + } + } + + sk_tool_utils::create_hemi_normal_map(&atlas, + SkIRect::MakeXYWH(kNormXOff, kBigYOff, + kBigSize, kBigSize)); + } + + // medium asteroid + { + for (int y = kMedYOff; y < kMedYOff+kMedSize; ++y) { + for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) { + *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0); + } + } + + sk_tool_utils::create_frustum_normal_map(&atlas, + SkIRect::MakeXYWH(kNormXOff, kMedYOff, + kMedSize, kMedSize)); + } + + // small asteroid + { + SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f); + + for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) { + for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) { + SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) + + (y - smCenter.fY) * (y - smCenter.fY); + if (distSq > kSmSize*kSmSize/4.0f) { + *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0); + } else { + *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF); + } + } + } + + sk_tool_utils::create_hemi_normal_map(&atlas, + SkIRect::MakeXYWH(kNormXOff, kSmYOff, + kSmSize, kSmSize)); + } + + // ship + { + SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f; + + for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) { + SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1 + + for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) { + SkScalar scaledX; + + if (x < shipMidLine) { + scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1 + } else { + scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1 + } + + if (scaledX < scaledY) { + *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF); + } else { + *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0); + } + } + } + + sk_tool_utils::create_tetra_normal_map(&atlas, + SkIRect::MakeXYWH(kNormXOff, kShipYOff, + kMedSize, kMedSize)); + } + + return atlas; + } + + class ObjectRecord { + public: + void initAsteroid(SkRandom *rand, const SkRect& bounds, + SkRect* diffTex, SkRect* normTex) { + static const SkScalar gMaxSpeeds[3] = { 1, 2, 5 }; // smaller asteroids can go faster + static const SkScalar gYOffs[3] = { kBigYOff, kMedYOff, kSmYOff }; + static const SkScalar gSizes[3] = { kBigSize, kMedSize, kSmSize }; + + static unsigned int asteroidType = 0; + fObjType = static_cast(asteroidType++ % 3); + + fPosition.set(bounds.fLeft + rand->nextUScalar1() * bounds.width(), + bounds.fTop + rand->nextUScalar1() * bounds.height()); + fVelocity.fX = rand->nextSScalar1(); + fVelocity.fY = sqrt(1.0f - fVelocity.fX * fVelocity.fX); + SkASSERT(SkScalarNearlyEqual(fVelocity.length(), 1.0f)); + fVelocity *= gMaxSpeeds[fObjType]; + fRot = 0; + fDeltaRot = rand->nextSScalar1() / 32; + + diffTex->setXYWH(SkIntToScalar(kDiffXOff), gYOffs[fObjType], + gSizes[fObjType], gSizes[fObjType]); + normTex->setXYWH(SkIntToScalar(kNormXOff), gYOffs[fObjType], + gSizes[fObjType], gSizes[fObjType]); + } + + void initShip(const SkRect& bounds, SkRect* diffTex, SkRect* normTex) { + fObjType = kShip_ObjType; + fPosition.set(bounds.centerX(), bounds.centerY()); + fVelocity = SkVector::Make(0.0f, 0.0f); + fRot = 0.0f; + fDeltaRot = 0.0f; + + diffTex->setXYWH(SkIntToScalar(kDiffXOff), SkIntToScalar(kShipYOff), + SkIntToScalar(kMedSize), SkIntToScalar(kMedSize)); + normTex->setXYWH(SkIntToScalar(kNormXOff), SkIntToScalar(kShipYOff), + SkIntToScalar(kMedSize), SkIntToScalar(kMedSize)); + } + + void advance(const SkRect& bounds) { + fPosition += fVelocity; + if (fPosition.fX > bounds.right()) { + SkASSERT(fVelocity.fX > 0); + fVelocity.fX = -fVelocity.fX; + } else if (fPosition.fX < bounds.left()) { + SkASSERT(fVelocity.fX < 0); + fVelocity.fX = -fVelocity.fX; + } + if (fPosition.fY > bounds.bottom()) { + if (fVelocity.fY > 0) { + fVelocity.fY = -fVelocity.fY; + } + } else if (fPosition.fY < bounds.top()) { + if (fVelocity.fY < 0) { + fVelocity.fY = -fVelocity.fY; + } + } + + fRot += fDeltaRot; + fRot = SkScalarMod(fRot, 2 * SK_ScalarPI); + } + + const SkPoint& pos() const { return fPosition; } + + SkScalar rot() const { return fRot; } + void setRot(SkScalar rot) { fRot = rot; } + + const SkPoint& velocity() const { return fVelocity; } + void setVelocity(const SkPoint& velocity) { fVelocity = velocity; } + + SkRSXform asRSXform() const { + static const SkScalar gHalfSizes[kObjTypeCount] = { + SkScalarHalf(kBigSize), + SkScalarHalf(kMedSize), + SkScalarHalf(kSmSize), + SkScalarHalf(kMedSize), + }; + + return SkRSXform::MakeFromRadians(1.0f, fRot, fPosition.x(), fPosition.y(), + gHalfSizes[fObjType], + gHalfSizes[fObjType]); + } + + private: + ObjType fObjType; + SkPoint fPosition; + SkVector fVelocity; + SkScalar fRot; // In radians. + SkScalar fDeltaRot; // In radiands. Not used by ship. + }; + +private: + static const int kNumLights = 2; + static const int kNumAsteroids = 6; + static const int kNumShips = 1; + + static const int kBigSize = 128; + static const int kMedSize = 64; + static const int kSmSize = 32; + static const int kPad = 1; + static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle + static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad; + + static const int kDiffXOff = 0; + static const int kNormXOff = kBigSize + 2 * kPad; + + static const int kBigYOff = 0; + static const int kMedYOff = kBigSize + kPad; + static const int kSmYOff = kMedYOff + kMedSize + kPad; + static const int kShipYOff = kSmYOff + kSmSize + kPad; + static const int kMaxShipSpeed = 5; + + SkBitmap fAtlas; + ObjectRecord fAsteroids[kNumAsteroids]; + ObjectRecord fShip; + SkRect fDiffTex[kNumAsteroids+kNumShips]; + SkRect fNormTex[kNumAsteroids+kNumShips]; + SkRect fBounds; + bool fUseColors; + SkVector3 fLightDir; + sk_sp fLights; + + typedef SkDrawable INHERITED; +}; + +class DrawLitAtlasView : public SampleView { +public: + DrawLitAtlasView() : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) {} + +protected: + bool onQuery(SkEvent* evt) override { + if (SampleCode::TitleQ(*evt)) { + SampleCode::TitleR(evt, "DrawLitAtlas"); + return true; + } + SkUnichar uni; + if (SampleCode::CharQ(*evt, &uni)) { + switch (uni) { + case 'C': + fDrawable->toggleUseColors(); + return true; + case 'j': + fDrawable->left(); + return true; + case 'k': + fDrawable->thrust(); + return true; + case 'l': + fDrawable->right(); + return true; + case 'o': + fDrawable->rotateLight(); + return true; + default: + break; + } + } + return this->INHERITED::onQuery(evt); + } + + void onDrawContent(SkCanvas* canvas) override { + canvas->drawDrawable(fDrawable.get()); + } + + bool onAnimate(const SkAnimTimer& timer) override { + return true; + } + +private: + sk_sp fDrawable; + + typedef SampleView INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static SkView* MyFactory() { return new DrawLitAtlasView; } +static SkViewRegister reg(MyFactory); diff --git a/src/core/SkLights.cpp b/src/core/SkLights.cpp new file mode 100644 index 0000000000..e6c4796506 --- /dev/null +++ b/src/core/SkLights.cpp @@ -0,0 +1,91 @@ + +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkColorSpaceXformer.h" +#include "SkLights.h" +#include "SkReadBuffer.h" + +sk_sp SkLights::MakeFromBuffer(SkReadBuffer& buf) { + Builder builder; + + SkColor3f ambColor; + if (!buf.readScalarArray(&ambColor.fX, 3)) { + return nullptr; + } + + builder.setAmbientLightColor(ambColor); + + int numLights = buf.readInt(); + + for (int l = 0; l < numLights; ++l) { + bool isPoint = buf.readBool(); + + SkColor3f color; + if (!buf.readScalarArray(&color.fX, 3)) { + return nullptr; + } + + SkVector3 dirOrPos; + if (!buf.readScalarArray(&dirOrPos.fX, 3)) { + return nullptr; + } + + if (isPoint) { + SkScalar intensity; + intensity = buf.readScalar(); + Light light = Light::MakePoint(color, dirOrPos, intensity); + builder.add(light); + } else { + Light light = Light::MakeDirectional(color, dirOrPos); + builder.add(light); + } + } + + return builder.finish(); +} + +static SkColor3f xform_color(const SkColor3f& color, SkColorSpaceXformer* xformer) { + SkColor origColor = SkColorSetARGB(0xFF, + SkScalarRoundToInt(color.fX), + SkScalarRoundToInt(color.fY), + SkScalarRoundToInt(color.fZ)); + SkColor xformedColor = xformer->apply(origColor); + return SkColor3f::Make(SkIntToScalar(SkGetPackedR32(xformedColor)), + SkIntToScalar(SkGetPackedG32(xformedColor)), + SkIntToScalar(SkGetPackedB32(xformedColor))); +} + +sk_sp SkLights::makeColorSpace(SkColorSpaceXformer* xformer) const { + SkLights::Builder builder; + for (int i = 0; i < this->numLights(); i++) { + Light light(fLights[i].type(), xform_color(fLights[i].color(), xformer), + fLights[i].fDirOrPos, fLights[i].fIntensity); + builder.add(light); + } + builder.setAmbientLightColor(xform_color(fAmbientLightColor, xformer)); + return builder.finish(); +} + +void SkLights::flatten(SkWriteBuffer& buf) const { + buf.writeScalarArray(&this->ambientLightColor().fX, 3); + + buf.writeInt(this->numLights()); + for (int l = 0; l < this->numLights(); ++l) { + const Light& light = this->light(l); + + bool isPoint = Light::kPoint_LightType == light.type(); + + buf.writeBool(isPoint); + buf.writeScalarArray(&light.color().fX, 3); + buf.writeScalarArray(&light.dir().fX, 3); + + if (isPoint) { + buf.writeScalar(light.intensity()); + } + } +} diff --git a/src/core/SkNormalFlatSource.cpp b/src/core/SkNormalFlatSource.cpp new file mode 100644 index 0000000000..f174e185c0 --- /dev/null +++ b/src/core/SkNormalFlatSource.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkNormalFlatSource.h" + +#include "SkArenaAlloc.h" +#include "SkNormalSource.h" +#include "SkPoint3.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" + +class NormalFlatFP : public GrFragmentProcessor { +public: + static std::unique_ptr Make() { + return std::unique_ptr(new NormalFlatFP()); + } + + const char* name() const override { return "NormalFlatFP"; } + + std::unique_ptr clone() const override { return Make(); } + +private: + class GLSLNormalFlatFP : public GrGLSLFragmentProcessor { + public: + GLSLNormalFlatFP() {} + + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + + fragBuilder->codeAppendf("%s = float4(0, 0, 1, 0);", args.fOutputColor); + } + + private: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override {} + }; + + NormalFlatFP() + : INHERITED(kFlatNormalsFP_ClassID, kConstantOutputForConstantInput_OptimizationFlag) { + } + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {} + + GrColor4f constantOutputForConstantInput(GrColor4f) const override { + return GrColor4f(0, 0, 1, 0); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalFlatFP; } + + bool onIsEqual(const GrFragmentProcessor&) const override { return true; } + + typedef GrFragmentProcessor INHERITED; +}; + +std::unique_ptr SkNormalFlatSourceImpl::asFragmentProcessor( + const GrFPArgs&) const { + return NormalFlatFP::Make(); +} + +#endif // SK_SUPPORT_GPU + +//////////////////////////////////////////////////////////////////////////// + +SkNormalFlatSourceImpl::Provider::Provider() {} + +SkNormalFlatSourceImpl::Provider::~Provider() {} + +SkNormalSource::Provider* SkNormalFlatSourceImpl::asProvider(const SkShaderBase::ContextRec &rec, + SkArenaAlloc *alloc) const { + return alloc->make(); +} + +void SkNormalFlatSourceImpl::Provider::fillScanLine(int x, int y, SkPoint3 output[], + int count) const { + for (int i = 0; i < count; i++) { + output[i] = {0.0f, 0.0f, 1.0f}; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalFlatSourceImpl::CreateProc(SkReadBuffer& buf) { + return sk_make_sp(); +} + +void SkNormalFlatSourceImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); +} + +//////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalSource::MakeFlat() { + return sk_make_sp(); +} diff --git a/src/core/SkNormalFlatSource.h b/src/core/SkNormalFlatSource.h new file mode 100644 index 0000000000..9303ba10af --- /dev/null +++ b/src/core/SkNormalFlatSource.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNormalFlatSource_DEFINED +#define SkNormalFlatSource_DEFINED + +#include "SkNormalSource.h" + +class SK_API SkNormalFlatSourceImpl : public SkNormalSource { +public: + SkNormalFlatSourceImpl(){} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs& args) const override; +#endif + + SkNormalSource::Provider* asProvider(const SkShaderBase::ContextRec& rec, + SkArenaAlloc* alloc) const override; + + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkNormalFlatSourceImpl) + +protected: + void flatten(SkWriteBuffer& buf) const override; + +private: + class Provider : public SkNormalSource::Provider { + public: + Provider(); + + ~Provider() override; + + void fillScanLine(int x, int y, SkPoint3 output[], int count) const override; + + private: + typedef SkNormalSource::Provider INHERITED; + }; + + friend class SkNormalSource; + + typedef SkNormalSource INHERITED; +}; + +#endif diff --git a/src/core/SkNormalMapSource.cpp b/src/core/SkNormalMapSource.cpp new file mode 100644 index 0000000000..1b88366690 --- /dev/null +++ b/src/core/SkNormalMapSource.cpp @@ -0,0 +1,253 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkNormalMapSource.h" + +#include "SkArenaAlloc.h" +#include "SkLightingShader.h" +#include "SkMatrix.h" +#include "SkNormalSource.h" +#include "SkPM4f.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" + +#if SK_SUPPORT_GPU +#include "GrCoordTransform.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "SkGr.h" + +class NormalMapFP : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr mapFP, + const SkMatrix& invCTM) { + return std::unique_ptr(new NormalMapFP(std::move(mapFP), invCTM)); + } + + const char* name() const override { return "NormalMapFP"; } + + const SkMatrix& invCTM() const { return fInvCTM; } + + std::unique_ptr clone() const override { + return Make(this->childProcessor(0).clone(), fInvCTM); + } + +private: + class GLSLNormalMapFP : public GrGLSLFragmentProcessor { + public: + GLSLNormalMapFP() : fColumnMajorInvCTM22{0.0f} {} + + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + + // add uniform + const char* xformUniName = nullptr; + fXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2x2_GrSLType, + kDefault_GrSLPrecision, "Xform", &xformUniName); + + SkString dstNormalColorName("dstNormalColor"); + this->emitChild(0, &dstNormalColorName, args); + fragBuilder->codeAppendf("float3 normal = normalize(%s.rgb - float3(0.5));", + dstNormalColorName.c_str()); + + // If there's no x & y components, return (0, 0, +/- 1) instead to avoid division by 0 + fragBuilder->codeAppend( "if (abs(normal.z) > 0.999) {"); + fragBuilder->codeAppendf(" %s = normalize(float4(0.0, 0.0, normal.z, 0.0));", + args.fOutputColor); + // Else, Normalizing the transformed X and Y, while keeping constant both Z and the + // vector's angle in the XY plane. This maintains the "slope" for the surface while + // appropriately rotating the normal regardless of any anisotropic scaling that occurs. + // Here, we call 'scaling factor' the number that must divide the transformed X and Y so + // that the normal's length remains equal to 1. + fragBuilder->codeAppend( "} else {"); + fragBuilder->codeAppendf(" float2 transformed = %s * normal.xy;", + xformUniName); + fragBuilder->codeAppend( " float scalingFactorSquared = " + "( (transformed.x * transformed.x) " + "+ (transformed.y * transformed.y) )" + "/(1.0 - (normal.z * normal.z));"); + fragBuilder->codeAppendf(" %s = float4(transformed*inversesqrt(scalingFactorSquared)," + "normal.z, 0.0);", + args.fOutputColor); + fragBuilder->codeAppend( "}"); + } + + static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(0x0); + } + + private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) override { + const NormalMapFP& normalMapFP = proc.cast(); + + const SkMatrix& invCTM = normalMapFP.invCTM(); + fColumnMajorInvCTM22[0] = invCTM.get(SkMatrix::kMScaleX); + fColumnMajorInvCTM22[1] = invCTM.get(SkMatrix::kMSkewY); + fColumnMajorInvCTM22[2] = invCTM.get(SkMatrix::kMSkewX); + fColumnMajorInvCTM22[3] = invCTM.get(SkMatrix::kMScaleY); + pdman.setMatrix2f(fXformUni, fColumnMajorInvCTM22); + } + + private: + // Upper-right 2x2 corner of the inverse of the CTM in column-major form + float fColumnMajorInvCTM22[4]; + GrGLSLProgramDataManager::UniformHandle fXformUni; + }; + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLNormalMapFP::GenKey(*this, caps, b); + } + NormalMapFP(std::unique_ptr mapFP, const SkMatrix& invCTM) + : INHERITED(kMappedNormalsFP_ClassID, kNone_OptimizationFlags) + , fInvCTM(invCTM) { + this->registerChildProcessor(std::move(mapFP)); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalMapFP; } + + bool onIsEqual(const GrFragmentProcessor& proc) const override { + const NormalMapFP& normalMapFP = proc.cast(); + return fInvCTM == normalMapFP.fInvCTM; + } + + SkMatrix fInvCTM; + + typedef GrFragmentProcessor INHERITED; +}; + +std::unique_ptr SkNormalMapSourceImpl::asFragmentProcessor( + const GrFPArgs& args) const { + std::unique_ptr mapFP = as_SB(fMapShader)->asFragmentProcessor(args); + if (!mapFP) { + return nullptr; + } + + return NormalMapFP::Make(std::move(mapFP), fInvCTM); +} + +#endif // SK_SUPPORT_GPU + +//////////////////////////////////////////////////////////////////////////// + +SkNormalMapSourceImpl::Provider::Provider(const SkNormalMapSourceImpl& source, + SkShaderBase::Context* mapContext) + : fSource(source) + , fMapContext(mapContext) {} + +SkNormalSource::Provider* SkNormalMapSourceImpl::asProvider(const SkShaderBase::ContextRec &rec, + SkArenaAlloc* alloc) const { + SkMatrix normTotalInv; + if (!this->computeNormTotalInverse(rec, &normTotalInv)) { + return nullptr; + } + + // Overriding paint's alpha because we need the normal map's RGB channels to be unpremul'd + SkPaint overridePaint {*(rec.fPaint)}; + overridePaint.setAlpha(0xFF); + SkShaderBase::ContextRec overrideRec(overridePaint, *(rec.fMatrix), rec.fLocalMatrix, + rec.fPreferredDstType, rec.fDstColorSpace); + + auto* context = as_SB(fMapShader)->makeContext(overrideRec, alloc); + if (!context) { + return nullptr; + } + + return alloc->make(*this, context); +} + +bool SkNormalMapSourceImpl::computeNormTotalInverse(const SkShaderBase::ContextRec& rec, + SkMatrix* normTotalInverse) const { + SkMatrix total = SkMatrix::Concat(*rec.fMatrix, fMapShader->getLocalMatrix()); + if (rec.fLocalMatrix) { + total.preConcat(*rec.fLocalMatrix); + } + + return total.invert(normTotalInverse); +} + +#define BUFFER_MAX 16 +void SkNormalMapSourceImpl::Provider::fillScanLine(int x, int y, SkPoint3 output[], + int count) const { + SkPMColor tmpNormalColors[BUFFER_MAX]; + + do { + int n = SkTMin(count, BUFFER_MAX); + + fMapContext->shadeSpan(x, y, tmpNormalColors, n); + + for (int i = 0; i < n; i++) { + SkPoint3 tempNorm; + + tempNorm.set(SkIntToScalar(SkGetPackedR32(tmpNormalColors[i])) - 127.0f, + SkIntToScalar(SkGetPackedG32(tmpNormalColors[i])) - 127.0f, + SkIntToScalar(SkGetPackedB32(tmpNormalColors[i])) - 127.0f); + + tempNorm.normalize(); + + + if (!SkScalarNearlyEqual(SkScalarAbs(tempNorm.fZ), 1.0f)) { + SkVector transformed = fSource.fInvCTM.mapVector(tempNorm.fX, tempNorm.fY); + + // Normalizing the transformed X and Y, while keeping constant both Z and the + // vector's angle in the XY plane. This maintains the "slope" for the surface while + // appropriately rotating the normal for any anisotropic scaling that occurs. + // Here, we call scaling factor the number that must divide the transformed X and Y + // so that the normal's length remains equal to 1. + SkScalar scalingFactorSquared = + (SkScalarSquare(transformed.fX) + SkScalarSquare(transformed.fY)) + / (1.0f - SkScalarSquare(tempNorm.fZ)); + SkScalar invScalingFactor = SkScalarInvert(SkScalarSqrt(scalingFactorSquared)); + + output[i].fX = transformed.fX * invScalingFactor; + output[i].fY = transformed.fY * invScalingFactor; + output[i].fZ = tempNorm.fZ; + } else { + output[i] = {0.0f, 0.0f, tempNorm.fZ}; + output[i].normalize(); + } + + SkASSERT(SkScalarNearlyEqual(output[i].length(), 1.0f)); + } + + output += n; + x += n; + count -= n; + } while (count > 0); +} + +//////////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalMapSourceImpl::CreateProc(SkReadBuffer& buf) { + + sk_sp mapShader = buf.readFlattenable(); + + SkMatrix invCTM; + buf.readMatrix(&invCTM); + + return sk_make_sp(std::move(mapShader), invCTM); +} + +void SkNormalMapSourceImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); + + buf.writeFlattenable(fMapShader.get()); + buf.writeMatrix(fInvCTM); +} + +//////////////////////////////////////////////////////////////////////////// + +sk_sp SkNormalSource::MakeFromNormalMap(sk_sp map, const SkMatrix& ctm) { + SkMatrix invCTM; + + if (!ctm.invert(&invCTM) || !map) { + return nullptr; + } + + return sk_make_sp(std::move(map), invCTM); +} diff --git a/src/core/SkNormalMapSource.h b/src/core/SkNormalMapSource.h new file mode 100644 index 0000000000..dfedfd35d5 --- /dev/null +++ b/src/core/SkNormalMapSource.h @@ -0,0 +1,57 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNormalMapSource_DEFINED +#define SkNormalMapSource_DEFINED + +#include "SkNormalSource.h" + +class SkNormalMapSourceImpl : public SkNormalSource { +public: + SkNormalMapSourceImpl(sk_sp mapShader, const SkMatrix& invCTM) + : fMapShader(std::move(mapShader)) + , fInvCTM(invCTM) {} + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs& args) const override; +#endif + + SkNormalSource::Provider* asProvider(const SkShaderBase::ContextRec& rec, + SkArenaAlloc* alloc) const override; + + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkNormalMapSourceImpl) + +protected: + void flatten(SkWriteBuffer& buf) const override; + + bool computeNormTotalInverse(const SkShaderBase::ContextRec& rec, + SkMatrix* normTotalInverse) const; + +private: + class Provider : public SkNormalSource::Provider { + public: + Provider(const SkNormalMapSourceImpl& source, SkShaderBase::Context* mapContext); + + void fillScanLine(int x, int y, SkPoint3 output[], int count) const override; + + private: + const SkNormalMapSourceImpl& fSource; + SkShaderBase::Context* fMapContext; + + typedef SkNormalSource::Provider INHERITED; + }; + + sk_sp fMapShader; + SkMatrix fInvCTM; // Inverse of the canvas total matrix, used for rotating normals. + + friend class SkNormalSource; + + typedef SkNormalSource INHERITED; +}; + +#endif + diff --git a/src/core/SkNormalSource.cpp b/src/core/SkNormalSource.cpp new file mode 100644 index 0000000000..ad1f5a3e40 --- /dev/null +++ b/src/core/SkNormalSource.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkNormalFlatSource.h" +#include "SkNormalMapSource.h" +#include "SkNormalSource.h" + +// Generating vtable +SkNormalSource::~SkNormalSource() {} + +//////////////////////////////////////////////////////////////////////////// + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkNormalSource) +SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkNormalMapSourceImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkNormalFlatSourceImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + +//////////////////////////////////////////////////////////////////////////// diff --git a/src/core/SkNormalSource.h b/src/core/SkNormalSource.h new file mode 100644 index 0000000000..7339890bef --- /dev/null +++ b/src/core/SkNormalSource.h @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkNormalSource_DEFINED +#define SkNormalSource_DEFINED + +#include "SkFlattenable.h" +#include "SkShaderBase.h" + +class SkMatrix; +struct SkPoint3; + +#if SK_SUPPORT_GPU +class GrFragmentProcessor; +#endif + +/** Abstract class that generates or reads in normals for use by SkLightingShader. +*/ +class SK_API SkNormalSource : public SkFlattenable { +public: + virtual ~SkNormalSource() override; + +#if SK_SUPPORT_GPU + /** Returns a fragment processor that takes no input and outputs a normal (already rotated) + as its output color. To be used as a child fragment processor. + */ + virtual std::unique_ptr asFragmentProcessor(const GrFPArgs&) const = 0; +#endif + + class Provider { + public: + virtual ~Provider() {} + + /** Called for each span of the object being drawn on the CPU. Your subclass should set + the appropriate normals that correspond to the specified device coordinates. + */ + virtual void fillScanLine(int x, int y, SkPoint3 output[], int count) const = 0; + }; + + /** Returns an instance of 'Provider' that provides normals for the CPU pipeline. The + necessary data will be initialized in place at 'storage'. + */ + virtual Provider* asProvider(const SkShaderBase::ContextRec&, SkArenaAlloc*) const = 0; + + /** Returns a normal source that provides normals sourced from the the normal map argument. + + @param map a shader that outputs the normal map + @param ctm the current canvas' total matrix, used to rotate normals when necessary. + + nullptr will be returned if 'map' is null + + The normal map is currently assumed to be an 8888 image where the normal at a texel + is retrieved by: + N.x = R-127; + N.y = G-127; + N.z = B-127; + N.normalize(); + The +Z axis is thus encoded in RGB as (127, 127, 255) while the -Z axis is + (127, 127, 0). + */ + static sk_sp MakeFromNormalMap(sk_sp map, const SkMatrix& ctm); + + /** Returns a normal source that provides straight-up normals only <0, 0, 1>. + */ + static sk_sp MakeFlat(); + + SK_DEFINE_FLATTENABLE_TYPE(SkNormalSource) + SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP() +}; + +#endif diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h index 696ed0a96d..038ce16717 100644 --- a/src/gpu/GrProcessor.h +++ b/src/gpu/GrProcessor.h @@ -153,6 +153,9 @@ public: kSwizzleFragmentProcessor_ClassID, kTestFP_ClassID, kTextureGeometryProcessor_ClassID, + kFlatNormalsFP_ClassID, + kMappedNormalsFP_ClassID, + kLightingFP_ClassID, }; virtual ~GrProcessor() = default; diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp index 22aabc2314..ecb9ccb1ca 100644 --- a/src/ports/SkGlobalInitialization_default.cpp +++ b/src/ports/SkGlobalInitialization_default.cpp @@ -15,7 +15,9 @@ #include "SkGradientShader.h" #include "SkHighContrastFilter.h" #include "SkLayerDrawLooper.h" +#include "SkLightingShader.h" #include "SkLumaColorFilter.h" +#include "SkNormalSource.h" #include "SkOverdrawColorFilter.h" #include "SkPerlinNoiseShader.h" #include "SkShaderMaskFilter.h" @@ -51,6 +53,8 @@ void SkFlattenable::PrivateInitializer::InitEffects() { // Shader SkPerlinNoiseShader::InitializeFlattenables(); SkGradientShader::InitializeFlattenables(); + SkLightingShader::InitializeFlattenables(); + SkNormalSource::InitializeFlattenables(); // PathEffect SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkCornerPathEffect) diff --git a/src/shaders/SkLightingShader.cpp b/src/shaders/SkLightingShader.cpp new file mode 100644 index 0000000000..6fa754a6fa --- /dev/null +++ b/src/shaders/SkLightingShader.cpp @@ -0,0 +1,497 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkBitmapProcShader.h" +#include "SkBitmapProcState.h" +#include "SkColor.h" +#include "SkColorSpaceXformer.h" +#include "SkEmptyShader.h" +#include "SkLightingShader.h" +#include "SkMathPriv.h" +#include "SkNormalSource.h" +#include "SkPoint3.h" +#include "SkReadBuffer.h" +#include "SkShaderBase.h" +#include "SkUnPreMultiply.h" +#include "SkWriteBuffer.h" + +//////////////////////////////////////////////////////////////////////////// + +/* + SkLightingShader TODOs: + support different light types + support multiple lights + fix non-opaque diffuse textures + + To Test: + A8 diffuse textures + down & upsampled draws +*/ + + + +/** \class SkLightingShaderImpl + This subclass of shader applies lighting. +*/ +class SkLightingShaderImpl : public SkShaderBase { +public: + /** Create a new lighting shader that uses the provided normal map and + lights to light the diffuse bitmap. + @param diffuseShader the shader that provides the diffuse colors + @param normalSource the source of normals for lighting computation + @param lights the lights applied to the geometry + */ + SkLightingShaderImpl(sk_sp diffuseShader, + sk_sp normalSource, + sk_sp lights) + : fDiffuseShader(std::move(diffuseShader)) + , fNormalSource(std::move(normalSource)) + , fLights(std::move(lights)) {} + + bool isOpaque() const override; + +#if SK_SUPPORT_GPU + std::unique_ptr asFragmentProcessor(const GrFPArgs&) const override; +#endif + + class LightingShaderContext : public Context { + public: + // The context takes ownership of the context and provider. It will call their destructors + // and then indirectly free their memory by calling free() on heapAllocated + LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&, + SkShaderBase::Context* diffuseContext, SkNormalSource::Provider*, + void* heapAllocated); + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + + uint32_t getFlags() const override { return fFlags; } + + private: + SkShaderBase::Context* fDiffuseContext; + SkNormalSource::Provider* fNormalProvider; + SkColor fPaintColor; + uint32_t fFlags; + + typedef Context INHERITED; + }; + + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingShaderImpl) + +protected: + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + sk_sp fDiffuseShader; + sk_sp fNormalSource; + sk_sp fLights; + + friend class SkLightingShader; + + typedef SkShaderBase INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrCoordTransform.h" +#include "GrFragmentProcessor.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkGr.h" + +// This FP expects a premul'd color input for its diffuse color. Premul'ing of the paint's color is +// handled by the asFragmentProcessor() factory, but shaders providing diffuse color must output it +// premul'd. +class LightingFP : public GrFragmentProcessor { +public: + static std::unique_ptr Make(std::unique_ptr normalFP, + sk_sp lights) { + return std::unique_ptr(new LightingFP(std::move(normalFP), + std::move(lights))); + } + + const char* name() const override { return "LightingFP"; } + + std::unique_ptr clone() const override { + return std::unique_ptr(new LightingFP(*this)); + } + + const SkTArray& directionalLights() const { return fDirectionalLights; } + const SkColor3f& ambientColor() const { return fAmbientColor; } + +private: + class GLSLLightingFP : public GrGLSLFragmentProcessor { + public: + GLSLLightingFP() { + fAmbientColor.fX = 0.0f; + } + + void emitCode(EmitArgs& args) override { + + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + const LightingFP& lightingFP = args.fFp.cast(); + + const char *lightDirsUniName = nullptr; + const char *lightColorsUniName = nullptr; + if (lightingFP.fDirectionalLights.count() != 0) { + fLightDirsUni = uniformHandler->addUniformArray( + kFragment_GrShaderFlag, + kFloat3_GrSLType, + kDefault_GrSLPrecision, + "LightDir", + lightingFP.fDirectionalLights.count(), + &lightDirsUniName); + fLightColorsUni = uniformHandler->addUniformArray( + kFragment_GrShaderFlag, + kFloat3_GrSLType, + kDefault_GrSLPrecision, + "LightColor", + lightingFP.fDirectionalLights.count(), + &lightColorsUniName); + } + + const char* ambientColorUniName = nullptr; + fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat3_GrSLType, kDefault_GrSLPrecision, + "AmbientColor", &ambientColorUniName); + + fragBuilder->codeAppendf("float4 diffuseColor = %s;", args.fInputColor); + + SkString dstNormalName("dstNormal"); + this->emitChild(0, &dstNormalName, args); + + fragBuilder->codeAppendf("float3 normal = %s.xyz;", dstNormalName.c_str()); + + fragBuilder->codeAppend( "float3 result = float3(0.0);"); + + // diffuse light + if (lightingFP.fDirectionalLights.count() != 0) { + fragBuilder->codeAppendf("for (int i = 0; i < %d; i++) {", + lightingFP.fDirectionalLights.count()); + // TODO: modulate the contribution from each light based on the shadow map + fragBuilder->codeAppendf(" float NdotL = clamp(dot(normal, %s[i]), 0.0, 1.0);", + lightDirsUniName); + fragBuilder->codeAppendf(" result += %s[i]*diffuseColor.rgb*NdotL;", + lightColorsUniName); + fragBuilder->codeAppend("}"); + } + + // ambient light + fragBuilder->codeAppendf("result += %s * diffuseColor.rgb;", ambientColorUniName); + + // Clamping to alpha (equivalent to an unpremul'd clamp to 1.0) + fragBuilder->codeAppendf("%s = float4(clamp(result.rgb, 0.0, diffuseColor.a), " + "diffuseColor.a);", args.fOutputColor); + } + + static void GenKey(const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const LightingFP& lightingFP = proc.cast(); + b->add32(lightingFP.fDirectionalLights.count()); + } + + protected: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) override { + const LightingFP& lightingFP = proc.cast(); + + const SkTArray& directionalLights = lightingFP.directionalLights(); + if (directionalLights != fDirectionalLights) { + SkTArray lightDirs(directionalLights.count()); + SkTArray lightColors(directionalLights.count()); + for (const SkLights::Light& light : directionalLights) { + lightDirs.push_back(light.dir()); + lightColors.push_back(light.color()); + } + + pdman.set3fv(fLightDirsUni, directionalLights.count(), &(lightDirs[0].fX)); + pdman.set3fv(fLightColorsUni, directionalLights.count(), &(lightColors[0].fX)); + + fDirectionalLights = directionalLights; + } + + const SkColor3f& ambientColor = lightingFP.ambientColor(); + if (ambientColor != fAmbientColor) { + pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX); + fAmbientColor = ambientColor; + } + } + + private: + SkTArray fDirectionalLights; + GrGLSLProgramDataManager::UniformHandle fLightDirsUni; + GrGLSLProgramDataManager::UniformHandle fLightColorsUni; + + SkColor3f fAmbientColor; + GrGLSLProgramDataManager::UniformHandle fAmbientColorUni; + }; + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLLightingFP::GenKey(*this, caps, b); + } + + LightingFP(std::unique_ptr normalFP, sk_sp lights) + : INHERITED(kLightingFP_ClassID, kPreservesOpaqueInput_OptimizationFlag) { + // fuse all ambient lights into a single one + fAmbientColor = lights->ambientLightColor(); + for (int i = 0; i < lights->numLights(); ++i) { + if (SkLights::Light::kDirectional_LightType == lights->light(i).type()) { + fDirectionalLights.push_back(lights->light(i)); + // TODO get the handle to the shadow map if there is one + } else { + SkDEBUGFAIL("Unimplemented Light Type passed to LightingFP"); + } + } + + this->registerChildProcessor(std::move(normalFP)); + } + + LightingFP(const LightingFP& that) + : INHERITED(kLightingFP_ClassID, kPreservesOpaqueInput_OptimizationFlag) + , fDirectionalLights(that.fDirectionalLights) + , fAmbientColor(that.fAmbientColor) { + this->registerChildProcessor(that.childProcessor(0).clone()); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLLightingFP; } + + bool onIsEqual(const GrFragmentProcessor& proc) const override { + const LightingFP& lightingFP = proc.cast(); + return fDirectionalLights == lightingFP.fDirectionalLights && + fAmbientColor == lightingFP.fAmbientColor; + } + + SkTArray fDirectionalLights; + SkColor3f fAmbientColor; + + typedef GrFragmentProcessor INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// + +std::unique_ptr SkLightingShaderImpl::asFragmentProcessor(const GrFPArgs& args) const { + std::unique_ptr normalFP(fNormalSource->asFragmentProcessor(args)); + if (!normalFP) { + return nullptr; + } + + if (fDiffuseShader) { + std::unique_ptr fpPipeline[] = { + as_SB(fDiffuseShader)->asFragmentProcessor(args), + LightingFP::Make(std::move(normalFP), fLights) + }; + if (!fpPipeline[0] || !fpPipeline[1]) { + return nullptr; + } + + std::unique_ptr innerLightFP = GrFragmentProcessor::RunInSeries(fpPipeline, 2); + // FP is wrapped because paint's alpha needs to be applied to output + return GrFragmentProcessor::MulChildByInputAlpha(std::move(innerLightFP)); + } else { + // FP is wrapped because paint comes in unpremul'd to fragment shader, but LightingFP + // expects premul'd color. + return GrFragmentProcessor::PremulInput(LightingFP::Make(std::move(normalFP), fLights)); + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////// + +bool SkLightingShaderImpl::isOpaque() const { + return (fDiffuseShader ? fDiffuseShader->isOpaque() : false); +} + +SkLightingShaderImpl::LightingShaderContext::LightingShaderContext( + const SkLightingShaderImpl& shader, const ContextRec& rec, + SkShaderBase::Context* diffuseContext, SkNormalSource::Provider* normalProvider, + void* heapAllocated) + : INHERITED(shader, rec) + , fDiffuseContext(diffuseContext) + , fNormalProvider(normalProvider) { + bool isOpaque = shader.isOpaque(); + + // update fFlags + uint32_t flags = 0; + if (isOpaque && (255 == this->getPaintAlpha())) { + flags |= kOpaqueAlpha_Flag; + } + + fPaintColor = rec.fPaint->getColor(); + fFlags = flags; +} + +static inline SkPMColor convert(SkColor3f color, U8CPU a) { + if (color.fX <= 0.0f) { + color.fX = 0.0f; + } else if (color.fX >= 255.0f) { + color.fX = 255.0f; + } + + if (color.fY <= 0.0f) { + color.fY = 0.0f; + } else if (color.fY >= 255.0f) { + color.fY = 255.0f; + } + + if (color.fZ <= 0.0f) { + color.fZ = 0.0f; + } else if (color.fZ >= 255.0f) { + color.fZ = 255.0f; + } + + return SkPreMultiplyARGB(a, (int) color.fX, (int) color.fY, (int) color.fZ); +} + +// larger is better (fewer times we have to loop), but we shouldn't +// take up too much stack-space (each one here costs 16 bytes) +#define BUFFER_MAX 16 +void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y, + SkPMColor result[], int count) { + const SkLightingShaderImpl& lightShader = static_cast(fShader); + + SkPMColor diffuse[BUFFER_MAX]; + SkPoint3 normals[BUFFER_MAX]; + + SkColor diffColor = fPaintColor; + + do { + int n = SkTMin(count, BUFFER_MAX); + + fNormalProvider->fillScanLine(x, y, normals, n); + + if (fDiffuseContext) { + fDiffuseContext->shadeSpan(x, y, diffuse, n); + } + + for (int i = 0; i < n; ++i) { + if (fDiffuseContext) { + diffColor = SkUnPreMultiply::PMColorToColor(diffuse[i]); + } + + SkColor3f accum = SkColor3f::Make(0.0f, 0.0f, 0.0f); + + // Adding ambient light + accum.fX += lightShader.fLights->ambientLightColor().fX * SkColorGetR(diffColor); + accum.fY += lightShader.fLights->ambientLightColor().fY * SkColorGetG(diffColor); + accum.fZ += lightShader.fLights->ambientLightColor().fZ * SkColorGetB(diffColor); + + // This is all done in linear unpremul color space (each component 0..255.0f though) + for (int l = 0; l < lightShader.fLights->numLights(); ++l) { + const SkLights::Light& light = lightShader.fLights->light(l); + + SkScalar illuminanceScalingFactor = 1.0f; + + if (SkLights::Light::kDirectional_LightType == light.type()) { + illuminanceScalingFactor = normals[i].dot(light.dir()); + if (illuminanceScalingFactor < 0.0f) { + illuminanceScalingFactor = 0.0f; + } + } + + accum.fX += light.color().fX * SkColorGetR(diffColor) * illuminanceScalingFactor; + accum.fY += light.color().fY * SkColorGetG(diffColor) * illuminanceScalingFactor; + accum.fZ += light.color().fZ * SkColorGetB(diffColor) * illuminanceScalingFactor; + } + + // convert() premultiplies the accumulate color with alpha + result[i] = convert(accum, SkColorGetA(diffColor)); + } + + result += n; + x += n; + count -= n; + } while (count > 0); +} + +//////////////////////////////////////////////////////////////////////////// + +sk_sp SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) { + + // Discarding SkShader flattenable params + bool hasLocalMatrix = buf.readBool(); + SkAssertResult(!hasLocalMatrix); + + sk_sp lights = SkLights::MakeFromBuffer(buf); + + sk_sp normalSource(buf.readFlattenable()); + + bool hasDiffuse = buf.readBool(); + sk_sp diffuseShader = nullptr; + if (hasDiffuse) { + diffuseShader = buf.readFlattenable(); + } + + return sk_make_sp(std::move(diffuseShader), std::move(normalSource), + std::move(lights)); +} + +void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); + + fLights->flatten(buf); + + buf.writeFlattenable(fNormalSource.get()); + buf.writeBool(static_cast(fDiffuseShader)); + if (fDiffuseShader) { + buf.writeFlattenable(fDiffuseShader.get()); + } +} + +SkShaderBase::Context* SkLightingShaderImpl::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + SkShaderBase::Context *diffuseContext = nullptr; + if (fDiffuseShader) { + diffuseContext = as_SB(fDiffuseShader)->makeContext(rec, alloc); + if (!diffuseContext) { + return nullptr; + } + } + + SkNormalSource::Provider* normalProvider = fNormalSource->asProvider(rec, alloc); + if (!normalProvider) { + return nullptr; + } + + return alloc->make(*this, rec, diffuseContext, normalProvider, nullptr); +} + +sk_sp SkLightingShaderImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + sk_sp xformedDiffuseShader = + fDiffuseShader ? xformer->apply(fDiffuseShader.get()) : nullptr; + return SkLightingShader::Make(std::move(xformedDiffuseShader), fNormalSource, + fLights->makeColorSpace(xformer)); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp SkLightingShader::Make(sk_sp diffuseShader, + sk_sp normalSource, + sk_sp lights) { + SkASSERT(lights); + if (!normalSource) { + normalSource = SkNormalSource::MakeFlat(); + } + + return sk_make_sp(std::move(diffuseShader), std::move(normalSource), + std::move(lights)); +} + +/////////////////////////////////////////////////////////////////////////////// + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingShaderImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/shaders/SkLightingShader.h b/src/shaders/SkLightingShader.h new file mode 100644 index 0000000000..62c881ccf6 --- /dev/null +++ b/src/shaders/SkLightingShader.h @@ -0,0 +1,40 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLightingShader_DEFINED +#define SkLightingShader_DEFINED + +#include "SkFlattenablePriv.h" +#include "SkLights.h" +#include "SkShader.h" + +class SkBitmap; +class SkMatrix; +class SkNormalSource; + +class SK_API SkLightingShader { +public: + /** Returns a shader that lights the shape, colored by the diffuseShader, using the + normals from normalSource, with the set of lights provided. + + @param diffuseShader the shader that provides the colors. If nullptr, uses the paint's + color. + @param normalSource the source for the shape's normals. If nullptr, assumes straight + up normals (<0,0,1>). + @param lights the lights applied to the normals + + The lighting equation is currently: + result = (LightColor * dot(Normal, LightDir) + AmbientColor) * DiffuseColor + + */ + static sk_sp Make(sk_sp diffuseShader, sk_sp normalSource, + sk_sp lights); + + SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP() +}; + +#endif diff --git a/tests/SerializationTest.cpp b/tests/SerializationTest.cpp index 5de77cd9fb..10838a9f74 100644 --- a/tests/SerializationTest.cpp +++ b/tests/SerializationTest.cpp @@ -13,9 +13,11 @@ #include "SkFontDescriptor.h" #include "SkImage.h" #include "SkImageSource.h" +#include "SkLightingShader.h" #include "SkMakeUnique.h" #include "SkMallocPixelRef.h" #include "SkMatrixPriv.h" +#include "SkNormalSource.h" #include "SkOSFile.h" #include "SkReadBuffer.h" #include "SkPicturePriv.h" @@ -558,6 +560,64 @@ DEF_TEST(Serialization, reporter) { } TestPictureTypefaceSerialization(reporter); + + // Test SkLightingShader/NormalMapSource serialization + { + const int kTexSize = 2; + + SkLights::Builder builder; + + builder.add(SkLights::Light::MakeDirectional(SkColor3f::Make(1.0f, 1.0f, 1.0f), + SkVector3::Make(1.0f, 0.0f, 0.0f))); + builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f)); + + sk_sp fLights = builder.finish(); + + SkBitmap diffuse = sk_tool_utils::create_checkerboard_bitmap( + kTexSize, kTexSize, + sk_tool_utils::color_to_565(0x0), + sk_tool_utils::color_to_565(0xFF804020), + 8); + + SkRect bitmapBounds = SkRect::MakeIWH(diffuse.width(), diffuse.height()); + + SkMatrix matrix; + SkRect r = SkRect::MakeWH(SkIntToScalar(kTexSize), SkIntToScalar(kTexSize)); + matrix.setRectToRect(bitmapBounds, r, SkMatrix::kFill_ScaleToFit); + + SkMatrix ctm; + ctm.setRotate(45); + SkBitmap normals; + normals.allocN32Pixels(kTexSize, kTexSize); + + sk_tool_utils::create_frustum_normal_map(&normals, SkIRect::MakeWH(kTexSize, kTexSize)); + sk_sp normalMap = SkShader::MakeBitmapShader(normals, SkShader::kClamp_TileMode, + SkShader::kClamp_TileMode, &matrix); + sk_sp normalSource = SkNormalSource::MakeFromNormalMap(std::move(normalMap), + ctm); + sk_sp diffuseShader = SkShader::MakeBitmapShader(diffuse, + SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &matrix); + + sk_sp lightingShader = SkLightingShader::Make(diffuseShader, + normalSource, + fLights); + sk_sp(TestFlattenableSerialization(as_SB(lightingShader.get()), true, reporter)); + + lightingShader = SkLightingShader::Make(std::move(diffuseShader), + nullptr, + fLights); + sk_sp(TestFlattenableSerialization(as_SB(lightingShader.get()), true, reporter)); + + lightingShader = SkLightingShader::Make(nullptr, + std::move(normalSource), + fLights); + sk_sp(TestFlattenableSerialization(as_SB(lightingShader.get()), true, reporter)); + + lightingShader = SkLightingShader::Make(nullptr, + nullptr, + fLights); + sk_sp(TestFlattenableSerialization(as_SB(lightingShader.get()), true, reporter)); + } } /////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/sk_tool_utils.cpp b/tools/sk_tool_utils.cpp index df4d75fbe1..ae6e90f7cf 100644 --- a/tools/sk_tool_utils.cpp +++ b/tools/sk_tool_utils.cpp @@ -18,7 +18,7 @@ #include "SkPath.h" #include "SkPixelRef.h" #include "SkPixmap.h" -#include "SkPoint.h" +#include "SkPoint3.h" #include "SkRRect.h" #include "SkShader.h" #include "SkSurface.h" @@ -164,6 +164,114 @@ SkPath make_star(const SkRect& bounds, int numPts, int step) { return path; } +static inline void norm_to_rgb(SkBitmap* bm, int x, int y, const SkVector3& norm) { + SkASSERT(SkScalarNearlyEqual(norm.length(), 1.0f)); + unsigned char r = static_cast((0.5f * norm.fX + 0.5f) * 255); + unsigned char g = static_cast((-0.5f * norm.fY + 0.5f) * 255); + unsigned char b = static_cast((0.5f * norm.fZ + 0.5f) * 255); + *bm->getAddr32(x, y) = SkPackARGB32(0xFF, r, g, b); +} + +void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst) { + const SkPoint center = SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), + dst.fTop + (dst.height() / 2.0f)); + const SkPoint halfSize = SkPoint::Make(dst.width() / 2.0f, dst.height() / 2.0f); + + SkVector3 norm; + + for (int y = dst.fTop; y < dst.fBottom; ++y) { + for (int x = dst.fLeft; x < dst.fRight; ++x) { + norm.fX = (x + 0.5f - center.fX) / halfSize.fX; + norm.fY = (y + 0.5f - center.fY) / halfSize.fY; + + SkScalar tmp = norm.fX * norm.fX + norm.fY * norm.fY; + if (tmp >= 1.0f) { + norm.set(0.0f, 0.0f, 1.0f); + } else { + norm.fZ = sqrtf(1.0f - tmp); + } + + norm_to_rgb(bm, x, y, norm); + } + } +} + +void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst) { + const SkPoint center = SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), + dst.fTop + (dst.height() / 2.0f)); + + SkIRect inner = dst; + inner.inset(dst.width()/4, dst.height()/4); + + SkPoint3 norm; + const SkPoint3 left = SkPoint3::Make(-SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2); + const SkPoint3 up = SkPoint3::Make(0.0f, -SK_ScalarRoot2Over2, SK_ScalarRoot2Over2); + const SkPoint3 right = SkPoint3::Make(SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2); + const SkPoint3 down = SkPoint3::Make(0.0f, SK_ScalarRoot2Over2, SK_ScalarRoot2Over2); + + for (int y = dst.fTop; y < dst.fBottom; ++y) { + for (int x = dst.fLeft; x < dst.fRight; ++x) { + if (inner.contains(x, y)) { + norm.set(0.0f, 0.0f, 1.0f); + } else { + SkScalar locX = x + 0.5f - center.fX; + SkScalar locY = y + 0.5f - center.fY; + + if (locX >= 0.0f) { + if (locY > 0.0f) { + norm = locX >= locY ? right : down; // LR corner + } else { + norm = locX > -locY ? right : up; // UR corner + } + } else { + if (locY > 0.0f) { + norm = -locX > locY ? left : down; // LL corner + } else { + norm = locX > locY ? up : left; // UL corner + } + } + } + + norm_to_rgb(bm, x, y, norm); + } + } +} + +void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst) { + const SkPoint center = SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), + dst.fTop + (dst.height() / 2.0f)); + + static const SkScalar k1OverRoot3 = 0.5773502692f; + + SkPoint3 norm; + const SkPoint3 leftUp = SkPoint3::Make(-k1OverRoot3, -k1OverRoot3, k1OverRoot3); + const SkPoint3 rightUp = SkPoint3::Make(k1OverRoot3, -k1OverRoot3, k1OverRoot3); + const SkPoint3 down = SkPoint3::Make(0.0f, SK_ScalarRoot2Over2, SK_ScalarRoot2Over2); + + for (int y = dst.fTop; y < dst.fBottom; ++y) { + for (int x = dst.fLeft; x < dst.fRight; ++x) { + SkScalar locX = x + 0.5f - center.fX; + SkScalar locY = y + 0.5f - center.fY; + + if (locX >= 0.0f) { + if (locY > 0.0f) { + norm = locX >= locY ? rightUp : down; // LR corner + } else { + norm = rightUp; + } + } else { + if (locY > 0.0f) { + norm = -locX > locY ? leftUp : down; // LL corner + } else { + norm = leftUp; + } + } + + norm_to_rgb(bm, x, y, norm); + } + } +} + #if !defined(__clang__) && defined(_MSC_VER) // MSVC takes ~2 minutes to compile this function with optimization. // We don't really care to wait that long for this function. diff --git a/tools/sk_tool_utils.h b/tools/sk_tool_utils.h index 89c43ad93a..27fcc00f17 100644 --- a/tools/sk_tool_utils.h +++ b/tools/sk_tool_utils.h @@ -140,6 +140,12 @@ namespace sk_tool_utils { // numPts and step must be co-prime. SkPath make_star(const SkRect& bounds, int numPts = 5, int step = 2); + void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst); + + void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst); + + void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst); + void make_big_path(SkPath& path); // Return a blurred version of 'src'. This doesn't use a separable filter