3 * Copyright 2015 Google Inc.
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
9 #include "SkBitmapProcState.h"
11 #include "SkEmptyShader.h"
12 #include "SkErrorInternals.h"
13 #include "SkLightingShader.h"
14 #include "SkMathPriv.h"
16 #include "SkReadBuffer.h"
17 #include "SkWriteBuffer.h"
19 ////////////////////////////////////////////////////////////////////////////
22 SkLightingShader TODOs:
23 support other than clamp mode
24 allow 'diffuse' & 'normal' to be of different dimensions?
25 support different light types
26 support multiple lights
27 enforce normal map is 4 channel
28 use SkImages instead if SkBitmaps
31 non-opaque diffuse textures
33 down & upsampled draws
38 /** \class SkLightingShaderImpl
39 This subclass of shader applies lighting.
41 class SK_API SkLightingShaderImpl : public SkShader {
44 /** Create a new lighting shader that uses the provided normal map and
45 lights to light the diffuse bitmap.
46 @param diffuse the diffuse bitmap
47 @param normal the normal map
48 @param lights the lights applied to the normal map
49 @param invNormRotation rotation applied to the normal map's normals
50 @param diffLocalM the local matrix for the diffuse coordinates
51 @param normLocalM the local matrix for the normal coordinates
53 SkLightingShaderImpl(const SkBitmap& diffuse, const SkBitmap& normal,
54 const SkLightingShader::Lights* lights,
55 const SkVector& invNormRotation,
56 const SkMatrix* diffLocalM, const SkMatrix* normLocalM)
57 : INHERITED(diffLocalM)
58 , fDiffuseMap(diffuse)
60 , fLights(SkRef(lights))
61 , fInvNormRotation(invNormRotation) {
64 fNormLocalMatrix = *normLocalM;
66 fNormLocalMatrix.reset();
68 // Pre-cache so future calls to fNormLocalMatrix.getType() are threadsafe.
69 (void)fNormLocalMatrix.getType();
73 bool isOpaque() const override;
76 const GrFragmentProcessor* asFragmentProcessor(GrContext*,
77 const SkMatrix& viewM,
78 const SkMatrix* localMatrix,
79 SkFilterQuality) const override;
82 size_t contextSize() const override;
84 class LightingShaderContext : public SkShader::Context {
86 // The context takes ownership of the states. It will call their destructors
87 // but will NOT free the memory.
88 LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&,
89 SkBitmapProcState* diffuseState, SkBitmapProcState* normalState);
90 ~LightingShaderContext() override;
92 void shadeSpan(int x, int y, SkPMColor[], int count) override;
94 uint32_t getFlags() const override { return fFlags; }
97 SkBitmapProcState* fDiffuseState;
98 SkBitmapProcState* fNormalState;
101 typedef SkShader::Context INHERITED;
104 SK_TO_STRING_OVERRIDE()
105 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingShaderImpl)
108 void flatten(SkWriteBuffer&) const override;
109 Context* onCreateContext(const ContextRec&, void*) const override;
110 bool computeNormTotalInverse(const ContextRec& rec, SkMatrix* normTotalInverse) const;
113 SkBitmap fDiffuseMap;
116 SkAutoTUnref<const SkLightingShader::Lights> fLights;
118 SkMatrix fNormLocalMatrix;
119 SkVector fInvNormRotation;
121 friend class SkLightingShader;
123 typedef SkShader INHERITED;
126 ////////////////////////////////////////////////////////////////////////////
130 #include "GrCoordTransform.h"
131 #include "GrFragmentProcessor.h"
132 #include "GrTextureAccess.h"
133 #include "glsl/GrGLSLFragmentProcessor.h"
134 #include "glsl/GrGLSLFragmentShaderBuilder.h"
135 #include "glsl/GrGLSLProgramBuilder.h"
136 #include "glsl/GrGLSLProgramDataManager.h"
138 #include "SkGrPriv.h"
140 class LightingFP : public GrFragmentProcessor {
142 LightingFP(GrTexture* diffuse, GrTexture* normal, const SkMatrix& diffMatrix,
143 const SkMatrix& normMatrix, const GrTextureParams& diffParams,
144 const GrTextureParams& normParams, const SkLightingShader::Lights* lights,
145 const SkVector& invNormRotation)
146 : fDiffDeviceTransform(kLocal_GrCoordSet, diffMatrix, diffuse, diffParams.filterMode())
147 , fNormDeviceTransform(kLocal_GrCoordSet, normMatrix, normal, normParams.filterMode())
148 , fDiffuseTextureAccess(diffuse, diffParams)
149 , fNormalTextureAccess(normal, normParams)
150 , fInvNormRotation(invNormRotation) {
151 this->addCoordTransform(&fDiffDeviceTransform);
152 this->addCoordTransform(&fNormDeviceTransform);
153 this->addTextureAccess(&fDiffuseTextureAccess);
154 this->addTextureAccess(&fNormalTextureAccess);
156 // fuse all ambient lights into a single one
157 fAmbientColor.set(0.0f, 0.0f, 0.0f);
158 for (int i = 0; i < lights->numLights(); ++i) {
159 if (SkLight::kAmbient_LightType == lights->light(i).type()) {
160 fAmbientColor += lights->light(i).color();
162 // TODO: handle more than one of these
163 fLightColor = lights->light(i).color();
164 fLightDir = lights->light(i).dir();
168 this->initClassID<LightingFP>();
171 class LightingGLFP : public GrGLSLFragmentProcessor {
174 fLightDir.fX = 10000.0f;
175 fLightColor.fX = 0.0f;
176 fAmbientColor.fX = 0.0f;
177 fInvNormRotation.set(0.0f, 0.0f);
180 void emitCode(EmitArgs& args) override {
182 GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
185 const char* lightDirUniName = nullptr;
186 fLightDirUni = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
187 kVec3f_GrSLType, kDefault_GrSLPrecision,
188 "LightDir", &lightDirUniName);
190 const char* lightColorUniName = nullptr;
191 fLightColorUni = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
192 kVec3f_GrSLType, kDefault_GrSLPrecision,
193 "LightColor", &lightColorUniName);
195 const char* ambientColorUniName = nullptr;
196 fAmbientColorUni = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
197 kVec3f_GrSLType, kDefault_GrSLPrecision,
198 "AmbientColor", &ambientColorUniName);
200 const char* xformUniName = nullptr;
201 fXformUni = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
202 kVec2f_GrSLType, kDefault_GrSLPrecision,
203 "Xform", &xformUniName);
205 fragBuilder->codeAppend("vec4 diffuseColor = ");
206 fragBuilder->appendTextureLookupAndModulate(args.fInputColor, args.fSamplers[0],
207 args.fCoords[0].c_str(),
208 args.fCoords[0].getType());
209 fragBuilder->codeAppend(";");
211 fragBuilder->codeAppend("vec4 normalColor = ");
212 fragBuilder->appendTextureLookup(args.fSamplers[1],
213 args.fCoords[1].c_str(),
214 args.fCoords[1].getType());
215 fragBuilder->codeAppend(";");
217 fragBuilder->codeAppend("vec3 normal = normalColor.rgb - vec3(0.5);");
219 fragBuilder->codeAppendf(
220 "mat3 m = mat3(%s.x, -%s.y, 0.0, %s.y, %s.x, 0.0, 0.0, 0.0, 1.0);",
221 xformUniName, xformUniName, xformUniName, xformUniName);
223 // TODO: inverse map the light direction vectors in the vertex shader rather than
224 // transforming all the normals here!
225 fragBuilder->codeAppend("normal = normalize(m*normal);");
227 fragBuilder->codeAppendf("float NdotL = clamp(dot(normal, %s), 0.0, 1.0);",
230 fragBuilder->codeAppendf("vec3 result = %s*diffuseColor.rgb*NdotL;", lightColorUniName);
232 fragBuilder->codeAppendf("result += %s;", ambientColorUniName);
233 fragBuilder->codeAppendf("%s = vec4(result.rgb, diffuseColor.a);", args.fOutputColor);
236 static void GenKey(const GrProcessor& proc, const GrGLSLCaps&,
237 GrProcessorKeyBuilder* b) {
238 // const LightingFP& lightingFP = proc.cast<LightingFP>();
239 // only one shader generated currently
244 void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {
245 const LightingFP& lightingFP = proc.cast<LightingFP>();
247 const SkVector3& lightDir = lightingFP.lightDir();
248 if (lightDir != fLightDir) {
249 pdman.set3fv(fLightDirUni, 1, &lightDir.fX);
250 fLightDir = lightDir;
253 const SkColor3f& lightColor = lightingFP.lightColor();
254 if (lightColor != fLightColor) {
255 pdman.set3fv(fLightColorUni, 1, &lightColor.fX);
256 fLightColor = lightColor;
259 const SkColor3f& ambientColor = lightingFP.ambientColor();
260 if (ambientColor != fAmbientColor) {
261 pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX);
262 fAmbientColor = ambientColor;
265 const SkVector& invNormRotation = lightingFP.invNormRotation();
266 if (invNormRotation != fInvNormRotation) {
267 pdman.set2fv(fXformUni, 1, &invNormRotation.fX);
268 fInvNormRotation = invNormRotation;
274 GrGLSLProgramDataManager::UniformHandle fLightDirUni;
276 SkColor3f fLightColor;
277 GrGLSLProgramDataManager::UniformHandle fLightColorUni;
279 SkColor3f fAmbientColor;
280 GrGLSLProgramDataManager::UniformHandle fAmbientColorUni;
282 SkVector fInvNormRotation;
283 GrGLSLProgramDataManager::UniformHandle fXformUni;
286 void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
287 LightingGLFP::GenKey(*this, caps, b);
290 const char* name() const override { return "LightingFP"; }
292 void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
293 inout->mulByUnknownFourComponents();
296 const SkVector3& lightDir() const { return fLightDir; }
297 const SkColor3f& lightColor() const { return fLightColor; }
298 const SkColor3f& ambientColor() const { return fAmbientColor; }
299 const SkVector& invNormRotation() const { return fInvNormRotation; }
302 GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new LightingGLFP; }
304 bool onIsEqual(const GrFragmentProcessor& proc) const override {
305 const LightingFP& lightingFP = proc.cast<LightingFP>();
306 return fDiffDeviceTransform == lightingFP.fDiffDeviceTransform &&
307 fNormDeviceTransform == lightingFP.fNormDeviceTransform &&
308 fDiffuseTextureAccess == lightingFP.fDiffuseTextureAccess &&
309 fNormalTextureAccess == lightingFP.fNormalTextureAccess &&
310 fLightDir == lightingFP.fLightDir &&
311 fLightColor == lightingFP.fLightColor &&
312 fAmbientColor == lightingFP.fAmbientColor &&
313 fInvNormRotation == lightingFP.fInvNormRotation;
316 GrCoordTransform fDiffDeviceTransform;
317 GrCoordTransform fNormDeviceTransform;
318 GrTextureAccess fDiffuseTextureAccess;
319 GrTextureAccess fNormalTextureAccess;
321 SkColor3f fLightColor;
322 SkColor3f fAmbientColor;
324 SkVector fInvNormRotation;
327 ////////////////////////////////////////////////////////////////////////////
329 static bool make_mat(const SkBitmap& bm,
330 const SkMatrix& localMatrix1,
331 const SkMatrix* localMatrix2,
334 result->setIDiv(bm.width(), bm.height());
337 if (!localMatrix1.invert(&lmInverse)) {
342 if (!localMatrix2->invert(&inv)) {
345 lmInverse.postConcat(inv);
347 result->preConcat(lmInverse);
352 const GrFragmentProcessor* SkLightingShaderImpl::asFragmentProcessor(
354 const SkMatrix& viewM,
355 const SkMatrix* localMatrix,
356 SkFilterQuality filterQuality) const {
357 // we assume diffuse and normal maps have same width and height
358 // TODO: support different sizes
359 SkASSERT(fDiffuseMap.width() == fNormalMap.width() &&
360 fDiffuseMap.height() == fNormalMap.height());
361 SkMatrix diffM, normM;
363 if (!make_mat(fDiffuseMap, this->getLocalMatrix(), localMatrix, &diffM)) {
367 if (!make_mat(fNormalMap, fNormLocalMatrix, localMatrix, &normM)) {
372 GrTextureParams::FilterMode diffFilterMode = GrSkFilterQualityToGrFilterMode(
373 SkTMin(filterQuality, kMedium_SkFilterQuality),
375 this->getLocalMatrix(),
377 SkASSERT(!doBicubic);
379 GrTextureParams::FilterMode normFilterMode = GrSkFilterQualityToGrFilterMode(
380 SkTMin(filterQuality, kMedium_SkFilterQuality),
384 SkASSERT(!doBicubic);
386 // TODO: support other tile modes
387 GrTextureParams diffParams(kClamp_TileMode, diffFilterMode);
388 SkAutoTUnref<GrTexture> diffuseTexture(GrRefCachedBitmapTexture(context,
389 fDiffuseMap, diffParams));
390 if (!diffuseTexture) {
391 SkErrorInternals::SetError(kInternalError_SkError, "Couldn't convert bitmap to texture.");
395 GrTextureParams normParams(kClamp_TileMode, normFilterMode);
396 SkAutoTUnref<GrTexture> normalTexture(GrRefCachedBitmapTexture(context,
397 fNormalMap, normParams));
398 if (!normalTexture) {
399 SkErrorInternals::SetError(kInternalError_SkError, "Couldn't convert bitmap to texture.");
403 SkAutoTUnref<const GrFragmentProcessor> inner (
404 new LightingFP(diffuseTexture, normalTexture, diffM, normM, diffParams, normParams, fLights,
406 return GrFragmentProcessor::MulOutputByInputAlpha(inner);
411 ////////////////////////////////////////////////////////////////////////////
413 bool SkLightingShaderImpl::isOpaque() const {
414 return fDiffuseMap.isOpaque();
417 size_t SkLightingShaderImpl::contextSize() const {
418 return 2 * sizeof(SkBitmapProcState) + sizeof(LightingShaderContext);
421 SkLightingShaderImpl::LightingShaderContext::LightingShaderContext(const SkLightingShaderImpl& shader,
422 const ContextRec& rec,
423 SkBitmapProcState* diffuseState,
424 SkBitmapProcState* normalState)
425 : INHERITED(shader, rec)
426 , fDiffuseState(diffuseState)
427 , fNormalState(normalState)
429 const SkPixmap& pixmap = fDiffuseState->fPixmap;
430 bool isOpaque = pixmap.isOpaque();
434 if (isOpaque && (255 == this->getPaintAlpha())) {
435 flags |= kOpaqueAlpha_Flag;
441 SkLightingShaderImpl::LightingShaderContext::~LightingShaderContext() {
442 // The bitmap proc states have been created outside of the context on memory that will be freed
443 // elsewhere. Call the destructors but leave the freeing of the memory to the caller.
444 fDiffuseState->~SkBitmapProcState();
445 fNormalState->~SkBitmapProcState();
448 static inline SkPMColor convert(SkColor3f color, U8CPU a) {
449 if (color.fX <= 0.0f) {
451 } else if (color.fX >= 255.0f) {
455 if (color.fY <= 0.0f) {
457 } else if (color.fY >= 255.0f) {
461 if (color.fZ <= 0.0f) {
463 } else if (color.fZ >= 255.0f) {
467 return SkPreMultiplyARGB(a, (int) color.fX, (int) color.fY, (int) color.fZ);
470 // larger is better (fewer times we have to loop), but we shouldn't
471 // take up too much stack-space (each one here costs 16 bytes)
474 void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y,
475 SkPMColor result[], int count) {
476 const SkLightingShaderImpl& lightShader = static_cast<const SkLightingShaderImpl&>(fShader);
478 uint32_t tmpColor[TMP_COUNT], tmpNormal[TMP_COUNT];
479 SkPMColor tmpColor2[2*TMP_COUNT], tmpNormal2[2*TMP_COUNT];
481 SkBitmapProcState::MatrixProc diffMProc = fDiffuseState->getMatrixProc();
482 SkBitmapProcState::SampleProc32 diffSProc = fDiffuseState->getSampleProc32();
484 SkBitmapProcState::MatrixProc normalMProc = fNormalState->getMatrixProc();
485 SkBitmapProcState::SampleProc32 normalSProc = fNormalState->getSampleProc32();
487 int diffMax = fDiffuseState->maxCountForBufferSize(sizeof(tmpColor[0]) * TMP_COUNT);
488 int normMax = fNormalState->maxCountForBufferSize(sizeof(tmpNormal[0]) * TMP_COUNT);
489 int max = SkTMin(diffMax, normMax);
491 SkASSERT(fDiffuseState->fPixmap.addr());
492 SkASSERT(fNormalState->fPixmap.addr());
494 SkPoint3 norm, xformedNorm;
502 diffMProc(*fDiffuseState, tmpColor, n, x, y);
503 diffSProc(*fDiffuseState, tmpColor, n, tmpColor2);
505 normalMProc(*fNormalState, tmpNormal, n, x, y);
506 normalSProc(*fNormalState, tmpNormal, n, tmpNormal2);
508 for (int i = 0; i < n; ++i) {
509 SkASSERT(0xFF == SkColorGetA(tmpNormal2[i])); // opaque -> unpremul
510 norm.set(SkIntToScalar(SkGetPackedR32(tmpNormal2[i]))-127.0f,
511 SkIntToScalar(SkGetPackedG32(tmpNormal2[i]))-127.0f,
512 SkIntToScalar(SkGetPackedB32(tmpNormal2[i]))-127.0f);
515 xformedNorm.fX = lightShader.fInvNormRotation.fX * norm.fX +
516 lightShader.fInvNormRotation.fY * norm.fY;
517 xformedNorm.fY = lightShader.fInvNormRotation.fX * norm.fX -
518 lightShader.fInvNormRotation.fY * norm.fY;
519 xformedNorm.fZ = norm.fZ;
521 SkColor diffColor = SkUnPreMultiply::PMColorToColor(tmpColor2[i]);
523 SkColor3f accum = SkColor3f::Make(0.0f, 0.0f, 0.0f);
524 // This is all done in linear unpremul color space (each component 0..255.0f though)
525 for (int l = 0; l < lightShader.fLights->numLights(); ++l) {
526 const SkLight& light = lightShader.fLights->light(l);
528 if (SkLight::kAmbient_LightType == light.type()) {
529 accum += light.color().makeScale(255.0f);
531 SkScalar NdotL = xformedNorm.dot(light.dir());
536 accum.fX += light.color().fX * SkColorGetR(diffColor) * NdotL;
537 accum.fY += light.color().fY * SkColorGetG(diffColor) * NdotL;
538 accum.fZ += light.color().fZ * SkColorGetB(diffColor) * NdotL;
542 result[i] = convert(accum, SkColorGetA(diffColor));
551 ////////////////////////////////////////////////////////////////////////////
553 #ifndef SK_IGNORE_TO_STRING
554 void SkLightingShaderImpl::toString(SkString* str) const {
555 str->appendf("LightingShader: ()");
559 SkFlattenable* SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) {
561 bool hasDiffLocalM = buf.readBool();
563 buf.readMatrix(&diffLocalM);
569 bool hasNormLocalM = buf.readBool();
571 buf.readMatrix(&normLocalM);
577 if (!buf.readBitmap(&diffuse)) {
580 diffuse.setImmutable();
583 if (!buf.readBitmap(&normal)) {
586 normal.setImmutable();
588 int numLights = buf.readInt();
590 SkLightingShader::Lights::Builder builder;
592 for (int l = 0; l < numLights; ++l) {
593 bool isAmbient = buf.readBool();
596 if (!buf.readScalarArray(&color.fX, 3)) {
601 builder.add(SkLight(color));
604 if (!buf.readScalarArray(&dir.fX, 3)) {
607 builder.add(SkLight(color, dir));
611 SkAutoTUnref<const SkLightingShader::Lights> lights(builder.finish());
613 return new SkLightingShaderImpl(diffuse, normal, lights, SkVector::Make(1.0f, 0.0f),
614 &diffLocalM, &normLocalM);
617 void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const {
618 this->INHERITED::flatten(buf);
620 bool hasNormLocalM = !fNormLocalMatrix.isIdentity();
621 buf.writeBool(hasNormLocalM);
623 buf.writeMatrix(fNormLocalMatrix);
626 buf.writeBitmap(fDiffuseMap);
627 buf.writeBitmap(fNormalMap);
629 buf.writeInt(fLights->numLights());
630 for (int l = 0; l < fLights->numLights(); ++l) {
631 const SkLight& light = fLights->light(l);
633 bool isAmbient = SkLight::kAmbient_LightType == light.type();
635 buf.writeBool(isAmbient);
636 buf.writeScalarArray(&light.color().fX, 3);
638 buf.writeScalarArray(&light.dir().fX, 3);
643 bool SkLightingShaderImpl::computeNormTotalInverse(const ContextRec& rec,
644 SkMatrix* normTotalInverse) const {
646 total.setConcat(*rec.fMatrix, fNormLocalMatrix);
648 const SkMatrix* m = &total;
649 if (rec.fLocalMatrix) {
650 total.setConcat(*m, *rec.fLocalMatrix);
653 return m->invert(normTotalInverse);
656 SkShader::Context* SkLightingShaderImpl::onCreateContext(const ContextRec& rec,
657 void* storage) const {
659 SkMatrix diffTotalInv;
660 // computeTotalInverse was called in SkShader::createContext so we know it will succeed
661 SkAssertResult(this->computeTotalInverse(rec, &diffTotalInv));
663 SkMatrix normTotalInv;
664 if (!this->computeNormTotalInverse(rec, &normTotalInv)) {
668 void* diffuseStateStorage = (char*)storage + sizeof(LightingShaderContext);
669 SkBitmapProcState* diffuseState = new (diffuseStateStorage) SkBitmapProcState(fDiffuseMap,
670 SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
671 SkASSERT(diffuseState);
672 if (!diffuseState->chooseProcs(diffTotalInv, *rec.fPaint)) {
673 diffuseState->~SkBitmapProcState();
677 void* normalStateStorage = (char*)storage + sizeof(LightingShaderContext) + sizeof(SkBitmapProcState);
678 SkBitmapProcState* normalState = new (normalStateStorage) SkBitmapProcState(fNormalMap,
679 SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
680 SkASSERT(normalState);
681 if (!normalState->chooseProcs(normTotalInv, *rec.fPaint)) {
682 diffuseState->~SkBitmapProcState();
683 normalState->~SkBitmapProcState();
687 return new (storage) LightingShaderContext(*this, rec, diffuseState, normalState);
690 ///////////////////////////////////////////////////////////////////////////////
692 static bool bitmap_is_too_big(const SkBitmap& bm) {
693 // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it
694 // communicates between its matrix-proc and its sampler-proc. Until we can
695 // widen that, we have to reject bitmaps that are larger.
697 static const int kMaxSize = 65535;
699 return bm.width() > kMaxSize || bm.height() > kMaxSize;
702 SkShader* SkLightingShader::Create(const SkBitmap& diffuse, const SkBitmap& normal,
703 const Lights* lights,
704 const SkVector& invNormRotation,
705 const SkMatrix* diffLocalM, const SkMatrix* normLocalM) {
706 if (diffuse.isNull() || bitmap_is_too_big(diffuse) ||
707 normal.isNull() || bitmap_is_too_big(normal) ||
708 diffuse.width() != normal.width() ||
709 diffuse.height() != normal.height()) {
713 SkASSERT(SkScalarNearlyEqual(invNormRotation.lengthSqd(), SK_Scalar1));
715 return new SkLightingShaderImpl(diffuse, normal, lights, invNormRotation, diffLocalM,
719 ///////////////////////////////////////////////////////////////////////////////
721 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader)
722 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingShaderImpl)
723 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
725 ///////////////////////////////////////////////////////////////////////////////