我们先来看一下,如果我们用openGl,怎样去渲染文字。
如果我们不对这个举行了解,那我们在看Android hwui相干代码的时候,很容易陷入渺茫,不知道我们的目的地到底在哪里!所以必需先对这个有了解。
资料来源:文字渲染
FreeType
Android是利用FreeType去加载字体。FreeType是一个能够用于加载字体并将他们渲染到位图以及提供多种字体相干的操纵的软件开发库。
简单理解就是,如果我们想渲染字符“A”到屏幕上,首先就要用FreeType库将字符“A”转换成一个位图,然后将这个位图纹理上传到gpu,这样就体现到我们的屏幕。我们利用这个纹理就可以做一系列效果了,比如大小,颜色,alpha,旋转等等变革。
FreeType的真正吸引力在于它能够加载TrueType字体。TrueType字体不是用像素或其他不可缩放的方式来界说的,它是通过数学公式(曲线的组合)来界说的。类似于矢量图像,这些光栅化后的字体图像可以根据需要的字体高度来生成。通过利用TrueType字体,你可以容易渲染不同大小的字形而不造成任何质量损失。
示例
要加载一个字体,我们只需要初始化FreeType库,而且将这个字体加载为一个FreeType称之为面(Face)的东西。。这里为我们加载一个从Windows/Fonts目录中拷贝来的TrueType字体文件arial.ttf。
- FT_Library ft;
- if (FT_Init_FreeType(&ft))
- std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
- FT_Face face;
- if (FT_New_Face(ft, "fonts/arial.ttf", 0, &face))
- std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
复制代码 这些FreeType函数在出现错误时将返回一个非零的整数值。
劈面加载完成之后,我们需要界说字体大小,这表示着我们要从字体面中生成多大的字形:
- FT_Set_Pixel_Sizes(face, 0, 48);
复制代码 此函数设置了字体面的宽度和高度,将宽度值设为0表示我们要从字体面通过给定的高度中动态计算出字形的宽度。
一个FreeType面中包含了一个字形的集合。我们可以调用FT_Load_Char函数来将其中一个字形设置为激活字形。这里我们选择加载字符字形’X’:
- if (FT_Load_Char(face, 'X', FT_LOAD_RENDER))
- std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
复制代码 通过将FT_LOAD_RENDER设为加载标志之一,我们告诉FreeType去创建一个8位的灰度位图,我们可以通过face->glyph->bitmap来访问这个位图。
利用FreeType加载的每个字形没有类似的大小(不像位图字体那样)。利用FreeType生成的位图的大小恰好能包含这个字符可看法区。比方生成用于表示’.’的位图的大小要比表示’X’的小得多。因此,FreeType同样也加载了一些度量值来指定每个字符的大小和位置。下面这张图展示了FreeType对每一个字符字形计算的全部度量值。
比方我们要生成ASCII字符集的前128个字符:
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //禁用字节对齐限制
- for (GLubyte c = 0; c < 128; c++)
- {
- // 加载字符的字形
- if (FT_Load_Char(face, c, FT_LOAD_RENDER))
- {
- std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
- continue;
- }
- // 生成纹理
- GLuint texture;
- glGenTextures(1, &texture);
- glBindTexture(GL_TEXTURE_2D, texture);
- glTexImage2D(
- GL_TEXTURE_2D,
- 0,
- GL_RED,
- face->glyph->bitmap.width,
- face->glyph->bitmap.rows,
- 0,
- GL_RED,
- GL_UNSIGNED_BYTE,
- face->glyph->bitmap.buffer
- );
- // 设置纹理选项
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- // 储存字符供之后使用
- Character character = {
- texture,
- glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
- glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
- face->glyph->advance.x
- };
- Characters.insert(std::pair<GLchar, Character>(c, character));
- }
复制代码 关键步调
总结一下关键步调:
- // FreeType FT_Library ft; // 初始化FreeType库 if (FT_Init_FreeType(&ft)) std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl; // 加载字体文件 FT_Face face; if (FT_New_Face(ft, "fonts/arial.ttf", 0, &face)) std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl; // 界说字体大小 FT_Set_Pixel_Sizes(face, 0, 48);
- //加载字符字形’X’ if (FT_Load_Char(face, 'X', FT_LOAD_RENDER)) std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl; //生成纹理,上传gpu glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D( GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer );
复制代码 Android 文字渲染
app开发职员怎样渲染一段文字到屏幕上呢?
一样平常会重写view的draw方法。调用cavas.drawText方法。因此我们这篇文章的起点就是drawText,终点就是上面分析的生成纹理,上传gpu。
DrawTextBlob
我们知道调用cavas.drawXXX方法,终极都是在DisplayListData中push一个对应的结构体(这个过程其他文章会有介绍,限于篇幅,这里就不再介绍),drawText对应的是DrawTextBlob:
- struct DrawTextBlob final : Op {
- static const auto kType = Type::DrawTextBlob;
- DrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
- : blob(sk_ref_sp(blob)), x(x), y(y), paint(paint), drawTextBlobMode(gDrawTextBlobMode) {}
- sk_sp<const SkTextBlob> blob;
- SkScalar x, y;
- SkPaint paint;
- DrawTextBlobMode drawTextBlobMode;
- void draw(SkCanvas* c, const SkMatrix&) const { c->drawTextBlob(blob.get(), x, y, paint); }
- };
复制代码 这个结构的关键成员对象:SkTextBlob是怎样生成的呢?以MakeFromString为例:
- /**
- * 创建一个包含单个文本运行的 SkTextBlob。
- *
- * font 包含用于定义文本运行的属性。
- *
- * 当编码为 SkTextEncoding::kUTF8、SkTextEncoding::kUTF16 或
- * SkTextEncoding::kUTF32 时,此函数使用 font 中 SkTypeface 的默认字符到字形映射。
- * 对于在 SkTypeface 中未找到的字符,它不执行字体回退。
- * 它不执行字距调整或其他复杂排版;字形根据其默认进位进行定位。
- *
- * @param text 要绘制的字符代码点或字形。
- * @param byteLength 文本数组的字节长度。
- * @param font 用于绘制的文本大小、字体、文本缩放等信息。
- * @param encoding 文本数组中使用的文本编码。
- * @return 由单个文本运行构造的 SkTextBlob。
- */
- sk_sp<SkTextBlob> SkTextBlob::MakeFromText(const void* text, size_t byteLength, const SkFont& font,
- SkTextEncoding encoding) {
- // 注意:我们故意将其提升为完全定位的 blob,因为我们需要在后续步骤中支付相同的成本
- // (例如计算边界),因此现在一次性支付成本更划算。
- const int count = font.countText(text, byteLength, encoding);
- if (count < 1) {
- return nullptr; // 如果没有有效的文本,则返回 nullptr。
- }
- SkTextBlobBuilder builder; // 创建一个文本 blob 构建器。
- auto buffer = builder.allocRunPos(font, count); // 为文本运行分配空间。
-
- // 将文本转换为字形 ID。
- font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count);
-
- // 获取每个字形的位置,初始化为 (0, 0)。
- font.getPos(buffer.glyphs, count, buffer.points(), {0, 0});
-
- return builder.make(); // 创建并返回 SkTextBlob。
- }
- /**
- * 将文本转换为对应的字形 ID。
- *
- * @param text 输入文本的指针,表示待转换的字符数据。
- * @param byteLength 输入文本的字节长度。
- * @param encoding 输入文本的编码格式(如:UTF-8、UTF-16 等)。
- * @param glyphs 存储转换后字形 ID 的数组指针。
- * @param maxGlyphCount glyphs 数组的最大容量。
- *
- * @return 返回转换得到的字形数量。如果输入长度为 0,返回 0。
- */
- int SkTypeface::textToGlyphs(const void* text, size_t byteLength, SkTextEncoding encoding,
- SkGlyphID glyphs[], int maxGlyphCount) const {
- // 检查输入的字节长度是否为 0,若是,则直接返回 0。
- if (0 == byteLength) {
- return 0;
- }
- // 确保文本指针不为空。
- SkASSERT(text);
- // 计算输入文本中的字符数量。
- int count = SkFontPriv::CountTextElements(text, byteLength, encoding);
-
- // 如果 glyphs 数组为空或字符数量超过最大字形计数,则返回字符数量。
- if (!glyphs || count > maxGlyphCount) {
- return count;
- }
- // 如果编码格式为 GlyphID,直接复制数据到 glyphs 数组。
- if (encoding == SkTextEncoding::kGlyphID) {
- memcpy(glyphs, text, count << 1); // 每个 GlyphID 占 2 字节
- return count;
- }
- // 创建一个存储转换后字符的对象。
- SkConvertToUTF32 storage;
- const SkUnichar* uni = storage.convert(text, byteLength, encoding); // 将输入文本转换为 UTF-32 编码。
- // 将转换后的 Unicode 字符映射到对应的字形 ID。
- this->unicharsToGlyphs(uni, count, glyphs);
-
- // 返回转换得到的字形数量。
- return count;
- }
- void SkTypeface::unicharsToGlyphs(const SkUnichar uni[], int count, SkGlyphID glyphs[]) const {
- if (count > 0 && glyphs && uni) {
- this->onCharsToGlyphs(uni, count, glyphs);
- }
- }
复制代码 从上面的代码来看,会先将字符换成成gpyphId,将结果存在一个数组,根据这个数组创建SkTextBlob。而将字符转换成gpyphId是借助SkTypeface_FreeType类完成的。
- void SkTypeface_FreeType::onCharsToGlyphs(const SkUnichar uni[], int count,
- SkGlyphID glyphs[]) const {
- // 首先尝试访问缓存,*在*访问 FreeType 库/面之前,因为后者可能非常慢。
- // 如果确实需要计算新的 glyphID,则访问这些 FreeType 对象并继续循环。
- int i;
- {
- // 乐观地使用共享锁。
- SkAutoSharedMutexShared ama(fC2GCacheMutex);
- for (i = 0; i < count; ++i) {
- int index = fC2GCache.findGlyphIndex(uni[i]);
- if (index < 0) {
- break; // 找不到,跳出循环
- }
- glyphs[i] = SkToU16(index); // 从缓存获取 glyphID
- }
- if (i == count) {
- // 所有字符都已处理,无需访问 FreeType 对象
- return;
- }
- }
- // 需要添加更多字符,因此获取独占锁。
- SkAutoSharedMutexExclusive ama(fC2GCacheMutex);
- AutoFTAccess fta(this);
- FT_Face face = fta.face(); // 获取 FreeType 面
- if (!face) {
- sk_bzero(glyphs, count * sizeof(glyphs[0])); // 清零 glyphs 数组
- return;
- }
- for (; i < count; ++i) {
- SkUnichar c = uni[i];
- int index = fC2GCache.findGlyphIndex(c);
- if (index >= 0) {
- glyphs[i] = SkToU16(index); // 从缓存获取 glyphID
- } else {
- // 获取 FreeType 中的字符索引并缓存
- glyphs[i] = SkToU16(FT_Get_Char_Index(face, c));
- fC2GCache.insertCharAndGlyph(~index, c, glyphs[i]);
- }
- }
- // 如果缓存数量超过最大限制,则重置缓存
- if (fC2GCache.count() > kMaxC2GCacheCount) {
- fC2GCache.reset();
- }
- }
复制代码 如今我们到了第一个途经点:
- // 获取 FreeType 中的字符索引并缓存
- glyphs[i] = SkToU16(FT_Get_Char_Index(face, c));
复制代码 注意此时只是生成了glyphId,后面能根据这个id去加载位图,如今只是暂时存储了id。
类图关系:根据RunRecord的glyphBuffer就可以获取glyphId了。
drawText函数底层其实就是创建一个DrawTextBlob,push进对应RendeNode的DisplayListData数据结构。DrawTextBlob的成员变量blob:SkTextBlob则存储了字符对应的位置和glyphId信息。这个过程并不涉及具体的渲染。
drawContent

接下来就到了,渲染线程去举行一帧渲染的时候了。
渲染线程在prepareTree遍历完RenderNode树(可以简单理解为和view树对应,大部分环境两者相差不大)后,就会去
drawContent。
- DisplayListData中遇到DrawDrawable,会递归调用drawContent
- void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
- RenderNode* renderNode = mRenderNode.get();
- float alphaMultiplier = 1.0f;
- const RenderProperties& properties = renderNode->properties();
- if (mComposeLayer) {
- //会将view的属性(放缩,位移,alpha等)设置记录到对应的skdevice
- //这样后续这个view内部的元素(文字,纹理等)就会根据对应的属性去渲染
- setViewProperties(properties, canvas, &alphaMultiplier); // 应用节点属性到画布
- }
- // 获取渲染节点的 Skia 显示列表
- SkiaDisplayList* displayList = mRenderNode->getDisplayList().asSkiaDl();
- // 设置显示列表的父级矩阵为画布的总矩阵
- displayList->mParentMatrix = canvas->getTotalMatrix();
- // 创建 SkRect 来表示节点的宽度和高度
- const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
- // 检查是否快速拒绝:如果裁剪到边界并且画布拒绝绘制,则认为是快速拒绝
- bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
- // 如果没有被快速拒绝
- if (!quickRejected) {
- // 获取画布的本地裁剪边界
- auto clipBounds = canvas->getLocalClipBounds();
- // 创建 SkIRect 表示源边界
- SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
- // 设置默认的位移点
- SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
- // 如果节点有图层且需要合成图层
- if (renderNode->getLayerSurface() && mComposeLayer) {
- // 确保有效的图层类型
- SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
- SkPaint paint; // 创建 SkPaint 对象
- // 设置图层的绘制需要,根据透明度倍数
- layerNeedsPaint(properties.layerProperties(), alphaMultiplier, &paint);
- sk_sp<SkImage> snapshotImage; // 存储图层的快照
- auto* imageFilter = properties.layerProperties().getImageFilter(); // 获取图层的图像滤镜
- auto recordingContext = canvas->recordingContext(); // 获取画布的记录上下文
- // 如果不启用渲染效果缓存
- if (!Properties::enableRenderEffectCache) {
- // 获取图层的快照
- snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
- // 如果存在图像滤镜
- if (imageFilter) {
- // 应用图像滤镜,可能需要记录上下文
- snapshotImage = applyImageFilter(imageFilter, snapshotImage, clipBounds, srcBounds, offset, canvas);
- }
- } else {
- // 更新快照图像
- const auto snapshotResult = renderNode->updateSnapshotIfRequired(
- recordingContext, imageFilter, clipBounds.roundOut());
- snapshotImage = snapshotResult->snapshot;
- srcBounds = snapshotResult->outSubset;
- offset = snapshotResult->outOffset;
- }
- // 计算目标边界
- const auto dstBounds = SkIRect::MakeXYWH(offset.x(), offset.y(), srcBounds.width(), srcBounds.height());
- // 设置采样选项
- SkSamplingOptions sampling(SkFilterMode::kLinear);
- // 绘制图像快照到目标边界
- canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds), SkRect::Make(dstBounds), sampling, &paint);
- // 处理图层的更新标记
- if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
- renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true;
- // 检查是否启用图层调试
- if (CC_UNLIKELY(Properties::debugLayersUpdates)) {
- SkPaint layerPaint;
- layerPaint.setColor(0x7f00ff00); // 设置调试颜色
- canvas->drawRect(bounds, layerPaint); // 绘制调试矩形
- } else if (CC_UNLIKELY(Properties::debugOverdraw)) {
- SkPaint transparentPaint;
- transparentPaint.setColor(SK_ColorTRANSPARENT); // 设置透明颜色
- canvas->drawRect(bounds, transparentPaint); // 绘制透明矩形以表示过度绘制区域
- }
- }
- } else {
- // 如果不是合成图层且透明度倍数低于 1.0
- if (alphaMultiplier < 1.0f) {
- // 使用 Alpha 过滤画布进行绘制
- AlphaFilterCanvas alphaCanvas(canvas, alphaMultiplier);
- displayList->draw(&alphaCanvas);
- } else {
- // 如果透明度倍数为 1.0,直接绘制显示列表
- displayList->draw(canvas);
- }
- }
- }
- }
复制代码
- 首先调用setViewProperties,这个函数会将view的属性(放缩,位移,alpha等)设置记录到对应的skdevice,这样后续这个view内部的元素(文字,纹理等)就会根据对应的属性去渲染。
- 如果此view设置了hardware,而且内容没有变革则复用之前的渲染缓存。如果渲染缓存失效(比如view内部内容有变革),会重新渲染。
- 没有设置hardware,调用displayList->draw,举行渲染
- displayList->draw就是会去遍历DisplayListData,去调用对应结构体的draw函数。这个过程简单理解就是将DrawTextBlob等结构记录的渲染操纵,映射到AtlasTextOp,TextureOp等结构。
drawTextBlob
我们来看一下DrawTextBlob的drawContent过程。先看一下DrawTextBlob的draw函数:
- void draw(SkCanvas* c, const SkMatrix&) const { c->drawTextBlob(blob.get(), x, y, paint); }
- void SkCanvas::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) {
- TRACE_EVENT0("skia", TRACE_FUNC); // 记录跟踪事件,用于性能分析或调试
- RETURN_ON_NULL(blob); // 如果 blob 为 null,直接返回,防止后续操作出错
- RETURN_ON_FALSE(blob->bounds().makeOffset(x, y).isFinite()); // 检查偏移后的边界是否是有限值,如果不是,终止绘制
- // 如果字形数量超过 2^21(超过 200 万个字形),会导致缓冲区溢出,停止操作。
- // 这是为了避免堆栈溢出等问题,参见 chromium 项目中的问题编号 1080481。
- // 如果这个限制变得麻烦,可以考虑每次处理较小的批次。
- int totalGlyphCount = 0; // 初始化总字形数量为 0
- constexpr int kMaxGlyphCount = 1 << 21; // 定义最大字形数量 2^21
- SkTextBlob::Iter i(*blob); // 创建迭代器,用于遍历 SkTextBlob 中的所有字形运行(run)
- SkTextBlob::Iter::Run r; // 用于存储当前遍历到的字形运行
- while (i.next(&r)) { // 遍历每个字形运行
- int glyphsLeft = kMaxGlyphCount - totalGlyphCount; // 计算剩余允许的字形数量
- RETURN_ON_FALSE(r.fGlyphCount <= glyphsLeft); // 如果当前字形运行中的字形数量超过剩余允许值,终止绘制
- totalGlyphCount += r.fGlyphCount; // 累加当前字形运行中的字形数量
- }
- // 如果所有检查通过,调用内部方法 onDrawTextBlob 进行实际绘制操作
- this->onDrawTextBlob(blob, x, y, paint);
- }
- void SkCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
- const SkPaint& paint) {
- //根据blob,创建glyphRunList
- auto glyphRunList = fScratchGlyphRunBuilder->blobToGlyphRunList(*blob, {x, y});
- this->onDrawGlyphRunList(glyphRunList, paint);
- }
复制代码- // 将 SkTextBlob 转换为 GlyphRunList,包含所有相关的字形信息和位置信息。
- // 此函数会预先调整所有缓冲区的大小,以避免在处理过程中移动。
- const GlyphRunList& sktext::GlyphRunBuilder::blobToGlyphRunList(
- const SkTextBlob& blob, // 输入:要转换的 SkTextBlob 对象
- SkPoint origin) { // 输入:字形的原点位置
- // 预先初始化所有的缓冲区,以便在处理过程中不会移动
- this->initialize(blob);
- // 设置光标以遍历位置和缩放旋转信息
- SkPoint* positionCursor = fPositions; // 位置光标,指向当前存储的位置
- SkVector* scaledRotationsCursor = fScaledRotations; // 缩放旋转光标
- // 遍历 SkTextBlob 中的每个 run
- for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) {
- size_t runSize = it.glyphCount(); // 获取当前 run 中的字形数量
- if (runSize == 0 || !SkFontPriv::IsFinite(it.font())) {
- // 如果没有字形或字体不是有限的,则跳过该 run
- continue;
- }
- const SkFont& font = it.font(); // 获取当前 run 的字体
- auto glyphIDs = SkSpan<const SkGlyphID>{it.glyphs(), runSize}; // 字形 ID 数组
- SkSpan<const SkPoint> positions; // 用于存储字形位置的 span
- SkSpan<const SkVector> scaledRotations; // 用于存储缩放旋转的 span
- switch (it.positioning()) { // 根据定位类型处理不同的情况
- case SkTextBlobRunIterator::kDefault_Positioning: {
- // 默认定位,调用绘制文本位置的函数
- positions = draw_text_positions(font, glyphIDs, it.offset(), positionCursor);
- positionCursor += positions.size(); // 更新位置光标
- break;
- }
- case SkTextBlobRunIterator::kHorizontal_Positioning: {
- // 水平定位,直接设置 x 坐标
- positions = SkSpan(positionCursor, runSize); // 位置 span
- for (auto x : SkSpan<const SkScalar>{it.pos(), glyphIDs.size()}) {
- // 将 x 坐标和 y 坐标(offset.y)一起存储
- *positionCursor++ = SkPoint::Make(x, it.offset().y());
- }
- break;
- }
- case SkTextBlobRunIterator::kFull_Positioning: {
- // 完全定位,使用现有的点
- positions = SkSpan(it.points(), runSize);
- break;
- }
- case SkTextBlobRunIterator::kRSXform_Positioning: {
- // 使用 RSXform 定位,存储变换后的坐标和旋转信息
- positions = SkSpan(positionCursor, runSize);
- scaledRotations = SkSpan(scaledRotationsCursor, runSize);
- for (const SkRSXform& xform : SkSpan(it.xforms(), runSize)) {
- *positionCursor++ = {xform.fTx, xform.fTy}; // 存储 x, y 坐标
- *scaledRotationsCursor++ = {xform.fSCos, xform.fSSin}; // 存储缩放旋转
- }
- break;
- }
- }
- // 获取字形聚类信息
- const uint32_t* clusters = it.clusters();
- // 创建字形 run
- this->makeGlyphRun(
- font, // 当前字体
- glyphIDs, // 当前 run 的字形 ID
- positions, // 字形位置
- SkSpan<const char>(it.text(), it.textSize()), // 文本内容
- SkSpan<const uint32_t>(clusters, clusters ? runSize : 0), // 字形聚类信息
- scaledRotations); // 缩放旋转信息
- }
- // 设置最终的字形 run 列表并返回
- return this->setGlyphRunList(&blob, blob.bounds(), origin);
- }
复制代码- void SkCanvas::onDrawGlyphRunList(const sktext::GlyphRunList& glyphRunList, const SkPaint& paint) {
- // 获取字形运行列表的边界框
- SkRect bounds = glyphRunList.sourceBoundsWithOrigin();
-
- // 如果边界框与画布的可见区域不相交,则提前返回,跳过绘制
- if (this->internalQuickReject(bounds, paint)) {
- return;
- }
- // 文本尝试内部应用任何 SkMaskFilter 并将模糊掩码保存到 strike 缓存中;
- // 如果字形必须以路径或可绘制对象形式绘制,SkDevice 将路由回此 SkCanvas 进行重试,
- // 这将通过一个不会跳过掩码过滤层的函数。
- auto layer = this->aboutToDraw(paint, &bounds, PredrawFlags::kSkipMaskFilterAutoLayer);
-
- // 如果 layer 有效,则使用顶层设备绘制字形运行列表
- if (layer) {
- this->topDevice()->drawGlyphRunList(this, glyphRunList, layer->paint());
- }
- }
复制代码 skcanvas的绘制函数drawXXX,最后是通过对应的topdevice的drawXXX函数实现。
- void Device::onDrawGlyphRunList(SkCanvas* canvas,
- const sktext::GlyphRunList& glyphRunList,
- const SkPaint& paint) {
- ASSERT_SINGLE_OWNER
- GR_CREATE_TRACE_MARKER_CONTEXT("skgpu::ganesh::Device", "drawGlyphRunList", fContext.get());
- SkASSERT(!glyphRunList.hasRSXForm());
- if (glyphRunList.blob() == nullptr) {
- // If the glyphRunList does not have an associated text blob, then it was created by one of
- // the direct draw APIs (drawGlyphs, etc.). Use a Slug to draw the glyphs.
- auto slug = this->convertGlyphRunListToSlug(glyphRunList, paint);
- if (slug != nullptr) {
- this->drawSlug(canvas, slug.get(), paint);
- }
- } else {
- //注意这个函数的参数:this->localToDevice()。
- //这个获取的device的一个3x3矩阵,前面的view的属性设置,以及一些scale Op都会影响到这个矩阵。
- fSurfaceDrawContext->drawGlyphRunList(canvas,
- this->clip(),
- this->localToDevice(),
- glyphRunList,
- this->strikeDeviceInfo(),
- paint);
- }
- }
复制代码- void SurfaceDrawContext::drawGlyphRunList(SkCanvas* canvas,
- const GrClip* clip,
- const SkMatrix& viewMatrix,
- const sktext::GlyphRunList& glyphRunList,
- SkStrikeDeviceInfo strikeDeviceInfo,
- const SkPaint& paint) {
- // 确保该操作是在拥有唯一上下文访问权限的线程中执行。
- ASSERT_SINGLE_OWNER
-
- // 如果当前的绘制上下文已被放弃(即它的底层资源已无效或被释放),则返回,不再执行任何操作。
- RETURN_IF_ABANDONED
- // 仅在调试模式下进行验证,检查当前 SurfaceDrawContext 是否处于有效状态。
- SkDEBUGCODE(this->validate();)
-
- // 创建用于追踪性能的标记,标记当前执行的操作是 "drawGlyphRunList",
- // 方便调试和性能分析。这部分主要用于 GPU 渲染的调试跟踪。
- GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceDrawContext", "drawGlyphRunList", fContext);
- // Vulkan 二级命令缓冲区(VkSecondaryCB)不支持内联上传操作,
- // 因为内联上传可能需要在执行过程中暂停并重启渲染通道。
- // 如果当前的上下文包装了一个 Vulkan 二级命令缓冲区,则返回。
- if (this->wrapsVkSecondaryCB()) {
- return;
- }
- // 获取用于缓存和重绘文本图块(text blob)的协调器。此协调器负责管理
- // 文本绘制相关的数据缓存,提升后续文本绘制的效率。
- sktext::gpu::TextBlobRedrawCoordinator* textBlobCache = fContext->priv().getTextBlobCache();
- // 定义一个用于处理文本绘制的委托(lambda 函数),这个函数将在文本图块的子运行中调用。
- // 它会生成文本的绘制操作(op),并将其添加到绘制队列中。
- auto atlasDelegate = [&](const sktext::gpu::AtlasSubRun* subRun,
- SkPoint drawOrigin, // 字符串绘制的原点
- const SkPaint& paint, // 绘制的样式
- sk_sp<SkRefCnt> subRunStorage, // 子运行存储的指针
- sktext::gpu::RendererData) { // 渲染器数据(上下文)
- // 根据子运行生成绘制操作(op),这个绘制操作负责在屏幕上绘制相应的文本。
- auto [drawingClip, op] = subRun->makeAtlasTextOp(
- clip, // 当前的剪裁区域
- viewMatrix, // 当前视图的变换矩阵
- drawOrigin, // 绘制的起点
- paint, // 绘制样式
- std::move(subRunStorage), // 子运行存储
- this); // 当前的 SurfaceDrawContext
-
- // 如果生成的绘制操作(op)不为空,则将其添加到当前的绘制操作列表中。
- if (op != nullptr) {
- this->addDrawOp(drawingClip, std::move(op));
- }
- };
- // 调用文本缓存的绘制方法,将文本图块列表传递给缓存,委托给 atlasDelegate 执行具体的绘制操作。
- textBlobCache->drawGlyphRunList(
- canvas, // 绘制的目标画布
- viewMatrix, // 视图变换矩阵
- glyphRunList, // 要绘制的文本图块列表
- paint, // 绘制样式
- strikeDeviceInfo, // 字体信息
- atlasDelegate); // 委托用于执行具体的绘制操作
- }
复制代码
- SurfaceDrawContext::drawGlyphRunList界说了一个匿名函数atlasDelegate 。主要内容就是创建一个AtlasTextOp(不一定会直接去新建一个,大概会复用,通过重载AtlasTextOp的new运算符),并调用addDrawOp。
- 调用textBlobCache->drawGlyphRunList,这里会把匿名函数传进去,在subrun内部举行调用。
- void TextBlobRedrawCoordinator::drawGlyphRunList(SkCanvas* canvas,
- const SkMatrix& viewMatrix,
- const sktext::GlyphRunList& glyphRunList,
- const SkPaint& paint,
- SkStrikeDeviceInfo strikeDeviceInfo,
- const AtlasDrawDelegate& atlasDelegate) {
- //查找或者创建TextBlob
- sk_sp<TextBlob> blob = this->findOrCreateBlob(viewMatrix, glyphRunList, paint,
- strikeDeviceInfo);
- blob->draw(canvas, glyphRunList.origin(), paint, atlasDelegate);
- }
复制代码
- 查找或者创建TextBlob,这个过程很容易会因为view的一些属性变革,判断文字缓存失效,重新生成位图。一些文字较多的页面,容易因为一些放缩,alpha等动效导致缓存失效,不断重复渲染,产生性能问题。
- 调用blob->draw,这个内存会调用界说在SurfaceDrawContext::drawGlyphRunList函数内部的匿名函数。
创建TextBlob的过程
- sk_sp<TextBlob> TextBlobRedrawCoordinator::findOrCreateBlob(const SkMatrix& viewMatrix,
- const GlyphRunList& glyphRunList,
- const SkPaint& paint,
- SkStrikeDeviceInfo strikeDeviceInfo) {
- // 创建一个位置矩阵,并根据 glyphRunList 的原点进行平移
- SkMatrix positionMatrix{viewMatrix};
- positionMatrix.preTranslate(glyphRunList.origin().x(), glyphRunList.origin().y());
- // 生成用于缓存的键
- auto [canCache, key] = TextBlob::Key::Make(
- glyphRunList, paint, positionMatrix, strikeDeviceInfo);
-
- sk_sp<TextBlob> blob;
- // 如果可以缓存,尝试从缓存中查找已存在的 blob
- if (canCache) {
- blob = this->find(key);
- }
- // 如果未找到 blob 或现有的 blob 不能重用,则需要创建新的 blob
- if (blob == nullptr || !blob->canReuse(paint, positionMatrix)) {
- // 如果找到的 blob 不是 nullptr,则移除它,因为可能会有变化导致掩码无效
- if (blob != nullptr) {
- this->remove(blob.get());
- }
- // 创建新的 TextBlob 对象
- blob = TextBlob::Make(
- glyphRunList, paint, positionMatrix,
- strikeDeviceInfo, SkStrikeCache::GlobalStrikeCache());
- // 如果可以缓存,将新的 blob 加入缓存
- if (canCache) {
- blob->addKey(key);
- // blob 可能已经在不同的线程上被创建,使用第一个存在的
- blob = this->addOrReturnExisting(glyphRunList, blob);
- }
- }
- // 返回找到或创建的 blob
- return blob;
- }
复制代码
- 尝试从缓存中查找已存在的 blob,如果已有缓存,那后面就不消重新去生成,会淘汰很多性能开销
- 重新生成blob,如果可以缓存,将新的 blob 加入缓存
- sk_sp<TextBlob> TextBlob::Make(const GlyphRunList& glyphRunList,
- const SkPaint& paint,
- const SkMatrix& positionMatrix,
- SkStrikeDeviceInfo strikeDeviceInfo,
- StrikeForGPUCacheInterface* strikeCache) {
- // 估算所需的内存大小,用于存储子运行的容器
- size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
-
- // 分配内存,包括文本 blob 和所需的 arena
- auto [initializer, totalMemoryAllocated, alloc] =
- SubRunAllocator::AllocateClassMemoryAndArena<TextBlob>(subRunSizeHint);
- // 在分配的内存中创建子运行容器,并将其与传入的参数关联
- auto container = SubRunContainer::MakeInAlloc(
- glyphRunList, positionMatrix, paint,
- strikeDeviceInfo, strikeCache, &alloc, SubRunContainer::kAddSubRuns, "TextBlob");
- // 计算初始的亮度颜色
- SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint);
- // 使用初始化器创建并返回一个新的 TextBlob 对象
- sk_sp<TextBlob> blob = sk_sp<TextBlob>(initializer.initialize(std::move(alloc),
- std::move(container),
- totalMemoryAllocated,
- initialLuminance));
- return blob;
- }
复制代码 SubRunContainer::MakeInAlloc
调用静态方法SubRunContainer::MakeInAlloc,创建一个subRunContainer。这个函数会根据字体大小,放缩大小,位置,颜色等生成一个SkDescriptor。然后根据这个SkDescriptor从缓存中寻找SkStrike。
后续渲染过程加载位图都缓存在这个SkStrike中,如果SkStrike失效,就会重新去加载位图,产生很多性能消耗。
因此如果一个view内部存在较多文字,而且还有一些阴影等效果,发起在这个view的动效期间设置hardware。不然很容易因为动效,导致SkStrike缓存失效,反复渲染文字。
- SubRunContainer::MakeInAlloc{
- for (auto& glyphRun : glyphRunList){
- // Draw using the creationMatrix.
- //传入的参数都会影响SkDescriptor的生成,很容易造成SkStrike缓存失效
- SkStrikeSpec strikeSpec = SkStrikeSpec::MakeTransformMask(
- runFont, runPaint, deviceProps, scalerContextFlags, creationMatrix);
- sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
- auto [accepted, rejected, creationBounds] =
- prepare_for_mask_drawing(
- strike.get(), creationMatrix, source, acceptedBuffer, rejectedBuffer);
- }
- }
复制代码 创建SkStrikeSpec
- SkStrikeSpec SkStrikeSpec::MakeTransformMask(const SkFont& font,
- const SkPaint& paint,
- const SkSurfaceProps& surfaceProps,
- SkScalerContextFlags scalerContextFlags,
- const SkMatrix& deviceMatrix) {
- SkFont sourceFont{font};
- sourceFont.setSubpixel(false);
- return SkStrikeSpec(sourceFont, paint, surfaceProps, scalerContextFlags, deviceMatrix);
- }
- // 构造函数:使用指定的 SkFont、SkPaint、SkSurfaceProps、缩放上下文标志和设备矩阵
- // 来初始化 SkStrikeSpec 对象。
- SkStrikeSpec::SkStrikeSpec(const SkFont& font, const SkPaint& paint,
- const SkSurfaceProps& surfaceProps,
- SkScalerContextFlags scalerContextFlags,
- const SkMatrix& deviceMatrix) {
- // 创建一个 SkScalerContextEffects 对象,用于存储渲染字体时的效果,
- // 包括掩码滤镜 (MaskFilter) 和路径效果 (PathEffect)。
- SkScalerContextEffects effects;
- // 调用 SkScalerContext::CreateDescriptorAndEffectsUsingPaint 函数:
- // 1. 根据字体 (font)、绘制属性 (paint)、表面属性 (surfaceProps) 和设备矩阵 (deviceMatrix)
- // 来生成字体渲染的描述符 (Descriptor)。
- // 2. 同时计算并返回相关的渲染效果 (如 MaskFilter 和 PathEffect),这些效果将影响字体的呈现。
- SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
- font, // 传入字体对象,包含字体大小、样式等信息
- paint, // 传入绘制属性,指定如何绘制文本(如颜色、抗锯齿等)
- surfaceProps, // 传入表面属性,描述设备如何处理文本渲染(如是否启用 LCD 文本渲染等)
- scalerContextFlags, // 缩放上下文标志,控制字体渲染中的一些特性(如抗锯齿)
- deviceMatrix, // 设备矩阵,用于将字体从本地坐标系转换到设备坐标系
- &fAutoDescriptor, // 输出的自动描述符,标识字体的具体缩放及渲染配置
- &effects // 输出的效果对象,包含遮罩滤镜和路径效果
- );
- // 从 effects 中提取 fMaskFilter(掩码滤镜),并增加其引用计数。
- // 掩码滤镜可以影响字体的形状遮罩,例如应用模糊效果。
- fMaskFilter = sk_ref_sp(effects.fMaskFilter);
- // 从 effects 中提取 fPathEffect(路径效果),并增加其引用计数。
- // 路径效果可以对字体的边缘或路径进行修改,例如添加虚线效果。
- fPathEffect = sk_ref_sp(effects.fPathEffect);
- // 从 font 中引用该字体的类型 (SkTypeface),并增加其引用计数。
- // SkTypeface 是实际字体的引用,用于具体的文本渲染。
- fTypeface = font.refTypeface();
- }
复制代码 SkScalerContext::CreateDescriptorAndEffectsUsingPaint会创建一个形貌符输出到fAutoDescriptor
- SkDescriptor* SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
- const SkFont& font, const SkPaint& paint, const SkSurfaceProps& surfaceProps,
- SkScalerContextFlags scalerContextFlags, const SkMatrix& deviceMatrix, SkAutoDescriptor* ad,
- SkScalerContextEffects* effects)
- {
- SkScalerContextRec rec;
- // 这个函数是一个静态函数,它将 SkFont、SkPaint、SkSurfaceProps、SkScalerContextFlags、设备矩阵
- // 组合起来,生成 SkScalerContextRec(描述字体渲染配置的结构体)和 SkScalerContextEffects(描述
- // 影响渲染效果的滤镜或路径效果)。
- MakeRecAndEffects(font, paint, surfaceProps, scalerContextFlags, deviceMatrix, &rec, effects);
- return AutoDescriptorGivenRecAndEffects(rec, *effects, ad);
- }
- // SkDescriptor 是一个包含字体渲染所需参数的结构体,用于描述字体渲染
- // 的具体配置(如字体大小、样式、滤镜效果等)。该函数使用 SkAutoDescriptor 来临时管理 SkDescriptor
- // 的分配和生命周期。
- // rec 是描述字体渲染配置的结构体。
- // effects 包含渲染效果(如掩码滤镜和路径效果)。
- // ad 是用于管理 SkDescriptor 的自动对象,它负责描述符的分配和清理。
- SkDescriptor* SkScalerContext::AutoDescriptorGivenRecAndEffects(
- const SkScalerContextRec& rec, // 输入:字体渲染的配置
- const SkScalerContextEffects& effects, // 输入:字体渲染的效果
- SkAutoDescriptor* ad) { // 输出:生成的 SkDescriptor
- // SkBinaryWriteBuffer 是一个二进制写缓冲区,用于序列化 rec 和 effects。它可以记录对象的大小,
- // 并提供一个接口来将对象以二进制形式写入缓冲区。
- SkBinaryWriteBuffer buf({});
- // 调用 calculate_size_and_flatten 函数,计算 rec 和 effects 序列化后的大小,并将其写入缓冲区。
- // ad->reset() 会根据这个大小为描述符分配所需的内存空间。
- ad->reset(calculate_size_and_flatten(rec, effects, &buf));
- // 调用 generate_descriptor 函数,用来根据 rec 和序列化后的缓冲区 buf 生成 SkDescriptor 对象。
- // 这个函数填充 SkDescriptor,使其包含正确的字体渲染配置和效果。
- generate_descriptor(rec, buf, ad->getDesc());
- // 返回生成的 SkDescriptor 对象。
- return ad->getDesc();
- }
复制代码 创建或者复用SkStrike
- sk_sp<StrikeForGPU> SkStrikeCache::findOrCreateScopedStrike(const SkStrikeSpec& strikeSpec) {
- return this->findOrCreateStrike(strikeSpec);
- }
- auto SkStrikeCache::findOrCreateStrike(const SkStrikeSpec& strikeSpec) -> sk_sp<SkStrike> {
- SkAutoMutexExclusive ac(fLock);
- //根据传入的描述符进行查找
- sk_sp<SkStrike> strike = this->internalFindStrikeOrNull(strikeSpec.descriptor());
- if (strike == nullptr) {
- //缓存失效
- strike = this->internalCreateStrike(strikeSpec);
- }
- this->internalPurge();
- return strike;
- }
- // SkStrikeCache::internalCreateStrike 函数用于创建一个 SkStrike 实例。
- // SkStrike 是 Skia 中字体渲染的核心类之一,用于缓存和管理与字体渲染相关的资源(如 glyph 缓存等)。
- // strikeSpec 是 SkStrikeSpec 类型的对象,包含创建 SkStrike 的所有必要参数。
- // maybeMetrics 是可选的 SkFontMetrics,用于保存字体度量信息。
- // pinner 是一个用于管理 SkStrike 生命周期的指针,确保 SkStrike 对象不会被过早销毁。
- auto SkStrikeCache::internalCreateStrike(
- const SkStrikeSpec& strikeSpec, // 输入:字体规格描述符
- SkFontMetrics* maybeMetrics, // 输入:可选的字体度量信息
- std::unique_ptr<SkStrikePinner> pinner) // 输入:用于管理生命周期的 pinner
- -> sk_sp<SkStrike> { // 返回:一个包含 SkStrike 对象的智能指针
- // 使用 strikeSpec 创建一个 SkScalerContext 实例。SkScalerContext 是负责计算
- // 字体缩放、渲染位图以及其他字体相关操作的上下文对象。
- std::unique_ptr<SkScalerContext> scaler = strikeSpec.createScalerContext();
- // 使用 sk_make_sp 创建一个共享指针 sk_sp<SkStrike>,并初始化一个 SkStrike 对象。
- // SkStrike 的构造函数需要传入 SkStrikeCache、SkStrikeSpec、SkScalerContext、字体度量
- // 和 pinner 等参数。
- auto strike =
- sk_make_sp<SkStrike>(this, strikeSpec, std::move(scaler), maybeMetrics, std::move(pinner));
- // 调用 internalAttachToHead 函数,将新创建的 SkStrike 添加到缓存的头部。
- // 这保证了最新使用的字体资源会被快速访问。
- this->internalAttachToHead(strike);
- // 返回包含新创建的 SkStrike 对象的智能指针。
- return strike;
- }
复制代码 根据SkStrike查找glyph缓存
SkStrike::digestFor。这个函数会先从SkStrike的fDigestForPackedGlyphID哈希表中查找glyphId对应的SkGlyphDigest。
- 如果可以找到,就会复用对应的字形数据。
- 如果没有找到,就会创建1个新的glyph。注意,此时只是为glyph分配内存,并没有将位图加载进内存。
- SkGlyphDigest SkStrike::digestFor(ActionType actionType, SkPackedGlyphID packedGlyphID) {
- // 尝试从哈希表中查找与压缩字形 ID 相关的字形摘要
- SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(packedGlyphID);
-
- // 如果找到摘要并且该操作类型的动作已设置,则返回摘要
- if (digestPtr != nullptr && digestPtr->actionFor(actionType) != GlyphAction::kUnset) {
- return *digestPtr;
- }
- SkGlyph* glyph;
- // 如果找到了摘要但没有对应的动作
- if (digestPtr != nullptr) {
- // 获取该摘要对应的字形
- glyph = fGlyphForIndex[digestPtr->index()];
- } else {
- // 如果没有找到摘要,则创建新的字形并增加内存使用
- // 此时只是为glyph分配内存,并没有将位图加载进内存
- glyph = fAlloc.make<SkGlyph>(fScalerContext->makeGlyph(packedGlyphID, &fAlloc));
- fMemoryIncrease += sizeof(SkGlyph);
-
- // 添加字形和摘要,并更新指针
- digestPtr = this->addGlyphAndDigest(glyph);
- }
- // 为给定的操作类型设置相应的字形和摘要
- digestPtr->setActionFor(actionType, glyph, this);
- // 返回最终的字形摘要
- return *digestPtr;
- }
复制代码 blob->draw
- void TextBlob::draw(SkCanvas* canvas,
- SkPoint drawOrigin,
- const SkPaint& paint,
- const AtlasDrawDelegate& atlasDelegate) {
- //fSubRuns就是之前创建的SubRunContainer
- fSubRuns->draw(canvas, drawOrigin, paint, this, atlasDelegate);
- }
- void SubRunContainer::draw(SkCanvas* canvas,
- SkPoint drawOrigin,
- const SkPaint& paint,
- const SkRefCnt* subRunStorage,
- const AtlasDrawDelegate& atlasDelegate) const {
- for (auto& subRun : fSubRuns) {
- //遍历fSubRuns
- subRun.draw(canvas, drawOrigin, paint, sk_ref_sp(subRunStorage), atlasDelegate);
- }
- }
- void draw(SkCanvas*,
- SkPoint drawOrigin,
- const SkPaint& paint,
- sk_sp<SkRefCnt> subRunStorage,
- const AtlasDrawDelegate& drawAtlas) const override {
- //drawAtlas就是SurfaceDrawContext中的匿名函数
- drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
- {/* isSDF = */false, fVertexFiller.isLCD()});
- }
复制代码 drawAtlas
SurfaceDrawContext的匿名函数
- // 定义一个用于处理文本绘制的委托(lambda 函数),这个函数将在文本图块的子运行中调用。
- // 它会生成文本的绘制操作(op),并将其添加到绘制队列中。
- auto atlasDelegate = [&](const sktext::gpu::AtlasSubRun* subRun,
- SkPoint drawOrigin, // 字符串绘制的原点
- const SkPaint& paint, // 绘制的样式
- sk_sp<SkRefCnt> subRunStorage, // 子运行存储的指针
- sktext::gpu::RendererData) { // 渲染器数据(上下文)
- // 根据子运行生成绘制操作(op),这个绘制操作负责在屏幕上绘制相应的文本。
- auto [drawingClip, op] = subRun->makeAtlasTextOp(
- clip, // 当前的剪裁区域
- viewMatrix, // 当前视图的变换矩阵
- drawOrigin, // 绘制的起点
- paint, // 绘制样式
- std::move(subRunStorage), // 子运行存储
- this); // 当前的 SurfaceDrawContext
-
- // 如果生成的绘制操作(op)不为空,则将其添加到当前的绘制操作列表中。
- if (op != nullptr) {
- this->addDrawOp(drawingClip, std::move(op));
- }
- };
复制代码 makeAtlasTextOp,创建AtlasTextOp
- /**
- * 创建一个文本绘制操作(Op)以在 GPU 上绘制文本。该操作使用字形图集来渲染文本。
- * @param clip 渲染的裁剪区域
- * @param viewMatrix 视图矩阵,用于转换到设备坐标
- * @param drawOrigin 文本绘制的起始位置
- * @param paint 用于绘制文本的画笔
- * @param subRunStorage 文本子运行存储,包含字形数据
- * @param sdc 指向 SurfaceDrawContext 的智能指针
- * @return 返回一个包含裁剪区域和操作的元组
- */
- std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
- const GrClip* clip, // 输入:裁剪区域
- const SkMatrix& viewMatrix, // 输入:视图矩阵
- SkPoint drawOrigin, // 输入:绘制起点
- const SkPaint& paint, // 输入:绘制所用的画笔
- sk_sp<SkRefCnt>&& subRunStorage, // 输入:字形数据的存储
- skgpu::ganesh::SurfaceDrawContext* sdc) const override {
-
- // 确保字形计数不为零
- SkASSERT(this->glyphCount() != 0);
- // 根据视图矩阵和绘制起点计算位置矩阵
- const SkMatrix& positionMatrix = position_matrix(viewMatrix, drawOrigin);
- // 获取整数平移标志和子运行设备边界
- auto [integerTranslate, subRunDeviceBounds] =
- fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
-
- // 如果设备边界为空,返回空的操作
- if (subRunDeviceBounds.isEmpty()) {
- return {nullptr, nullptr};
- }
- // 用于几何裁剪的矩形,初始为空
- SkIRect geometricClipRect = SkIRect::MakeEmpty();
-
- if (integerTranslate) {
- // 可以进行几何裁剪,忽略非抗锯齿裁剪
- const SkRect deviceBounds = SkRect::MakeWH(sdc->width(), sdc->height());
- auto [clipMethod, clipRect] = calculate_clip(clip, deviceBounds, subRunDeviceBounds);
- switch (clipMethod) {
- case kClippedOut:
- // 如果被裁剪出去,返回空操作
- return {nullptr, nullptr};
- case kUnclipped:
- case kGeometryClipped:
- // 不需要 GPU 裁剪
- clip = nullptr;
- break;
- case kGPUClipped:
- // 使用 GPU 裁剪,clipRect 被忽略
- break;
- }
-
- // 设置几何裁剪矩形
- geometricClipRect = clipRect;
- // 如果几何裁剪矩形不为空,确保裁剪区域为 nullptr
- if (!geometricClipRect.isEmpty()) { SkASSERT(clip == nullptr); }
- }
- // 准备绘制颜色
- GrPaint grPaint;
- const SkPMColor4f drawingColor = calculate_colors(sdc,
- paint,
- viewMatrix,
- fVertexFiller.grMaskType(),
- &grPaint);
- // 创建几何体信息
- auto geometry = AtlasTextOp::Geometry::Make(*this,
- viewMatrix,
- drawOrigin,
- geometricClipRect,
- std::move(subRunStorage),
- drawingColor,
- sdc->arenaAlloc());
- // 获取绘制上下文
- GrRecordingContext* const rContext = sdc->recordingContext();
- // 创建新的操作,使用 AtlasTextOp
- GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
- fVertexFiller.opMaskType(),
- !integerTranslate,
- this->glyphCount(),
- subRunDeviceBounds,
- geometry,
- sdc->colorInfo(),
- std::move(grPaint));
- // 返回包含裁剪区域和新操作的元组
- return {clip, std::move(op)};
- }
复制代码 看似每一个字形都会去创建AtlasTextOp。但AtlasTextOp重载了new运算符,在new时会复用。
- // If we have thread local, then cache memory for a single AtlasTextOp.
- static thread_local void* gCache = nullptr;
- void* AtlasTextOp::operator new(size_t s) {
- if (gCache != nullptr) {
- //gCache 不为空,返回gCache,并且gCache 设置为空
- return std::exchange(gCache, nullptr);
- }
- return ::operator new(s);
- }
- void AtlasTextOp::operator delete(void* bytes) noexcept {
- if (gCache == nullptr) {
- //保存到gCache
- gCache = bytes;
- return;
- }
- ::operator delete(bytes);
- }
复制代码 addDrawOp
- 首先界说了一个匿名函数:addDependency 。这个地方涉及纹理上传(textureUpload),当dstProxyView.proxy存在,则会将一个textureProxy push到GrRenderTask的fDeferredProxies队列:fDeferredProxies.push_back(textureProxy);固然此处不涉及。
- recordOp
- void OpsTask::addDrawOp(GrDrawingManager* drawingMgr, GrOp::Owner op, bool usesMSAA,
- const GrProcessorSet::Analysis& processorAnalysis, GrAppliedClip&& clip,
- const GrDstProxyView& dstProxyView,
- GrTextureResolveManager textureResolveManager, const GrCaps& caps) {
- // 定义一个 lambda 函数,用于处理依赖关系的添加
- auto addDependency = [&](GrSurfaceProxy* p, skgpu::Mipmapped mipmapped) {
- // 添加被采样的纹理
- this->addSampledTexture(p);
- // 添加依赖项到绘制管理器
- this->addDependency(drawingMgr, p, mipmapped, textureResolveManager, caps);
- };
- // 访问操作中的所有代理并添加依赖
- op->visitProxies(addDependency);
- // 访问剪辑中的所有代理并添加依赖
- clip.visitProxies(addDependency);
- // 如果目标代理视图存在
- if (dstProxyView.proxy()) {
- // 如果目标样本标志不包含将目标视图作为输入附件
- if (!(dstProxyView.dstSampleFlags() & GrDstSampleFlags::kAsInputAttachment)) {
- // 添加被采样的目标纹理
- this->addSampledTexture(dstProxyView.proxy());
- }
- // 如果需要纹理屏障
- if (dstProxyView.dstSampleFlags() & GrDstSampleFlags::kRequiresTextureBarrier) {
- fRenderPassXferBarriers |= GrXferBarrierFlags::kTexture; // 设置纹理屏障标志
- }
- // 添加目标代理的依赖项
- addDependency(dstProxyView.proxy(), skgpu::Mipmapped::kNo);
- // 确保如果将目标视图作为输入附件,偏移量必须为零
- SkASSERT(!(dstProxyView.dstSampleFlags() & GrDstSampleFlags::kAsInputAttachment) ||
- dstProxyView.offset().isZero());
- }
- // 检查处理器分析是否使用了非一致的硬件混合
- if (processorAnalysis.usesNonCoherentHWBlending()) {
- fRenderPassXferBarriers |= GrXferBarrierFlags::kBlend; // 设置混合屏障标志
- }
- // 记录操作,传入相关信息
- this->recordOp(std::move(op), usesMSAA, processorAnalysis, clip.doesClip() ? &clip : nullptr,
- &dstProxyView, caps);
- }
复制代码 ####### recordOp
- 会先从后往前遍历fOpChains,看能否追加
- 追加失败调用:fOpChains.emplace_back
- void OpsTask::recordOp(
- GrOp::Owner op, bool usesMSAA, GrProcessorSet::Analysis processorAnalysis,
- GrAppliedClip* clip, const GrDstProxyView* dstProxyView, const GrCaps& caps) {
- // 获取当前目标代理,通常是绘制的目标纹理
- GrSurfaceProxy* proxy = this->target(0);
- #ifdef SK_DEBUG
- // 在调试模式下,执行一些断言以确保状态的正确性
- op->validate(); // 验证操作的有效性
- SkASSERT(processorAnalysis.requiresDstTexture() == (dstProxyView && dstProxyView->proxy())); // 检查是否需要目标纹理
- SkASSERT(proxy); // 确保目标代理存在
- SkASSERT(!this->isClosed()); // 确保 OpsTask 未关闭
- // 如果调用者请求使用动态多重采样(MSAA),确保目标支持
- if (proxy->asRenderTargetProxy()->numSamples() == 1 && usesMSAA) {
- SkASSERT(caps.supportsDynamicMSAA(proxy->asRenderTargetProxy())); // 验证支持动态 MSAA
- }
- #endif
- // 如果操作的边界不是有限的,则返回,避免记录无效的操作
- if (!op->bounds().isFinite()) {
- return;
- }
- // 更新标志,指示是否使用了多重采样表面
- fUsesMSAASurface |= usesMSAA;
- // 在尝试合并之前,记录这个操作的边界
- // 注意:调用者应该已经调用了 "op->setClippedBounds()" 方法(如果适用)
- fTotalBounds.join(op->bounds(); // 更新总边界以包含当前操作的边界
- // 检查是否有可以合并的操作,向后线性搜索直到:
- // 1) 检查所有操作
- // 2) 与某个操作相交
- // 3) 找到一个"阻塞者"
- GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get(), proxy->uniqueID()); // 记录操作到审计轨迹
- GrOP_INFO("opsTask: %d Recording (%s, opID: %u)\n"
- "\tBounds [L: %.2f, T: %.2f R: %.2f B: %.2f]\n",
- this->uniqueID(),
- op->name(),
- op->uniqueID(),
- op->bounds().fLeft, op->bounds().fTop,
- op->bounds().fRight, op->bounds().fBottom); // 打印操作信息
- GrOP_INFO(SkTabString(op->dumpInfo(), 1).c_str());
- GrOP_INFO("\tOutcome:\n");
- // 设置最大候选者数量,控制向后查找的最大深度
- int maxCandidates = std::min(kMaxOpChainDistance, fOpChains.size());
- if (maxCandidates) {
- int i = 0;
- while (true) {
- // 从后往前查找候选者
- OpChain& candidate = fOpChains.fromBack(i);
- // 尝试将当前操作追加到候选者链中
- op = candidate.appendOp(std::move(op), processorAnalysis, dstProxyView, clip, caps,
- fArenas->arenaAlloc(), fAuditTrail);
- if (!op) {
- return; // 如果无法追加,则返回
- }
- // 如果将导致绘图顺序冲突,则停止向后查找
- if (!can_reorder(candidate.bounds(), op->bounds())) {
- GrOP_INFO("\t\tBackward: Intersects with chain (%s, head opID: %u)\n",
- candidate.head()->name(), candidate.head()->uniqueID());
- break; // 找到阻塞者,停止查找
- }
- if (++i == maxCandidates) {
- GrOP_INFO("\t\tBackward: Reached max lookback or beginning of op array %d\n", i);
- break; // 达到最大回溯次数,停止查找
- }
- }
- } else {
- GrOP_INFO("\t\tBackward: FirstOp\n");
- }
- // 处理剪辑信息,如果存在剪辑则创建其副本
- if (clip) {
- clip = fArenas->arenaAlloc()->make<GrAppliedClip>(std::move(*clip));
- SkDEBUGCODE(fNumClips++;) // 记录剪辑的数量
- }
- // 将操作添加到操作链中
- fOpChains.emplace_back(std::move(op), processorAnalysis, clip, dstProxyView);
- }
复制代码 追加失败返回空指针
- GrOp::Owner OpsTask::OpChain::appendOp(
- GrOp::Owner op, // 要追加的绘制操作,负责图形渲染的操作对象。
- GrProcessorSet::Analysis processorAnalysis, // 处理器的分析信息,提供操作所需的处理器状态。
- const GrDstProxyView* dstProxyView, // 目标视图,指定绘制操作的目标表面视图。
- const GrAppliedClip* appliedClip, // 应用的剪辑对象,用于限制绘制区域。
- const GrCaps& caps, // 图形硬件的功能描述,提供可用的硬件特性。
- SkArenaAlloc* opsTaskArena, // 用于内存分配的 Arena,优化内存管理。
- GrAuditTrail* auditTrail) { // 审计轨迹记录,跟踪绘制操作的历史。
-
- // 如果没有提供目标代理视图,则使用一个默认的空目标代理视图
- const GrDstProxyView noDstProxyView;
- if (!dstProxyView) {
- dstProxyView = &noDstProxyView; // 设置为默认的空视图
- }
- // 确保操作是链的头和尾
- SkASSERT(op->isChainHead() && op->isChainTail());
- // 获取操作的边界
- SkRect opBounds = op->bounds();
- // 创建一个新的链,包含当前操作
- List chain(std::move(op));
- // 尝试将当前操作与链中的操作连接
- if (!this->tryConcat(&chain, processorAnalysis, *dstProxyView, appliedClip, opBounds, caps,
- opsTaskArena, auditTrail)) {
- // 连接失败,将操作返回给调用者
- this->validate(); // 验证链的状态
- return chain.popHead(); // 返回链的头部操作
- }
- // 连接成功,确保链为空
- SkASSERT(chain.empty());
- this->validate(); // 再次验证链的状态
- return nullptr; // 连接成功时返回空指针
- }
复制代码 调用对应Op的onCombineIfPossible,看能否合并
- // 尝试将给定的链与当前链连接,并合并两个链中的操作。返回操作是否成功。
- // 成功时,提供的链将被清空。
- bool OpsTask::OpChain::tryConcat(
- List* list, // 要连接的链,包含待合并的操作。
- GrProcessorSet::Analysis processorAnalysis, // 处理器分析信息,描述当前操作的处理器需求。
- const GrDstProxyView& dstProxyView, // 目标代理视图,指定操作的目标表面。
- const GrAppliedClip* appliedClip, // 应用的剪辑对象,用于限制绘制区域。
- const SkRect& bounds, // 当前操作的边界矩形,用于合并判断。
- const GrCaps& caps, // 图形硬件的功能描述。
- SkArenaAlloc* opsTaskArena, // 内存分配的 Arena,用于优化内存管理。
- GrAuditTrail* auditTrail) { // 审计轨迹记录,跟踪操作历史。
- SkASSERT(!fList.empty()); // 确保当前链不为空。
- SkASSERT(!list->empty()); // 确保要连接的链不为空。
- SkASSERT(fProcessorAnalysis.requiresDstTexture() == SkToBool(fDstProxyView.proxy())); // 检查处理器分析与目标视图的一致性。
- SkASSERT(processorAnalysis.requiresDstTexture() == SkToBool(dstProxyView.proxy())); // 检查传入分析与目标视图的一致性。
- // 如果当前链和要连接的链不满足合并条件,则返回 false。
- if (fList.head()->classID() != list->head()->classID() ||
- SkToBool(fAppliedClip) != SkToBool(appliedClip) ||
- (fAppliedClip && *fAppliedClip != *appliedClip) ||
- (fProcessorAnalysis.requiresNonOverlappingDraws() != processorAnalysis.requiresNonOverlappingDraws()) ||
- (fProcessorAnalysis.requiresNonOverlappingDraws() &&
- GrRectsTouchOrOverlap(fBounds, bounds)) || // 检查矩形是否重叠。
- (fProcessorAnalysis.requiresDstTexture() != processorAnalysis.requiresDstTexture()) ||
- (fProcessorAnalysis.requiresDstTexture() && fDstProxyView != dstProxyView)) {
- return false; // 不满足条件,连接失败。
- }
- SkDEBUGCODE(bool first = true;) // 用于调试的标志,标记首次迭代。
- do {
- // 尝试合并当前链的尾部操作与待连接链的头部操作。
- //combineIfPossible会调用对应Op的onCombineIfPossible
- switch (fList.tail()->combineIfPossible(list->head(), opsTaskArena, caps))
- {
- case GrOp::CombineResult::kCannotCombine:
- // 如果操作支持链式合并,则需要保证合并是传递的。
- SkASSERT(first); // 仅在首次迭代时可能出现此情况。
- return false; // 合并失败。
-
- case GrOp::CombineResult::kMayChain:
- // 可能合并,进行连接。
- fList = DoConcat(std::move(fList), std::exchange(*list, List()), caps, opsTaskArena,
- auditTrail);
- // 上面的交换清空了 'list',此时 'list' 应该为空。
- SkASSERT(list->empty());
- break;
- case GrOp::CombineResult::kMerged: {
- // 成功合并两个操作。
- GrOP_INFO("\t\t: (%s opID: %u) -> Combining with (%s, opID: %u)\n",
- list->tail()->name(), list->tail()->uniqueID(), list->head()->name(),
- list->head()->uniqueID());
- GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(auditTrail, fList.tail(), list->head());
- // GrOp::Owner 会释放操作。
- list->popHead(); // 从待连接链中移除头部操作。
- break;
- }
- }
- SkDEBUGCODE(first = false); // 标记已完成首次迭代。
- } while (!list->empty()); // 继续直到待连接链为空。
- // 新的操作成功合并并连接到当前链。
- fBounds.joinPossiblyEmptyRect(bounds); // 更新边界矩形。
- return true; // 返回成功。
- }
复制代码 查抄两个AtlasTextOp能否合并
- // 尝试将当前操作与给定操作合并。如果合并成功,返回合并结果;
- // 否则返回无法合并的结果。
- GrOp::CombineResult AtlasTextOp::onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) {
- // 将传入的操作转换为 AtlasTextOp 类型。
- auto that = t->cast<AtlasTextOp>();
- // 检查所有必要的标志是否相等。所有标志必须匹配才能合并操作。
- if (fDFGPFlags != that->fDFGPFlags ||
- fMaskType != that->fMaskType ||
- fUsesLocalCoords != that->fUsesLocalCoords ||
- fNeedsGlyphTransform != that->fNeedsGlyphTransform ||
- fHasPerspective != that->fHasPerspective ||
- fUseGammaCorrectDistanceTable != that->fUseGammaCorrectDistanceTable) {
- return CombineResult::kCannotCombine; // 返回无法合并的结果。
- }
- // 检查处理器是否相同。如果不同,则无法合并。
- if (fProcessors != that->fProcessors) {
- return CombineResult::kCannotCombine; // 返回无法合并的结果。
- }
- // 如果使用局部坐标,确保几何体的矩阵相同。
- if (fUsesLocalCoords) {
- // 使用视图矩阵的逆来计算局部坐标,因此所有几何体必须有相同的矩阵。
- const SkMatrix& thisFirstMatrix = fHead->fDrawMatrix; // 当前操作的绘制矩阵。
- const SkMatrix& thatFirstMatrix = that->fHead->fDrawMatrix; // 传入操作的绘制矩阵。
- if (!SkMatrixPriv::CheapEqual(thisFirstMatrix, thatFirstMatrix)) {
- return CombineResult::kCannotCombine; // 返回无法合并的结果。
- }
- }
- #if !defined(SK_DISABLE_SDF_TEXT
- // 如果当前操作使用距离场,确保传入操作也使用距离场。
- if (this->usesDistanceFields()) {
- SkASSERT(that->usesDistanceFields()); // 确保它们都使用距离场。
- if (fLuminanceColor != that->fLuminanceColor) {
- return CombineResult::kCannotCombine; // 如果亮度颜色不同,返回无法合并的结果。
- }
- } else
- #endif
- {
- // 确保所有合并的位图颜色文本操作具有相同的颜色。
- if (this->maskType() == MaskType::kColorBitmap &&
- fHead->fColor != that->fHead->fColor) {
- return CombineResult::kCannotCombine; // 返回无法合并的结果。
- }
- }
- // 增加当前操作的字形总数。
- fNumGlyphs += that->fNumGlyphs;
- // 在合并后,清空 'that' 的几何体列表,以避免在析构时取消引用其数据。
- this->addGeometry(that->fHead); // 将 'that' 的几何体添加到当前操作。
- that->fHead = nullptr; // 确保 'that' 不再引用其几何体。
- return CombineResult::kMerged; // 返回合并成功的结果。
- }
复制代码 renderTask->prepare
将上层的各个操纵转化为Op,接下来就是遍历各个Op,转化为渲问鼎令。
首先调用GrRenderTask::prepare
- void GrRenderTask::prepare(GrOpFlushState* flushState) {
- for (int i = 0; i < fDeferredProxies.size(); ++i) {
- fDeferredProxies[i]->texPriv().scheduleUpload(flushState);
- }
- this->onPrepare(flushState);
- }
复制代码 scheduleUpload
scheduleUpload界说了一个匿名函数,主要将GrDeferredProxyUploader保存的像素地点传到函数:writePixelsFn。
- // 调度纹理上传到指定的纹理代理。确保每个上传只调度一次。
- void scheduleUpload(GrOpFlushState* flushState, GrTextureProxy* proxy) {
- // 检查是否已调度上传。如果已经调度,直接返回,避免重复操作。
- if (fScheduledUpload) {
- // 多个对拥有代理的引用可能已经导致我们执行过上传。
- return;
- }
- // 创建上传掩码,定义上传操作的具体实现。
- auto uploadMask = [this, proxy](GrDeferredTextureUploadWritePixelsFn& writePixelsFn) {
- this->wait(); // 等待任何需要的操作完成,确保在上传之前一切就绪。
-
- // 将 SkColorType 转换为 GrColorType,获取像素的颜色类型。
- GrColorType pixelColorType = SkColorTypeToGrColorType(this->fPixels.info().colorType());
-
- // 检查像素地址是否有效。如果无法分配像素,地址可能为 null,避免崩溃。
- if (this->fPixels.addr()) {
- // 使用给定的写像素函数上传像素数据到代理。
- writePixelsFn(proxy, // 目标纹理代理
- SkIRect::MakeSize(fPixels.dimensions()), // 上传区域的大小
- pixelColorType, // 像素的颜色类型
- this->fPixels.addr(), // 像素数据的地址
- this->fPixels.rowBytes()); // 每行的字节数
- }
- // 上传完成,通知代理释放这个 GrDeferredProxyUploader。
- proxy->texPriv().resetDeferredUploader();
- };
- // 将上传掩码添加到 flushState,以便尽快执行。
- flushState->addASAPUpload(std::move(uploadMask));
- fScheduledUpload = true; // 标记上传已调度,避免后续重复调度。
- }
复制代码 OpsTask: nPrepare
- // 准备操作任务中的所有操作以供执行。此函数在绘制阶段调用。
- void OpsTask::onPrepare(GrOpFlushState* flushState) {
- // 确保目标渲染目标有效。
- SkASSERT(this->target(0)->peekRenderTarget());
- // 确保任务已经关闭,不能再添加新操作。
- SkASSERT(this->isClosed());
- // TODO: 一旦启用减少操作分割,移除此检查。当前情况下,如果只有丢弃加载操作而没有其他操作,可能会导致问题。
- // 对于 Vulkan 验证,必须保留该丢弃操作。
- if (this->isColorNoOp() || (fClippedContentBounds.isEmpty() && fColorLoadOp != GrLoadOp::kDiscard)) {
- return; // 如果没有有效的颜色操作或无有效边界且不是丢弃操作,直接返回。
- }
-
- // 记录函数调用以供调试。
- TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC);
- // 设置要采样的代理数组,以便在操作执行期间使用。
- flushState->setSampledProxyArray(&fSampledProxies);
-
- // 创建目标视图,包含目标代理、目标原点和颜色交换信息。
- GrSurfaceProxyView dstView(sk_ref_sp(this->target(0)), fTargetOrigin, fTargetSwizzle);
-
- // 遍历尚未准备好的操作链。
- for (const auto& chain : fOpChains) {
- if (chain.shouldExecute()) { // 检查该操作链是否应该执行。
- // 创建操作参数,包含操作头、目标视图、MSAA 状态、应用的剪辑、目标代理视图等信息。
- GrOpFlushState::OpArgs opArgs(chain.head(),
- dstView,
- fUsesMSAASurface,
- chain.appliedClip(),
- chain.dstProxyView(),
- fRenderPassXferBarriers,
- fColorLoadOp);
- flushState->setOpArgs(&opArgs); // 将操作参数设置到 flushState。
- // 调用操作的准备方法以进行必要的准备。
- chain.head()->prepare(flushState);
- flushState->setOpArgs(nullptr); // 清空操作参数。
- }
- }
-
- // 完成所有准备后,清空采样代理数组。
- flushState->setSampledProxyArray(nullptr);
- }
复制代码
- 遍历Geometry队列,界说regenerateDelegate 匿名函数
- 遍历Geometry队列中的subRun,调用subRun.regenerateAtlas
- // 准备绘制操作,设置顶点数据和几何处理器。此方法在绘制阶段被调用。
- void AtlasTextOp::onPrepareDraws(GrMeshDrawTarget* target) {
- auto resourceProvider = target->resourceProvider();
- // 如果需要使用局部坐标,计算视图矩阵的逆。如果是纯色,则处理器分析不会要求局部坐标。
- SkMatrix localMatrix = SkMatrix::I();
- if (fUsesLocalCoords && !fHead->fDrawMatrix.invert(&localMatrix)) {
- return; // 如果矩阵无法逆转,直接返回。
- }
- // 获取纹理管理器
- GrAtlasManager* atlasManager = target->atlasManager();
- // 获取掩码格式
- MaskFormat maskFormat = this->maskFormat();
- unsigned int numActiveViews;
- // 获取视图,返回当前激活的纹理视图数
- const GrSurfaceProxyView* views = atlasManager->getViews(maskFormat, &numActiveViews);
- if (!views) {
- SkDebugf("Could not allocate backing texture for atlas\n");
- return; // 如果无法分配背部纹理,返回。
- }
- SkASSERT(views[0].proxy()); // 确保获取的视图有效。
- // 定义最大纹理数
- static constexpr int kMaxTextures = GrBitmapTextGeoProc::kMaxTextures;
- #if !defined(SK_DISABLE_SDF_TEXT)
- static_assert(GrDistanceFieldA8TextGeoProc::kMaxTextures == kMaxTextures);
- static_assert(GrDistanceFieldLCDTextGeoProc::kMaxTextures == kMaxTextures);
- #endif
- // 分配处理器代理指针
- auto primProcProxies = target->allocPrimProcProxyPtrs(kMaxTextures);
- for (unsigned i = 0; i < numActiveViews; ++i) {
- primProcProxies[i] = views[i].proxy();
- // 在添加到 OpsTasks 时,操作不知道其纹理代理,因此在此处添加它们。
- target->sampledProxyArray()->push_back(views[i].proxy());
- }
- FlushInfo flushInfo; // 创建用于存储刷新信息的结构体
- flushInfo.fPrimProcProxies = primProcProxies; // 设置几何处理器代理
- flushInfo.fIndexBuffer = resourceProvider->refNonAAQuadIndexBuffer(); // 引用非抗锯齿四边形索引缓冲区
- #if !defined(SK_DISABLE_SDF_TEXT)
- if (this->usesDistanceFields()) {
- // 如果使用距离场文本,设置相应的几何处理器
- flushInfo.fGeometryProcessor = this->setupDfProcessor(target->allocator(),
- *target->caps().shaderCaps(),
- localMatrix, views, numActiveViews);
- } else
- #endif
- {
- // 对于位图文本,设置采样器过滤模式
- auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear
- : GrSamplerState::Filter::kNearest;
- // 位图文本使用单一颜色,确保所有几何图形具有相同颜色
- flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
- target->allocator(), *target->caps().shaderCaps(), fHead->fColor,
- /*wideColor=*/false, fColorSpaceXform, views, numActiveViews, filter,
- maskFormat, localMatrix, fHasPerspective);
- }
- const int vertexStride = (int)flushInfo.fGeometryProcessor->vertexStride(); // 获取顶点步幅
- // 确保不会请求过大的连续顶点分配
- static const int kMaxVertexBytes = GrBufferAllocPool::kDefaultBufferSize;
- const int quadSize = vertexStride * kVerticesPerGlyph; // 计算四边形的大小
- const int maxQuadsPerBuffer = kMaxVertexBytes / quadSize; // 每个缓冲区最大四边形数量
- int allGlyphsCursor = 0; // 所有字形的游标
- const int allGlyphsEnd = fNumGlyphs; // 所有字形的结束位置
- int quadCursor; // 当前四边形游标
- int quadEnd; // 当前四边形结束位置
- char* vertices; // 存储顶点数据的指针
- // 重置顶点缓冲区,准备新的顶点数据
- auto resetVertexBuffer = [&] {
- quadCursor = 0;
- quadEnd = std::min(maxQuadsPerBuffer, allGlyphsEnd - allGlyphsCursor);
- vertices = (char*)target->makeVertexSpace(
- vertexStride,
- kVerticesPerGlyph * quadEnd,
- &flushInfo.fVertexBuffer,
- &flushInfo.fVertexOffset);
- if (!vertices || !flushInfo.fVertexBuffer) {
- SkDebugf("Could not allocate vertices\n");
- return false; // 分配失败,返回错误
- }
- return true; // 分配成功
- };
- if (!resetVertexBuffer()) {
- return; // 如果重置顶点缓冲区失败,返回。
- }
- // 遍历几何体,填充顶点数据
- for (const Geometry* geo = fHead; geo != nullptr; geo = geo->fNext) {
- const sktext::gpu::AtlasSubRun& subRun = geo->fSubRun; // 获取当前几何体的子运行
- SkASSERTF((int) subRun.vertexStride(geo->fDrawMatrix) == vertexStride,
- "subRun stride: %d vertex buffer stride: %d\n",
- (int)subRun.vertexStride(geo->fDrawMatrix), vertexStride);
- const int subRunEnd = subRun.glyphCount(); // 获取子运行中的字形总数
- auto regenerateDelegate = [&](sktext::gpu::GlyphVector* glyphs,
- int begin,
- int end,
- skgpu::MaskFormat maskFormat,
- int padding) {
- return glyphs->regenerateAtlasForGanesh(begin, end, maskFormat, padding, target);
- };
- for (int subRunCursor = 0; subRunCursor < subRunEnd;) {
- // 再生字形图集,直到填满缓冲区或没有字形可处理
- int regenEnd = subRunCursor + std::min(subRunEnd - subRunCursor, quadEnd - quadCursor);
- auto[ok, glyphsRegenerated] = subRun.regenerateAtlas(subRunCursor, regenEnd,
- regenerateDelegate);
- if (!ok) {
- return; // 如果再生失败,返回
- }
- geo->fillVertexData(vertices + quadCursor * quadSize, subRunCursor, glyphsRegenerated); // 填充顶点数据
- subRunCursor += glyphsRegenerated; // 更新字形游标
- quadCursor += glyphsRegenerated; // 更新四边形游标
- allGlyphsCursor += glyphsRegenerated; // 更新所有字形游标
- flushInfo.fGlyphsToFlush += glyphsRegenerated; // 更新待刷新字形计数
- // 如果缓冲区已满或仍有字形未处理,进行刷新
- if (quadCursor == quadEnd || subRunCursor < subRunEnd) {
- if (subRunCursor < subRunEnd) {
- ATRACE_ANDROID_FRAMEWORK_ALWAYS("Atlas full"); // 如果字形图集已满,记录信息
- }
- this->createDrawForGeneratedGlyphs(target, &flushInfo); // 创建绘制操作
- if (quadCursor == quadEnd && allGlyphsCursor < allGlyphsEnd) {
- // 如果缓冲区满且还有字形需要绘制,则重置缓冲区
- if (!resetVertexBuffer()) {
- return; // 如果重置缓冲区失败,返回
- }
- }
- }
- }
- }
- }
复制代码 终极会调用:GlyphVector::regenerateAtlasForGanesh
GlyphVector::regenerateAtlasForGanesh
- GlyphVector::packedGlyphIDToGlyph,从之前的SkStrike缓存中获取Glyph对象。
- 根据Glyph获取SkGlyph,此处会调用FT_load_glyph,加载位图
- atlasManager->addGlyphToAtlas,将纹理上传工作加到延迟队列,后面会统一上传纹理
- // 重新生成字形图集,更新字形在图集中的位置和纹理坐标。
- std::tuple<bool, int> GlyphVector::regenerateAtlasForGanesh(
- int begin, int end, MaskFormat maskFormat, int srcPadding, GrMeshDrawTarget* target) {
- GrAtlasManager* atlasManager = target->atlasManager(); // 获取纹理管理器
- GrDeferredUploadTarget* uploadTarget = target->deferredUploadTarget(); // 获取延迟上传目标
- uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat); // 获取当前图集的生成版本
- // 将字形ID映射到GPU目标的字形缓存
- this->packedGlyphIDToGlyph(target->strikeCache());
- // 如果图集生成版本发生变化,则需要重新计算纹理坐标
- if (fAtlasGeneration != currentAtlasGen) {
- fBulkUseUpdater.reset(); // 重置批量使用更新器
- SkBulkGlyphMetricsAndImages metricsAndImages{fTextStrike->strikeSpec()}; // 获取字形度量和图像
- // 更新GrStrike中的图集信息
- auto tokenTracker = uploadTarget->tokenTracker(); // 获取令牌跟踪器
- auto glyphs = fGlyphs.subspan(begin, end - begin); // 获取当前处理的字形范围
- int glyphsPlacedInAtlas = 0; // 记录已放置在图集中的字形数量
- bool success = true; // 记录操作是否成功
- // 遍历所有要处理的字形变体
- for (const Variant& variant : glyphs) {
- Glyph* gpuGlyph = variant.glyph; // 获取GPU字形
- SkASSERT(gpuGlyph != nullptr); // 确保字形不为空
- // 如果字形不在图集中,则将其添加到图集
- if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) {
- const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID); // 获取SkGlyph
- // 尝试将字形添加到图集中
- auto code = atlasManager->addGlyphToAtlas(
- skGlyph, gpuGlyph, srcPadding, target->resourceProvider(), uploadTarget);
- // 检查添加操作的结果
- if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
- success = code != GrDrawOpAtlas::ErrorCode::kError; // 记录是否成功
- break; // 发生错误,跳出循环
- }
- }
- // 将字形添加到批量使用更新器并设置使用令牌
- atlasManager->addGlyphToBulkAndSetUseToken(
- &fBulkUseUpdater, maskFormat, gpuGlyph,
- tokenTracker->nextDrawToken());
- glyphsPlacedInAtlas++; // 更新已放置字形计数
- }
- // 如果所有字形都成功放置到图集中,更新图集生成版本
- if (success && begin + glyphsPlacedInAtlas == SkCount(fGlyphs)) {
- // 获取图集最新的生成版本
- fAtlasGeneration = atlasManager->atlasGeneration(maskFormat);
- }
- return {success, glyphsPlacedInAtlas}; // 返回操作结果和放置的字形数量
- } else {
- // 图集未变化,纹理坐标仍然有效
- if (end == SkCount(fGlyphs)) {
- // 如果所有纹理坐标仍然有效,更新所有使用的图形为新令牌
- atlasManager->setUseTokenBulk(fBulkUseUpdater,
- uploadTarget->tokenTracker()->nextDrawToken(),
- maskFormat);
- }
- return {true, end - begin}; // 返回成功和放置的字形数量
- }
- }
复制代码 GlyphVector::packedGlyphIDToGlyph
- // packedGlyphIDToGlyph 必须在单线程模式下运行。
- // 如果 fSkStrike 不是 sk_sp<SkStrike>,则说明转换为 Glyph* 尚未完成。
- void GlyphVector::packedGlyphIDToGlyph(StrikeCache* cache) {
- // 如果 fTextStrike 尚未初始化,则进行初始化
- if (fTextStrike == nullptr) {
- SkStrike* strike = fStrikePromise.strike(); // 获取承诺的字形打击
- // 在缓存中查找或创建对应的 Strike
- fTextStrike = cache->findOrCreateStrike(strike->strikeSpec());
- // 为每个字形获取图集位置
- for (Variant& variant : fGlyphs) {
- variant.glyph = fTextStrike->getGlyph(variant.packedGlyphID); // 根据打击中的 ID 获取字形
- }
- // 确保打击被固定,以便图集填充正常工作
- strike->verifyPinnedStrike();
- // 重置承诺,允许打击被清理
- fStrikePromise.resetStrike();
- }
- }
复制代码 *metricsAndImages.glyph
- SkSpan<const SkGlyph*> SkBulkGlyphMetricsAndImages::glyphs(SkSpan<const SkPackedGlyphID> glyphIDs) {
- fGlyphs.reset(glyphIDs.size());
- return fStrike->prepareImages(glyphIDs, fGlyphs.get());
- }
- SkSpan<const SkGlyph*> SkBulkGlyphMetricsAndImages::glyphs(SkSpan<const SkPackedGlyphID> glyphIDs) {
- fGlyphs.reset(glyphIDs.size());
- return fStrike->prepareImages(glyphIDs, fGlyphs.get());
- }
- SkSpan<const SkGlyph*> SkStrike::prepareImages(
- SkSpan<const SkPackedGlyphID> glyphIDs, const SkGlyph* results[]) {
- const SkGlyph** cursor = results;
- Monitor m{this};
- for (auto glyphID : glyphIDs) {
- //根据glyphId获取glyph
- SkGlyph* glyph = this->glyph(glyphID);
- //生成位图
- this->prepareForImage(glyph);
- *cursor++ = glyph;
- }
- return {results, glyphIDs.size()};
- }
- SkGlyph* SkStrike::glyph(SkPackedGlyphID packedGlyphID) {
- SkGlyphDigest digest = this->digestFor(kDirectMask, packedGlyphID);
- //加入缓存
- return this->glyph(digest);
- }
- bool SkStrike::prepareForImage(SkGlyph* glyph) {
- //此处会调用FT_load_glyph
- if (glyph->setImage(&fAlloc, fScalerContext.get())) {
- fMemoryIncrease += glyph->imageSize();
- }
- return glyph->image() != nullptr;
- }
复制代码 setImage会生成位图
- // 设置图形的图像数据。如果图像尚未设置,则分配图像内存并从给定的 SkScalerContext 中获取图像。
- //
- // 参数:
- // - alloc: 用于内存分配的 SkArenaAlloc 对象。
- // - scalerContext: 用于获取图像的 SkScalerContext 对象,负责提供图形的渲染信息。
- //
- // 返回值:
- // - 如果成功设置图像,则返回 true;
- // - 如果图像已经设置,则返回 false。
- bool SkGlyph::setImage(SkArenaAlloc* alloc, SkScalerContext* scalerContext) {
- if (!this->setImageHasBeenCalled()) {
- // 检查 getImage() 是否会改变 fMaskFormat,以防止回归
- SkDEBUGCODE(SkMask::Format oldFormat = this->maskFormat());
-
- this->allocImage(alloc); // 分配图像内存
- scalerContext->getImage(*this); // 从 scalerContext 获取图像
-
- SkASSERT(oldFormat == this->maskFormat()); // 确保格式未改变
- return true; // 成功设置图像
- }
- return false; // 图像已经设置,返回 false
- }
复制代码- // 生成指定 glyph 的图像并将其存储在提供的 imageBuffer 中。
- // 如果设置失败,或无法加载 glyph,则会用零填充图像缓冲区。
- //
- // 参数:
- // - glyph: 需要生成图像的 SkGlyph 对象,包含有关 glyph 的信息。
- // - imageBuffer: 指向存储生成图像的内存缓冲区的指针,必须足够大以容纳图像数据。
- void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph, void* imageBuffer) {
- SkAutoMutexExclusive ac(f_t_mutex()); // 确保线程安全
- if (this->setupSize()) {
- sk_bzero(imageBuffer, glyph.imageSize()); // 清空图像缓冲区
- return;
- }
- // 处理带有颜色的 glyphs
- if (glyph.extraBits() == ScalerContextBits::COLRv0 ||
- glyph.extraBits() == ScalerContextBits::COLRv1 ||
- glyph.extraBits() == ScalerContextBits::SVG)
- {
- SkASSERT(glyph.maskFormat() == SkMask::kARGB32_Format); // 确保格式正确
- SkBitmap dstBitmap;
- // TODO: 在 blits 为 sRGB 时标记为 sRGB。
- dstBitmap.setInfo(SkImageInfo::Make(glyph.width(), glyph.height(),
- kN32_SkColorType,
- kPremul_SkAlphaType),
- glyph.rowBytes());
- dstBitmap.setPixels(imageBuffer);
- SkCanvas canvas(dstBitmap);
- if constexpr (kSkShowTextBlitCoverage) {
- canvas.clear(0x33FF0000); // 用于调试的覆盖色
- } else {
- canvas.clear(SK_ColorTRANSPARENT); // 清空画布
- }
- canvas.translate(-glyph.left(), -glyph.top()); // 平移到 glyph 的原点
- SkSpan<SkColor> palette(fFaceRec->fSkPalette.get(), fFaceRec->fFTPaletteEntryCount);
- if (glyph.extraBits() == ScalerContextBits::COLRv0) {
- #ifdef FT_COLOR_H
- fUtils.drawCOLRv0Glyph(fFace, glyph, fLoadGlyphFlags, palette, &canvas);
- #endif
- } else if (glyph.extraBits() == ScalerContextBits::COLRv1) {
- #ifdef TT_SUPPORT_COLRV1
- fUtils.drawCOLRv1Glyph(fFace, glyph, fLoadGlyphFlags, palette, &canvas);
- #endif
- } else if (glyph.extraBits() == ScalerContextBits::SVG) {
- #if defined(FT_CONFIG_OPTION_SVG)
- if (FT_Load_Glyph(fFace, glyph.getGlyphID(), fLoadGlyphFlags)) {
- return; // 加载失败,直接返回
- }
- fUtils.drawSVGGlyph(fFace, glyph, fLoadGlyphFlags, palette, &canvas);
- #endif
- }
- return; // 处理完毕
- }
- // 尝试加载普通 glyph
- if (FT_Load_Glyph(fFace, glyph.getGlyphID(), fLoadGlyphFlags)) {
- sk_bzero(imageBuffer, glyph.imageSize()); // 加载失败,清空缓冲区
- return;
- }
-
- emboldenIfNeeded(fFace, fFace->glyph, glyph.getGlyphID()); // 加粗处理
- SkMatrix* bitmapMatrix = &fMatrix22Scalar; // 默认位图矩阵
- SkMatrix subpixelBitmapMatrix;
- if (this->shouldSubpixelBitmap(glyph, *bitmapMatrix)) {
- subpixelBitmapMatrix = fMatrix22Scalar;
- subpixelBitmapMatrix.postTranslate(SkFixedToScalar(glyph.getSubXFixed()),
- SkFixedToScalar(glyph.getSubYFixed())); // 应用子像素平移
- bitmapMatrix = &subpixelBitmapMatrix;
- }
- // 生成最终的 glyph 图像,这里会进行
- fUtils.generateGlyphImage(fFace, glyph, imageBuffer, *bitmapMatrix, fPreBlend);
- }
复制代码 atlasManager->addGlyphToAtlas
- // 尝试将字形添加到纹理图集中。如果成功返回 true,否则返回 false。
- //
- // 参数:
- // - skGlyph: 要添加的字形对象,包含字形的像素数据和格式信息。
- // - glyph: 目标字形对象,用于保存图集定位信息,包括该字形在图集中的位置。
- // - srcPadding: 源图像的填充大小,控制字形周围的额外像素(用于避免混叠).
- // - resourceProvider: 资源提供者,用于访问图形资源,包括纹理和缓冲区。
- // - uploadTarget: 延迟上传目标,用于处理图形上传到 GPU 的任务。
- GrDrawOpAtlas::ErrorCode GrAtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph,
- Glyph* glyph,
- int srcPadding,
- GrResourceProvider* resourceProvider,
- GrDeferredUploadTarget* uploadTarget) {
- #if !defined(SK_DISABLE_SDF_TEXT)
- SkASSERT(0 <= srcPadding && srcPadding <= SK_DistanceFieldInset);
- #else
- SkASSERT(0 <= srcPadding);
- #endif
- // 检查字形图像是否有效
- if (skGlyph.image() == nullptr) {
- return GrDrawOpAtlas::ErrorCode::kError;
- }
- SkASSERT(glyph != nullptr);
- // 获取字形格式并解析为预期的掩码格式
- MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat());
- MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat);
- int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat);
- int padding;
- switch (srcPadding) {
- case 0:
- // 直接掩码/图像情况
- padding = 0;
- if (fSupportBilerpAtlas) {
- // 如果支持双线性过滤,将直接掩码强制添加填充
- padding = 1;
- srcPadding = 1;
- }
- break;
- case 1:
- // 变换掩码/图像情况
- padding = 1;
- break;
- #if !defined(SK_DISABLE_SDF_TEXT)
- case SK_DistanceFieldInset:
- // SDFT(距离场文本)情况
- // 如果 srcPadding 为 SK_DistanceFieldInset,填充已在字形图像中构建,无需额外填充。
- padding = 0;
- break;
- #endif
- default:
- // 填充参数无效
- return GrDrawOpAtlas::ErrorCode::kError;
- }
- // 计算字形的宽度和高度(包含填充)
- const int width = skGlyph.width() + 2 * padding;
- const int height = skGlyph.height() + 2 * padding;
- int rowBytes = width * bytesPerPixel; // 每行字形数据的字节数
- size_t size = height * rowBytes; // 总字形数据大小
- // 临时存储用于规范化字形图像
- SkAutoSMalloc<1024> storage(size);
- void* dataPtr = storage.get();
- if (padding > 0) {
- sk_bzero(dataPtr, size); // 用零填充
- // 在数据指针中前进一行和一列以适应填充
- dataPtr = (char*)(dataPtr) + rowBytes + bytesPerPixel;
- }
- // 从字形中提取图像并填充到 dataPtr
- get_packed_glyph_image(skGlyph, rowBytes, expectedMaskFormat, dataPtr);
- // 尝试将图像添加到图集中
- auto errorCode = this->addToAtlas(resourceProvider,
- uploadTarget,
- expectedMaskFormat,
- width,
- height,
- storage.get(),
- &glyph->fAtlasLocator);
- // 如果添加成功,更新字形的图集定位器
- if (errorCode == GrDrawOpAtlas::ErrorCode::kSucceeded) {
- glyph->fAtlasLocator.insetSrc(srcPadding);
- }
- return errorCode; // 返回操作结果
- }
复制代码- // 尝试将图像添加到与指定格式匹配的纹理图集中。
- //
- // 参数:
- // - resourceProvider: 资源提供者,用于访问图形资源,包括纹理和缓冲区。
- // - target: 延迟上传目标,用于处理图像上传到 GPU 的任务。
- // - format: 目标图集的掩码格式,决定如何处理图像数据。
- // - width: 要添加的图像的宽度(以像素为单位)。
- // - height: 要添加的图像的高度(以像素为单位)。
- // - image: 指向要添加到图集的图像数据的指针。
- // - atlasLocator: 用于保存字形在图集中的位置的定位器。
- GrDrawOpAtlas::ErrorCode GrAtlasManager::addToAtlas(GrResourceProvider* resourceProvider,
- GrDeferredUploadTarget* target,
- MaskFormat format,
- int width, int height, const void* image,
- skgpu::AtlasLocator* atlasLocator) {
- return this->getAtlas(format)->addToAtlas(resourceProvider, target, width, height, image,
- atlasLocator);
- }
- // 尝试将图像添加到纹理图集中。如果图像大小超过了可用空间,返回错误代码。
- //
- // 参数:
- // - resourceProvider: 资源提供者,用于访问图形资源,如纹理和缓冲区。
- // - target: 延迟上传目标,处理图像上传到 GPU 的任务。
- // - width: 要添加的图像的宽度(以像素为单位)。
- // - height: 要添加的图像的高度(以像素为单位)。
- // - image: 指向要添加到图集的图像数据的指针。
- // - atlasLocator: 用于保存图像在图集中的位置的定位器。
- //
- // 返回值:
- // - 如果成功将图像添加到图集,则返回 ErrorCode::kSucceeded;
- // - 如果失败,返回适当的错误代码。
- GrDrawOpAtlas::ErrorCode GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider,
- GrDeferredUploadTarget* target,
- int width, int height, const void* image,
- AtlasLocator* atlasLocator) {
- if (width > fPlotWidth || height > fPlotHeight) {
- return ErrorCode::kError; // 图像大小超过可用空间
- }
- // 检查每一页,尝试在不刷新图集的情况下上传
- for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
- //uploadToPage会将调用target->addASAPUpload。将纹理上传添加到延迟队列,统一上传
- if (this->uploadToPage(pageIdx, target, width, height, image, atlasLocator)) {
- return ErrorCode::kSucceeded; // 成功上传到图集
- }
- }
- // 如果上传失败,检查是否可以释放不再使用的图块
- if (fNumActivePages == this->maxPages()) {
- for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
- Plot* plot = fPages[pageIdx].fPlotList.tail();
- SkASSERT(plot);
- if (plot->lastUseToken() < target->tokenTracker()->nextFlushToken()) {
- this->processEvictionAndResetRects(plot);
- SkASSERT(GrBackendFormatBytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) == plot->bpp());
- SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, atlasLocator);
- SkASSERT(verify);
- if (!this->updatePlot(target, atlasLocator, plot)) {
- return ErrorCode::kError; // 更新图块失败
- }
- return ErrorCode::kSucceeded; // 成功更新图块
- }
- }
- } else {
- // 如果没有激活所有可用页面,尝试创建一个新的页面
- if (!this->activateNewPage(resourceProvider)) {
- return ErrorCode::kError; // 激活新页面失败
- }
- if (this->uploadToPage(fNumActivePages - 1, target, width, height, image, atlasLocator)) {
- return ErrorCode::kSucceeded; // 成功上传到新页面
- } else {
- return ErrorCode::kError; // 上传到新页面失败
- }
- }
- if (!fNumActivePages) {
- return ErrorCode::kError; // 没有可用页面
- }
- // 尝试找到可以进行内联上传的图块
- Plot* plot = nullptr;
- for (int pageIdx = ((int)fNumActivePages) - 1; pageIdx >= 0; --pageIdx) {
- Plot* currentPlot = fPages[pageIdx].fPlotList.tail();
- if (currentPlot->lastUseToken() != target->tokenTracker()->nextDrawToken()) {
- plot = currentPlot; // 找到可以上传的图块
- break;
- }
- }
- // 如果找不到合适的图块,返回重试
- if (!plot) {
- return ErrorCode::kTryAgain;
- }
- this->processEviction(plot->plotLocator());
- int pageIdx = plot->pageIndex();
- fPages[pageIdx].fPlotList.remove(plot);
- sk_sp<Plot>& newPlot = fPages[pageIdx].fPlotArray[plot->plotIndex()];
- newPlot = plot->clone();
- fPages[pageIdx].fPlotList.addToHead(newPlot.get());
- SkASSERT(GrBackendFormatBytesPerPixel(fViews[pageIdx].proxy()->backendFormat()) == newPlot->bpp());
- SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, atlasLocator);
- SkASSERT(verify);
- // 将新的图块内联上传
- sk_sp<Plot> plotsp(SkRef(newPlot.get()));
- GrTextureProxy* proxy = fViews[pageIdx].asTextureProxy();
- SkASSERT(proxy && proxy->isInstantiated());
- AtlasToken lastUploadToken = target->addInlineUpload(
- [this, plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
- this->uploadPlotToTexture(writePixels, proxy, plotsp.get());
- });
- newPlot->setLastUploadToken(lastUploadToken);
- atlasLocator->updatePlotLocator(newPlot->plotLocator());
- SkDEBUGCODE(this->validate(*atlasLocator);)
- return ErrorCode::kSucceeded; // 成功完成
- }
复制代码 updatePlot会将纹理上传加到延迟队列,在preExecuteDraws统一上传
- // 更新指定图块的上传状态,并在需要时安排将图块上传到 GPU。
- //
- // 参数:
- // - target: 延迟上传目标,负责处理图像数据的上传任务。
- // - atlasLocator: 用于更新图块在图集中位置的定位器。
- // - plot: 要更新的图块,包含图像在图集中的信息。
- //
- // 返回值:
- // - 如果成功更新图块,则返回 true;
- // - 如果更新失败(例如,图块超出活动页面范围),返回 false。
- inline bool GrDrawOpAtlas::updatePlot(GrDeferredUploadTarget* target,
- AtlasLocator* atlasLocator, Plot* plot) {
- uint32_t pageIdx = plot->pageIndex();
- if (pageIdx >= fNumActivePages) {
- return false; // 图块超出活动页面范围,更新失败
- }
- this->makeMRU(plot, pageIdx); // 将图块标记为最近使用
- // 检查是否需要插入新的上传
- if (plot->lastUploadToken() < target->tokenTracker()->nextFlushToken()) {
- sk_sp<Plot> plotsp(SkRef(plot)); // 保持对图块的引用
- GrTextureProxy* proxy = fViews[pageIdx].asTextureProxy();
- SkASSERT(proxy && proxy->isInstantiated()); // 这发生在刷新时
- // 安排上传图块到 GPU
- AtlasToken lastUploadToken = target->addASAPUpload(
- [this, plotsp, proxy](GrDeferredTextureUploadWritePixelsFn& writePixels) {
- this->uploadPlotToTexture(writePixels, proxy, plotsp.get());
- });
- plot->setLastUploadToken(lastUploadToken); // 更新图块的最后上传令牌
- }
- atlasLocator->updatePlotLocator(plot->plotLocator()); // 更新定位器
- SkDEBUGCODE(this->validate(*atlasLocator);) // 验证定位器状态
- return true; // 成功更新
- }
复制代码 preExecuteDraws纹理上传
- void GrOpFlushState::doUpload(GrDeferredTextureUploadFn& upload,
- bool shouldPrepareSurfaceForSampling) {
- GrDeferredTextureUploadWritePixelsFn wp = [this, shouldPrepareSurfaceForSampling](
- GrTextureProxy* dstProxy,
- SkIRect rect,
- GrColorType colorType,
- const void* buffer,
- size_t rowBytes) {
- ATRACE_ANDROID_FRAMEWORK_ALWAYS("GrOpFlushState::doUpload");
- GrSurface* dstSurface = dstProxy->peekSurface();
- ...
- return this->fGpu->writePixels(dstSurface,
- rect,
- colorType,
- supportedWrite.fColorType,
- buffer,
- rowBytes,
- shouldPrepareSurfaceForSampling);
- };
- // 执行wp回调
- upload(wp);
- }
复制代码 这里主要通过通过匿名函数,调用GrGpu::writePixels进而调用GrGLGpu: nWritePixels来执行纹理上传。
onWritePixels中,首先调用 GL_CALL(TexParameteri(glTex->target(), GR_GL_TEXTURE_MAX_LEVEL, maxLevel))设置mipmaps的层级,然后调用GrGLGpu::uploadColorTypeTexData->GrGLGpu::uploadTexData->TexSubImage2D
这里终极调用glTexSubImage2D去加载纹理
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |