ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SkiaSharp 1.68.2 Android API 变更解读:SKPaintGLSurfaceEventArgs 新增构造函数与 GPU 绘制实践

SkiaSharp 1.68.2 Android API 变更解读:SKPaintGLSurfaceEventArgs 新增构造函数与 GPU 绘制实践
  • 图形学
  • 图像处理
  • 跨平台

【免费下载链接】SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.

项目地址:https://gitcode.com/gh_mirrors/sk/SkiaSharp
点击查看免费下载

本文围绕 SkiaSharp 1.68.2 版本发布说明中 SkiaSharp.Views.Android.dll 的 API 变更文档 展开,聚焦SKPaintGLSurfaceEventArgs新增构造函数这一核心变更,并结合当前仓库的 事件参数源码、Android 平台渲染器实现与官方示例,讲解该类型在SKGLSurfaceView/SKGLTextureViewGPU 渲染链路中的角色。读完本文,你将理解这组构造参数的来源与含义、PaintSurface事件的触发机制,以及如何基于新 API 编写 Android 上的硬件加速绘图代码。

变更速览:1.68.2 中 SkiaSharp.Views.Android 的 API 变更

1.68.2 的发布说明(API diff)针对SkiaSharp.Views.Android.dll记录了唯一一处变更——类型SkiaSharp.Views.Android.SKPaintGLSurfaceEventArgs新增了一个构造函数:

public SKPaintGLSurfaceEventArgs ( SkiaSharp.SKSurface surface, SkiaSharp.GRBackendRenderTarget renderTarget, SkiaSharp.GRSurfaceOrigin origin, SkiaSharp.SKColorType colorType, SkiaSharp.GRGlFramebufferInfo glInfo);

该条目位于 documentation/docfx/releases/1.68.2/SkiaSharp.Views/SkiaSharp.Views.Android.md 的Namespace SkiaSharp.Views.Android一节。与之相对,同版本其他平台的 Views 程序集(如 SkiaSharp.Views.Desktop.md)标注为 "No changes",可见本次 Android 侧的关注点正是这个 GPU 绘制事件参数类型。

新增构造函数的五个参数逐个拆解

这一构造函数把一次 GPU 绘制所需的全部上下文集中在一个事件参数对象中,五个参数各司其职:

参数类型含义
surfaceSKSurface当前正在被绘制的 Skia 表面,绘制指令通过surface.Canvas发出
renderTargetGRBackendRenderTarget当前正在绘制的 GPU 后端渲染目标(封装 OpenGL framebuffer)
originGRSurfaceOrigin渲染目标的表面原点,决定画布坐标系的 Y 轴方向
colorTypeSKColorType渲染目标的颜色类型,描述像素的存储格式
glInfoGRGlFramebufferInfoOpenGL framebuffer 信息,包含 framebuffer ID 与 sized internal format

其中GRGlFramebufferInfo是 GRDefinitions.cs 中定义的结构体,它持有两个关键字段:

  • fboId:OpenGL framebuffer 对象 ID(即GL_FRAMEBUFFER_BINDING查询到的句柄);
  • format:framebuffer 的 sized internal format(如RGBA8),它由SKColorType通过ToGlSizedFormat扩展方法换算而来。

从源码看 SKPaintGLSurfaceEventArgs 的完整构造函数家族

当前仓库中,SKPaintGLSurfaceEventArgs定义于共享源码 SKPaintGLSurfaceEventArgs.cs,通过条件编译映射到 Android、iOS、tvOS、macOS、Tizen、WinUI、UWP、Blazor 等平台命名空间。该类继承自EventArgs,共提供四个公开构造函数:

// 1) 双参数重载:使用默认的 origin 与 colorType public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget renderTarget) : this(surface, renderTarget, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888) { } // 2) 四参数重载(origin + colorType),Info 由 renderTarget 尺寸推导 public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget renderTarget, GRSurfaceOrigin origin, SKColorType colorType) { Surface = surface; BackendRenderTarget = renderTarget; ColorType = colorType; Origin = origin; Info = new SKImageInfo(renderTarget.Width, renderTarget.Height, ColorType); RawInfo = Info; } // 3) 四参数重载(origin + SKImageInfo) public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget renderTarget, GRSurfaceOrigin origin, SKImageInfo info) : this(surface, renderTarget, origin, info, info) { } // 4) 五参数重载(origin + info + rawInfo) public SKPaintGLSurfaceEventArgs(SKSurface surface, GRBackendRenderTarget renderTarget, GRSurfaceOrigin origin, SKImageInfo info, SKImageInfo rawInfo) { Surface = surface; BackendRenderTarget = renderTarget; ColorType = info.ColorType; Origin = origin; Info = info; RawInfo = rawInfo; }

可以看到,双参数重载隐含了两个默认值:GRSurfaceOrigin.BottomLeft与SKColorType.Rgba8888——这与 Android 渲染器内部使用的常量完全一致(详见下文渲染链路)。而 1.68.2 发布说明记录的带glInfo参数重载,从当前源码结构看,GRGlFramebufferInfo已被封装进GRBackendRenderTarget内部(渲染器在创建 render target 时传入glInfo),事件参数层面不再需要单独暴露该参数。

该类的公开属性(只读)包括:

  • Surface:当前绘制表面;
  • BackendRenderTarget:后端渲染目标;
  • ColorType:渲染目标的颜色类型;
  • Origin:渲染目标表面原点;
  • Info:表面图像信息(尺寸 + 颜色类型);
  • RawInfo:底层渲染目标的原始图像信息(在需要区分逻辑尺寸与物理尺寸时使用)。

GRBackendRenderTarget 与 GRGlFramebufferInfo:事件参数背后的 GPU 对象

GRBackendRenderTarget在 GRBackendRenderTarget.cs 中定义,其 OpenGL 构造路径为:

public GRBackendRenderTarget (int width, int height, int sampleCount, int stencilBits, GRGlFramebufferInfo glInfo)

对应底层调用SkiaApi.gr_backendrendertarget_new_gl(width, height, sampleCount, stencilBits, &glInfo)。该类同样支持 Vulkan(GRVkImageInfo)、Metal(GRMtlTextureInfo)与 Direct3D(GRD3DTextureResourceInfo)后端,并通过Backend属性标识当前后端。事件处理中常用的属性包括:

  • Width/Height:渲染目标像素尺寸,绘制时通常直接以它们作为画布范围;
  • SampleCount:每像素采样数(多重采样抗锯齿相关);
  • StencilBits:每像素模板缓冲位数;
  • IsValid:底层对象是否已完成初始化;
  • GetGlFramebufferInfo():若包装的是 OpenGL framebuffer,可取回其信息。

GRGlFramebufferInfo的 format 字段一般不需要手工指定——GRDefinitions.cs 中的ToGlSizedFormat(this SKColorType)扩展方法提供了SKColorType到 GL sized format 的完整映射,例如Rgba8888 → RGBA8、Bgra8888 → BGRA8、RgbaF16 → RGBA16F、Srgba8888 → SRGB8_ALPHA8,无法映射的类型(如Unknown、Rgba10x6等)返回 0。

事件如何触发:PaintSurface 与 OnPaintSurface

SKPaintGLSurfaceEventArgs是SKGLSurfaceView与SKGLTextureView上PaintSurface事件的数据载体。以 SKGLSurfaceView.cs 为例,视图提供两种绘制入口:

方式一:订阅事件

myView.PaintSurface += (sender, e) => { var surface = e.Surface; var surfaceWidth = e.BackendRenderTarget.Width; var surfaceHeight = e.BackendRenderTarget.Height; var canvas = surface.Canvas; // 在 canvas 上绘制…… canvas.Flush(); };

方式二:覆写虚方法

protected override void OnPaintSurface(SKPaintGLSurfaceEventArgs e) { // 注意:必须调用 base,否则事件不会触发 base.OnPaintSurface(e); var surface = e.Surface; var canvas = surface.Canvas; // 绘制…… canvas.Flush(); }

源码中OnPaintSurface的默认实现即为PaintSurface?.Invoke(this, e),并且视图内部通过私有的InternalRenderer把渲染器的回调转发给视图的OnPaintSurface。因此文档与 XML 注释都明确强调:如果覆写OnPaintSurface而不调用base.OnPaintSurface(e),外部订阅的PaintSurface事件将不会被触发。两种方式任选其一即可,不必同时使用。

底层渲染链路:OnDrawFrame 中如何构造事件参数

要真正理解这些参数从何而来,需要阅读渲染器 SKGLSurfaceViewRenderer.cs 的OnDrawFrame实现。每一次 GL 帧回调大致经历以下步骤:

  1. 清屏:GLES20.GlClear清空颜色、深度与模板缓冲;
  2. 创建 GPU 上下文:首次回调时通过GRGlInterface.Create()取得 GL 接口,再GRContext.CreateGl(glInterface)创建GRContext;
  3. 读取 framebuffer 状态并重建 render target:通过GlGetIntegerv分别读取GL_FRAMEBUFFER_BINDING、GL_STENCIL_BITS、GL_SAMPLES,将采样数裁剪到context.GetMaxSurfaceSampleCount(colorType)允许的范围,然后构造:
glInfo = new GRGlFramebufferInfo((uint)buffer[0], colorType.ToGlSizedFormat()); renderTarget = new GRBackendRenderTarget(newSize.Width, newSize.Height, samples, buffer[1], glInfo);
  1. 创建 Skia 表面:SKSurface.Create(context, renderTarget, surfaceOrigin, colorType);
  2. 构造事件参数并分发:
using (new SKAutoCanvasRestore(canvas, true)) { var e = new SKPaintGLSurfaceEventArgs(surface, renderTarget, surfaceOrigin, colorType); OnPaintSurface(e); }
  1. 冲刷:canvas.Flush()与context.Flush()把 Skia 绘制指令提交给 GL。

渲染器内部的两个常量colorType = SKColorType.Rgba8888、surfaceOrigin = GRSurfaceOrigin.BottomLeft,正是事件参数四参数重载的默认值来源;OnSurfaceChanged中执行GLES20.GlViewport(0, 0, width, height)并记录新尺寸,OnSurfaceCreated(SKGLTextureViewRenderer中为创建/销毁时释放旧上下文)负责生命周期管理。视图初始化时还会调用SetEGLContextClientVersion(2)与SetEGLConfigChooser(8, 8, 8, 8, 0, 8)配置 EGL 环境。SKGLTextureViewRenderer(见 SKGLTextureViewRenderer.cs)采用相同的流程,区别在于它基于GLTextureView,并在OnSurfaceCreated/OnSurfaceDestroyed时通过FreeContext()释放并重建 GPU 资源。

实战:在 SKGLSurfaceView 与 SKGLTextureView 上绘制

官方示例 samples/Basic/Android/SkiaSharpSample 提供了两种视图的完整用法:GpuSurfaceFragment.cs基于SKGLSurfaceView,GpuTextureFragment.cs基于SKGLTextureView。两者的核心绘制逻辑相同,均使用SKRuntimeEffect生成着色器并在PaintSurface事件中绘制全屏矩形:

skiaView.PaintSurface += OnPaintSurface; private void OnPaintSurface(object sender, SKPaintGLSurfaceEventArgs e) { var canvas = e.Surface.Canvas; var width = e.BackendRenderTarget.Width; var height = e.BackendRenderTarget.Height; var builder = shaderBuilder.Value; builder.Uniforms["iTime"] = fpsCounter.ElapsedSeconds; builder.Uniforms["iResolution"] = new float[] { (float)width, (float)height }; using var shader = builder.Build(); using var paint = new SKPaint { Shader = shader }; canvas.DrawRect(0, 0, width, height, paint); }

示例中的几个实践要点值得注意:

  • 绘制区域直接取自e.BackendRenderTarget.Width / Height,而不是视图的布局尺寸——因为画布大小会受设备像素密度影响,CanvasSize可能与视图尺寸不一致(渲染器源码注释对此有明确说明);
  • SKGLSurfaceView支持RenderMode = Rendermode.Continuously实现持续重绘(FPS 场景常用),而SKGLTextureView默认按需绘制,示例中只处理了触摸事件驱动的重绘;
  • 在OnDestroyView中务必解除事件订阅(skiaView.PaintSurface -= OnPaintSurface)并释放着色器构建器,避免 Fragment 重建造成的事件泄漏与资源泄漏。

此外,在 MAUI 平台,SKGLView的 Android Handler(见 SKGLViewHandler.Android.cs)会把平台事件的e.Surface、e.BackendRenderTarget、e.Origin、e.Info、e.RawInfo原样转发给虚拟视图,即使用五参数重载构造新的事件参数——这说明SKPaintGLSurfaceEventArgs的构造函数族也是跨平台封装层之间传递绘制上下文的标准载体。

升级迁移注意事项

结合前序版本的 API diff(见 1.68.0 的 SkiaSharp.Views.Android.md)可以梳理出完整的演进脉络:

  • 1.68.0 起,SKPaintGLSurfaceEventArgs旧构造函数SKPaintGLSurfaceEventArgs(SKSurface, GRBackendRenderTargetDesc)被标记[Obsolete],对应属性RenderTarget(类型GRBackendRenderTargetDesc)同样废弃,替代方案是BackendRenderTarget(类型GRBackendRenderTarget)+Origin+ColorType的组合;
  • 1.68.0 同时新增了SKGLSurfaceView/SKGLTextureView的PaintSurface事件与OnPaintSurface虚方法,并废弃了旧的SetRenderer(ISKRenderer)渲染器模式以及SKGLSurfaceViewRenderer.OnDrawFrame(SKSurface, GRBackendRenderTargetDesc)抽象方法;
  • 1.68.2 则进一步补充了SKPaintGLSurfaceEventArgs的构造函数重载,完善了事件参数在不同调用场景下的构建能力。

因此,从 1.68 之前的版本升级时,建议迁移路径为:以PaintSurface事件(或覆写OnPaintSurface)替代自定义ISKRenderer,在事件参数中读取Surface与BackendRenderTarget进行绘制,并同步检查是否存在对已废弃GRBackendRenderTargetDesc类型的引用。

小结

SKPaintGLSurfaceEventArgs是连接 SkiaSharp GPU 表面与 Android GL 视图的枢纽:渲染器在每一帧读取 framebuffer 状态、构建GRBackendRenderTarget与SKSurface之后,把绘制上下文封装成事件参数交给PaintSurface事件或OnPaintSurface虚方法。1.68.2 记录的新增构造函数与 1.68.0 引入的事件模型一起,构成了当前仓库 Android 平台 GPU 绘制的标准 API 形态;理解这组参数的来源与默认值(BottomLeft+Rgba8888),即可准确把控硬件加速绘制的每一个环节。

  • 图形学
  • 图像处理
  • 跨平台

【免费下载链接】SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.

项目地址:https://gitcode.com/gh_mirrors/sk/SkiaSharp
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进