skia2/samplecode/SampleLitAtlas.cpp
reed 320a40d773 Always return ImageShader, even from SkShader::MakeBitmapShader
Lessons learned

1. ImageShader (correctly) always compresses (typically via PNG) during serialization. This has the surprise results of
- if the image was marked opaque, but has some non-opaque pixels (i.e. bug in blitter or caller), then compressing may "fix" those pixels, making the deserialized version draw differently. bug filed.
- 565 compressess/decompresses to 8888 (at least on Mac), which draws differently (esp. under some filters). bug filed.

2. BitmapShader did not enforce a copy for mutable bitmaps, but ImageShader does (since it creates an Image). Thus the former would see subsequent changes to the pixels after shader creation, while the latter does not, hence the change to the BlitRow test to avoid this modify-after-create pattern. I sure hope this prev. behavior was a bug/undefined-behavior, since this CL changes that.

BUG=skia:5595
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2195893002

Review-Url: https://codereview.chromium.org/2195893002
2016-08-02 06:12:06 -07:00

517 lines
18 KiB
C++

/*
* 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"
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;
if (newVel.lengthSqd() > kMaxShipSpeed*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<SkShader> normalMap = SkMakeBitmapShader(fAtlas, SkShader::kClamp_TileMode,
SkShader::kClamp_TileMode, &normalMat, nullptr);
sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap(
std::move(normalMap), m);
sk_sp<SkShader> 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(SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir));
builder.add(SkLights::Light(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<ObjType>(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<SkLights> 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();
this->inval(NULL);
return true;
case 'j':
fDrawable->left();
this->inval(NULL);
return true;
case 'k':
fDrawable->thrust();
this->inval(NULL);
return true;
case 'l':
fDrawable->right();
this->inval(NULL);
return true;
case 'o':
fDrawable->rotateLight();
this->inval(NULL);
return true;
default:
break;
}
}
return this->INHERITED::onQuery(evt);
}
void onDrawContent(SkCanvas* canvas) override {
canvas->drawDrawable(fDrawable);
this->inval(NULL);
}
#if 0
// TODO: switch over to use this for our animation
bool onAnimate(const SkAnimTimer& timer) override {
SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360));
fAnimatingDrawable->setSweep(angle);
return true;
}
#endif
private:
SkAutoTUnref<DrawLitAtlasDrawable> fDrawable;
typedef SampleView INHERITED;
};
//////////////////////////////////////////////////////////////////////////////
static SkView* MyFactory() { return new DrawLitAtlasView; }
static SkViewRegister reg(MyFactory);