沭阳网站建设多少钱,怎么做兼职类网站,深圳网络营销方案,新平台推广文案ps#xff1a;本文内容较干#xff0c;建议收藏后反复边跟进源码边思考设计思想。壹渲染管线的基础架构为什么叫渲染管线#xff1f;这里是因为整个渲染的过程涉及多道工序#xff0c;像管道里的流水线一样#xff0c;一道一道的处理数据的过程#xff0c;所以使用渲染管…ps本文内容较干建议收藏后反复边跟进源码边思考设计思想。壹渲染管线的基础架构为什么叫渲染管线这里是因为整个渲染的过程涉及多道工序像管道里的流水线一样一道一道的处理数据的过程所以使用渲染管线还是比较形象的。接下来我们来看下渲染的整个架构。Android的渲染过程需要按照是否开启硬件加速分别看待默认是开启硬件加速的那么在应用层使用Canvas的一些绘图API会预先转换成OpenGL指令或者Vulkan指令然后由OpenGL/Vulkan直接操作GPU执行像素化的过程。而如果不开启硬件加速Canvas的绘图指令就会调用skia库直接利用CPU绘制Bitmap图。1、开启硬件加速DisplayList到底是什么class DisplayListData { // 基础绘制指令 VectorDisplayListOp* displayListOps; // 子视图的应用 VectorDrawRenderNodeOp* children; // 命令分组用于 Z 轴排序 VectorChunk chunks;}这里说的DisplayList中的指令、GPU可执行的指令序列和GPU原生指令有什么不同呢Android应用程序窗口的根视图是虚拟的抽象为一个Root Render Node。此外一个视图如果设置有Background那么这个Background也会抽象为一个Background Render Node。Root Render Node、Background Render Node和其它真实的子视图除了TextureView和软件渲染的子视图之外都具有Display List并且是通过一个称为Display List Renderer的对象进行构建的。TextureView不具有Display List它们是通过一个称为Layer Renderer的对象以Open GL纹理的形式来绘制的不过这个纹理也不是直接就进行渲染的而是先记录在父视图的Display List中以后再进行渲染的。同样软件渲染的子视图也不具有Display List它们先绘制在一个Bitmap上然后这个Bitmap再记录在父视图的Display List中以后再进行渲染的。DisplayList中的指令是一种抽象化的GPU绘制指令GPU是无法直接使用的,每个View对应一个包含这些类型的指令了解即可基础绘制操作位图绘制DrawBitmapOp(无法硬件渲染只能软件渲染的视图的Bitmap)纹理绘制DrawLayerOp(如TextureView的OpenGL 纹理)图形绘制DrawPathOp、DrawRectOp等由Canvas.drawXXX()生成视图层级操作子视图引用DrawRenderNodeOp,封装子视图的RenderNode递归执行其Display List背景绘制Background Render Node的绘制指令独立DisplayList状态控制指令ReorderBarrier标记后续子视图需要按照Z轴排序用于重叠视图InorderBarrier标记后续子视图按默认顺序排序无重叠Save/Restore保存/恢复画布状态等等概括起来说DisplayList存储的指令OpenGL/Vulkan命令的预处理抽象这些指令在渲染线程中被转换为GPU可执行的OpenGL/Vulkan指令。CPU可执行的指令序列是指在Render Thread中将Displaylist中的抽象指令转换成OpenGL/Vulkan的指令。GPU原生指令是根据不同硬件生成的最基础的硬件操作指令比如操作寄存器等。2、关闭硬件加速如果将整个执行流程按照从应用到底层GPU操作分层可以这样分层贰渲染阶段过程硬件加速1、UI线程DisplayList生成当需要进行画面绘制的时候(VSync信号来临)ViewRootImpl.TraversalRunnable.run()收到回调调用ViewRootImpl.doTraversal()方法这个方法中调用ViewRootImpl中的performTraversal()方法到performDraw()再到draw()方法判断是否开启硬件加速如果不开启就调用drawSoftware()如果开启就调用ThreadRenderer.draw()方法再继续调用ThreadRenderer的updateRootDisplayList()然后调到View的updateDisplayListDirty()然后调到RenderNode.beginRecording()方法这里面调到RecordingCanvas.obtain()方法obtain()方法里面通过new RecordingCanvas(),然后通过JNI调用nCreateDisplayListCanvas()具体是调用Native哪个类方法创建Native层的RecordingCanvas。回到View的updateDisplayListDirty()方法里执行完RenderNode.beginRecording()方法后得到RecordingCanvas的实例canvas然后继续执行View里面的draw()方法根据实际情况drawBackground(),再调onDraw()以及draw子视图里面都是调用RecordingCanvas的api,最终这些API都是JNI调用在Native层调用的时候会将各种操作记录为各种抽象命令并不直接进行画图。2、渲染线程的并行化处理上一步已经构建好DisplayList数据ThreadedRenderer调用父类HardwareRenderer函数syncAndrDrawFrame()函数里使用JNI调用nSyncAndDrawFrame(...)也就调用到android_graphics_HardwareRenderer.cpp中的android_view_ThreadedRenderer_syncAndDrawFrame()函数。这个函数里调用RenderProxy.cpp的syncAndDrawFrame()函数syncAndDrawFrame()函数里调用DrawFrameTask.cpp的drawFrame()函数此函数调用函数当前类的postAndWait()函数然后函数里调用RenderThread的queue()函数将当前Task入到渲染线程的队列当任务执行时回调到DrawFrameTask.cpp中的run()函数这个函数就是渲染的核心函数void DrawFrameTask::run() { ... if (CC_LIKELY(canDrawThisFrame)) { // 渲染上下文CanvasContext.draw() context-draw(); } else { // wait on fences so tasks dont overlap next frame context-waitOnFences(); } ...}真正的绘制是通过调用ContextCanvas的draw()函数里面再通过mRenderPipeline-draw()函数将DisplayList命令转换成GPU命令mRenderPipeLine这个就是实际实现渲染的管线他有可能是通过OpenGL来实现或者通过Vulkan来实现代码中有三种渲染管线类型(不同Android版本有一些区别)SkiaOpenGLPipeline基于OpenGL的Skia渲染管线使用OpenGL API进行GPU渲染兼容性好支持大多数GPU性能稳定SkiaVulkanPipeline基于Vulkan的Skia渲染管线使用Vulkan API进行GPU渲染更低的CPU开销更好的多线程支持更现代的GPU APISkiaCpuPipeline基于CPU的Skia渲染管线用于非Android平台纯CPU渲染不依赖GPU用于调试或者特殊环境那么Android源码中是怎么选择使用哪种渲染管线呢我们看CanvasContext的create()函数CanvasContext* CanvasContext::create(RenderThread thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, pid_t uiThreadId, pid_t renderThreadId) { // 根据系统属性配置获取使用的渲染管线类型 auto renderType Properties::getRenderPipelineType(); switch (renderType) { case RenderPipelineType::SkiaGL: return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_uniqueskiapipeline::SkiaOpenGLPipeline(thread), uiThreadId, renderThreadId); case RenderPipelineType::SkiaVulkan: return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_uniqueskiapipeline::SkiaVulkanPipeline(thread), uiThreadId, renderThreadId);#ifndef __ANDROID__ case RenderPipelineType::SkiaCpu: return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_uniqueskiapipeline::SkiaCpuPipeline(thread), uiThreadId, renderThreadId);#endif default: LOG_ALWAYS_FATAL(canvas context type %d not supported, (int32_t)renderType); break; } return nullptr;}目前官方已经全方面切换到Vulkan库那我们就以Vulkan库继续分析。那么上文中的mRenderPipeline-draw()函数实际就是SkiaVulkanPipeline-draw()函数调用draw()函数中调用到父类SkiaGpuPipeline的父类SkiaPipeline的renderFrame()函数mark1:下方寻找SkCanvas实例时机时返回到这里然后继续调到SkiaPipeline-renderFrameImpl()函数。这个函数里调用RenderNodeDrawable的基类SkDrawable-draw()函数draw()函数又调到子类RenderNodeDrawable-onDraw()函数渲染线程阶段时序图1接着继续调到RenderNodeDrawable-forceDraw()函数再到drawContent()函数函数调用SkiaDisplaylist-draw()函数draw()函数委托给DisplayListData-draw()函数DisplayListData的定义在RecordingCanvas.h文件中实现在RecordingCanvas.cpp文件中也就是DisplayListData-draw()在RecordingCanvas.cpp的DisplayListData::draw()函数。 void DisplayListData::draw(SkCanvas* canvas) const { SkAutoCanvasRestore acr(canvas, false); this-map(draw_fns, canvas, canvas-getTotalMatrix()); }inline void DisplayListData::map(const Fn fns[], Args... args) const { auto end fBytes.get() fUsed; for (const uint8_t* ptr fBytes.get(); ptr end;) { auto op (const Op*)ptr; auto type op-type; auto skip op-skip; if (auto fn fns[type]) { // We replace no-op functions with nullptrs fn(op, args...); // to avoid the overhead of a pointless call. } ptr skip; } }作为一个Java开发者这代码看过去可能会一脸懵。别急让我们来稍微拆解下代码首先看【this-map(draw_fns, canvas, canvas-getTotalMatrix());】这一行调用map函数时传入的draw_fns是啥#define X(T) \ [](const void* op, SkCanvas* c, const SkMatrix original) { \ ((const T*)op)-draw(c, original); \ }, static const draw_fn draw_fns[] { #include DisplayListOps.in }; #undef X 这是C中的一个X宏X Macro技术为了避免造成更多的困惑不再细讲便于Java开发者理解可粗略的认为draw_fns就是一个数组里面存储了绘制相关的操作类型函数指针具体存储了哪些操作定义在DisplayListOps.in文件中X(Save)X(Restore)X(SaveLayer)X(SaveBehind)X(Concat)X(SetMatrix)X(Scale)X(Translate)X(ClipPath)X(ClipRect)X(ClipRRect)X(ClipRegion)X(ClipShader)X(ResetClip)X(DrawPaint)X(DrawBehind)X(DrawPath)X(DrawRect)X(DrawRegion)X(DrawOval)X(DrawArc)X(DrawRRect)X(DrawDRRect)X(DrawAnnotation)X(DrawDrawable)X(DrawPicture)X(DrawImage)X(DrawImageRect)X(DrawImageLattice)X(DrawTextBlob)X(DrawPatch)X(DrawPoints)X(DrawVertices)X(DrawAtlas)X(DrawShadowRec)X(DrawVectorDrawable)X(DrawRippleDrawable)X(DrawWebView)X(DrawSkMesh)X(DrawMesh)回到map()函数中结合上面的分析可以理解为fBytes.get()是DisplayList绘图指令的内存地址块的首地址然后不断遍历这块内存取出指令根据指令的type结合fns[]函数指针数组匹配到指令对应的函数指针然后调用对应的函数我们这里以DrawPath()为例回到RecordingCanvas.cpp文件中根据宏定义会调用【((const T*)op)-draw(c, original)】对应到struct DrawPath final : Op { static const auto kType Type::DrawPath; DrawPath(const SkPath path, const SkPaint paint) : path(path), paint(paint) {} SkPath path; SkPaint paint; void draw(SkCanvas* c, const SkMatrix) const { c-drawPath(path, paint); }};然后调到SkCanvas-drawPath(),函数里继续调用onDrawPath()函数 void SkCanvas::onDrawPath(const SkPath path, const SkPaint paint) { if (!path.isFinite()) { return; } const SkRect pathBounds path.getBounds(); if (!path.isInverseFillType() this-internalQuickReject(pathBounds, paint)) { return; } if (path.isInverseFillType() pathBounds.width() 0 pathBounds.height() 0) { this-internalDrawPaint(paint); return; } auto layer this-aboutToDraw(paint, path.isInverseFillType() ? nullptr : pathBounds); if (layer) { this-topDevice()-drawPath(path, layer-paint(), false); } }关键的一行this-topDevice()-drawPath(path, layer-paint(), false); mark0那么topDevice()获取到的设备是什么呢代码跟到这里我们心中是否有个疑问从开始渲染的时候选择Vulkan渲染管线进行渲染怎么跳来跳去都是在Skia库中呢Vulkan不是要直接转换GPU命令的吗在哪里去转的所以我们回到选择Vulkan渲染管线的代码的前面先回到标注为mark1的位置SkiaPipeline的renderFrame()函数void SkiaPipeline::renderFrame() { bool previousSkpEnabled Properties::skpCaptureEnabled; if (mPictureCapturedCallback) { Properties::skpCaptureEnabled true; } // Initialize the canvas for the current frame, that might be a recording canvas if SKP // capture is enabled. SkCanvas* canvas tryCapture(surface.get(), nodes[0].get(), layers); // draw all layers up front renderLayersImpl(layers, opaque); renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform); endCapture(surface.get()); if (CC_UNLIKELY(Properties::debugOverdraw)) { renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform); } Properties::skpCaptureEnabled previousSkpEnabled;}我们找到canvas是怎么得到的tryCapture(surface.get(), nodes[0].get(), layers);mark2这个函数也就是通过surface中获取的那么surface是怎么实例化的再往上回到SkiaVulkanPipeline-draw()函数中IRenderPipeline::DrawResult SkiaVulkanPipeline::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::vectorspRenderNode renderNodes, FrameInfoVisualizer* profiler, const HardwareBufferRenderParams bufferParams, std::mutex profilerLock) { sk_spSkSurface backBuffer; SkMatrix preTransform; if (mHardwareBuffer) { backBuffer getBufferSkSurface(bufferParams); preTransform bufferParams.getTransform(); } else { backBuffer mVkSurface-getCurrentSkSurface(); preTransform mVkSurface-getCurrentPreTransform(); } ... renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, preTransform); ... return {true, drawResult.submissionTime, std::move(drawResult.presentFence)};}省略了其他的干扰代码backBuffer getBufferSkSurface(bufferParams); 这一行是获取SkSurface的我们一直将代码跟进去这里省略了有兴趣的同学自行去跟下源码跳转实在是太多了。最终跳到SkSurface_Ganesh.cpp类中sk_spSkSurface WrapBackendTexture() { ... auto device rContext-priv().createDevice(grColorType, std::move(proxy), std::move(colorSpace), origin, SkSurfacePropsCopyOrDefault(props), skgpu::ganesh::Device::InitContents::kUninit); ... return sk_make_spSkSurface_Ganesh(std::move(device));}可以看到device实例的创建代码了继续跟进调进了GrRecordingContextPriv.cpp这个类sk_spskgpu::ganesh::Device GrRecordingContextPriv::createDevice() { return skgpu::ganesh::Device::Make(this-context(), budgeted, ii, fit, sampleCount, mipmapped, isProtected, origin, props, init);}可以看到是创建skgpu::ganesh::Device实例让我们再回到SkCanvas.cpp中的onDrawPath()函数中的this-topDevice()-drawPath(path, layer-paint(), false);这里的device和topDevice是不是同一个东西呢topDevice()函数中是通过fMCRec-fDevice返回fMCRec的实例是在SkCanvas-init()函数中实例化的void SkCanvas::init(sk_spSkDevice device) { ... fMCRec new (fMCStack.push_back()) MCRec(device.get()); ...}而init()函数是在SkCanvas的构造函数中调用的这里又回到上面mark2的位置SkCanvas的获得过程SkiaPipeLine-tryCapture()函数中通过surface-getCanvas()获得canvas,一直调到SkSurface_Base.h中定义的onNewCanvas()函数这个是基类实现类也就回到了上面的surface的创建过程的分析过程知道为SkSurface_Ganesh.cpp文件我们看里面的onNewCanvas()函数的实现里面的device就是在SkSurface_Ganesh的构造函数中传入的也就是我们WrapBackendTexture()函数中创建的device到这里终于闭环了所以topDevice()就是skgpu::ganesh::Device实例那么继续skgpu::ganesh::Device-drawPath()然后调到SurfaceDrawContext-drawPath();因为调用过程比较多这里省略部分调用链一直到获得一个适合的Renderer进行渲染这里我们以画虚线为例使用DashLinePathRenderer.cpp-onDrawPath()函数bool DashLinePathRenderer::onDrawPath(const DrawPathArgs args) { ... GrOp::Owner op DashOp::MakeDashLineOp(args.fContext, std::move(args.fPaint), *args.fViewMatrix, pts, aaMode, args.fShape-style(), args.fUserStencilSettings); if (!op) { return false; } args.fSurfaceDrawContext-addDrawOp(args.fClip, std::move(op)); return true;}这里将绘制指令封装在DashOp中。至此本阶段过程分析完成。渲染线程阶段时序图23、提交的GPU操作命令接上面生成的命令封装实例后调用args.fSurfaceDrawContext-addDrawOp(args.fClip, std::move(op));看方法名猜测是将封装的操作加到什么地方去我们继续跟踪代码即SurfaceDrawContex-addDrawOp函数中void SurfaceDrawContext::addDrawOp(const GrClip* clip, GrOp::Owner op, const std::functionWillAddOpFn willAddFn) { ... opsTask-addDrawOp(this-drawingManager(), std::move(op), drawNeedsMSAA, analysis,std::move(appliedClip), dstProxyView,GrTextureResolveManager(this-drawingManager()), *this-caps()); ...}是通过调用OpsTask-addDrawOp()函数进行添加的addDrawOp函数中调用recordOp()函数将操作指令记录到操作链OpChains中包含一些合并优化操作减少GPU命令的数量等待flush指令执行任务OpsTask的onExcute函数flush发生的时机可能有以下几个自动触发AutoCheckFlush析构函数资源压力检测帧缓冲区交换前手动触发SkSurface::flush()GrContext::flush()显示API调用当GrDrawingManager-flush()函数被调用函数里调用GrRenderTask-execute()函数实际调用的是其实现类OpsTask-onExecute()函数bool OpsTask::onExecute(GrOpFlushState* flushState) { const GrCaps caps *flushState-gpu()-caps(); GrRenderTarget* renderTarget proxy-peekRenderTarget(); SkASSERT(renderTarget); // 创建渲染通道 GrOpsRenderPass* renderPass create_render_pass(flushState-gpu(), proxy-peekRenderTarget(), fUsesMSAASurface, stencil, fTargetOrigin, fClippedContentBounds, fColorLoadOp, fLoadClearColor, stencilLoadOp, stencilStoreOp, fSampledProxies, fRenderPassXferBarriers); flushState-setOpsRenderPass(renderPass); renderPass-begin(); GrSurfaceProxyView dstView(sk_ref_sp(this-target(0)), fTargetOrigin, fTargetSwizzle); // Draw all the generated geometry. for (const auto chain : fOpChains) { if (!chain.shouldExecute()) { continue; } GrOpFlushState::OpArgs opArgs(chain.head(), dstView, fUsesMSAASurface, chain.appliedClip(), chain.dstProxyView(), fRenderPassXferBarriers, fColorLoadOp); flushState-setOpArgs(opArgs); // 遍历指令将其转换成GPU指令 chain.head()-execute(flushState, chain.bounds()); flushState-setOpArgs(nullptr); } renderPass-end(); // 提交给GPU执行指令 flushState-gpu()-submit(renderPass); flushState-setOpsRenderPass(nullptr); return true;}chain.head()-execute(flushState, chain.bounds());这一行将按画虚线为例会调到DashOpImpl-onExecute()函数 void onExecute(GrOpFlushState* flushState, const SkRect chainBounds) override { if (!fProgramInfo || !fMesh) { return; } flushState-bindPipelineAndScissorClip(*fProgramInfo, chainBounds); flushState-bindTextures(fProgramInfo-geomProc(), nullptr, fProgramInfo-pipeline()); flushState-drawMesh(*fMesh); }onExecute函数中调用flushState-drawMesh()函数然后一直调用到GrVkOpsRenderPass.cpp-onDraw()函数继续调用到GrVkCommandBuffer.cpp-draw()函数生成Vulkan 的GPU命令然后回到OpsTask-onExecute()这个函数继续往下看通过flushState-gpu()-submit(renderPass);提交到GPU中执行渲染命令。剩下的就交给GPU去执行命令绘制像素了。叁总结与展望Android通过分层抽象在兼容性与性能间取得完美平衡。每层只需关注相邻接口使Vulkan等新技术可无缝接入现有架构。Vulkan通过瓦解GPU驱动瓶颈将CPU渲染开销从15ms压缩至3ms释放出12ms/帧的GPU算力空间1.指令编译革命•DisplayList → SPIR-V中间指令预编译避免运行时解析•对比OpenGL减少80%驱动层校验指令2.并行化引擎•OpsTask.onExecute()实现OpChain多核分发•渲染通道(RenderPass)无锁提交使DrawCall并发量提升8倍3.零拷贝控制•GrVkCommandBuffer直接操作设备内存•消除OpenGL的显存二次拷贝省去3ms/帧现代图形架构的核心矛盾是绘制复杂度与帧时间确定性的对抗。Android的解法是将非确定操作提前指令编译将确定操作并发管线并行最终驯服GPU这头性能猛兽。此刻我们正站在渲染技术的奇点当Vulkan封印揭开帧率已不是终点而是重构视觉体验的起点。未来会有哪些期待DisplayList预测生成LSTM模型预判下帧指令将Path计算卸载到DSP处理节省GPU 30%负载实时光追管线Vulkan Ray-Tracing扩展点赞关注下一期更精彩本文分析源码基于最新的AOSPhttps://cs.android.com/android/platform/superproject?hlzh-cn