Represents an OpenGL render buffer. Render buffers are attached to layers to perform stencil work.
渲染缓冲,这里创建的目的是用来执行模板测试。
void allocate() {
if (!mAllocated) {
glRenderbufferStorage(GL_RENDERBUFFER, mFormat, mWidth, mHeight);
mAllocated = true;
}
}
glRenderbufferStorage函数可以创建一个深度和模板渲染缓冲对象。
[转载]在Android中,提供了Shader类专门用来渲染图像以及一些几何图形。
Shader类包括了5个直接子类,分别为:BitmapShader、ComposeShader、LinearGradient、RadialGradient
以及SweepGradient。
RadialGradient用于环形渲染;
而SweepGradient则用于梯度渲染。
使用Shader类进行图像渲染时,首先需要构建Shader对象,然后通过Paint的setShader()方法来设置渲染对象,
最后将这个Paint对象绘制到屏幕上即可。
原文链接:https://www.cnblogs.com/menlsh/archive/2012/12/09/2810372.html
Shader的颜色如何过渡?
[转载]在最常用的RGB模式中,任何颜色都是由红绿蓝3种颜色叠加而成的,只要逐个增减它的RGB值,就是在渐变。
以黄(255,255,0)到蓝(0,0,255)为例,黄色就这么渐变成蓝色了。
所以实现渐变时,两种颜色中间需要过渡256次,过渡色彩就要有256个。
原文链接:https://www.zhihu.com/question/28972919/answer/42743881
LinearGradient的作用是实现某一区域内颜色的线性渐变效果。
LinearGradient的函数原型为:
/** 实现某一区域内颜色的线性渐变效果
@param x0 x0表示渐变的起始点x坐标
@param y0 y0表示渐变的起始点y坐标
@param x1 x1表示渐变的终点x坐标
@param y1 y1表示渐变的终点y坐标
@param colors colors表示渐变的颜色数组
@param positions positions用来指定颜色数组的相对位置
@param tile tile表示平铺方式
*/
public LinearGradient (float x0, float y0, float x1, float y1, int[] colors, float[]
positions, Shader.TileMode tile);
通常,参数positions设为null,表示颜色数组以斜坡线的形式均匀分布。
Shader.TileMode有3种参数可供选择,分别为CLAMP、REPEAT和MIRROR。
CLAMP的作用是如果渲染器超出原始边界范围,则会复制边缘颜色对超出范围的区域进行着色。
REPEAT的作用是在横向和纵向上以平铺的形式重复渲染位图。MIRROR的作用是在横向和纵向上以镜像的方式重复渲染位图。
RadialGradient的作用是在某一区域内实现环形的渐变效果。
RadialGradient的函数原型为:
/** 在某一区域内实现环形的渐变效果
@param centerX x表示环形的圆心x坐标
@param centerY y表示环形的圆心y坐标
@param radius radius表示环形的半径
@param colors colors表示环形渐变的颜色数组
@param stops positions用来指定颜色数组的相对位置
@param tileMode tile表示平铺的方式
*/
public RadialGradient (float x, float y, float radius, int[] colors, float[] positions,
Shader.TileMode tile);
SweepGradient也称为扫描渲染,是指在某一中心以x轴正方向逆时针旋转一周而形成的扫描效果的渲染形式。
SweepGradient的函数原型为:
/**
* A subclass of Shader that draws a sweep gradient around a center point.
*
* @param cx cx表示扫描的中心x坐标
* @param cy cy表示扫描的中心y坐标
* @param colors colors表示梯度渐变的颜色数组
* @param positions positions用来指定颜色数组的相对位置。
*/
public SweepGradient (float cx, float cy, int[] colors, float[] positions);
SkiaShader.cpp
bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 modelViewMatrix,
GLuint* textureUnit, ProgramDescription* description,
SkiaShaderData::GradientShaderData* outData) {
SkShader::GradientInfo gradInfo;
gradInfo.fColorCount = 0;
gradInfo.fColors = nullptr;
gradInfo.fColorOffsets = nullptr;
SkMatrix unitMatrix;
switch (shader.asAGradient(&gradInfo)) {
case SkShader::kLinear_GradientType: //对应LinearGradient(线性渲染)
description->gradientType = ProgramDescription::kGradientLinear;
toLinearUnitMatrix(gradInfo.fPoint, &unitMatrix);
break;
case SkShader::kRadial_GradientType: //对应RadialGradient(线性渲染)
description->gradientType = ProgramDescription::kGradientCircular;
toCircularUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY,
gradInfo.fRadius[0], &unitMatrix);
break;
case SkShader::kSweep_GradientType: //对应SweepGradient(梯度渲染)
description->gradientType = ProgramDescription::kGradientSweep;
toSweepUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, &unitMatrix);
break;
default:
// Do nothing. This shader is unsupported.
return false;
}
}
GlopBuilder::build
SkiaShader::store
SkiaShader::tryStoreGradient
GradientCache::get
GradientCache::addLinearGradient
Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
uint32_t* colors, float* positions, int count) {
GradientInfo info;
//获取渐变的宽度和是否有alpha值
getGradientInfo(colors, count, info);
//创建纹理
Texture* texture = new Texture(Caches::getInstance());
texture->blend = info.hasAlpha;
texture->generation = 1;
// Assume the cache is always big enough
//渐变图片高度方向是重复的,只需要储存两个像素大小的高度就足够了。
const uint32_t size = info.width * 2 * bytesPerPixel();
while (getSize() + size > mMaxSize) {
LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(),
"Ran out of things to remove from the cache? getSize() = %" PRIu32
", size = %" PRIu32 ", mMaxSize = %" PRIu32 ", width = %" PRIu32,
getSize(), size, mMaxSize, info.width);
}
//将根据shader产生的渐变图片上传为纹理。
generateTexture(colors, positions, info.width, 2, texture);
//增加cache大小
mSize += size;
LOG_ALWAYS_FATAL_IF((int)size != texture->objectSize(),
"size != texture->objectSize(), size %" PRIu32 ", objectSize %d"
" width = %" PRIu32 " bytesPerPixel() = %zu",
size, texture->objectSize(), info.width, bytesPerPixel());
//保存cache
mCache.put(gradient, texture);
return texture;
}
void GradientCache::getGradientInfo(const uint32_t* colors, const int count,
GradientInfo& info) {
uint32_t width = 256 * (count - 1);//两个颜色之间渐变的颜色 * 几次颜色渐变
// If the npot extension is not supported we cannot use non-clamp
// wrap modes. We therefore find the nearest largest power of 2
// unless width is already a power of 2
if (!mHasNpot && (width & (width - 1)) != 0) {
width = 1 << (32 - __builtin_clz(width));
}
//计算有没有alpha
bool hasAlpha = false;
for (int i = 0; i < count; i++) {
if (((colors[i] >> 24) & 0xff) < 255) {
hasAlpha = true;
break;
}
}
info.width = min(width, uint32_t(mMaxTextureSize));
info.hasAlpha = hasAlpha;
}
getGradientInfo函数会根据colors代表颜色数值的大小,确定渐变的宽度。
下一步就是将根据shader产生的渐变图片上传为纹理。
void GradientCache::generateTexture(uint32_t* colors, float* positions,
const uint32_t width, const uint32_t height, Texture* texture) {
const GLsizei rowBytes = width * bytesPerPixel();
uint8_t pixels[rowBytes * height];
static ChannelSplitter gSplitters[] = {
&android::uirenderer::GradientCache::splitToBytes,
&android::uirenderer::GradientCache::splitToFloats,
};
ChannelSplitter split = gSplitters[mUseFloatTexture];
static ChannelMixer gMixers[] = {
&android::uirenderer::GradientCache::mixBytes,
&android::uirenderer::GradientCache::mixFloats,
};
ChannelMixer mix = gMixers[mUseFloatTexture];//根据bool型mUseFloatTexture确定是那个函数
GradientColor start;
(this->*split)(colors[0], start);
GradientColor end;
(this->*split)(colors[1], end);
int currentPos = 1;
float startPos = positions[0];
float distance = positions[1] - startPos;
uint8_t* dst = pixels;
for (uint32_t x = 0; x < width; x++) {
float pos = x / float(width - 1);
if (pos > positions[currentPos]) {
start = end;
startPos = positions[currentPos];
currentPos++;
(this->*split)(colors[currentPos], end);
distance = positions[currentPos] - startPos;
}
float amount = (pos - startPos) / distance;
(this->*mix)(start, end, amount, dst);
}
memcpy(pixels + rowBytes, pixels, rowBytes);
//upload()中实现纹理上传
if (mUseFloatTexture) {
// We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels);
} else {
texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
texture->setFilter(GL_LINEAR);
//GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
//这个值用来拉伸纵向图形
texture->setWrap(GL_CLAMP_TO_EDGE);
}
再看bytesPerPixel()函数,目前版本mUseFloatTexture = true,那么sizeof(float) = 4。
size_t GradientCache::bytesPerPixel() const {
// We use 4 channels (RGBA)
return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t));
}
所以cache的计算公式为:
size = 256 * (count - 1) * 2 * 4 * 4
PathCache中使用lru算法维护cache生命周期,使用PathProcessor单独开一个线程进行资源预加载。
PathProcessor如何运作,需要了解TaskManager逻辑:
先看一下PathProcessor的类图:
FrameBuilder::deferPathOp
PathCache::precache
precache之后的调用流程图如下:
TaskManager中创建了一个线程池,线程名称为hwuiTask+order
TaskManager::TaskManager() {
// Get the number of available CPUs. This value does not change over time.
int cpuCount = sysconf(_SC_NPROCESSORS_CONF);
// Really no point in making more than 2 of these worker threads, but
// we do want to limit ourselves to 1 worker thread on dual-core devices.
int workerCount = cpuCount > 2 ? 2 : 1;//cpu数量大于2,两个线程
for (int i = 0; i < workerCount; i++) {
String8 name;
name.appendFormat("hwuiTask%d", i + 1);//hwuiTask1和hwuiTask2
mThreads.push_back(new WorkerThread(name));
}
}
当有任务入队时,开启线程:
bool TaskManager::WorkerThread::addTask(const TaskWrapper& task) {
if (!isRunning()) {
run(mName.string(), PRIORITY_DEFAULT);//开启线程
} else if (exitPending()) {
return false;
}
... ...
}
线程运行时,会回调Task的onProcess方法:
bool TaskManager::WorkerThread::threadLoop() {
mSignal.wait();
... ...
for (size_t i = 0; i < tasks.size(); i++) {
const TaskWrapper& task = tasks[i];
task.mProcessor->process(task.mTask);
}
return true;
}
void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
PathTask* t = static_cast<PathTask*>(task.get());
ATRACE_NAME("pathPrecache");
float left, top, offset;
uint32_t width, height;
PathCache::computePathBounds(&t->path, &t->paint, left, top, offset, width, height);
PathTexture* texture = t->texture;
texture->left = left;
texture->top = top;
texture->offset = offset;
if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
SkBitmap* bitmap = new SkBitmap();
drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height);
t->setResult(bitmap);
} else {
t->setResult(nullptr);
}
}
pathTexture大小计算公式:
void PathCache::computeBounds(const SkRect& bounds, const SkPaint* paint,
float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
const float pathWidth = std::max(bounds.width(), 1.0f);
const float pathHeight = std::max(bounds.height(), 1.0f);
left = bounds.fLeft;
top = bounds.fTop;
//1、先取画笔getStrokeWidth和1.0中的最大
//2、使用floorf取计算出的最大的不超过计算值的整数
offset = (int) floorf(std::max(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
width = uint32_t(pathWidth + offset * 2.0 + 0.5);
height = uint32_t(pathHeight + offset * 2.0 + 0.5);
}
比如我的代码如下:
mPaint.setStrokeWidth(1);
Path path = new Path();
path.lineTo(100, 100);
path.close();
canvas.drawPath(path,mPaint);
那么width=100 + floorf((1 * 1.5f) + 0.5f) * 2.0 + 0.5 = 104
.9图片的cache
调用流程:
RecordingCanvas::drawNinePatch --- addOp(PatchOp)
... ...
BakedOpDispatcher::onPatchOp
PatchCache::get
先给大家看一张字体的纹理方便理解:
Demo code
canvas.drawText("字体", 200,500,mPaint);
调用流程:
//addOp
Canvas::drawText
DrawTextFunctor::operator
RecordingCanvas::drawGlyphs
addOp(alloc().create_trivial<TextOp>)
//precache
FontRenderer::precache
Font::precache
Font::getCachedGlyph
Font::cacheGlyph 或者
Font::updateGlyphCache
FontRenderer::cacheBitmap
//defer结束,绘制开始,cache结束
FrameBuilder::replayBakedOps
FrameBuilder::finishDefer
GammaFontRenderer::endPrecaching
FontRenderer::endPrecaching
FontRenderer::checkTextureUpdate
FontRenderer::checkTextureUpdateForCache
CacheTexture::upload
GpuPixelBuffer::upload或
CpuPixelBuffer::upload
//绘制
BakedOpDispatcher::onMergedTextOps
BakedOpDispatcher::renderText
FontRenderer::setFont
FontRenderer::renderPosText
Font::render
Font::drawCachedGlyph
FontRenderer::appendRotatedMeshQuad
FontRenderer::issueDrawCommand
TextDrawFunctor::draw
GlopBuilder
void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount,
float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface) {
// minikin may modify the original paint
Paint paint(origPaint);
Layout layout;
MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);//调用Minikin和Skia获取字体参数
size_t nGlyphs = layout.nGlyphs();
std::unique_ptr glyphs(new uint16_t[nGlyphs]);
std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
MinikinRect bounds;
layout.getBounds(&bounds);
if (!drawTextAbsolutePos()) {
bounds.offset(x, y);
}
// Set align to left for drawing, as we don't want individual
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
paint.setTextAlign(Paint::kLeft_Align);
DrawTextFunctor f(layout, this, glyphs.get(), pos.get(),
paint, x, y, bounds, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);//获取字形等参数
}
FontRenderer在初始化时,会根据配置创建CacheTexture
FontUtil
#define DEFAULT_TEXT_SMALL_CACHE_WIDTH 1024
#define DEFAULT_TEXT_SMALL_CACHE_HEIGHT 512
#define DEFAULT_TEXT_LARGE_CACHE_WIDTH 2048
#define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512
FontRenderer::FontRenderer(const uint8_t* gammaTable)
... ...
mSmallCacheWidth = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_WIDTH,
DEFAULT_TEXT_SMALL_CACHE_WIDTH);
mSmallCacheHeight = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_HEIGHT,
DEFAULT_TEXT_SMALL_CACHE_HEIGHT);
mLargeCacheWidth = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_WIDTH,
DEFAULT_TEXT_LARGE_CACHE_WIDTH);
mLargeCacheHeight = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_HEIGHT,
DEFAULT_TEXT_LARGE_CACHE_HEIGHT);
... ...
std::vector mACacheTextures;
std::vector mRGBACacheTextures;
void FontRenderer::initTextTexture() {
clearCacheTextures(mACacheTextures);
clearCacheTextures(mRGBACacheTextures);
mUploadTexture = false;
mACacheTextures.push_back(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
GL_ALPHA, true));
mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
GL_ALPHA, false));
mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
GL_ALPHA, false));
mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight,
GL_ALPHA, false));
mRGBACacheTextures.push_back(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
GL_RGBA, false));
mRGBACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
GL_RGBA, false));
mCurrentCacheTexture = mACacheTextures[0];
}
CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format,
bool allocate) {
CacheTexture* cacheTexture = new CacheTexture(width, height, format, kMaxNumberOfQuads);
// allocate=false,创建对象但是未分配内存
if (allocate) {
Caches::getInstance().textureState().activateTexture(0);
cacheTexture->allocatePixelBuffer();
cacheTexture->allocateMesh();
}
return cacheTexture;
}
cacheBitmap用来cache字形对应的图片和位置信息
void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
... ...
//所有的字体保存在同一张纹理中,有CacheTexture维护
//1、获取CacheTexture
//2、计算字体在大纹理中的大小和坐标
CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY);
if (!cacheTexture) {
if (!precaching) {
// If the new glyph didn't fit and we are not just trying to precache it,
// clear out the cache and try again
flushAllAndInvalidate();
cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY);
}
if (!cacheTexture) {
// either the glyph didn't fit or we're precaching and will cache it when we draw
return;
}
}
cachedGlyph->mCacheTexture = cacheTexture;
*retOriginX = startX;
*retOriginY = startY;
uint32_t endX = startX + glyph.fWidth;
uint32_t endY = startY + glyph.fHeight;
uint32_t cacheWidth = cacheTexture->getWidth();
if (!cacheTexture->getPixelBuffer()) {
//hwui使用的是多重纹理贴图,字体的纹理是GL_TEXTURE0
Caches::getInstance().textureState().activateTexture(0);
// Large-glyph texture memory is allocated only as needed
cacheTexture->allocatePixelBuffer();
}
if (!cacheTexture->mesh()) {
cacheTexture->allocateMesh();
}
//获取PixelBuffer指向的内存指针
uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map();
uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
int srcStride = glyph.rowBytes();
//将glyph imagecopy到内存中
// Copy the glyph image, taking the mask format into account
switch (format) {
case SkMask::kA8_Format: {
uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
- TEXTURE_BORDER_SIZE;
// write leading border line
memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
// write glyph data
if (mGammaTable) {
for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
row = cacheY * cacheWidth;
cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
uint8_t tempCol = bitmapBuffer[bY + bX];
cacheBuffer[row + cacheX] = mGammaTable[tempCol];
}
cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
}
} else {
for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
row = cacheY * cacheWidth;
memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
}
}
// write trailing border line
row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
break;
}
... ...
}
CacheTexture
PixelBuffer* mPixelBuffer = nullptr; //储存字形对应的像素信息
Texture mTexture; //大纹理,储存所有的纹理信息
uint32_t mWidth, mHeight; //纹理宽高
GLenum mFormat;
bool mLinearFiltering = false;
bool mDirty = false;
uint16_t mNumGlyphs = 0;
TextureVertex* mMesh = nullptr;
uint32_t mCurrentQuad = 0;
uint32_t mMaxQuadCount;
Caches& mCaches;
CacheBlock* mCacheBlocks; //单个字形对应的宽高信息
bool mHasUnpackRowLength;
Rect mDirtyRect;
endPrecaching的操作主要是纹理上传,以GpuPixelBuffer为例子:
void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) {
// If the buffer is not mapped, unmap() will not bind it
mCaches.pixelBufferState().bind(mBuffer);
unmap();
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat,
GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset));
mCaches.pixelBufferState().unbind();
}
glTexSubImage2D函数
提供修改图像函数,因为修改一个纹理比重新创建一个纹理开销小得多,对于一些视频捕捉程序可以先将视频图像存储在更大
的初始图像中(该图像大小要求是2的次方,OGL 2.0后没有这个限制),创建一个渲染用的纹理, 然后反复调用
glTexSubImage2D(修改的图像区域不用是2的次方)函数从图像视频图像区域读取数据到渲染纹理图像中。渲染用的纹理图
像只需要创建一次即可。
根据glyph获取字形对应的bitmap的u1,u2,v1,v2坐标信息,储存在CacheTexture储存的mesh中。
渲染时,GlopBuilder时再去取这些信息。
void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) {
... ...
GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
.setRoundRectClipState(bakedState->roundRectClipState)
.setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
.setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
.setTransform(bakedState->computedState.transform, transformFlags)
.setModelViewIdentityEmptyBounds()
.build();
... ...
}
Demo代码:
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);//设置为空心
mPaint.setStrokeWidth(1);
mPaint.setTextSize(50);
mPaint.setShadowLayer(10, 3, 3, 0xFFFF00FF);
canvas.drawText("阴影", 200,500,mPaint);
调用堆栈:
BakedOpDispatcher::renderTextShadow
TextDropShadowCache::get
FontRenderer::renderDropShadow
Font::measure
Font::render //RenderMode = MEASURE
Font::measureCachedGlyph
Font::render //RenderMode = BITMAP
FontRenderer::blurImage
static void renderTextShadow(BakedOpRenderer& renderer,
const TextOp& op, const BakedOpState& textOpState) {
... ...
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
fontRenderer.setFont(op.paint, SkMatrix::I());
renderer.caches().textureState().activateTexture(0);
... ...
ShadowTexture* texture = renderer.caches().dropShadowCache.get(
op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions);
... ...
}
ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
float radius, const float* positions) {
ShadowText entry(paint, radius, numGlyphs, glyphs, positions);
ShadowTexture* texture = mCache.get(entry);
if (!texture) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs,
radius, positions);
if (!shadow.image) {
return nullptr;
}
Caches& caches = Caches::getInstance();
//创建ShadowTexture
texture = new ShadowTexture(caches);
texture->left = shadow.penX;
texture->top = shadow.penY;
texture->generation = 0;
texture->blend = true;
const uint32_t size = shadow.width * shadow.height;
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(),
"Failed to remove oldest from cache. mSize = %"
PRIu32 ", mCache.size() = %zu", mSize, mCache.size());
}
}
//上传ShadowTexture
// Textures are Alpha8
texture->upload(GL_ALPHA, shadow.width, shadow.height,
GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image);
texture->setFilter(GL_LINEAR);
texture->setWrap(GL_CLAMP_TO_EDGE);
if (size < mMaxSize) {
if (mDebugEnabled) {
ALOGD("Shadow texture created, size = %d", texture->bitmapSize);
}
entry.copyTextLocally();
mSize += texture->objectSize();
mCache.put(entry, texture);
} else {
texture->cleanup = true;
}
// Cleanup shadow
free(shadow.image);
}
return texture;
}
FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const glyph_t *glyphs,
int numGlyphs, float radius, const float* positions) {
... ...
mCurrentFont->measure(paint, glyphs, numGlyphs, &bounds, positions);
... ...
uint8_t* dataBuffer = (uint8_t*) malloc(size);
memset(dataBuffer, 0, size);
int penX = intRadius - bounds.left;
int penY = intRadius - bounds.bottom;
if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
// text has non-whitespace, so draw and blur to create the shadow
// NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
// TODO: don't draw pure whitespace in the first place, and avoid needing this check
//渲染字体到Bitmap,用于之后的模糊操作(类似于离屏渲染)
mCurrentFont->render(paint, glyphs, numGlyphs, penX, penY,
Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions);
// Unbind any PBO we might have used
Caches::getInstance().pixelBufferState().unbind();
//创建模糊阴影
blurImage(&dataBuffer, paddedWidth, paddedHeight, radius);
}
image.width = paddedWidth;
image.height = paddedHeight;
image.image = dataBuffer;
image.penX = penX;
image.penY = penY;
return image;
}
void Font::measure(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, Rect *bounds, const float* positions) {
if (bounds == nullptr) {
ALOGE("No return rectangle provided to measure text");
return;
}
bounds->set(1e6, -1e6, -1e6, 1e6);
//第六个参数是RenderMode mode=MEASURE
render(paint, glyphs, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
}
RenderMode的定义
enum RenderMode {
FRAMEBUFFER,
BITMAP,
MEASURE,
};
void Font::render(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
//RenderMode对应的函数如下:
static RenderGlyph gRenderGlyph[] = {
&android::uirenderer::Font::drawCachedGlyph,
&android::uirenderer::Font::drawCachedGlyphTransformed,
&android::uirenderer::Font::drawCachedGlyphBitmap,
&android::uirenderer::Font::drawCachedGlyphBitmap,
&android::uirenderer::Font::measureCachedGlyph,
&android::uirenderer::Font::measureCachedGlyph
};
}
绘制阴影时,现在执行RenderMode = MEASURE
的Render,确定字体的属性;再执行RenderMode = BITMAP
的render,产生用于blur的字体。
字体模糊
执行字体模糊的函数是FontRenderer::blurImage
,这个函数使用RenderScript进行模糊操作。
另外注意的一点是,当TextDropShadowCache的mMaxSize配置的太小时,会引起应用崩溃。
ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
float radius, const float* positions) {
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(),
"Failed to remove oldest from cache. mSize = %"
PRIu32 ", mCache.size() = %zu", mSize, mCache.size());
}
}
}
已经弃用
TextureCache的作用是缓存已经上传的图片,通过图片的fStableID获取已经上传过的Texture。
TextureCache使用Lru算法维护缓存的生命周期。
Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
//如果使用的是Atlas中的资源,返回Atlas中的Texture
if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef());
if (CC_UNLIKELY(entry)) {
return entry->texture;
}
}
//根据bitmap的fStableID获取对应的cache
Texture* texture = mCache.get(bitmap->pixelRef()->getStableID());
if (!texture) {
//这个判断是:如果图片过大,不进行缓存和上传纹理操作
if (!canMakeTextureFromBitmap(bitmap)) {
return nullptr;
}
//判断是否超过了mMaxSize,超过了使用Lru策略清理旧的缓存
const uint32_t size = bitmap->rowBytes() * bitmap->height();
bool canCache = size < mMaxSize;
// Don't even try to cache a bitmap that's bigger than the cache
while (canCache && mSize + size > mMaxSize) {
Texture* oldest = mCache.peekOldestValue();
if (oldest && !oldest->isInUse) {
mCache.removeOldest();
} else {
canCache = false;
}
}
if (canCache) {
//对于新的bitmap,获取fStableID,创建对应的Texture并上传纹理
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
texture->generation = bitmap->getGenerationID();
texture->upload(*bitmap);
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
bitmap, texture->id, size, mSize);
if (mDebugEnabled) {
ALOGD("Texture created, size = %d", size);
}
mCache.put(bitmap->pixelRef()->getStableID(), texture);
}
} else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
// Texture was in the cache but is dirty, re-upload
// TODO: Re-adjust the cache size if the bitmap's dimensions have changed
texture->upload(*bitmap);
texture->generation = bitmap->getGenerationID();
}
return texture;
}
demo代码:
RectF r1=new RectF(); //RectF对象
r1.left=250; //左边
r1.top=250; //上边
r1.right=350; //右边
r1.bottom=350; //下边
canvas.drawRoundRect(r1,10,10,mPaint);
调用流程:
RecordingCanvas::drawRoundRect
addOp(alloc().create_trivial<RoundRectOp>
FrameBuilder::deferRoundRectOp
TessellationCache::precacheRoundRect
TessellationCache::getRoundRectBuffer(
TessellationCache::getOrCreateBuffer
TessellationProcessor::onProcess
FrameBuilder::defer3dChildren
FrameBuilder::deferShadow
TessellationCache::getShadowTask
TessellationCache::precacheShadows
ShadowProcessor::onProcess
TessellationCache::tessellateShadows
PathTessellator::approximatePathOutlineVertices
PathTessellator::approximatePathOutlineVertices //递归二次贝塞尔顶点
ShadowTessellator::tessellateAmbientShadow
AmbientShadow::createAmbientShadow //将阴影计算为三角形,阴影值为alpha
ShadowTessellator::tessellateSpotShadow
SpotShadow::createSpotShadow //从光中心计算每个多边形顶点的投影轮廓