/*
* 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<SkShader> diffuseShader,
sk_sp<SkNormalSource> normalSource,
sk_sp<SkLights> lights)
: fDiffuseShader(std::move(diffuseShader))
, fNormalSource(std::move(normalSource))
, fLights(std::move(lights)) {}
bool isOpaque() const override;
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor> 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;
};
protected:
void flatten(SkWriteBuffer&) const override;
#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
#endif
sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override;
private:
SK_FLATTENABLE_HOOKS(SkLightingShaderImpl)
sk_sp<SkShader> fDiffuseShader;
sk_sp<SkNormalSource> fNormalSource;
sk_sp<SkLights> 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<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> normalFP,
sk_sp<SkLights> lights) {
return std::unique_ptr<GrFragmentProcessor>(new LightingFP(std::move(normalFP),
std::move(lights)));
}
const char* name() const override { return "LightingFP"; }
std::unique_ptr<GrFragmentProcessor> clone() const override {
return std::unique_ptr<GrFragmentProcessor>(new LightingFP(*this));
}
const SkTArray<SkLights::Light>& 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<LightingFP>();
const char *lightDirsUniName = nullptr;
const char *lightColorsUniName = nullptr;
if (lightingFP.fDirectionalLights.count() != 0) {
fLightDirsUni = uniformHandler->addUniformArray(
kFragment_GrShaderFlag,
kFloat3_GrSLType,
"LightDir",
lightingFP.fDirectionalLights.count(),
&lightDirsUniName);
fLightColorsUni = uniformHandler->addUniformArray(
kFragment_GrShaderFlag,
kFloat3_GrSLType,
"LightColor",
lightingFP.fDirectionalLights.count(),
&lightColorsUniName);
}
const char* ambientColorUniName = nullptr;
fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat3_GrSLType,
"AmbientColor", &ambientColorUniName);
fragBuilder->codeAppendf("half4 diffuseColor = %s;", args.fInputColor);
SkString dstNormalName("dstNormal");
this->emitChild(0, &dstNormalName, args);
fragBuilder->codeAppendf("float3 normal = %s.xyz;", dstNormalName.c_str());
fragBuilder->codeAppend( "half3 result = half3(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(" half NdotL = saturate(half(dot(normal, %s[i])));",
lightDirsUniName);
fragBuilder->codeAppendf(" result += half3(%s[i])*diffuseColor.rgb*NdotL;",
lightColorsUniName);
fragBuilder->codeAppend("}");
}
// ambient light
fragBuilder->codeAppendf("result += half3(%s) * diffuseColor.rgb;",
ambientColorUniName);
// Clamping to alpha (equivalent to an unpremul'd clamp to 1.0)
fragBuilder->codeAppendf("%s = half4(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<LightingFP>();
b->add32(lightingFP.fDirectionalLights.count());
}
protected:
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& proc) override {
const LightingFP& lightingFP = proc.cast<LightingFP>();
const SkTArray<SkLights::Light>& directionalLights = lightingFP.directionalLights();
if (directionalLights != fDirectionalLights) {
SkTArray<SkColor3f> lightDirs(directionalLights.count());
SkTArray<SkVector3> 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<SkLights::Light> 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<GrFragmentProcessor> normalFP, sk_sp<SkLights> 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<LightingFP>();
return fDirectionalLights == lightingFP.fDirectionalLights &&
fAmbientColor == lightingFP.fAmbientColor;
}
SkTArray<SkLights::Light> fDirectionalLights;
SkColor3f fAmbientColor;
typedef GrFragmentProcessor INHERITED;
};
////////////////////////////////////////////////////////////////////////////
std::unique_ptr<GrFragmentProcessor> SkLightingShaderImpl::asFragmentProcessor(const GrFPArgs& args) const {
std::unique_ptr<GrFragmentProcessor> normalFP(fNormalSource->asFragmentProcessor(args));
if (!normalFP) {
return nullptr;
}
if (fDiffuseShader) {
std::unique_ptr<GrFragmentProcessor> fpPipeline[] = {
as_SB(fDiffuseShader)->asFragmentProcessor(args),
LightingFP::Make(std::move(normalFP), fLights)
};
if (!fpPipeline[0] || !fpPipeline[1]) {
return nullptr;
}
std::unique_ptr<GrFragmentProcessor> 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<const SkLightingShaderImpl&>(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<SkFlattenable> SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) {
// Discarding SkShader flattenable params
bool hasLocalMatrix = buf.readBool();
if (hasLocalMatrix) {
return nullptr;
}
sk_sp<SkLights> lights = SkLights::MakeFromBuffer(buf);
sk_sp<SkNormalSource> normalSource(buf.readFlattenable<SkNormalSource>());
bool hasDiffuse = buf.readBool();
sk_sp<SkShader> diffuseShader = nullptr;
if (hasDiffuse) {
diffuseShader = buf.readFlattenable<SkShaderBase>();
}
return sk_make_sp<SkLightingShaderImpl>(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<bool>(fDiffuseShader));
if (fDiffuseShader) {
buf.writeFlattenable(fDiffuseShader.get());
}
}
#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
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;
}
// The diffuse shader can inspect the rec and make its decision about rec's colorspace.
// What about the lighting shader? Is lighting sensitive to the rec's (device) colorspace?
return alloc->make<LightingShaderContext>(*this, rec, diffuseContext, normalProvider, nullptr);
}
#endif
sk_sp<SkShader> SkLightingShaderImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
sk_sp<SkShader> xformedDiffuseShader =
fDiffuseShader ? xformer->apply(fDiffuseShader.get()) : nullptr;
return SkLightingShader::Make(std::move(xformedDiffuseShader), fNormalSource,
fLights->makeColorSpace(xformer));
}
///////////////////////////////////////////////////////////////////////////////
sk_sp<SkShader> SkLightingShader::Make(sk_sp<SkShader> diffuseShader,
sk_sp<SkNormalSource> normalSource,
sk_sp<SkLights> lights) {
SkASSERT(lights);
if (!normalSource) {
normalSource = SkNormalSource::MakeFlat();
}
return sk_make_sp<SkLightingShaderImpl>(std::move(diffuseShader), std::move(normalSource),
std::move(lights));
}
///////////////////////////////////////////////////////////////////////////////
void SkLightingShader::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkLightingShaderImpl); }