使用SkiaSharp SKCanvasView的自定义SkSL着色器导致.NET MAUI应用崩溃
我正在开发一款面向iPad Pro的.NET MAUI应用,需要根据参数采样图像区域并显示,因此选择SkiaSharp的SKRuntimeEffect和SKShader类实现自定义SkSL着色器。我创建了继承自SKCanvasView的自定义控件,但每当OnPaintSurface方法执行到DrawRect()调用时,应用立即崩溃。
我制作了极简测试应用,仅包含将像素设为红色的简单着色器,崩溃问题仍存在。自定义控件代码如下:
using SkiaSharp; using SkiaSharp.Views.Maui.Controls; using System.Diagnostics; namespace SkiaShaderTest.Controls { public class Panel : SKCanvasView { public static BindableProperty ShaderOnProperty = BindableProperty.Create( nameof(ShaderOn), typeof(bool), typeof(Panel), false, propertyChanged: (bindable, oldValue, newValue) => { var panel = (Panel)bindable; panel.InvalidateSurface(); } ); public bool ShaderOn { get => (bool)GetValue(ShaderOnProperty); set => SetValue(ShaderOnProperty, value); } protected override void OnPaintSurface(SkiaSharp.Views.Maui.SKPaintSurfaceEventArgs args) { var canvas = args.Surface.Canvas; var info = args.Info; canvas.Clear(); if (ShaderOn) { var src = @" float4 main(float2 fragCoord) { return float4(1,0,0,1); }"; using var effect = SKRuntimeEffect.Create(src, out string error); using var shader = effect.ToShader(true); using (SKPaint paint = new SKPaint() { Shader = shader }) { try { canvas.DrawRect(info.Rect, paint); } catch (Exception e) { Debug.WriteLine($"Exception {e.GetType()}: {e.Message}"); } } } else { using (SKPaint paint = new SKPaint()) { paint.Color = SKColors.BlueViolet; canvas.DrawRect(info.Rect, paint); } } } } }
应用的XAML布局如下:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SkiaShaderTest.MainPage" xmlns:controls="clr-namespace:SkiaShaderTest.Controls" > <Grid RowDefinitions="*,auto" IgnoreSafeArea="True"> <controls:Panel x:Name="panel" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.Row="0" Grid.RowSpan="2"/> <CheckBox IsChecked="{Binding ShaderOn, Source={x:Reference panel}, Mode=TwoWay}" MinimumWidthRequest="100" BackgroundColor="RoyalBlue" Color="AliceBlue" HorizontalOptions="Center" VerticalOptions="End" /> </Grid> </ContentPage>
每当ShaderOn设为true并执行到canvas.DrawRect(info.Rect, paint);时,应用立即崩溃。我在iPad Pro真机、iOS模拟器、Mac Mini和Windows 11笔记本测试:iPad无错误信息,Mac有控制台输出,Windows抛出SEHException:
System.Runtime.InteropServices.SEHException HResult=0x80004005 Message=External component has thrown an exception. Source=<Cannot evaluate the exception source> StackTrace: <Cannot evaluate the exception stack trace>
(注:代码未进入catch块,调试器直接在DrawRect()行抛出异常)
我尝试使用SkiaSharp自带的着色器(如下),运行完全正常:
using (SKPaint paint = new SKPaint()) { SKShader shader = SKShader.CreateLinearGradient( new SKPoint(0, 0), new SKPoint(1000, 1000), new SKColor[] { new SKColor(0,0,255), new SKColor(0,255,0) }, null, SKShaderTileMode.Repeat ); paint.Shader = shader; canvas.DrawRect(info.Rect, paint); }
我已安装NuGet上最新的SkiaSharp包,控制台输出和异常指向SkiaSharp内部错误,但我也怀疑自身代码问题。请问我可能哪里出错了?该如何解决此问题?
问题出在SkSL着色器的入口函数签名和SKRuntimeEffect的使用方式上,具体修正点如下:
1. 修正SkSL入口函数的参数签名
Skia的SKRuntimeEffect要求片段着色器的入口函数必须使用规范的参数定义,你当前使用的fragCoord不是默认提供的输入变量,应该改用默认的coords参数,同时推荐使用sk_in修饰符明确输入属性:
float4 main(sk_in float2 coords) { return float4(1,0,0,1); }
2. 修正ToShader的调用方式
effect.ToShader(true)中的true表示使用设备坐标,但你的着色器未处理该坐标空间逻辑,应改为使用本地坐标(默认行为),可以直接省略参数或传入false:
using var shader = effect.ToShader();
3. 添加着色器编译错误检查
在创建SKRuntimeEffect后,立即检查编译错误,避免后续执行无效的着色器:
if (!string.IsNullOrEmpty(error)) { Debug.WriteLine($"Shader compile error: {error}"); return; }
修正后的完整OnPaintSurface代码
protected override void OnPaintSurface(SkiaSharp.Views.Maui.SKPaintSurfaceEventArgs args) { var canvas = args.Surface.Canvas; var info = args.Info; canvas.Clear(); if (ShaderOn) { var src = @" float4 main(sk_in float2 coords) { return float4(1,0,0,1); }"; using var effect = SKRuntimeEffect.Create(src, out string error); if (!string.IsNullOrEmpty(error)) { Debug.WriteLine($"Shader compile error: {error}"); return; } using var shader = effect.ToShader(); using (SKPaint paint = new SKPaint() { Shader = shader }) { canvas.DrawRect(info.Rect, paint); } } else { using (SKPaint paint = new SKPaint()) { paint.Color = SKColors.BlueViolet; canvas.DrawRect(info.Rect, paint); } } }
原因解释
SKRuntimeEffect的SkSL语法有严格规范,使用未定义的输入变量会导致Skia内部渲染管线崩溃,且这类原生异常无法被C#的try-catch捕获。ToShader(true)传入的设备坐标未被着色器正确处理,会触发坐标空间不匹配的底层错误,进而导致应用崩溃。
内容的提问来源于stack exchange,提问作者LFLumi

