蒙版前处理方法(Native)
最終更新: 2024年12月19日
在Cubism 5 SDK及更高版本中,部分类名等已变更。
有关详细信息,请参考CHANGELOG。
在Live2D Cubism SDK for Native中,为了保持智能手机等的绘制速度,在模型绘制处理开始时,采用了为一个蒙版缓冲区绘制所有蒙版形状的“预处理方法”。
在原理绘制方法中,每次绘制需要蒙版的Drawable时,都会绘制蒙版形状(见图)。
使用这种方法,每次Drawable需要蒙版时,都会发生切换渲染目标和清除缓冲区等相对高成本的处理。
因此,它可能会导致智能手机等上的绘图速度降低。

但是,仅仅提前准备好蒙版需要多个蒙版缓冲区,这会给内存记忆带来压力。
为了解决这个问题,可以对一个蒙版缓冲区进行以下处理,在将其视为正在使用多个蒙版缓冲区的同时,减少内存记忆压力。
蒙版集成
由于所有的蒙版都是预先生成的,因此接受相同蒙版指定的Drawable可以通过使用相同蒙版图片以减少生成的张数。

这个处理在CubismRenderer_OpenGLES2::Initialize函数调用中通过CubismClippingManager::Initialize函数完成。
template <class T_ClippingContext, class T_OffscreenSurface>
void CubismClippingManager<T_ClippingContext, T_OffscreenSurface>::Initialize(CubismModel& model, const csmInt32 maskBufferCount)
{
_renderTextureCount = maskBufferCount;
// 设置渲染纹理的清除标志
for (csmInt32 i = 0; i < _renderTextureCount; ++i)
{
_clearedMaskBufferFlags.PushBack(false);
}
// 注册所有使用剪贴蒙版的绘制物件
// 剪贴蒙版通常仅限于几个
for (csmInt32 i = 0; i < model.GetDrawableCount(); i++)
{
if (model.GetDrawableMaskCounts()[i] <= 0)
{
// 没有使用剪贴蒙版的图形网格(大多数情况下不使用)
_clippingContextListForDraw.PushBack(NULL);
continue;
}
// 检查是否与已有的ClipContext相同
T_ClippingContext* cc = FindSameClip(model.GetDrawableMasks()[i], model.GetDrawableMaskCounts()[i]);
if (cc == NULL)
{
// 如果不存在相同的蒙版则生成
cc = CSM_NEW T_ClippingContext(this, model, model.GetDrawableMasks()[i], model.GetDrawableMaskCounts()[i]);
_clippingContextListForMask.PushBack(cc);
}
cc->AddClippedDrawable(i);
_clippingContextListForDraw.PushBack(cc);
}
}
使用多个蒙版纹理
Cubism SDK for Native R6或更高版本中,您可以任意使用多个蒙版纹理。
因此,即使给模型设置了超过R5之前存在的36个蒙版上限的蒙版,也能够在SDK上正常显示。
但是,如果使用两个或多个蒙版纹理,则可以为一个蒙版纹理生成的蒙版上限数量为32个。
(仅使用一个蒙版时的蒙版上限数量为36个。相关详情会在之后说明。)
如果使用两个蒙版纹理,则可以使用的蒙版上限数量为32 * 2 = 64个。
使用多个蒙版纹理的设置如下。
void CreateRenderer(csmInt32 maskBufferCount = 2);
按颜色信息分开
蒙版缓冲区是一个RGBA视频数组,与通常的纹理缓冲区等一样。
通常的蒙版处理仅使用此A通道来应用蒙版,而不使用RGB通道。
因此,通过在RGBA中具有单独的蒙版数据,一个蒙版缓冲区可以作为四个蒙版缓冲区处理。
分割分开
当4张蒙版图片不够用时,通过2等份、4等份和9等份处理蒙版缓冲区来增加蒙版数量。
由于还存在按颜色信息分割的情况,因此最多可以保存4 × 9、共36个不同蒙版。
此外,为防止蒙版图片被压扁,请使用应用蒙版的所有Drawable矩形绘制蒙版。
因此,需要生成范围、蒙版、使用蒙版生成矩阵。
使用多个蒙版纹理时,分割方法与仅使用一个蒙版纹理时相同。
但是,当使用多个蒙版纹理时,每个蒙版纹理的蒙版分配会尽可能平均分配,因此即使使用相同的模型也可以提高绘制质量。(如果增加蒙版纹理,处理成本会相应增加。)
例如,拥有32个蒙版的模型通常使用一个蒙版纹理绘制32个蒙版,但使用2个蒙版纹理时的蒙版分配为“每个16个”。
检查矩形
在蒙版生成的第一步中,对于每个蒙版,检查完全覆盖蒙版的矩形。
template <class T_ClippingContext, class T_OffscreenSurface>
void CubismClippingManager<T_ClippingContext, T_OffscreenSurface>::CalcClippedDrawTotalBounds(CubismModel& model, T_ClippingContext* clippingContext)
{
// 被剪贴蒙版(要添加蒙版的绘制物件)的全体矩形
csmFloat32 clippedDrawTotalMinX = FLT_MAX, clippedDrawTotalMinY = FLT_MAX;
csmFloat32 clippedDrawTotalMaxX = -FLT_MAX, clippedDrawTotalMaxY = -FLT_MAX;
// 判断该蒙版是否真的需要
// 如果至少可以使用一个使用该剪贴的“绘制物件”,则需要生成蒙版
const csmInt32 clippedDrawCount = clippingContext->_clippedDrawableIndexList->GetSize();
for (csmInt32 clippedDrawableIndex = 0; clippedDrawableIndex < clippedDrawCount; clippedDrawableIndex++)
{
// 查找使用蒙版的绘制物件的绘制矩形
const csmInt32 drawableIndex = (*clippingContext->_clippedDrawableIndexList)[clippedDrawableIndex];
csmInt32 drawableVertexCount = model.GetDrawableVertexCount(drawableIndex);
csmFloat32* drawableVertexes = const_cast<csmFloat32*>(model.GetDrawableVertices(drawableIndex));
csmFloat32 minX = FLT_MAX, minY = FLT_MAX;
csmFloat32 maxX = -FLT_MAX, maxY = -FLT_MAX;
csmInt32 loop = drawableVertexCount * Constant::VertexStep;
for (csmInt32 pi = Constant::VertexOffset; pi < loop; pi += Constant::VertexStep)
{
csmFloat32 x = drawableVertexes[pi];
csmFloat32 y = drawableVertexes[pi + 1];
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
//
if (minX == FLT_MAX) continue; // 由于无法获得任何有效点,因此跳过
// 应用到全体矩形
if (minX < clippedDrawTotalMinX) clippedDrawTotalMinX = minX;
if (minY < clippedDrawTotalMinY) clippedDrawTotalMinY = minY;
if (maxX > clippedDrawTotalMaxX) clippedDrawTotalMaxX = maxX;
if (maxY > clippedDrawTotalMaxY) clippedDrawTotalMaxY = maxY;
}
if (clippedDrawTotalMinX == FLT_MAX)
{
clippingContext->_allClippedDrawRect->X = 0.0f;
clippingContext->_allClippedDrawRect->Y = 0.0f;
clippingContext->_allClippedDrawRect->Width = 0.0f;
clippingContext->_allClippedDrawRect->Height = 0.0f;
clippingContext->_isUsing = false;
}
else
{
clippingContext->_isUsing = true;
csmFloat32 w = clippedDrawTotalMaxX - clippedDrawTotalMinX;
csmFloat32 h = clippedDrawTotalMaxY - clippedDrawTotalMinY;
clippingContext->_allClippedDrawRect->X = clippedDrawTotalMinX;
clippingContext->_allClippedDrawRect->Y = clippedDrawTotalMinY;
clippingContext->_allClippedDrawRect->Width = w;
clippingContext->_allClippedDrawRect->Height = h;
}
}
颜色分开、分割分开的编排决定
确定每个蒙版所属的蒙版缓冲区的颜色通道和分割位置。
template <class T_ClippingContext, class T_OffscreenSurface>
void CubismClippingManager<T_ClippingContext, T_OffscreenSurface>::SetupLayoutBounds(csmInt32 usingClipCount) const
{
const csmInt32 useClippingMaskMaxCount = _renderTextureCount <= 1
? ClippingMaskMaxCountOnDefault
: ClippingMaskMaxCountOnMultiRenderTexture * _renderTextureCount;
if (usingClipCount <= 0 || usingClipCount > useClippingMaskMaxCount)
{
if (usingClipCount > useClippingMaskMaxCount)
{
// 发布蒙版数量限制的警告
csmInt32 count = usingClipCount - useClippingMaskMaxCount;
CubismLogError("not supported mask count : %d\n[Details] render texture count: %d\n, mask count : %d"
, count, _renderTextureCount, usingClipCount);
}
// 在这种情况下,每次清除并使用一个蒙版目标
for (csmUint32 index = 0; index < _clippingContextListForMask.GetSize(); index++)
{
T_ClippingContext* cc = _clippingContextListForMask[index];
cc->_layoutChannelIndex = 0; // 反正每次都会被清除,所以可以设置为固定
cc->_layoutBounds->X = 0.0f;
cc->_layoutBounds->Y = 0.0f;
cc->_layoutBounds->Width = 1.0f;
cc->_layoutBounds->Height = 1.0f;
cc->_bufferIndex = 0;
}
return;
}
// 如果有1张渲染纹理,则将其分为9份(最多36张)
const csmInt32 layoutCountMaxValue = _renderTextureCount <= 1 ? 9 : 8;
// 尽可能使用一个RenderTexture来编排蒙版
// 如果蒙版组数为4个或更少,则为RGBA各通道置入一个蒙版,如果为5以上、6个以下,则将RGBA置入为2,2,1,1
const csmInt32 countPerSheetDiv = (usingClipCount + _renderTextureCount - 1) / _renderTextureCount; // 每张渲染纹理分配多少张纸(向上进位)
const csmInt32 reduceLayoutTextureCount = usingClipCount % _renderTextureCount; // 编排数量减一的渲染纹理数量(仅该数量的渲染纹理位对象范围)
// 按顺序使用RGBA
const csmInt32 divCount = countPerSheetDiv / ColorChannelCount; //一个通道中置入的基本蒙版数量
const csmInt32 modCount = countPerSheetDiv % ColorChannelCount; // 剩余,逐一分配到该编号的通道
// 准备RGBA各通道(0:R, 1:G, 2:B, 3:A)
csmInt32 curClipIndex = 0; // 按顺序设置
for (csmInt32 renderTextureIndex = 0; renderTextureIndex < _renderTextureCount; renderTextureIndex++)
{
for (csmInt32 channelIndex = 0; channelIndex < ColorChannelCount; channelIndex++)
{
// 在该通道上编排的数量
// NOTE:编排数量 = 置入1个通道上的基本蒙版 + 1个用于额外蒙版的附加通道
csmInt32 layoutCount = divCount + (channelIndex < modCount ? 1 : 0);
// 当编排数量减一时决定使用哪个通道
// 当div为0时,调整使其在正常索引范围内
const csmInt32 checkChannelIndex = modCount + (divCount < 1 ? -1 : 0);
// 如果这是对象通道并且存在编排数量减一的渲染纹理时
if (channelIndex == checkChannelIndex && reduceLayoutTextureCount > 0)
{
// 如果当前渲染纹理是对象渲染纹理,则将编排数量减少一张
layoutCount -= !(renderTextureIndex < reduceLayoutTextureCount) ? 1 : 0;
}
// 决定如何分割
if (layoutCount == 0)
{
// 什么都不做
}
else if (layoutCount == 1)
{
// 直接使用所有内容
T_ClippingContext* cc = _clippingContextListForMask[curClipIndex++];
cc->_layoutChannelIndex = channelIndex;
cc->_layoutBounds->X = 0.0f;
cc->_layoutBounds->Y = 0.0f;
cc->_layoutBounds->Width = 1.0f;
cc->_layoutBounds->Height = 1.0f;
cc->_bufferIndex = renderTextureIndex;
}
else if (layoutCount == 2)
{
for (csmInt32 i = 0; i < layoutCount; i++)
{
const csmInt32 xpos = i % 2;
T_ClippingContext* cc = _clippingContextListForMask[curClipIndex++];
cc->_layoutChannelIndex = channelIndex;
cc->_layoutBounds->X = xpos * 0.5f;
cc->_layoutBounds->Y = 0.0f;
cc->_layoutBounds->Width = 0.5f;
cc->_layoutBounds->Height = 1.0f;
cc->_bufferIndex = renderTextureIndex;
// 2等份并使用UV
}
}
else if (layoutCount <= 4)
{
// 4等份并使用
for (csmInt32 i = 0; i < layoutCount; i++)
{
const csmInt32 xpos = i % 2;
const csmInt32 ypos = i / 2;
T_ClippingContext* cc = _clippingContextListForMask[curClipIndex++];
cc->_layoutChannelIndex = channelIndex;
cc->_layoutBounds->X = xpos * 0.5f;
cc->_layoutBounds->Y = ypos * 0.5f;
cc->_layoutBounds->Width = 0.5f;
cc->_layoutBounds->Height = 0.5f;
cc->_bufferIndex = renderTextureIndex;
}
}
else if (layoutCount <= layoutCountMaxValue)
{
// 9等份并使用
for (csmInt32 i = 0; i < layoutCount; i++)
{
const csmInt32 xpos = i % 3;
const csmInt32 ypos = i / 3;
T_ClippingContext* cc = _clippingContextListForMask[curClipIndex++];
cc->_layoutChannelIndex = channelIndex;
cc->_layoutBounds->X = xpos / 3.0f;
cc->_layoutBounds->Y = ypos / 3.0f;
cc->_layoutBounds->Width = 1.0f / 3.0f;
cc->_layoutBounds->Height = 1.0f / 3.0f;
cc->_bufferIndex = renderTextureIndex;
}
}
// 超过蒙版限制数量时的处理
else
{
csmInt32 count = usingClipCount - useClippingMaskMaxCount;
CubismLogError("not supported mask count : %d\n[Details] render texture count: %d\n, mask count : %d"
, count, _renderTextureCount, usingClipCount);
// 如果处于开发模式则停止
CSM_ASSERT(0);
// 如果继续运行,SetupShaderProgram会出现过度访问,所以只能酌情插入
// 当然绘制结果会不佳
for (csmInt32 i = 0; i < layoutCount; i++)
{
T_ClippingContext* cc = _clippingContextListForMask[curClipIndex++];
cc->_layoutChannelIndex = 0;
cc->_layoutBounds->X = 0.0f;
cc->_layoutBounds->Y = 0.0f;
cc->_layoutBounds->Width = 1.0f;
cc->_layoutBounds->Height = 1.0f;
cc->_bufferIndex = 0;
}
}
}
}
}
蒙版绘制,使用蒙版生成矩阵
根据绘制前检查的矩形范围及其所属位置,准备用于蒙版生成和蒙版使用的转变矩阵。
// --- 实际绘制一个蒙版 ---
T_ClippingContext* clipContext = _clippingContextListForMask[clipIndex];
csmRectF* allClippedDrawRect = clipContext->_allClippedDrawRect; // 使用该蒙版的所有绘制物件逻辑座标上的矩形边界
csmRectF* layoutBoundsOnTex01 = clipContext->_layoutBounds; // 将蒙版放入其中
const csmFloat32 MARGIN = 0.05f;
csmFloat32 scaleX = 0.0f;
csmFloat32 scaleY = 0.0f;
const csmFloat32 ppu = model.GetPixelsPerUnit();
const csmFloat32 maskPixelWidth = clipContext->GetClippingManager()->_clippingMaskBufferSize.X;
const csmFloat32 maskPixelHeight = clipContext->GetClippingManager()->_clippingMaskBufferSize.Y;
const csmFloat32 physicalMaskWidth = layoutBoundsOnTex01->Width * maskPixelWidth;
const csmFloat32 physicalMaskHeight = layoutBoundsOnTex01->Height * maskPixelHeight;
_tmpBoundsOnModel.SetRect(allClippedDrawRect);
if (_tmpBoundsOnModel.Width * ppu > physicalMaskWidth)
{
_tmpBoundsOnModel.Expand(allClippedDrawRect->Width * MARGIN, 0.0f);
scaleX = layoutBoundsOnTex01->Width / _tmpBoundsOnModel.Width;
}
else
{
scaleX = ppu / physicalMaskWidth;
}
if (_tmpBoundsOnModel.Height * ppu > physicalMaskHeight)
{
_tmpBoundsOnModel.Expand(0.0f, allClippedDrawRect->Height * MARGIN);
scaleY = layoutBoundsOnTex01->Height / _tmpBoundsOnModel.Height;
}
else
{
scaleY = ppu / physicalMaskHeight;
}
// 找到生成蒙版时要使用的矩阵
createMatrixForMask(isRightHanded, layoutBoundsOnTex01, scaleX, scaleY);
clipContext->_matrixForMask.SetMatrix(_tmpMatrixForMask.GetArray());
clipContext->_matrixForDraw.SetMatrix(_tmpMatrixForDraw.GetArray());
蒙版缓冲区的动态大小变更

GLES2渲染器提供了一个API,以在运行时变更蒙版缓冲区的大小。
目前,蒙版缓冲区的大小设置为256 * 256(像素)作为初始值,但如果要将蒙版生成区域切割成9张,则将在85 * 85(像素)的矩形区域内绘制的蒙版形状进一步放大,作为剪贴区域使用。
因此,剪贴结果的边缘会模糊或渗色。
作为解决方案,我们提供了一个 API,可以在程序执行时变更蒙版缓冲区的大小。
例如,通过将蒙版缓冲区的大小从256 * 256变更为1024 * 1024,如果将蒙版生成区域切成9张,则可以在341 * 341的矩形区域中绘制蒙版形状,因此,您还可以放大并将其用作剪贴区域,以消除剪贴结果边缘的模糊和渗色。
* 扩大蒙版缓冲区的大小 ⇒ 如果要处理的像素增加,速度会变慢,但绘制结果会很漂亮。
* 缩小蒙版缓冲区的大小 ⇒ 由于要处理的像素减少了,所以速度会更快,但是绘制结果会很脏。
void CubismRenderer_OpenGLES2::SetClippingMaskBufferSize(csmFloat32 width, csmFloat32 height)
{
if (_clippingManager == NULL)
{
return;
}
// 在放弃副本前保存渲染纹理的数量
const csmInt32 renderTextureCount = _clippingManager->GetRenderTextureCount();
// 放弃/重新创建副本以变更OffscreenSurface的大小
CSM_DELETE_SELF(CubismClippingManager_OpenGLES2, _clippingManager);
_clippingManager = CSM_NEW CubismClippingManager_OpenGLES2();
_clippingManager->SetClippingMaskBufferSize(width, height);
_clippingManager->Initialize(
*GetModel(),
renderTextureCount
);
}
预处理方法能提高性能的原因
作为移动终端特有的情况,GPU的Clear指令和渲染目标切换指令的处理成本可能高于其他指令。
在使用原理方法绘制时,会根据需要蒙版的Drawable数量执行这些处理成本较高的指令。
然而,在预处理方法的情况下,可以减少这些指令的执行次数,这有望提高在智能手机等上的性能。
为了实际理解效果,我们来衡量一下每个处理单元在渲染中的时间成本。
作为测量方法,请检查下面显示的源代码。 按构建单独测量层。
另外,测试对象的模型是Haru集成的。
准备有两种Android设备和一种Windows设备进行测量。
测量是通过Android端的clock_gettime函数和Windows端的QueryPerformanceCounter函数,将结果缓存到缓冲区并计算平均值。
剪贴蒙版生成器(层1)
void CubismClippingManager_OpenGLES2::SetupClippingContext(CubismModel& model, CubismRenderer_OpenGLES2* renderer)
{
・
・
・
{ // ★绘制目标切换测量
P_TIME1(ProcessingTime cl(s_switch);)
// ---------- 蒙版绘制处理-----------
// 将蒙版的RenderTexture设置为active
glBindFramebuffer(GL_FRAMEBUFFER, _maskRenderTexture);
}
{ // ★缓冲区清空(填充处理)测量
P_TIME1(ProcessingTime cl(s_clear);)
// 清除蒙版
// (暂定规格)1为无效(未绘制)区域,0为有效(绘制)区域。(在着色器中使用Cd * Cs乘以接近0的值以制作蒙版。当乘以1时没有任何反应)
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
・
・
・
{ // ★绘制目标切换测量2
P_TIME1(ProcessingTime cl(s_switch);)
// --- 后处理 ---
glBindFramebuffer(GL_FRAMEBUFFER, oldFBO); // 返回绘制对象
}
・
・
・
}
CubismClippingManager_OpenGLES2::SetupClippingContext测量切换绘制目标和填充的时间。
全体模型绘制(混合层1、2)
void CubismRenderer_OpenGLES2::DoDrawModel()
{
{ // ★测量全体蒙版缓冲区的生成
P_TIME2(ProcessingTime makemask(s_maskmake);)
if (_clippingManager != NULL)
{
PreDraw();
_clippingManager->SetupClippingContext(*GetModel(), this);
}
}
{ // ★测量绘制预处理
P_TIME1(ProcessingTime makemask(s_predraw);)
// 即使在上述剪贴处理中,也将调用一次PreDraw,敬请注意!!
PreDraw();
}
const csmInt32 drawableCount = GetModel()->GetDrawableCount();
const csmInt32* renderOrder = GetModel()->GetDrawableRenderOrders();
{ // ★测量排序时间
P_TIME1(ProcessingTime makemask(s_sort);)
// 按绘制顺序排序索引
for (csmInt32 i = 0; i < drawableCount; ++i)
{
const csmInt32 order = renderOrder[i];
_sortedDrawableIndexList[order] = i;
}
}
{ // ★测量除蒙版以外的绘制时间
P_TIME2(ProcessingTime makemask(s_draw);)
// 绘制
for (csmInt32 i = 0; i < drawableCount; ++i)
{
const csmInt32 drawableIndex = _sortedDrawableIndexList[i];
// 设置剪贴蒙版
SetClippingContextBufferForDraw(clipContext);
IsCulling(GetModel()->GetDrawableCulling(drawableIndex) != 0);
DrawMeshOpenGL(*GetModel(), drawableIndex);
}
}
{ // ★测量绘制后处理
P_TIME1(ProcessingTime makemask(s_post);)
//
PostDraw();
}
}
测量绘制前、排序和绘制后处理。
通过划分层在蒙版生成和其他绘制的粗略中也进行测量。
绘制网格(第1层)
void CubismRenderer_OpenGLES2::DrawMeshOpenGL(const CubismModel& model, const csmInt32 index)
{
・
・
・
{ // ★测量着色器中的数据设置时间
P_TIME1(ProcessingTime sharder(s_sharder);)
if (IsGenerateMask()) // 生成蒙版时
{
CubismShader_OpenGLES2::GetInstance()->SetupShaderProgramForMask(this, model, index);
}
else
{
CubismShader_OpenGLES2::GetInstance()->SetupShaderProgramForDraw(this, model, index);
}
}
{ // ★测量单个绘制命令的时间
P_TIME1(ProcessingTime gldraw(s_gldraw);)
// 绘制多边形网格
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indexArray);
}
// 后处理
glUseProgram(0);
SetClippingContextBufferForDraw(NULL);
SetClippingContextBufferForMask(NULL);
}
在CubismRenderer_OpenGLES2::DrawMesh中,测量设置到着色器的时间和绘制命令的单次时间。
层3
void LAppModel::Update()
{
{
P_TIME3(ProcessingTime up(s_paramup);)
const csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime();
_userTimeSeconds += deltaTimeSeconds;
・
・
・
// 姿势设置
if (_pose != NULL)
{
_pose->UpdateParameters(_model, deltaTimeSeconds);
}
}
{
P_TIME3(ProcessingTime ren(s_modelup);)
_model->Update();
}
}
void LAppModel::Draw(CubismMatrix44& matrix)
{
P_TIME3(ProcessingTime ren(s_rendering);)
matrix.MultiplyByMatrix(_modelMatrix);
GetRenderer<Rendering::CubismRenderer_OpenGLES2>()->SetMvpMatrix(&matrix);
DoDraw();
}
粗略将Update的流程分为参数计算、模型更新和渲染三个部分。
结果
| Android1 | Android2 | Winpc1 | |
| L1clear | 1781.20 | 218.80 | 26.80 |
| L1gldraw | 45.47 | 51.63 | 10.58 |
| L1sharder | 12.31 | 9.34 | 5.37 |
| L1post | 1.50 | 1.00 | 0.10 |
| L1switch | 10.70 | 56.30 | 7.80 |
| L1predraw | 15.90 | 8.20 | 2.20 |
| L1sort | 7.60 | 7.00 | 0.60 |
| L2MaskMake | 2686.80 | 1357.60 | 318.50 |
| L2draw | 4004.10 | 4013.20 | 1217.00 |
| L3paramupdate | 392.00 | 375.40 | 89.70 |
| L3modelupdate | 1357.50 | 1410.90 | 1070.40 |
| L3rendering | 6715.70 | 5233.70 | 1892.00 |
上表显示了上述部分的执行时间。
可以看到移动终端的Clear成本很高,切换渲染目标比其他指令重。
这条繁重的指令会在按原理方法绘制时,运行蒙版所需的Drawable。
由于该计算只能进行一次,因此有望提高在智能手机等上的性能。
将蒙版处理切换为高清方法
如上所述,每次绘制时生成蒙版的方法会影响低规格终端的性能。
但是,当画面质量比运行时的性能更重要时(例如最终输出为视频),此方法更适合。
在2018/12/20之后的SDK中,可以将蒙版处理切换为高清方式。
要切换到高清方法,请将true传递给以下API。
CubismRenderer::UseHighPrecisionMask()