Revert "Update GrTextureStripAtlas for DDLs"
This reverts commit c042d41f2a
.
Reason for revert: Leaking
Original change's description:
> Update GrTextureStripAtlas for DDLs
>
> Change-Id: I9d1344103b338f7e9dcd12739a9f192390f4cb94
> Reviewed-on: https://skia-review.googlesource.com/144305
> Commit-Queue: Robert Phillips <robertphillips@google.com>
> Reviewed-by: Greg Daniel <egdaniel@google.com>
TBR=egdaniel@google.com,robertphillips@google.com
Change-Id: I1c4b10ef5a66585c9d85ddfcab8bffcabdee2c19
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/145180
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
parent
e1b36fe3e5
commit
20ee171b98
@ -358,8 +358,6 @@ skia_gpu_sources = [
|
||||
"$_src/gpu/effects/GrBicubicEffect.h",
|
||||
"$_src/gpu/effects/GrBitmapTextGeoProc.cpp",
|
||||
"$_src/gpu/effects/GrBitmapTextGeoProc.h",
|
||||
"$_src/gpu/effects/GrDDLTextureStripAtlas.cpp",
|
||||
"$_src/gpu/effects/GrDDLTextureStripAtlas.h",
|
||||
"$_src/gpu/effects/GrDisableColorXP.cpp",
|
||||
"$_src/gpu/effects/GrDisableColorXP.h",
|
||||
"$_src/gpu/effects/GrDistanceFieldGeoProc.cpp",
|
||||
|
@ -287,7 +287,6 @@ sk_sp<SkColorFilter> SkTable_ColorFilter::onMakeComposed(sk_sp<SkColorFilter> in
|
||||
#include "GrContext.h"
|
||||
#include "GrContextPriv.h"
|
||||
#include "GrFragmentProcessor.h"
|
||||
#include "GrTexture.h"
|
||||
#include "GrTextureStripAtlas.h"
|
||||
#include "SkGr.h"
|
||||
#include "glsl/GrGLSLFragmentProcessor.h"
|
||||
@ -308,10 +307,6 @@ public:
|
||||
|
||||
std::unique_ptr<GrFragmentProcessor> clone() const override;
|
||||
|
||||
int peekBackingHeight() const {
|
||||
return fTextureSampler.peekTexture()->height();
|
||||
}
|
||||
|
||||
private:
|
||||
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
|
||||
|
||||
@ -352,8 +347,8 @@ void GLColorTableEffect::onSetData(const GrGLSLProgramDataManager& pdm,
|
||||
float rgbaYValues[4];
|
||||
const ColorTableEffect& cte = proc.cast<ColorTableEffect>();
|
||||
if (cte.atlas()) {
|
||||
SkScalar yDelta = 1.0f / cte.peekBackingHeight();
|
||||
rgbaYValues[3] = cte.atlas()->rowToTextureY(cte.atlasRow()) * yDelta;
|
||||
SkScalar yDelta = cte.atlas()->getNormalizedTexelHeight();
|
||||
rgbaYValues[3] = cte.atlas()->getYOffset(cte.atlasRow()) + SK_ScalarHalf * yDelta;
|
||||
rgbaYValues[0] = rgbaYValues[3] + yDelta;
|
||||
rgbaYValues[1] = rgbaYValues[0] + yDelta;
|
||||
rgbaYValues[2] = rgbaYValues[1] + yDelta;
|
||||
@ -417,31 +412,26 @@ void GLColorTableEffect::emitCode(EmitArgs& args) {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
std::unique_ptr<GrFragmentProcessor> ColorTableEffect::Make(GrContext* context,
|
||||
const SkBitmap& bitmap) {
|
||||
SkASSERT(kPremul_SkAlphaType == bitmap.alphaType());
|
||||
SkASSERT(bitmap.isImmutable());
|
||||
|
||||
if (kUnknown_GrPixelConfig == SkColorType2GrPixelConfig(bitmap.colorType())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GrTextureStripAtlas::Desc desc;
|
||||
desc.fColorType = bitmap.colorType();
|
||||
desc.fWidth = bitmap.width();
|
||||
desc.fHeight = 128;
|
||||
desc.fRowHeight = bitmap.height();
|
||||
desc.fConfig = SkColorType2GrPixelConfig(bitmap.colorType());
|
||||
|
||||
if (kUnknown_GrPixelConfig == desc.fConfig) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto atlasManager = context->contextPriv().textureStripAtlasManager();
|
||||
|
||||
int row;
|
||||
sk_sp<GrTextureStripAtlas> atlas = atlasManager->addStrip(context, desc, bitmap, &row);
|
||||
if (!context->contextPriv().resourceProvider()) {
|
||||
SkASSERT(atlas && row >= 0); // In DDL mode we should always be able to atlas
|
||||
}
|
||||
|
||||
sk_sp<GrTextureStripAtlas> atlas = atlasManager->refAtlas(desc);
|
||||
int row = atlas->lockRow(context, bitmap);
|
||||
sk_sp<GrTextureProxy> proxy;
|
||||
if (-1 == row) {
|
||||
atlas = nullptr;
|
||||
|
||||
SkASSERT(bitmap.isImmutable());
|
||||
|
||||
sk_sp<SkImage> srcImage = SkImage::MakeFromBitmap(bitmap);
|
||||
if (!srcImage) {
|
||||
return nullptr;
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include "GrTexturePriv.h"
|
||||
#include "GrTextureProxy.h"
|
||||
#include "GrTextureProxyPriv.h"
|
||||
#include "GrTextureStripAtlas.h"
|
||||
#include "GrTracing.h"
|
||||
#include "SkDeferredDisplayList.h"
|
||||
#include "SkSurface_Gpu.h"
|
||||
@ -395,9 +394,6 @@ void GrDrawingManager::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlush
|
||||
}
|
||||
|
||||
void GrDrawingManager::moveOpListsToDDL(SkDeferredDisplayList* ddl) {
|
||||
fContext->contextPriv().textureStripAtlasManager()->finish(
|
||||
fContext->contextPriv().proxyProvider());
|
||||
|
||||
for (int i = 0; i < fOpLists.count(); ++i) {
|
||||
// no opList should receive a new command after this
|
||||
fOpLists[i]->makeClosed(*fContext->contextPriv().caps());
|
||||
|
@ -8,6 +8,8 @@
|
||||
#ifndef GrTextureStripAtlas_DEFINED
|
||||
#define GrTextureStripAtlas_DEFINED
|
||||
|
||||
#include "effects/GrDynamicTextureStripAtlas.h"
|
||||
|
||||
#include "SkNoncopyable.h"
|
||||
#include "SkOpts.h"
|
||||
#include "SkRefCnt.h"
|
||||
@ -18,100 +20,17 @@ class GrProxyProvider;
|
||||
class GrTextureProxy;
|
||||
class SkBitmap;
|
||||
|
||||
/**
|
||||
* Base class for the texture strip atlases.
|
||||
* It is ref counted because the GradientShader and TableColorFilter are given a pointer to it
|
||||
* so that they can lock and unlock rows.
|
||||
*/
|
||||
class GrTextureStripAtlas : public SkRefCnt {
|
||||
public:
|
||||
/**
|
||||
* Descriptor struct which we'll use both to find and initialize an atlas and as a hash
|
||||
* table key in the GrTextureStripAtlasManager.
|
||||
*/
|
||||
struct Desc {
|
||||
Desc() { sk_bzero(this, sizeof(*this)); }
|
||||
SkColorType fColorType;
|
||||
uint16_t fWidth;
|
||||
uint16_t fHeight; // the max height for the DDL version, the size of the atlas for normal
|
||||
uint16_t fRowHeight;
|
||||
uint16_t fUnusedPadding;
|
||||
|
||||
bool operator==(const Desc& other) const {
|
||||
return 0 == memcmp(this, &other, sizeof(Desc));
|
||||
}
|
||||
};
|
||||
|
||||
~GrTextureStripAtlas() override {}
|
||||
|
||||
/**
|
||||
* This is intended to be used when cloning a processor that already holds a lock. It is
|
||||
* assumed that the row already has at least one lock.
|
||||
*/
|
||||
virtual void lockRow(int row) = 0;
|
||||
|
||||
/**
|
||||
* Some user of a given row is done. Release that row for reuse.
|
||||
*/
|
||||
virtual void unlockRow(int row) = 0;
|
||||
|
||||
/**
|
||||
* This returns the absolute Y location of the given row in the atlas. For atlases with
|
||||
* 'fRowHeight' > 1, this is Y location of the topmost row of the atlas entry. It is always
|
||||
* the middle of the row.
|
||||
*/
|
||||
SkScalar rowToTextureY(int row) const {
|
||||
return row * fDesc.fRowHeight + SK_ScalarHalf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the texture proxy backing this atlas. Note that the texture proxy may be fully lazy
|
||||
* (i.e., when recording DDLs) and, in particular, the final height may not be known.
|
||||
*/
|
||||
virtual sk_sp<GrTextureProxy> asTextureProxyRef() const = 0;
|
||||
|
||||
protected:
|
||||
GrTextureStripAtlas(const Desc& desc) : fDesc(desc) {}
|
||||
|
||||
const Desc fDesc;
|
||||
|
||||
private:
|
||||
friend class GrTextureStripAtlasManager; // for addStrip, finish
|
||||
|
||||
/**
|
||||
* Add a texture strip to the atlas
|
||||
* @param context Everyone's favorite class
|
||||
* @param bitmap Bitmap data to copy into the row
|
||||
* @return The row index we inserted into, or -1 if we failed to find an open row. The caller
|
||||
* is responsible for calling unlockRow() with this row index when it's done with it.
|
||||
*/
|
||||
virtual int addStrip(GrContext*, const SkBitmap& bitmap) = 0;
|
||||
|
||||
/**
|
||||
* This method is called when an atlas needs to finish its work on the current texture.
|
||||
* Currently it is only called in DDL mode and when either:
|
||||
* a given atlas has become full or,
|
||||
* a DDL is being snapped from a DDL recorder
|
||||
*/
|
||||
virtual void finish(GrProxyProvider*) = 0;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class GrTextureStripAtlasManager {
|
||||
public:
|
||||
GrTextureStripAtlasManager() {}
|
||||
~GrTextureStripAtlasManager();
|
||||
|
||||
void abandon();
|
||||
void finish(GrProxyProvider*);
|
||||
|
||||
/**
|
||||
* Add a new texture strip to the atlas matching the descriptor. Upon failure, nullptr
|
||||
* will be returned and 'row' will be set to -1.
|
||||
* Try to find an atlas with the required parameters, creates a new one if necessary
|
||||
*/
|
||||
sk_sp<GrTextureStripAtlas> addStrip(GrContext*,
|
||||
const GrTextureStripAtlas::Desc&,
|
||||
const SkBitmap&, int* row);
|
||||
sk_sp<GrTextureStripAtlas> refAtlas(const GrTextureStripAtlas::Desc&);
|
||||
|
||||
private:
|
||||
void deleteAllAtlases();
|
||||
@ -119,17 +38,21 @@ private:
|
||||
// Hash table entry for atlases
|
||||
class AtlasEntry : public ::SkNoncopyable {
|
||||
public:
|
||||
AtlasEntry(sk_sp<GrTextureStripAtlas> atlas) : fAtlas(std::move(atlas)) {}
|
||||
AtlasEntry(const GrTextureStripAtlas::Desc& desc, sk_sp<GrTextureStripAtlas> atlas)
|
||||
: fDesc(desc)
|
||||
, fAtlas(std::move(atlas)) {
|
||||
}
|
||||
~AtlasEntry() { }
|
||||
|
||||
// for SkTDynamicHash
|
||||
static const GrTextureStripAtlas::Desc& GetKey(const AtlasEntry& entry) {
|
||||
return entry.fAtlas->fDesc;
|
||||
return entry.fDesc;
|
||||
}
|
||||
static uint32_t Hash(const GrTextureStripAtlas::Desc& desc) {
|
||||
return SkOpts::hash(&desc, sizeof(GrTextureStripAtlas::Desc));
|
||||
}
|
||||
|
||||
const GrTextureStripAtlas::Desc fDesc;
|
||||
sk_sp<GrTextureStripAtlas> fAtlas;
|
||||
};
|
||||
|
||||
|
@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "GrDDLTextureStripAtlas.h"
|
||||
|
||||
#include "GrContextPriv.h"
|
||||
#include "GrTexture.h"
|
||||
#include "SkGr.h"
|
||||
#include "SkTSearch.h"
|
||||
|
||||
GrDDLTextureStripAtlas::GrDDLTextureStripAtlas(const Desc& desc)
|
||||
: INHERITED(desc)
|
||||
, fAtlasBitmap(nullptr)
|
||||
, fMaxNumRows(desc.fHeight / desc.fRowHeight)
|
||||
, fCurRow(0)
|
||||
, fRows(new AtlasRow[fMaxNumRows]) {
|
||||
SkASSERT(fMaxNumRows * fDesc.fRowHeight == fDesc.fHeight);
|
||||
SkDEBUGCODE(this->validate();)
|
||||
}
|
||||
|
||||
GrDDLTextureStripAtlas::~GrDDLTextureStripAtlas() { delete[] fRows; }
|
||||
|
||||
// Flush the current state of the atlas.
|
||||
void GrDDLTextureStripAtlas::finish(GrProxyProvider* proxyProvider) {
|
||||
SkDEBUGCODE(this->validate();)
|
||||
|
||||
if (!fCurRow) {
|
||||
SkASSERT(!fCurProxy && !fAtlasBitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
int height = fCurRow * fDesc.fRowHeight;
|
||||
SkASSERT(height <= fDesc.fHeight);
|
||||
|
||||
SkImageInfo ii = SkImageInfo::Make(fDesc.fWidth, height,
|
||||
fDesc.fColorType, kPremul_SkAlphaType);
|
||||
fAtlasBitmap->allocPixels(ii);
|
||||
|
||||
for (int i = 0; i < fCurRow; ++i) {
|
||||
SkASSERT(fRows[i].fBitmap.height() == fDesc.fRowHeight);
|
||||
|
||||
int yPos = i * fDesc.fRowHeight;
|
||||
fAtlasBitmap->writePixels(fRows[i].fBitmap.pixmap(), 0, yPos);
|
||||
}
|
||||
|
||||
GrUniqueKey key;
|
||||
{
|
||||
static const GrUniqueKey::Domain kTextureStripAtlasDomain = GrUniqueKey::GenerateDomain();
|
||||
GrUniqueKey::Builder builder(&key, kTextureStripAtlasDomain, fCurRow,
|
||||
"DDL Texture Strip Atlas");
|
||||
for (int i = 0; i < fCurRow; ++i) {
|
||||
builder[i] = fRows[i].fBitmap.getGenerationID();
|
||||
}
|
||||
builder.finish();
|
||||
}
|
||||
|
||||
sk_sp<GrTextureProxy> interloper = proxyProvider->findProxyByUniqueKey(key,
|
||||
fCurProxy->origin());
|
||||
if (!interloper) {
|
||||
// In the unlikely event that there is already a proxy with this key (i.e., it has exactly
|
||||
// the same strips in exactly the same order) we'll just let it keep the key.
|
||||
proxyProvider->assignUniqueKeyToProxy(key, fCurProxy.get());
|
||||
}
|
||||
|
||||
// reset the state for the next aggregate texture
|
||||
for (int i = 0; i < fCurRow; ++i) {
|
||||
fRows[i].fBitmap.reset();
|
||||
}
|
||||
fCurRow = 0;
|
||||
fCurProxy = nullptr;
|
||||
fAtlasBitmap = nullptr;
|
||||
fKeyTable.rewind();
|
||||
SkDEBUGCODE(this->validate();)
|
||||
}
|
||||
|
||||
int GrDDLTextureStripAtlas::addStrip(GrContext* context, const SkBitmap& bitmap) {
|
||||
SkDEBUGCODE(this->validate();)
|
||||
|
||||
const int key = bitmap.getGenerationID();
|
||||
int index = this->searchByKey(key);
|
||||
|
||||
if (fCurRow >= fMaxNumRows && index < 0) {
|
||||
// The current atlas is full and adding another strip would make it overflow. Calve it off
|
||||
// and allow the next block to start a new one.
|
||||
this->finish(context->contextPriv().proxyProvider());
|
||||
index = this->searchByKey(key); // 'finish' cleared the table
|
||||
}
|
||||
|
||||
if (!fCurProxy) {
|
||||
SkASSERT(!fAtlasBitmap);
|
||||
|
||||
const GrCaps* caps = context->contextPriv().caps();
|
||||
GrPixelConfig pixelConfig = SkColorType2GrPixelConfig(fDesc.fColorType);
|
||||
SkASSERT(kUnknown_GrPixelConfig != pixelConfig);
|
||||
|
||||
SkBitmap* atlasBitmap = new SkBitmap();
|
||||
|
||||
fCurProxy = GrProxyProvider::MakeFullyLazyProxy(
|
||||
[atlasBitmap, pixelConfig](GrResourceProvider* provider) -> sk_sp<GrSurface> {
|
||||
if (!provider) {
|
||||
delete atlasBitmap;
|
||||
return sk_sp<GrSurface>();
|
||||
}
|
||||
// When this is called 'atlasBitmap' should've been filled in and be
|
||||
// non-empty
|
||||
SkASSERT(atlasBitmap->width() && atlasBitmap->height());
|
||||
GrSurfaceDesc desc;
|
||||
desc.fFlags = kNone_GrSurfaceFlags;
|
||||
desc.fWidth = atlasBitmap->width();
|
||||
desc.fHeight = atlasBitmap->height();
|
||||
desc.fConfig = pixelConfig;
|
||||
|
||||
GrMipLevel mipLevel = { atlasBitmap->getPixels(), atlasBitmap->rowBytes() };
|
||||
|
||||
return provider->createTexture(desc, SkBudgeted::kYes,
|
||||
SkBackingFit::kExact, mipLevel);
|
||||
},
|
||||
GrProxyProvider::Renderable::kNo, kTopLeft_GrSurfaceOrigin, pixelConfig, *caps);
|
||||
|
||||
fAtlasBitmap = atlasBitmap;
|
||||
}
|
||||
|
||||
SkASSERT(bitmap.width() == fDesc.fWidth);
|
||||
SkASSERT(bitmap.height() == fDesc.fRowHeight);
|
||||
SkASSERT(!context->contextPriv().resourceProvider()); // This atlas class is DDL specific
|
||||
SkASSERT(fCurRow < fMaxNumRows);
|
||||
|
||||
int rowNumber = -1;
|
||||
|
||||
if (index >= 0) {
|
||||
// We already have the data in a row, so we can just return that row
|
||||
AtlasRow* row = fKeyTable[index];
|
||||
|
||||
// Since all the rows are always stored in a contiguous array, we can save the memory
|
||||
// required for storing row numbers and just compute it with some pointer arithmetic
|
||||
rowNumber = static_cast<int>(row - fRows);
|
||||
} else {
|
||||
// ~index is the index where we will insert the new key to keep things sorted
|
||||
index = ~index;
|
||||
|
||||
rowNumber = fCurRow;
|
||||
fRows[fCurRow].fBitmap = bitmap;
|
||||
|
||||
AtlasRow* row = &fRows[rowNumber];
|
||||
fKeyTable.insert(index, 1, &row);
|
||||
|
||||
++fCurRow;
|
||||
SkASSERT(fCurRow <= fMaxNumRows);
|
||||
}
|
||||
|
||||
SkASSERT(rowNumber >= 0);
|
||||
SkDEBUGCODE(this->validate();)
|
||||
return rowNumber;
|
||||
}
|
||||
|
||||
int GrDDLTextureStripAtlas::searchByKey(uint32_t generationID) {
|
||||
static struct AtlasRowLessFunctor {
|
||||
bool operator()(const AtlasRow* row, const uint32_t& id) const {
|
||||
return row->fBitmap.getGenerationID() < id;
|
||||
}
|
||||
bool operator()(const uint32_t& id, const AtlasRow* row) const {
|
||||
return id < row->fBitmap.getGenerationID();
|
||||
}
|
||||
} functor;
|
||||
|
||||
return SkTSearch(fKeyTable.begin(), fKeyTable.count(), generationID, sizeof(AtlasRow*),
|
||||
functor);
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
void GrDDLTextureStripAtlas::validate() {
|
||||
static const int kBitmapInvalidGenID = 0;
|
||||
|
||||
// Our key table should be sorted
|
||||
uint32_t prev = fKeyTable.count() >= 1 ? fKeyTable[0]->fBitmap.getGenerationID() : 0;
|
||||
for (int i = 1; i < fKeyTable.count(); ++i) {
|
||||
AtlasRow* row = fKeyTable[i];
|
||||
SkASSERT(prev < row->fBitmap.getGenerationID());
|
||||
SkASSERT(row->fBitmap.getGenerationID() != kBitmapInvalidGenID);
|
||||
prev = row->fBitmap.getGenerationID();
|
||||
}
|
||||
|
||||
for (int i = 0; i < fCurRow; ++i) {
|
||||
// These should all have a valid bitmap and be in the search table
|
||||
SkASSERT(fRows[i].fBitmap.getGenerationID() != kBitmapInvalidGenID);
|
||||
SkASSERT(this->searchByKey(fRows[i].fBitmap.getGenerationID()) >= 0);
|
||||
}
|
||||
for (int i = fCurRow; i < fMaxNumRows; ++i) {
|
||||
// These should all be empty
|
||||
SkASSERT(fRows[i].fBitmap.getGenerationID() == kBitmapInvalidGenID);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrDDLTextureStripAtlas_DEFINED
|
||||
#define GrDDLTextureStripAtlas_DEFINED
|
||||
|
||||
#include "GrTextureStripAtlas.h"
|
||||
|
||||
#include "SkBitmap.h"
|
||||
#include "SkTDArray.h"
|
||||
|
||||
/**
|
||||
* The DDL version of the texture strip atlas consolidates individual strips into a larger texture
|
||||
* until some limit is reached, at which point a new large texture is started.
|
||||
* This can lead to the same strip being duplicated in VRAM. This can happen if a strip appears once
|
||||
* early in a rendering (that has, say, a lot of gradients) and then again later in the rendering
|
||||
* when one of the large textures has been filled. The second, probably more common, case is
|
||||
* if the same strip is used in different DDL recordings. Since the texture strip atlases aren't
|
||||
* dedupped across threads, if the same strip is used in two different DDL recordings it will
|
||||
* be duplicated in both of the DDL recorders' atlases.
|
||||
* Note, one additional feature of the DDL texture strip atlases is that, if DDL recording is ended
|
||||
* before one of the large textures is full, the large texture will be "shrunk" to fit its
|
||||
* contents.
|
||||
*/
|
||||
class GrDDLTextureStripAtlas final : public GrTextureStripAtlas {
|
||||
public:
|
||||
~GrDDLTextureStripAtlas() final;
|
||||
|
||||
// Overrides from GrTextureStripAtlas
|
||||
void lockRow(int row) final { /* The DDL version doesn't lock & unlock individual rows */}
|
||||
void unlockRow(int row) final { /* The DDL version doesn't lock & unlock individual rows */}
|
||||
|
||||
// Caution: this method will only return the appropriate proxy after a successful 'addStrip'
|
||||
// call has been made. Additionally, the proxy return will be fully lazy (i.e., its final
|
||||
// height will be unknown).
|
||||
sk_sp<GrTextureProxy> asTextureProxyRef() const final {
|
||||
SkASSERT(fCurProxy);
|
||||
return fCurProxy;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class GrTextureStripAtlasManager; // for ctor
|
||||
|
||||
// Overrides from GrTextureStripAtlas
|
||||
int addStrip(GrContext*, const SkBitmap&) final;
|
||||
void finish(GrProxyProvider*) final;
|
||||
|
||||
/**
|
||||
* The state of a single row in our cache. For the DDL texture strip atlas we hold onto all
|
||||
* the individual strip bitmaps and, upon finish, combine them all into a single bitmap.
|
||||
*/
|
||||
struct AtlasRow : ::SkNoncopyable {
|
||||
AtlasRow() {}
|
||||
|
||||
SkBitmap fBitmap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Only the GrTextureStripAtlasManager is allowed to create GrDDLTextureStripAtlas
|
||||
*/
|
||||
GrDDLTextureStripAtlas(const Desc& desc);
|
||||
|
||||
/**
|
||||
* Searches the key table for a key and returns the index if found; if not found, it returns
|
||||
* the bitwise not of the index at which we could insert the key to maintain a sorted list.
|
||||
**/
|
||||
int searchByKey(uint32_t key);
|
||||
|
||||
SkDEBUGCODE(void validate();)
|
||||
|
||||
sk_sp<GrTextureProxy> fCurProxy; // the lazy proxy that will be split off in the finish call
|
||||
SkBitmap* fAtlasBitmap; // the bitmap backing 'fCurProxy'
|
||||
|
||||
const uint16_t fMaxNumRows;
|
||||
uint16_t fCurRow;
|
||||
|
||||
AtlasRow* fRows; // We just store the source bitmap for each row.
|
||||
|
||||
// A list of pointers to AtlasRows that currently contain cached images, sorted by key
|
||||
SkTDArray<AtlasRow*> fKeyTable;
|
||||
|
||||
typedef GrTextureStripAtlas INHERITED;
|
||||
};
|
||||
|
||||
#endif
|
@ -18,7 +18,7 @@
|
||||
#define VALIDATE
|
||||
#endif
|
||||
|
||||
uint32_t GrDynamicTextureStripAtlas::CreateUniqueID() {
|
||||
uint32_t GrTextureStripAtlas::CreateUniqueID() {
|
||||
static int32_t gUniqueID = SK_InvalidUniqueID;
|
||||
uint32_t id;
|
||||
// Loop in case our global wraps around, as we never want to return a 0.
|
||||
@ -28,30 +28,30 @@ uint32_t GrDynamicTextureStripAtlas::CreateUniqueID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
GrDynamicTextureStripAtlas::GrDynamicTextureStripAtlas(const Desc& desc)
|
||||
: INHERITED(desc)
|
||||
, fCacheKey(CreateUniqueID())
|
||||
, fLockedRows(0)
|
||||
, fNumRows(desc.fHeight / desc.fRowHeight)
|
||||
, fRows(new AtlasRow[fNumRows])
|
||||
, fLRUFront(nullptr)
|
||||
, fLRUBack(nullptr) {
|
||||
GrTextureStripAtlas::GrTextureStripAtlas(const Desc& desc)
|
||||
: fCacheKey(CreateUniqueID())
|
||||
, fLockedRows(0)
|
||||
, fDesc(desc)
|
||||
, fNumRows(desc.fHeight / desc.fRowHeight)
|
||||
, fRows(new AtlasRow[fNumRows])
|
||||
, fLRUFront(nullptr)
|
||||
, fLRUBack(nullptr) {
|
||||
SkASSERT(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
|
||||
this->initLRU();
|
||||
fNormalizedYHeight = SK_Scalar1 / fDesc.fHeight;
|
||||
VALIDATE;
|
||||
}
|
||||
|
||||
GrDynamicTextureStripAtlas::~GrDynamicTextureStripAtlas() { delete[] fRows; }
|
||||
GrTextureStripAtlas::~GrTextureStripAtlas() { delete[] fRows; }
|
||||
|
||||
void GrDynamicTextureStripAtlas::lockRow(int row) {
|
||||
void GrTextureStripAtlas::lockRow(int row) {
|
||||
// This should only be called on a row that is already locked.
|
||||
SkASSERT(fRows[row].fLocks);
|
||||
fRows[row].fLocks++;
|
||||
++fLockedRows;
|
||||
}
|
||||
|
||||
int GrDynamicTextureStripAtlas::addStrip(GrContext* context, const SkBitmap& bitmap) {
|
||||
int GrTextureStripAtlas::lockRow(GrContext* context, const SkBitmap& bitmap) {
|
||||
VALIDATE;
|
||||
|
||||
if (!context->contextPriv().resourceProvider()) {
|
||||
@ -142,11 +142,11 @@ int GrDynamicTextureStripAtlas::addStrip(GrContext* context, const SkBitmap& bit
|
||||
return rowNumber;
|
||||
}
|
||||
|
||||
sk_sp<GrTextureProxy> GrDynamicTextureStripAtlas::asTextureProxyRef() const {
|
||||
sk_sp<GrTextureProxy> GrTextureStripAtlas::asTextureProxyRef() const {
|
||||
return fTexContext->asTextureProxyRef();
|
||||
}
|
||||
|
||||
void GrDynamicTextureStripAtlas::unlockRow(int row) {
|
||||
void GrTextureStripAtlas::unlockRow(int row) {
|
||||
VALIDATE;
|
||||
--fRows[row].fLocks;
|
||||
--fLockedRows;
|
||||
@ -160,13 +160,13 @@ void GrDynamicTextureStripAtlas::unlockRow(int row) {
|
||||
VALIDATE;
|
||||
}
|
||||
|
||||
GrDynamicTextureStripAtlas::AtlasRow* GrDynamicTextureStripAtlas::getLRU() {
|
||||
GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
|
||||
// Front is least-recently-used
|
||||
AtlasRow* row = fLRUFront;
|
||||
return row;
|
||||
}
|
||||
|
||||
void GrDynamicTextureStripAtlas::lockTexture(GrContext* context) {
|
||||
void GrTextureStripAtlas::lockTexture(GrContext* context) {
|
||||
|
||||
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
|
||||
GrUniqueKey key;
|
||||
@ -179,12 +179,10 @@ void GrDynamicTextureStripAtlas::lockTexture(GrContext* context) {
|
||||
sk_sp<GrTextureProxy> proxy = proxyProvider->findOrCreateProxyByUniqueKey(
|
||||
key, kTopLeft_GrSurfaceOrigin);
|
||||
if (!proxy) {
|
||||
GrPixelConfig pixelConfig = SkColorType2GrPixelConfig(fDesc.fColorType);
|
||||
|
||||
GrSurfaceDesc texDesc;
|
||||
texDesc.fWidth = fDesc.fWidth;
|
||||
texDesc.fHeight = fDesc.fHeight;
|
||||
texDesc.fConfig = pixelConfig;
|
||||
texDesc.fConfig = fDesc.fConfig;
|
||||
|
||||
proxy = proxyProvider->createProxy(texDesc, kTopLeft_GrSurfaceOrigin, SkBackingFit::kExact,
|
||||
SkBudgeted::kYes, GrInternalSurfaceFlags::kNoPendingIO);
|
||||
@ -202,12 +200,12 @@ void GrDynamicTextureStripAtlas::lockTexture(GrContext* context) {
|
||||
fTexContext = context->contextPriv().makeWrappedSurfaceContext(std::move(proxy));
|
||||
}
|
||||
|
||||
void GrDynamicTextureStripAtlas::unlockTexture() {
|
||||
void GrTextureStripAtlas::unlockTexture() {
|
||||
SkASSERT(fTexContext && 0 == fLockedRows);
|
||||
fTexContext.reset();
|
||||
}
|
||||
|
||||
void GrDynamicTextureStripAtlas::initLRU() {
|
||||
void GrTextureStripAtlas::initLRU() {
|
||||
fLRUFront = nullptr;
|
||||
fLRUBack = nullptr;
|
||||
// Initially all the rows are in the LRU list
|
||||
@ -221,7 +219,7 @@ void GrDynamicTextureStripAtlas::initLRU() {
|
||||
SkASSERT(nullptr == fLRUBack || nullptr == fLRUBack->fNext);
|
||||
}
|
||||
|
||||
void GrDynamicTextureStripAtlas::appendLRU(AtlasRow* row) {
|
||||
void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
|
||||
SkASSERT(nullptr == row->fPrev && nullptr == row->fNext);
|
||||
if (nullptr == fLRUFront && nullptr == fLRUBack) {
|
||||
fLRUFront = row;
|
||||
@ -233,7 +231,7 @@ void GrDynamicTextureStripAtlas::appendLRU(AtlasRow* row) {
|
||||
}
|
||||
}
|
||||
|
||||
void GrDynamicTextureStripAtlas::removeFromLRU(AtlasRow* row) {
|
||||
void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
|
||||
SkASSERT(row);
|
||||
if (row->fNext && row->fPrev) {
|
||||
row->fPrev->fNext = row->fNext;
|
||||
@ -258,17 +256,19 @@ void GrDynamicTextureStripAtlas::removeFromLRU(AtlasRow* row) {
|
||||
row->fPrev = nullptr;
|
||||
}
|
||||
|
||||
int GrDynamicTextureStripAtlas::searchByKey(uint32_t key) {
|
||||
int GrTextureStripAtlas::searchByKey(uint32_t key) {
|
||||
AtlasRow target;
|
||||
target.fKey = key;
|
||||
return SkTSearch<const AtlasRow, KeyLess>((const AtlasRow**)fKeyTable.begin(),
|
||||
fKeyTable.count(),
|
||||
&target,
|
||||
sizeof(AtlasRow*));
|
||||
return SkTSearch<const AtlasRow,
|
||||
GrTextureStripAtlas::KeyLess>((const AtlasRow**)fKeyTable.begin(),
|
||||
fKeyTable.count(),
|
||||
&target,
|
||||
sizeof(AtlasRow*));
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
void GrDynamicTextureStripAtlas::validate() {
|
||||
void GrTextureStripAtlas::validate() {
|
||||
|
||||
// Our key table should be sorted
|
||||
uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
|
||||
for (int i = 1; i < fKeyTable.count(); ++i) {
|
||||
|
@ -8,35 +8,39 @@
|
||||
#ifndef GrDynamicTextureStripAtlas_DEFINED
|
||||
#define GrDynamicTextureStripAtlas_DEFINED
|
||||
|
||||
#include "GrTextureStripAtlas.h"
|
||||
#include "GrTypesPriv.h"
|
||||
|
||||
#include "SkNoncopyable.h"
|
||||
#include "SkRefCnt.h"
|
||||
#include "SkScalar.h"
|
||||
#include "SkTDArray.h"
|
||||
|
||||
class GrContext;
|
||||
class GrSurfaceContext;
|
||||
class GrTextureProxy;
|
||||
|
||||
class SkBitmap;
|
||||
|
||||
/**
|
||||
* Maintains a single large texture whose rows store many textures of a small fixed height,
|
||||
* stored in rows across the x-axis such that we can safely wrap/repeat them horizontally.
|
||||
*/
|
||||
class GrDynamicTextureStripAtlas final : public GrTextureStripAtlas {
|
||||
class GrTextureStripAtlas : public SkRefCnt {
|
||||
public:
|
||||
~GrDynamicTextureStripAtlas() final;
|
||||
|
||||
/**
|
||||
* This is intended to be used when cloning a processor that already holds a lock. It is
|
||||
* assumed that the row already has at least one lock.
|
||||
* Descriptor struct which we'll use as a hash table key
|
||||
*/
|
||||
void lockRow(int row) final;
|
||||
void unlockRow(int row) final;
|
||||
struct Desc {
|
||||
Desc() { sk_bzero(this, sizeof(*this)); }
|
||||
GrPixelConfig fConfig;
|
||||
uint16_t fWidth, fHeight, fRowHeight;
|
||||
uint16_t fUnusedPadding;
|
||||
bool operator==(const Desc& other) const {
|
||||
return 0 == memcmp(this, &other, sizeof(Desc));
|
||||
}
|
||||
};
|
||||
|
||||
sk_sp<GrTextureProxy> asTextureProxyRef() const final;
|
||||
|
||||
private:
|
||||
friend class GrTextureStripAtlasManager; // for ctor
|
||||
|
||||
/**
|
||||
* Only the GrTextureStripAtlasManager is allowed to create GrTextureStripAtlases
|
||||
*/
|
||||
GrDynamicTextureStripAtlas(const Desc& desc);
|
||||
~GrTextureStripAtlas();
|
||||
|
||||
/**
|
||||
* Add a texture to the atlas
|
||||
@ -44,9 +48,38 @@ private:
|
||||
* @return The row index we inserted into, or -1 if we failed to find an open row. The caller
|
||||
* is responsible for calling unlockRow() with this row index when it's done with it.
|
||||
*/
|
||||
int addStrip(GrContext*, const SkBitmap&) final;
|
||||
int lockRow(GrContext*, const SkBitmap&);
|
||||
|
||||
void finish(GrProxyProvider*) final { SkASSERT(0); } // this is only called in DDL mode
|
||||
/**
|
||||
* This is intended to be used when cloning a processor that already holds a lock. It is
|
||||
* assumed that the row already has at least one lock.
|
||||
*/
|
||||
void lockRow(int row);
|
||||
void unlockRow(int row);
|
||||
|
||||
/**
|
||||
* These functions help turn an integer row index in [0, 1, 2, ... numRows] into a scalar y
|
||||
* texture coordinate in [0, 1] that we can use in a shader.
|
||||
*
|
||||
* If a regular texture access without using the atlas looks like:
|
||||
*
|
||||
* texture2D(sampler, float2(x, y))
|
||||
*
|
||||
* Then when using the atlas we'd replace it with:
|
||||
*
|
||||
* texture2D(sampler, float2(x, yOffset + y * scaleFactor))
|
||||
*
|
||||
* Where yOffset, returned by getYOffset(), is the offset to the start of the row within the
|
||||
* atlas and scaleFactor, returned by getNormalizedTexelHeight, is the normalized height of
|
||||
* one texel row.
|
||||
*/
|
||||
SkScalar getYOffset(int row) const { return SkIntToScalar(row) / fNumRows; }
|
||||
SkScalar getNormalizedTexelHeight() const { return fNormalizedYHeight; }
|
||||
|
||||
sk_sp<GrTextureProxy> asTextureProxyRef() const;
|
||||
|
||||
private:
|
||||
friend class GrTextureStripAtlasManager; // for ctor
|
||||
|
||||
static uint32_t CreateUniqueID();
|
||||
|
||||
@ -68,6 +101,11 @@ private:
|
||||
AtlasRow* fPrev;
|
||||
};
|
||||
|
||||
/**
|
||||
* Only the GrTextureStripAtlasManager is allowed to create GrTextureStripAtlases
|
||||
*/
|
||||
GrTextureStripAtlas(const Desc& desc);
|
||||
|
||||
void lockTexture(GrContext*);
|
||||
void unlockTexture();
|
||||
|
||||
@ -109,6 +147,7 @@ private:
|
||||
// Total locks on all rows (when this reaches zero, we can unlock our texture)
|
||||
int32_t fLockedRows;
|
||||
|
||||
const Desc fDesc;
|
||||
const uint16_t fNumRows;
|
||||
sk_sp<GrSurfaceContext> fTexContext;
|
||||
|
||||
@ -126,8 +165,6 @@ private:
|
||||
|
||||
// A list of pointers to AtlasRows that currently contain cached images, sorted by key
|
||||
SkTDArray<AtlasRow*> fKeyTable;
|
||||
|
||||
typedef GrTextureStripAtlas INHERITED;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -8,10 +8,10 @@
|
||||
#include "GrTextureStripAtlas.h"
|
||||
#include "GrContext.h"
|
||||
#include "GrContextPriv.h"
|
||||
#include "GrDDLTextureStripAtlas.h"
|
||||
#include "GrDynamicTextureStripAtlas.h"
|
||||
#include "SkBitmap.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
GrTextureStripAtlasManager::~GrTextureStripAtlasManager() {
|
||||
this->deleteAllAtlases();
|
||||
}
|
||||
@ -30,41 +30,15 @@ void GrTextureStripAtlasManager::abandon() {
|
||||
this->deleteAllAtlases();
|
||||
}
|
||||
|
||||
void GrTextureStripAtlasManager::finish(GrProxyProvider* proxyProvider) {
|
||||
for (AtlasHash::Iter iter(&fAtlasCache); !iter.done(); ++iter) {
|
||||
AtlasEntry* tmp = &(*iter);
|
||||
tmp->fAtlas->finish(proxyProvider);
|
||||
}
|
||||
fAtlasCache.reset();
|
||||
}
|
||||
|
||||
|
||||
sk_sp<GrTextureStripAtlas> GrTextureStripAtlasManager::addStrip(
|
||||
GrContext* context,
|
||||
const GrTextureStripAtlas::Desc& desc,
|
||||
const SkBitmap& bitmap,
|
||||
int* row) {
|
||||
SkASSERT(kPremul_SkAlphaType == bitmap.alphaType());
|
||||
|
||||
sk_sp<GrTextureStripAtlas> GrTextureStripAtlasManager::refAtlas(
|
||||
const GrTextureStripAtlas::Desc& desc) {
|
||||
AtlasEntry* entry = fAtlasCache.find(desc);
|
||||
if (!entry) {
|
||||
sk_sp<GrTextureStripAtlas> atlas;
|
||||
// TODO: Does the AtlasEntry need a copy of the Desc if the GrTextureStripAtlas has one?
|
||||
entry = new AtlasEntry(desc, sk_sp<GrTextureStripAtlas>(new GrTextureStripAtlas(desc)));
|
||||
|
||||
if (!context->contextPriv().resourceProvider()) {
|
||||
atlas.reset(new GrDDLTextureStripAtlas(desc));
|
||||
} else {
|
||||
atlas.reset(new GrDynamicTextureStripAtlas(desc));
|
||||
}
|
||||
|
||||
entry = new AtlasEntry(sk_sp<GrTextureStripAtlas>(std::move(atlas)));
|
||||
fAtlasCache.add(entry);
|
||||
}
|
||||
|
||||
*row = entry->fAtlas->addStrip(context, bitmap);
|
||||
if (*row < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return entry->fAtlas;
|
||||
}
|
||||
|
||||
|
@ -919,7 +919,6 @@ SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
|
||||
#include "GrContext.h"
|
||||
#include "GrContextPriv.h"
|
||||
#include "GrShaderCaps.h"
|
||||
#include "GrTexture.h"
|
||||
#include "GrTextureStripAtlas.h"
|
||||
#include "gl/GrGLContext.h"
|
||||
#include "glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
@ -967,8 +966,7 @@ void GrGradientEffect::GLSLProcessor::onSetData(const GrGLSLProgramDataManager&
|
||||
break;
|
||||
case GrGradientEffect::InterpolationStrategy::kTexture:
|
||||
if (e.fYCoord != fCachedYCoord) {
|
||||
SkScalar yDelta = 1.0f / e.fTextureSampler.peekTexture()->height();
|
||||
pdman.set1f(fFSYUni, e.fYCoord * yDelta);
|
||||
pdman.set1f(fFSYUni, e.fYCoord);
|
||||
fCachedYCoord = e.fYCoord;
|
||||
}
|
||||
break;
|
||||
@ -1248,8 +1246,6 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool
|
||||
SkBitmap bitmap;
|
||||
shader.getGradientTableBitmap(xformedColors.fColors, &bitmap, colorType);
|
||||
SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width()));
|
||||
SkASSERT(kPremul_SkAlphaType == bitmap.alphaType());
|
||||
SkASSERT(bitmap.isImmutable());
|
||||
|
||||
auto atlasManager = args.fContext->contextPriv().textureStripAtlasManager();
|
||||
|
||||
@ -1257,24 +1253,18 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool
|
||||
desc.fWidth = bitmap.width();
|
||||
desc.fHeight = 32;
|
||||
desc.fRowHeight = bitmap.height(); // always 1 here
|
||||
desc.fColorType = bitmap.colorType();
|
||||
|
||||
int row;
|
||||
fAtlas = atlasManager->addStrip(args.fContext, desc, bitmap, &row);
|
||||
if (!args.fContext->contextPriv().resourceProvider()) {
|
||||
// In DDL mode we should always be able to atlas
|
||||
SkASSERT(fAtlas && row >= 0);
|
||||
}
|
||||
desc.fConfig = SkColorType2GrPixelConfig(bitmap.colorType());
|
||||
fAtlas = atlasManager->refAtlas(desc);
|
||||
SkASSERT(fAtlas);
|
||||
|
||||
// We always filter the gradient table. Each table is one row of a texture, always
|
||||
// y-clamp.
|
||||
GrSamplerState samplerState(args.fWrapMode, GrSamplerState::Filter::kBilerp);
|
||||
|
||||
fRow = fAtlas->lockRow(args.fContext, bitmap);
|
||||
if (-1 != fRow) {
|
||||
SkASSERT(fAtlas);
|
||||
|
||||
fYCoord = fAtlas->rowToTextureY(fRow);
|
||||
// This is 1/2 places where auto-normalization is disabled bc the gradient T is 0..1
|
||||
fYCoord = fAtlas->getYOffset(fRow)+SK_ScalarHalf*fAtlas->getNormalizedTexelHeight();
|
||||
// This is 1/2 places where auto-normalization is disabled
|
||||
fCoordTransform.reset(*args.fMatrix, fAtlas->asTextureProxyRef().get(), false);
|
||||
fTextureSampler.reset(fAtlas->asTextureProxyRef(), samplerState);
|
||||
} else {
|
||||
@ -1286,6 +1276,7 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool
|
||||
// that GrMakeCachedImageProxy is sufficient (i.e., it won't need to be
|
||||
// extracted to a subset or mipmapped).
|
||||
|
||||
SkASSERT(bitmap.isImmutable());
|
||||
sk_sp<SkImage> srcImage = SkImage::MakeFromBitmap(bitmap);
|
||||
if (!srcImage) {
|
||||
return;
|
||||
@ -1298,13 +1289,11 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool
|
||||
SkDebugf("Gradient won't draw. Could not create texture.");
|
||||
return;
|
||||
}
|
||||
// This is 2/2 places where auto-normalization is disabled because the graient T is 0..1
|
||||
// This is 2/2 places where auto-normalization is disabled
|
||||
fCoordTransform.reset(*args.fMatrix, proxy.get(), false);
|
||||
fTextureSampler.reset(std::move(proxy), samplerState);
|
||||
SkASSERT(1 == bitmap.height());
|
||||
fYCoord = SK_ScalarHalf;
|
||||
}
|
||||
|
||||
this->setTextureSamplerCnt(1);
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,6 @@ struct SkColor4fXformer {
|
||||
#include "glsl/GrGLSLProgramDataManager.h"
|
||||
|
||||
class GrInvariantOutput;
|
||||
class GrTextureStripAtlas;
|
||||
|
||||
/*
|
||||
* The interpretation of the texture matrix depends on the sample mode. The
|
||||
@ -181,6 +180,8 @@ class GrTextureStripAtlas;
|
||||
* determines the gradient value.
|
||||
*/
|
||||
|
||||
class GrTextureStripAtlas;
|
||||
|
||||
// Base class for Gr gradient effects
|
||||
class GrGradientEffect : public GrFragmentProcessor {
|
||||
public:
|
||||
|
Loading…
Reference in New Issue
Block a user