Android图形显示系统4 图像生产者(下)

一 概述

在上一篇文章 Android图形显示系统2 图像消费者 中,我们详细地讲解了图像消费者,我们已经了解了 Android 中的图像元数据是如何被 SurfaceFlinger,HWComposer 或者 OpenGL ES 消费的,那么,图像元数据又是怎么生成的呢?这一篇文章就来详细介绍 Android 中的图像生产者—— SKIA,OPenGL ES,Vulkan,他们是 Android 中最重要的三支画笔。

二 Skia

Skia 是谷歌开源的一款跨平台的 2D 图形引擎,目前谷歌的 Chrome 浏览器、Android、Flutter、以及火狐浏览器、火狐操作系统和其它许多产品都使用它作为图形引擎,它作为 Android 系统第三方软件,放在 external/skia/ 目录下。虽然 Android 从 4.0 开始默认开启了硬件加速,但不代表 Skia 的作用就不大了,其实 Skia 在 Android 中的地位是越来越重要了,从 Android 8 开始,我们可以选择使用 Skia 进行硬件加速,Android 9 开始就默认使用 Skia 来进行硬件加速。Skia 的硬件加速主要是通过 copybit 模块调用 OpenGL 或者 SKia 来实现。

在这里插入图片描述
由于 Skia 的硬件加速也是通过 Copybit 模块调用的 OpenGL 或者 Vulkan 接口,所以我们这儿只说说 Skia 通过 cpu 绘制的,也就是软绘的方式。还是老规则,先看看 Skia 要如何使用

2.1 如何使用Skia

OpenGL ES 的使用要配合 EGL,需要初始化 Display,surface,context 等,用法还是比较繁琐的,Skia 在使用上就方便很多了。掌握 Skia 绘制三要素:画板 SkCanvas、画纸 SkBitmap、画笔 SkPaint,我们就能很轻松的用 Skia 来绘制图形。

下面详细的解释 Skia 的绘图三要素

1.SkBitmap 用来存储图形数据,它封装了与位图相关的一系列操作

SkBitmap bitmap = new SkBitmap();
//设置位图格式及宽高
bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
//分配位图所占空间
bitmap->allocPixels();

2.SkCanvas 封装了所有画图操作的函数,通过调用这些函数,我们就能实现绘制操作。

//使用前传入bitmap
SkCanvas canvas(bitmap);
//移位,缩放,旋转,变形操作
translate(SkiaScalar dx, SkiaScalar dy);
scale(SkScalar sx, SkScalar sy);
rotate(SkScalar degrees);
skew(SkScalar sx, SkScalar sy);
//绘制操作
drawARGB(u8 a, u8 r, u8 g, u8 b....)  //给定透明度以及红,绿,兰3色,填充整个可绘制区域。
drawColor(SkColor color...) //给定颜色color, 填充整个绘制区域。
drawPaint(SkPaint& paint) //用指定的画笔填充整个区域。
drawPoint(...)//根据各种不同参数绘制不同的点。
drawLine(x0, y0, x1, y1, paint) //画线,起点(x0, y0), 终点(x1, y1), 使用paint作为画笔。
drawRect(rect, paint) //画矩形,矩形大小由rect指定,画笔由paint指定。
drawRectCoords(left, top, right, bottom, paint),//给定4个边界画矩阵。
drawOval(SkRect& oval, SkPaint& paint) //画椭圆,椭圆大小由oval矩形指定。
//……其他操作

3.SkPaint 用来设置绘制内容的风格,样式,颜色等信息

setAntiAlias: 设置画笔的锯齿效果。 
setColor: 设置画笔颜色 
setARGB:  设置画笔的a,r,p,g值。 
setAlpha:  设置Alpha值 
setTextSize: 设置字体尺寸。 
setStyle:  设置画笔风格,空心或者实心。 
setStrokeWidth: 设置空心的边框宽度。 
getColor:  得到画笔的颜色 
getAlpha:  得到画笔的Alpha值。 

我们看一个完整的使用 Demo

void draw() {
   
    SkBitmap bitmap = new SkBitmap();
    //设置位图格式及宽高
    bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
    //分配位图所占空间
    bitmap->allocPixels();
    //使用前传入bitmap
    SkCanvas canvas(bitmap);
    //定义画笔
    SkPaint paint1, paint2, paint3;

    paint1.setAntiAlias(true);
    paint1.setColor(SkColorSetRGB(255, 0, 0));
    paint1.setStyle(SkPaint::kFill_Style);

    paint2.setAntiAlias(true);
    paint2.setColor(SkColorSetRGB(0, 136, 0));
    paint2.setStyle(SkPaint::kStroke_Style);
    paint2.setStrokeWidth(SkIntToScalar(3));

    paint3.setAntiAlias(true);
    paint3.setColor(SkColorSetRGB(136, 136, 136));

    sk_sp<SkTextBlob> blob1 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.0f, 0.0f));
    sk_sp<SkTextBlob> blob2 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.5f, 0.0f));

    canvas->clear(SK_ColorWHITE);
    canvas->drawTextBlob(blob1.get(), 20.0f, 64.0f, paint1);
    canvas->drawTextBlob(blob1.get(), 20.0f, 144.0f, paint2);
    canvas->drawTextBlob(blob2.get(), 20.0f, 224.0f, paint3);
}

这个 Demo 的效果如下:
在这里插入图片描述
了解了 Skia 如何使用,我们接着看两个场景:Skia 进行软件绘制,Flutter 界面绘制

2.2 Skia进行软件绘制

在上一篇文章中我们讲了通过使用 OpenGL 渲染的硬件绘制方式,这里会接着讲使用 Skia 渲染的软件绘制方式,虽然 Android 默认开启了硬件加速,但是由于硬件加速会有耗电和内存的问题,一些系统应用和常驻应用依然是使用的软件绘制的方式,软绘入口还是在 draw 方法中。

//文件-->/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
   
    ......
    draw(fullRedrawNeeded);
    ......
}

private void draw(boolean fullRedrawNeeded) {
   
    Surface surface = mSurface;
    if (!surface.isValid()) {
   
        return;
    }
    ......
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
   
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
   
            if (mAttachInfo.mThreadedRenderer != null &&
            mAttachInfo.mThreadedRenderer.isEnabled()) {
                   
                ......
                //硬件渲染
           mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

            } else {
                  
                ......
                //软件渲染
                if (!drawSoftware(surface, mAttachInfo, xOffset,
                    yOffset, scalingRequired, dirty)) {
   
                    return;
                }
            }
        }
        ......
    }
    ......
}

我们来看看 drawSoftware 函数的实现

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
   

    // Draw with software renderer.
    final Canvas canvas;
    ......
    canvas = mSurface.lockCanvas(dirty);    
    ......     
    mView.draw(canvas);    
    ......    
    surface.unlockCanvasAndPost(canvas);    
    ......    
    return true;
}

drawSoftware 函数的流程主要为三步:

  • 通过 mSurface.lockCanvas 获取 Canvas
  • 通过 draw 方法,将根 View 及其子 View 遍历绘制到 Canvas 上
  • 通过 surface.unlockCanvasAndPost 将绘制内容提交给 surfaceFlinger 进行合成

2.2.1 Lock Surface

我们先来看第一步,这个 Canvas 对应着 Native 层的 SkCanvas。

//文件-->/frameworks/base/core/java/android/view/Surface.java
public Canvas lockCanvas(Rect inOutDirty)
    throws Surface.OutOfResourcesException, IllegalArgumentException {
   
    synchronized (mLock) {
   
        checkNotReleasedLocked();
        if (mLockedObject != 0) {
              
            throw new IllegalArgumentException("Surface was already locked");
        }
        mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
        return mCanvas;
    }
}

lockCanvas 函数中通过 JNI 函数 nativeLockCanvas,创建 Nativce 层的 Canvas,nativeLockCanvas 的入参 mNativeObject 对应着 Native 层的 Surface,关于 Surface 和 Buffer 的知识,在下一篇图形缓冲区中会详细简介,这里不做太多介绍。我们直接看 nativeLockCanvas 的实现。

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
   
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
   
        doThrowIAE(env);
        return 0;
    }

    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;

    if (dirtyRectObj) {
   
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    }

    ANativeWindow_Buffer outBuffer;
    //关键点1,获取用来存储图形绘制的buffer
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);
    if (err < 0) {
   
        const
### Android 图形显示系统架构原理 #### VSYNC 同步机制 VSYNC信号同步了应用程序唤醒开始渲染的时间、SurfaceFlinger唤醒合成屏幕的时间以及显示器刷新周期[^3]。 #### 应用程序层面的绘制流程 当应用需要更新界面时,会通过Canvas对象进行绘图操作。这些绘图命令最终会被传递给底层的RenderThread线程来执行实际的OpenGL ES调用来完成画面渲染工作[^1]。 #### Surface 和 BufferQueue 的作用 每一个窗口都对应着一个Surface实例,在Android中负责管理图像缓冲区队列(BufferQueue),它连接生产者(如ViewRootImpl中的DrawHandler)和消费者(SurfaceFlinger)。 #### SurfaceFlinger 组件的功能 作为硬件抽象层(HAL)的一部分,SurfaceFlinger接收来自不同进程提交的画面帧并将其组合成完整的屏幕内容输出到物理屏幕上展示出来。此过程涉及到多个Layer叠加处理以实现透明度效果和其他视觉特效[^2]。 #### OpenGL ES 渲染管道 对于游戏或其他高性能需求的应用来说,它们可以直接利用OpenGLES API来进行复杂的三维场景构建与渲染而不必经过Java级别的视图体系结构;此时,HWC (Hardware Composer) 将协助GPU加速渲染效率更高的图形呈现方式。 ```java // Java 层面创建画布并绘制简单矩形的例子 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(Color.RED); canvas.drawRect(0, 0, getWidth(), getHeight(), paint); // 填充红色背景 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值