Update with recovered code from VM binaries (Jan 1)

Recovered from decompiled OpenMaui.Controls.Linux.dll:
- SkiaShell.cs: FlyoutHeader, FlyoutFooter, scroll support (918 -> 1325 lines)
- X11Window.cs: Cursor support (XCreateFontCursor, XDefineCursor)
- All handlers with dark mode support
- All services with latest implementations
- LinuxApplication with theme change handling
This commit is contained in:
2026-01-01 06:22:48 -05:00
parent 1e84c6168a
commit 1f096c38dc
254 changed files with 49359 additions and 38457 deletions

View File

@@ -1,349 +1,376 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using SkiaSharp;
using System;
using System.Collections.Generic;
using Microsoft.Maui.Platform.Linux.Window;
using System.Runtime.InteropServices;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Rendering;
/// <summary>
/// GPU-accelerated rendering engine using OpenGL.
/// Falls back to software rendering if GPU initialization fails.
/// </summary>
public class GpuRenderingEngine : IDisposable
{
private readonly X11Window _window;
private GRContext? _grContext;
private GRBackendRenderTarget? _renderTarget;
private SKSurface? _surface;
private SKCanvas? _canvas;
private bool _disposed;
private bool _gpuAvailable;
private int _width;
private int _height;
private readonly X11Window _window;
// Fallback to software rendering
private SKBitmap? _softwareBitmap;
private SKCanvas? _softwareCanvas;
private GRContext? _grContext;
// Dirty region tracking
private readonly List<SKRect> _dirtyRegions = new();
private readonly object _dirtyLock = new();
private bool _fullRedrawNeeded = true;
private const int MaxDirtyRegions = 32;
private GRBackendRenderTarget? _renderTarget;
/// <summary>
/// Gets whether GPU acceleration is available and active.
/// </summary>
public bool IsGpuAccelerated => _gpuAvailable && _grContext != null;
private SKSurface? _surface;
/// <summary>
/// Gets the current rendering backend name.
/// </summary>
public string BackendName => IsGpuAccelerated ? "OpenGL" : "Software";
private SKCanvas? _canvas;
public int Width => _width;
public int Height => _height;
private bool _disposed;
public GpuRenderingEngine(X11Window window)
{
_window = window;
_width = window.Width;
_height = window.Height;
private bool _gpuAvailable;
// Try to initialize GPU rendering
_gpuAvailable = TryInitializeGpu();
private int _width;
if (!_gpuAvailable)
{
Console.WriteLine("[GpuRenderingEngine] GPU not available, using software rendering");
InitializeSoftwareRendering();
}
private int _height;
_window.Resized += OnWindowResized;
_window.Exposed += OnWindowExposed;
}
private SKBitmap? _softwareBitmap;
private bool TryInitializeGpu()
{
try
{
// Check if we can create an OpenGL context
var glInterface = GRGlInterface.Create();
if (glInterface == null)
{
Console.WriteLine("[GpuRenderingEngine] Failed to create GL interface");
return false;
}
private SKCanvas? _softwareCanvas;
_grContext = GRContext.CreateGl(glInterface);
if (_grContext == null)
{
Console.WriteLine("[GpuRenderingEngine] Failed to create GR context");
glInterface.Dispose();
return false;
}
private readonly List<SKRect> _dirtyRegions = new List<SKRect>();
CreateGpuSurface();
Console.WriteLine("[GpuRenderingEngine] GPU acceleration enabled");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"[GpuRenderingEngine] GPU initialization failed: {ex.Message}");
return false;
}
}
private readonly object _dirtyLock = new object();
private void CreateGpuSurface()
{
if (_grContext == null) return;
private bool _fullRedrawNeeded = true;
_renderTarget?.Dispose();
_surface?.Dispose();
private const int MaxDirtyRegions = 32;
var width = Math.Max(1, _width);
var height = Math.Max(1, _height);
public bool IsGpuAccelerated
{
get
{
if (_gpuAvailable)
{
return _grContext != null;
}
return false;
}
}
// Create framebuffer info (assuming default framebuffer 0)
var framebufferInfo = new GRGlFramebufferInfo(0, SKColorType.Rgba8888.ToGlSizedFormat());
public string BackendName
{
get
{
if (!IsGpuAccelerated)
{
return "Software";
}
return "OpenGL";
}
}
_renderTarget = new GRBackendRenderTarget(
width, height,
0, // sample count
8, // stencil bits
framebufferInfo);
public int Width => _width;
_surface = SKSurface.Create(
_grContext,
_renderTarget,
GRSurfaceOrigin.BottomLeft,
SKColorType.Rgba8888);
public int Height => _height;
if (_surface == null)
{
Console.WriteLine("[GpuRenderingEngine] Failed to create GPU surface, falling back to software");
_gpuAvailable = false;
InitializeSoftwareRendering();
return;
}
public GpuRenderingEngine(X11Window window)
{
_window = window;
_width = window.Width;
_height = window.Height;
_gpuAvailable = TryInitializeGpu();
if (!_gpuAvailable)
{
Console.WriteLine("[GpuRenderingEngine] GPU not available, using software rendering");
InitializeSoftwareRendering();
}
_window.Resized += OnWindowResized;
_window.Exposed += OnWindowExposed;
}
_canvas = _surface.Canvas;
}
private bool TryInitializeGpu()
{
try
{
GRGlInterface val = GRGlInterface.Create();
if (val == null)
{
Console.WriteLine("[GpuRenderingEngine] Failed to create GL interface");
return false;
}
_grContext = GRContext.CreateGl(val);
if (_grContext == null)
{
Console.WriteLine("[GpuRenderingEngine] Failed to create GR context");
((SKNativeObject)val).Dispose();
return false;
}
CreateGpuSurface();
Console.WriteLine("[GpuRenderingEngine] GPU acceleration enabled");
return true;
}
catch (Exception ex)
{
Console.WriteLine("[GpuRenderingEngine] GPU initialization failed: " + ex.Message);
return false;
}
}
private void InitializeSoftwareRendering()
{
var width = Math.Max(1, _width);
var height = Math.Max(1, _height);
private void CreateGpuSurface()
{
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
if (_grContext != null)
{
GRBackendRenderTarget? renderTarget = _renderTarget;
if (renderTarget != null)
{
((SKNativeObject)renderTarget).Dispose();
}
SKSurface? surface = _surface;
if (surface != null)
{
((SKNativeObject)surface).Dispose();
}
int num = Math.Max(1, _width);
int num2 = Math.Max(1, _height);
GRGlFramebufferInfo val = default(GRGlFramebufferInfo);
((GRGlFramebufferInfo)(ref val))._002Ector(0u, SkiaExtensions.ToGlSizedFormat((SKColorType)4));
_renderTarget = new GRBackendRenderTarget(num, num2, 0, 8, val);
_surface = SKSurface.Create(_grContext, _renderTarget, (GRSurfaceOrigin)1, (SKColorType)4);
if (_surface == null)
{
Console.WriteLine("[GpuRenderingEngine] Failed to create GPU surface, falling back to software");
_gpuAvailable = false;
InitializeSoftwareRendering();
}
else
{
_canvas = _surface.Canvas;
}
}
}
_softwareBitmap?.Dispose();
_softwareCanvas?.Dispose();
private void InitializeSoftwareRendering()
{
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Expected O, but got Unknown
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Expected O, but got Unknown
int num = Math.Max(1, _width);
int num2 = Math.Max(1, _height);
SKBitmap? softwareBitmap = _softwareBitmap;
if (softwareBitmap != null)
{
((SKNativeObject)softwareBitmap).Dispose();
}
SKCanvas? softwareCanvas = _softwareCanvas;
if (softwareCanvas != null)
{
((SKNativeObject)softwareCanvas).Dispose();
}
SKImageInfo val = default(SKImageInfo);
((SKImageInfo)(ref val))._002Ector(num, num2, (SKColorType)6, (SKAlphaType)2);
_softwareBitmap = new SKBitmap(val);
_softwareCanvas = new SKCanvas(_softwareBitmap);
_canvas = _softwareCanvas;
}
var imageInfo = new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
_softwareBitmap = new SKBitmap(imageInfo);
_softwareCanvas = new SKCanvas(_softwareBitmap);
_canvas = _softwareCanvas;
}
private void OnWindowResized(object? sender, (int Width, int Height) size)
{
(_width, _height) = size;
if (_gpuAvailable && _grContext != null)
{
CreateGpuSurface();
}
else
{
InitializeSoftwareRendering();
}
_fullRedrawNeeded = true;
}
private void OnWindowResized(object? sender, (int Width, int Height) size)
{
_width = size.Width;
_height = size.Height;
private void OnWindowExposed(object? sender, EventArgs e)
{
_fullRedrawNeeded = true;
}
if (_gpuAvailable && _grContext != null)
{
CreateGpuSurface();
}
else
{
InitializeSoftwareRendering();
}
public void InvalidateRegion(SKRect region)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
if (((SKRect)(ref region)).IsEmpty || ((SKRect)(ref region)).Width <= 0f || ((SKRect)(ref region)).Height <= 0f)
{
return;
}
region = SKRect.Intersect(region, new SKRect(0f, 0f, (float)Width, (float)Height));
if (((SKRect)(ref region)).IsEmpty)
{
return;
}
lock (_dirtyLock)
{
if (_dirtyRegions.Count >= 32)
{
_fullRedrawNeeded = true;
_dirtyRegions.Clear();
}
else
{
_dirtyRegions.Add(region);
}
}
}
_fullRedrawNeeded = true;
}
public void InvalidateAll()
{
_fullRedrawNeeded = true;
}
private void OnWindowExposed(object? sender, EventArgs e)
{
_fullRedrawNeeded = true;
}
public void Render(SkiaView rootView)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
//IL_0110: Unknown result type (might be due to invalid IL or missing references)
//IL_0101: Unknown result type (might be due to invalid IL or missing references)
//IL_017a: Unknown result type (might be due to invalid IL or missing references)
if (_canvas == null)
{
return;
}
SKSize availableSize = default(SKSize);
((SKSize)(ref availableSize))._002Ector((float)Width, (float)Height);
rootView.Measure(availableSize);
rootView.Arrange(new SKRect(0f, 0f, (float)Width, (float)Height));
bool flag;
List<SKRect> list;
lock (_dirtyLock)
{
flag = _fullRedrawNeeded || _dirtyRegions.Count == 0;
if (flag)
{
list = new List<SKRect>
{
new SKRect(0f, 0f, (float)Width, (float)Height)
};
_dirtyRegions.Clear();
_fullRedrawNeeded = false;
}
else
{
list = new List<SKRect>(_dirtyRegions);
_dirtyRegions.Clear();
}
}
foreach (SKRect item in list)
{
_canvas.Save();
if (!flag)
{
_canvas.ClipRect(item, (SKClipOperation)1, false);
}
_canvas.Clear(SKColors.White);
rootView.Draw(_canvas);
_canvas.Restore();
}
SkiaView.DrawPopupOverlays(_canvas);
if (LinuxDialogService.HasActiveDialog)
{
LinuxDialogService.DrawDialogs(_canvas, new SKRect(0f, 0f, (float)Width, (float)Height));
}
_canvas.Flush();
if (_gpuAvailable && _grContext != null)
{
_grContext.Submit(false);
}
else if (_softwareBitmap != null)
{
IntPtr pixels = _softwareBitmap.GetPixels();
if (pixels != IntPtr.Zero)
{
_window.DrawPixels(pixels, Width, Height, _softwareBitmap.RowBytes);
}
}
}
/// <summary>
/// Marks a region as needing redraw.
/// </summary>
public void InvalidateRegion(SKRect region)
{
if (region.IsEmpty || region.Width <= 0 || region.Height <= 0)
return;
public GpuStats GetStats()
{
if (_grContext == null)
{
return new GpuStats
{
IsGpuAccelerated = false
};
}
int num = default(int);
long resourceCacheLimitBytes = default(long);
_grContext.GetResourceCacheLimits(ref num, ref resourceCacheLimitBytes);
return new GpuStats
{
IsGpuAccelerated = true,
MaxTextureSize = 4096,
ResourceCacheUsedBytes = 0L,
ResourceCacheLimitBytes = resourceCacheLimitBytes
};
}
region = SKRect.Intersect(region, new SKRect(0, 0, Width, Height));
if (region.IsEmpty) return;
public void PurgeResources()
{
GRContext? grContext = _grContext;
if (grContext != null)
{
grContext.PurgeResources();
}
}
lock (_dirtyLock)
{
if (_dirtyRegions.Count >= MaxDirtyRegions)
{
_fullRedrawNeeded = true;
_dirtyRegions.Clear();
return;
}
_dirtyRegions.Add(region);
}
}
public SKCanvas? GetCanvas()
{
return _canvas;
}
/// <summary>
/// Marks the entire surface as needing redraw.
/// </summary>
public void InvalidateAll()
{
_fullRedrawNeeded = true;
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_window.Resized -= OnWindowResized;
_window.Exposed -= OnWindowExposed;
SKSurface? surface = _surface;
if (surface != null)
{
((SKNativeObject)surface).Dispose();
}
GRBackendRenderTarget? renderTarget = _renderTarget;
if (renderTarget != null)
{
((SKNativeObject)renderTarget).Dispose();
}
GRContext? grContext = _grContext;
if (grContext != null)
{
((SKNativeObject)grContext).Dispose();
}
SKBitmap? softwareBitmap = _softwareBitmap;
if (softwareBitmap != null)
{
((SKNativeObject)softwareBitmap).Dispose();
}
SKCanvas? softwareCanvas = _softwareCanvas;
if (softwareCanvas != null)
{
((SKNativeObject)softwareCanvas).Dispose();
}
}
_disposed = true;
}
/// <summary>
/// Renders the view tree with dirty region optimization.
/// </summary>
public void Render(SkiaView rootView)
{
if (_canvas == null) return;
// Measure and arrange
var availableSize = new SKSize(Width, Height);
rootView.Measure(availableSize);
rootView.Arrange(new SKRect(0, 0, Width, Height));
// Determine regions to redraw
List<SKRect> regionsToRedraw;
bool isFullRedraw;
lock (_dirtyLock)
{
isFullRedraw = _fullRedrawNeeded || _dirtyRegions.Count == 0;
if (isFullRedraw)
{
regionsToRedraw = new List<SKRect> { new SKRect(0, 0, Width, Height) };
_dirtyRegions.Clear();
_fullRedrawNeeded = false;
}
else
{
regionsToRedraw = new List<SKRect>(_dirtyRegions);
_dirtyRegions.Clear();
}
}
// Render each dirty region
foreach (var region in regionsToRedraw)
{
_canvas.Save();
if (!isFullRedraw)
{
_canvas.ClipRect(region);
}
// Clear region
_canvas.Clear(SKColors.White);
// Draw view tree
rootView.Draw(_canvas);
_canvas.Restore();
}
// Draw popup overlays
SkiaView.DrawPopupOverlays(_canvas);
// Draw modal dialogs
if (LinuxDialogService.HasActiveDialog)
{
LinuxDialogService.DrawDialogs(_canvas, new SKRect(0, 0, Width, Height));
}
_canvas.Flush();
// Present to window
if (_gpuAvailable && _grContext != null)
{
_grContext.Submit();
// Swap buffers would happen here via GLX/EGL
}
else if (_softwareBitmap != null)
{
var pixels = _softwareBitmap.GetPixels();
if (pixels != IntPtr.Zero)
{
_window.DrawPixels(pixels, Width, Height, _softwareBitmap.RowBytes);
}
}
}
/// <summary>
/// Gets performance statistics for the GPU context.
/// </summary>
public GpuStats GetStats()
{
if (_grContext == null)
{
return new GpuStats { IsGpuAccelerated = false };
}
// Get resource cache limits from GRContext
_grContext.GetResourceCacheLimits(out var maxResources, out var maxBytes);
return new GpuStats
{
IsGpuAccelerated = true,
MaxTextureSize = 4096, // Common default, SkiaSharp doesn't expose this directly
ResourceCacheUsedBytes = 0, // Would need to track manually
ResourceCacheLimitBytes = maxBytes
};
}
/// <summary>
/// Purges unused GPU resources to free memory.
/// </summary>
public void PurgeResources()
{
_grContext?.PurgeResources();
}
public SKCanvas? GetCanvas() => _canvas;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_window.Resized -= OnWindowResized;
_window.Exposed -= OnWindowExposed;
_surface?.Dispose();
_renderTarget?.Dispose();
_grContext?.Dispose();
_softwareBitmap?.Dispose();
_softwareCanvas?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// GPU performance statistics.
/// </summary>
public class GpuStats
{
public bool IsGpuAccelerated { get; init; }
public int MaxTextureSize { get; init; }
public long ResourceCacheUsedBytes { get; init; }
public long ResourceCacheLimitBytes { get; init; }
public double ResourceCacheUsedMB => ResourceCacheUsedBytes / (1024.0 * 1024.0);
public double ResourceCacheLimitMB => ResourceCacheLimitBytes / (1024.0 * 1024.0);
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}