前面几篇文章介绍了绘制相关的组件,主要是SkSurface和GrSurface以及他们的代理类,这些都代表着GPU上的资源,对应的是纹理对象。绘制的时候,绘制函数会转换成绘制指令对象记录起来,到现在为止,绘制都是在做指令记录。当前部分的GPU资源也准备就绪,需要开始将绘制指令提交到GPU去做真正的像素处理。这是通过SkSurface的flush函数触发的。我们从RenderPipeLine看起
1. SkSurface.flushAndSubmit
frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) {
...
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
{
ATRACE_NAME("flush commands");
surface->flushAndSubmit();
}
...
}
renderFrame记录完一帧的绘制命令后,进一步调用flushAndSubmit方法
external/skia/include/core/SkSurface.h
void flushAndSubmit(bool syncCpu = false);
external/skia/src/image/SkSurface.cpp
void SkSurface::flushAndSubmit(bool syncCpu) {
this->flush(BackendSurfaceAccess::kNoAccess, GrFlushInfo());
}
GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, const GrFlushInfo& flushInfo) {
return asSB(this)->onFlush(access, flushInfo, nullptr);
}
GrSemaphoresSubmitted SkSurface::flush(const GrFlushInfo& info,
const GrBackendSurfaceMutableState* newState) {
return asSB(this)->onFlush(BackendSurfaceAccess::kNoAccess, info, newState);
}
然后进入子类的onFlush方法
external/skia/src/image/SkSurface_Gpu.cpp
GrSemaphoresSubmitted SkSurface_Gpu::onFlush(BackendSurfaceAccess access, const GrFlushInfo& info,
const GrBackendSurfaceMutableState* newState) {
auto dContext = fDevice->recordingContext()->asDirectContext();
if (!dContext) {
return GrSemaphoresSubmitted::kNo;
}
GrSurfaceDrawContext* sdc = fDevice->surfaceDrawContext();
return dContext->priv().flushSurface(sdc->asSurfaceProxy(), access, info, newState);
}
fDevice是一个GpuDevice类型的对象,前面介绍过,构造一个GpuDevice对象需要一个RecordingContext对象,它是在RenderThread初始化的时候创建的GrDirectContext对象,同时也需要一个GrSurfaceDrawContext对象,它实际持有GrSurface对象,因此这里通过调用asSurfaceProxy方法返回的就是这个GrSurface的代理对象。然后到调用flushSurface
来提交指令。
external/skia/src/gpu/GrDirectContextPriv.h
/** Version of above that flushes for a single proxy. Null is allowed. */
GrSemaphoresSubmitted flushSurface(
GrSurfaceProxy* proxy,
SkSurface::BackendSurfaceAccess access = SkSurface::BackendSurfaceAccess::kNoAccess,
const GrFlushInfo& info = {},
const GrBackendSurfaceMutableState* newState = nullptr) {
size_t size = proxy ? 1 : 0;
return this->flushSurfaces({&proxy, size}, access, info, newState);
}
GrSemaphoresSubmitted flushSurfaces(
SkSpan<GrSurfaceProxy*>,
SkSurface::BackendSurfaceAccess = SkSurface::BackendSurfaceAccess::kNoAccess,
const GrFlushInfo& = {},
const GrBackendSurfaceMutableState* newState = nullptr);
实现如下:
GrSemaphoresSubmitted GrDirectContextPriv::flushSurfaces(
SkSpan<GrSurfaceProxy*> proxies,
SkSurface::BackendSurfaceAccess access,
const GrFlushInfo& info,
const GrBackendSurfaceMutableState* newState) {
ASSERT_SINGLE_OWNER
GR_CREATE_TRACE_MARKER_CONTEXT("GrDirectContextPriv", "flushSurfaces", fContext);
if (fContext->abandoned()) {
if (info.fSubmittedProc) {
info.fSubmittedProc(info.fSubmittedContext, false);
}
if (info.fFinishedProc) {
info.fFinishedProc(info.fFinishedContext);
}
return GrSemaphoresSubmitted::kNo;
}
...
return fContext->drawingManager()->flushSurfaces(proxies, access, info, newState);
}
通过调用drawingManager的flushSurfaces来提交指令,这里就进入到本文的主要内容了。
2 . GrDrawingManager.flushSurfaces
先来看看下一下这个drawingManager是哪里来的。GrDirectContext继承自GrRecordingContext,在GrRecordingContext初始化时,初始化了一个drawingManager
external/skia/src/gpu/GrRecordingContext.cpp
bool GrRecordingContext::init() {
...
if (this->options().fDisableDistanceFieldPaths) {
prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall;
}
bool reduceOpsTaskSplitting = false;
if (this->caps()->avoidReorderingRenderTasks()) {
reduceOpsTaskSplitting = false;
} else if (GrContextOptions::Enable::kYes == this->options().fReduceOpsTaskSplitting) {
reduceOpsTaskSplitting = true;
} else if (GrContextOptions::Enable::kNo == this->options().fReduceOpsTaskSplitting) {
reduceOpsTaskSplitting = false;
}
fDrawingManager.reset(new GrDrawingManager(this,
prcOptions,
reduceOpsTaskSplitting));
return true;
在这里new出现一个GrDrawingManager,并用fDrawingManager引用它。 它的flushSurfaces方法如下
GrSemaphoresSubmitted GrDrawingManager::flushSurfaces(
SkSpan<GrSurfaceProxy*> proxies,
SkSurface::BackendSurfaceAccess access,
const GrFlushInfo& info,
const GrBackendSurfaceMutableState* newState) {
...
auto direct = fContext->asDirectContext();
GrGpu* gpu = direct->priv().getGpu();
SkASSERT(gpu);
...
bool didFlush = this->flush(proxies, access, info, newState);
...
if (!didFlush || (!direct->priv().caps()->semaphoreSupport() && info.fNumSemaphores)) {
return GrSemaphoresSubmitted::kNo;
}
return GrSemaphoresSubmitted::kYes;
}
继续调用了flush方法,然后didflush表示以及提交完毕,返回GrSemaphoresSubmitted::kYes。 flush方法非常复杂,需要详细捋一捋。
bool GrDrawingManager::flush(
SkSpan<GrSurfaceProxy*> proxies,
SkSurface::BackendSurfaceAccess access,
const GrFlushInfo& info,
const GrBackendSurfaceMutableState* newState) {
...
auto dContext = fContext->asDirectContext();
SkASSERT(dContext);
dContext->priv().clientMappedBufferManager()->process();
GrGpu* gpu = dContext->priv().getGpu();
// We have a non abandoned and direct GrContext. It must have a GrGpu.
SkASSERT(gpu);
fFlushing = true;
auto resourceProvider = dContext->priv().resourceProvider();
auto resourceCache = dContext->priv().getResourceCache();
this->sortTasks();
if (!fCpuBufferCache) {
// We cache more buffers when the backend is using client side arrays. Otherwise, we
// expect each pool will use a CPU buffer as a staging buffer before uploading to a GPU
// buffer object. Each pool only requires one staging buffer at a time.
int maxCachedBuffers = fContext->priv().caps()->preferClientSideDynamicBuffers() ? 2 : 6;
fCpuBufferCache = GrBufferAllocPool::CpuBufferCache::Make(maxCachedBuffers);
}
GrOpFlushState flushState(gpu, resourceProvider, &fTokenTracker, fCpuBufferCache);
GrOnFlushResourceProvider onFlushProvider(this);
....
bool usingReorderedDAG = false;
GrResourceAllocator resourceAllocator(dContext);
if (fReduceOpsTaskSplitting) {
usingReorderedDAG = this->reorderTasks(&resourceAllocator);
if (!usingReorderedDAG) {
resourceAllocator.reset();
}
}
if (!resourceAllocator.failedInstantiation()) {
if (!usingReorderedDAG) {
for (const auto& task : fDAG) {
SkASSERT(task);
task->gatherProxyIntervals(&resourceAllocator);
}
resourceAllocator.planAssignment();
}
resourceAllocator.assign();
}
bool flushed = !resourceAllocator.failedInstantiation() &&
this->executeRenderTasks(&flushState);
this->removeRenderTasks();
gpu->executeFlushInfo(proxies, access, info, newState);
// Give the cache a chance to purge resources that become purgeable due to flushing.
if (flushed) {
resourceCache->purgeAsNeeded();
flushed = false;
}
if (flushed) {
resourceCache->purgeAsNeeded();
}
fFlushingRenderTaskIDs.reset();
fFlushing = false;
return true;
}
flush函数主要在做几件事件,
- 1调用sortTasks对task进行topo排序 ,这里的task是来自于fDAG变量,它保存的是利用SkCanvas绘制的指令。
- 2 创建f在CPU侧的buffer缓存CpuBufferCache,
- 3 如果task对应的GrSurface没有分配资源的话,利用resourceProvider和resourceAllocator来为task分配GPU寄存器资源,
- 4 如果分配资源没有失败,则调用executeRenderTasks来执行绘制任务,并提交的GPU。
- 5 最后调用gpu->executeFlushInfo来完成flush。
分配寄存器資源的原理就不再这里进一步分析。我們來看一下executeRenderTasks的流程
3. GrDrawingManager.executeRenderTasks
bool GrDrawingManager::executeRenderTasks(GrOpFlushState* flushState) {
bool anyRenderTasksExecuted = false;
for (const auto& renderTask : fDAG) {
if (!renderTask || !renderTask->isInstantiated()) {
continue;
}
SkASSERT(renderTask->deferredProxiesAreInstantiated());
renderTask->prepare(flushState);
}
// Upload all data to the GPU
flushState->preExecuteDraws();
// For Vulkan, if we have too many oplists to be flushed we end up allocating a lot of resources
// for each command buffer associated with the oplists. If this gets too large we can cause the
// devices to go OOM. In practice we usually only hit this case in our tests, but to be safe we
// put a cap on the number of oplists we will execute before flushing to the GPU to relieve some
// memory pressure.
static constexpr int kMaxRenderTasksBeforeFlush = 100;
int numRenderTasksExecuted = 0;
// Execute the onFlush renderTasks first, if any.
for (sk_sp<GrRenderTask>& onFlushRenderTask : fOnFlushRenderTasks) {
if (!onFlushRenderTask->execute(flushState)) {
SkDebugf("WARNING: onFlushRenderTask failed to execute.\n");
}
SkASSERT(onFlushRenderTask->unique());
onFlushRenderTask->disown(this);
onFlushRenderTask = nullptr;
if (++numRenderTasksExecuted >= kMaxRenderTasksBeforeFlush) {
flushState->gpu()->submitToGpu(false);
numRenderTasksExecuted = 0;
}
}
fOnFlushRenderTasks.reset();
// Execute the normal op lists.
for (const auto& renderTask : fDAG) {
SkASSERT(renderTask);
if (!renderTask->isInstantiated()) {
continue;
}
if (renderTask->execute(flushState)) {
anyRenderTasksExecuted = true;
}
if (++numRenderTasksExecuted >= kMaxRenderTasksBeforeFlush) {
flushState->gpu()->submitToGpu(false);
numRenderTasksExecuted = 0;
}
}
SkASSERT(!flushState->opsRenderPass());
SkASSERT(fTokenTracker.nextDrawToken() == fTokenTracker.nextTokenToFlush());
// We reset the flush state before the RenderTasks so that the last resources to be freed are
// those that are written to in the RenderTasks. This helps to make sure the most recently used
// resources are the last to be purged by the resource cache.
flushState->reset();
return anyRenderTasksExecuted;
}
这里的逻辑相对来说不是很复杂。它这执行的任务有两种。一种是fOnFlushRenderTasks中的GrRenderTask,它是在flush过程中产生的新的rendertask,可能是空的。另外一种是fDAG中的GrRenderTask,这是绘制时产生的rendertask。他们的实际类型是GrOpsTask,是GrRenderTask的子类.
external/skia/src/gpu/GrOpsTask.h
class GrOpsTask : public GrRenderTask {}
他们是在GrDrawingManager的newOpsTask方法中生成的,SkCanas中绘制时调用的就是这个方法,因此skcanvas中的绘制生成的Ops最终其实就是保存在GrDrawingManager的fDAG中
sk_sp<GrOpsTask> GrDrawingManager::newOpsTask(GrSurfaceProxyView surfaceView,
sk_sp<GrArenas> arenas,
bool flushTimeOpsTask) {
SkDEBUGCODE(this->validate());
SkASSERT(fContext);
this->closeActiveOpsTask();
sk_sp<GrOpsTask> opsTask(new GrOpsTask(this,
std::move(surfaceView),
fContext->priv().auditTrail(),
std::move(arenas)));
SkASSERT(this->getLastRenderTask(opsTask->target(0)) == opsTask.get());
if (flushTimeOpsTask) {
fOnFlushRenderTasks.push_back(opsTask);
} else {
this->appendTask(opsTask);
fActiveOpsTask = opsTask.get();
}
SkDEBUGCODE(this->validate());
return opsTask;
}
对于这两种task,都会通过调用GrRenderTask::isInstantiated()去检查是否已经初始化分配好了GPU寄存器资源,没有分配的会被过滤掉。然后对有效的task的执行execute方法,最后进入到实现类GrOpsTask的onExecute方法。每执行完kMaxRenderTasksBeforeFlush个任务后再调用 flushState->gpu()->submitToGpu(false);来提交到GPU。
4. 总结
本文分析了绘制流程中一个非常重要的类GrDrawingManager,它保存着GPU渲染前的绘制命令最后形态的集合fDAG。flush为fDAG
中的绘制任务(task中持有一个GrSurface或者GrTexture)中需要分配资源的,会分配GPU寄存器资源,然后调用Gpu的submit方法来提交GPU渲染。基于个人的理解,如有疏误,欢迎同行指正。