简体   繁体   English

C# SkiaSharp OpenTK Winform - 如何从后台线程绘制?

[英]C# SkiaSharp OpenTK Winform - How to draw from a background thread?

I am trying to replace GDI+ with SkiaSharp for a data visualization framework that renders multi-layered pannable-zoomable graphs with real-time continuously changing engineering data.我正在尝试用 SkiaSharp 替换 GDI+,作为一个数据可视化框架,该框架使用实时不断变化的工程数据呈现多层可平移-可缩放图形。

In GDI+, the application did this:在 GDI+ 中,应用程序是这样做的:

  • Created a collection of drawing layers with transparent backgrounds, typically a Grid Layer, one or more Data Layers, and an Overlay Layer for cursor info and highlighting, each backed by a separate bitmap.创建了一组具有透明背景的绘图图层,通常是一个网格图层、一个或多个数据图层以及一个用于光标信息和突出显示的叠加图层,每个图层都由一个单独的位图支持。
  • In a rendering loop background thread, only the layers (bitmaps) that needed to be updated for each rendering cycle were redrawn using GDI+.在渲染循环后台线程中,仅使用 GDI+ 重绘每个渲染周期需要更新的图层(位图)。 This could require many thousands of calculated and transformed lines, rectangles, and text to create heatmaps, waveforms, histogram plots, data labels, etc.这可能需要数以千计的计算和转换线、矩形和文本来创建热图、波形、直方图、数据标签等。
  • Each drawing layer in the stack would then be BitBlt'd to a composite bitmap by the background thread堆栈中的每个绘图层将被后台线程 BitBlt 转换为复合位图
  • The final composite bitmap would then be drawn to a WinForm PictureBox in the GUI thread at up to 30fps.然后将最终的复合位图以高达 30fps 的速度绘制到 GUI 线程中的 WinForm PictureBox。

Everything up to the final image presentation was done in one or more background threads.直到最终图像呈现的所有内容都在一个或多个后台线程中完成。 The GUI thread was only involved to draw the finished image to the PictureBox. GUI 线程仅用于将完成的图像绘制到 PictureBox。 This is important because there are many other GUI controls that need to stay responsive.这很重要,因为还有许多其他 GUI 控件需要保持响应。 This worked great, except it is all CPU based.这很好用,除了它都是基于 CPU 的。 Small windows were no problem, but maximizing on a 4K screen would slow down the rendering enough to make the program pretty much unusable.小窗口没有问题,但在 4K 屏幕上最大化会减慢渲染速度,使程序几乎无法使用。

I would like to recreate this concept with GPU accelerated SkiaSharp.我想用 GPU 加速的 SkiaSharp 重新创建这个概念。

I tried creating dozens of different test programs and I keep getting Cross-Thread access violations, or nothing showing on the screen, or hard crashes.我尝试创建了几十个不同的测试程序,但我不断遇到跨线程访问冲突,或者屏幕上没有任何显示,或者严重崩溃。 Instead of posting code, let me ask some basic questions:让我问一些基本问题,而不是发布代码:

Questions:问题:

  • How would you approach creating this framework ?您将如何创建这个框架? Can SkiaSharp even do this ? SkiaSharp 甚至可以做到这一点吗?
  • Should each of my layer classes maintain an SKSurface, SKCanvas, SKImage, or SKBitmap ?我的每个图层类都应该维护一个 SKSurface、SKCanvas、SKImage 或 SKBitmap 吗? - Again, if a layer does not need to be redrawn for the current cycle, then the layer needs to maintain the previously drawn content for use in the next composite image. - 同样,如果当前循环不需要重新绘制图层,则该图层需要保留先前绘制的内容以供下一张合成图像使用。
  • A GLControl and GRContext is needed on the GUI thread for showing the final composite image, but should there be another separate GRContext for the background rendering thread to use ? GUI 线程需要一个 GLControl 和 GRContext 来显示最终的合成图像,但是是否应该有另一个单独的 GRContext 供后台渲染线程使用? - How to create with GPU acceleration ? - 如何使用 GPU 加速进行创作?
  • Are there any working examples of a similar concept that someone can point to ?有人可以指出类似概念的任何工作示例吗? ( GPU accelerating rendering from background thread to GLControl ) (GPU 加速渲染从后台线程到 GLControl )
  • Should I use SkiaSharp hidden in the background only, and GDI+ BitBlt with PictureBox to show the composite image on the screen ?我应该只使用隐藏在背景中的 SkiaSharp,并使用带有 PictureBox 的 GDI+ BitBlt 在屏幕上显示合成图像吗? - Would that solve some threading issues ? - 这会解决一些线程问题吗?

Any help defining the approach and the dos and don'ts would be greatly appreciated !!任何定义方法和注意事项的帮助将不胜感激!

I figured out how to get this to work using SKPicture objects to record the Draw Commands from each layer using a background rendering thread, then painting them back to an SKGLControl using the GUI thread.我想出了如何使用 SKPicture 对象来使用后台渲染线程记录每一层的绘制命令,然后使用 GUI 线程将它们绘制回 SKGLControl 的方法。 This satisfies all of my requirements: It allows for multiple drawing layers, renders with a background thread, renders only the layers that need updates, paints with GPU acceleration, and is extremely fast for a maximized 4K window.这满足了我的所有要求:它允许多个绘图层,使用后台线程渲染,仅渲染需要更新的层,使用 GPU 加速绘制,并且对于最大化的 4K 窗口非常快。

Lessons Learned得到教训

There are a few lessons that I learned along the way that were causing a lot of confusion for me...一路上我学到的一些教训给我带来了很多困惑......

  1. There are examples online of using an OpenTK.GLControl with GPU acceleration, and there are examples using the SkiaSharp.Views.Desktop.SKGLControl which has built in GPU acceleration.网上有使用带有 GPU 加速的 OpenTK.GLControl 的示例,也有使用内置 GPU 加速的 SkiaSharp.Views.Desktop.SKGLControl 的示例。 The SKGLControl is definitely the correct control for this task. SKGLControl 绝对是此任务的正确控件。 The GLControl was creating squares for DrawCircle and refusing to render any curves due to issues with the FramebufferBinding and StencilBits ?!?由于 FramebufferBinding 和 StencilBits 的问题,GLControl 正在为 DrawCircle 创建正方形并拒绝渲染任何曲线?!? - I gave up on it. - 我放弃了。 It is also slower than the SKGLControl for SKPicture objects.它也比 SKPicture 对象的 SKGLControl 慢。

  2. The SKGLControl does not need nor like the use of SwapBuffers or Canvas.Flush, which are required for the GLControl. SKGLControl 不需要也不喜欢使用 GLControl 所需的 SwapBuffers 或 Canvas.Flush。 This was causing strobing and glitching of the drawings for SKGLControl, which is why I went off in the weeds fighting with the GLControl.这导致 SKGLControl 的绘图出现频闪和故障,这就是我在与 GLControl 斗争的杂草中离开的原因。 When I rebuilt the project with SKGLControl and got rid of SwapBuffers and Canvas.Flush, everything started behaving.当我用 SKGLControl 重建项目并摆脱 SwapBuffers 和 Canvas.Flush 时,一切都开始正常了。

  3. References to Surfaces and Canvases should not be held past one PaintSurface cycle.对 Surfaces 和 Canvases 的引用不应超过一个 PaintSurface 循环。 The SKPicture is the magical object that will let you store the drawing commands for each layer and play them back again and again. SKPicture 是一个神奇的对象,它可以让您存储每个图层的绘图命令并一次又一次地播放它们。 This is different from an SKBitmap or SKImage which are generating pixel rasters instead of just recording the Draw commands.这与 SKBitmap 或 SKImage 不同,SKBitmap 或 SKImage 生成像素栅格,而不仅仅是记录 Draw 命令。 I couldn't get SKBitmap or SKImage to behave in a multithreaded environment and still be GPU accelerated.我无法让 SKBitmap 或 SKImage 在多线程环境中运行并且仍然是 GPU 加速的。 SKPicture works great for this. SKPicture 对此非常有用。

  4. There is a difference between the Paint event and the PaintSurface event for the SKGLControl. SKGLControl 的 Paint 事件和 PaintSurface 事件之间存在差异。 The PaintSurface event is what should be used and is by default GPU accelerated. PaintSurface 事件是应该使用的,默认情况下是 GPU 加速的。


Working Example Code工作示例代码

Below is fully functional demo of a multi-layer, multi-threaded, GPU accelerated SkiaSharp drawing下面是一个多层、多线程、GPU 加速的 SkiaSharp 绘图的全功能演示

This example creates 4 drawing layers:此示例创建 4 个绘图层:

  • Background Layer背景层
  • Grid Layer网格层
  • Data Layer数据层
  • Overlay Layer叠加层

The layers are drawn ( rendered ) using a background thread, then painted to an SKGLControl using the GUI thread.使用后台线程绘制(渲染)图层,然后使用 GUI 线程绘制到 SKGLControl。 Each layer is only rendered when needed, but all layers are painted with each PaintSurface event.每个图层仅在需要时渲染,但所有图层都使用每个 PaintSurface 事件绘制。

在此处输入图片说明

To try the code:要尝试代码:

  1. Create a new C# WinForms project in Visual Studio.在 Visual Studio 中创建一个新的 C# WinForms 项目。
  2. Add the NuGet Package: "SkiaSharp.Views.WindowsForms".添加 NuGet 包:“SkiaSharp.Views.WindowsForms”。 This will automatically add "SkiaSharp", and "SkiaSharp.Views.Desktop.Common".这将自动添加“SkiaSharp”和“SkiaSharp.Views.Desktop.Common”。
  3. Add a SkiaSharp.Views.Desktop.SKGLControl to Form1.将 SkiaSharp.Views.Desktop.SKGLControl 添加到 Form1。 Name it "skglControl1"将其命名为“skglControl1”
  4. Set Dock to "Fill" for skglControl1 so that it fills Form1.将 skglControl1 的 Dock 设置为“Fill”,以便它填充 Form1。
  5. Copy the code below to Form1:将下面的代码复制到 Form1:


    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;
    using SkiaSharp;
    using SkiaSharp.Views.Desktop;
    
    namespace SkiaSharp_Multi_Layer_GPU
    {
        // ---------------------------------------------------------------------
        // ---------------------------------------------------------------------
        // -------                                                       -------
        // -------                   WinForm - Form 1                    -------
        // -------                                                       -------
        // ---------------------------------------------------------------------
        // ---------------------------------------------------------------------

        public partial class Form1 : Form
        {
            private Thread m_RenderThread = null;
            private AutoResetEvent m_ThreadGate = null;
            private List<Layer> m_Layers = null;
            private Layer m_Layer_Background = null;
            private Layer m_Layer_Grid = null;
            private Layer m_Layer_Data = null;
            private Layer m_Layer_Overlay = null;
            private bool m_KeepSwimming = true;
            private SKPoint m_MousePos = new SKPoint();
            private bool m_ShowGrid = true;
            private Point m_PrevMouseLoc = new Point();
    
    
            // ---------------------------
            // --- Form1 - Constructor ---
            // ---------------------------
    
            public Form1()
            {
                InitializeComponent();
            }
    
    
            // ------------------------------
            // --- Event - Form1 - OnLoad ---
            // ------------------------------
    
            protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
    
                // Set the title of the Form
                this.Text = "SkiaSharp Demo - Multi-Layer, Multi-Threaded, GPU Accelerated";
    
                // Create layers to draw on, each with a dedicated SKPicture
                m_Layer_Background = new Layer("Background Layer");
                m_Layer_Grid = new Layer("Grid Layer");
                m_Layer_Data = new Layer("Data Layer");
                m_Layer_Overlay = new Layer("Overlay Layer");
    
                // Create a collection for the drawing layers
                m_Layers = new List<Layer>();
                m_Layers.Add(m_Layer_Background);
                m_Layers.Add(m_Layer_Grid);
                m_Layers.Add(m_Layer_Data);
                m_Layers.Add(m_Layer_Overlay);
    
                // Subscribe to the Draw Events for each layer
                m_Layer_Background.Draw += Layer_Background_Draw;
                m_Layer_Grid.Draw += Layer_Grid_Draw;
                m_Layer_Data.Draw += Layer_Data_Draw;
                m_Layer_Overlay.Draw += Layer_Overlay_Draw;
    
                // Subscribe to the SKGLControl events
                skglControl1.PaintSurface += SkglControl1_PaintSurface;
                skglControl1.Resize += SkglControl1_Resize;
                skglControl1.MouseMove += SkglControl1_MouseMove;
                skglControl1.MouseDoubleClick += SkglControl1_MouseDoubleClick;
                
                // Create a background rendering thread
                m_RenderThread = new Thread(RenderLoopMethod);
                m_ThreadGate = new AutoResetEvent(false);
    
                // Start the rendering thread
                m_RenderThread.Start();
            }
    
    
            // ---------------------------------
            // --- Event - Form1 - OnClosing ---
            // ---------------------------------
    
            protected override void OnClosing(CancelEventArgs e)
            {
                // Let the rendering thread terminate
                m_KeepSwimming = false;
                m_ThreadGate.Set();
    
                base.OnClosing(e);
            }
    
    
            // --------------------------------------------
            // --- Event - SkglControl1 - Paint Surface ---
            // --------------------------------------------
    
            private void SkglControl1_PaintSurface(object sender, SkiaSharp.Views.Desktop.SKPaintGLSurfaceEventArgs e)
            {
                // Clear the Canvas
                e.Surface.Canvas.Clear(SKColors.Black);
    
                // Paint each pre-rendered layer onto the Canvas using this GUI thread
                foreach (var layer in m_Layers)
                {
                    layer.Paint(e.Surface.Canvas);
                }
    
    
                using (var paint = new SKPaint())
                {
                    paint.Color = SKColors.LimeGreen;
    
                    for (int i = 0; i < m_Layers.Count; i++)
                    {
                        var layer = m_Layers[i];
                        var text = $"{layer.Title} - Renders = {layer.RenderCount}, Paints = {layer.PaintCount}";
                        var textLoc = new SKPoint(10, 10 + (i * 15));
    
                        e.Surface.Canvas.DrawText(text, textLoc, paint);
                    }
    
    
                    paint.Color = SKColors.Cyan;
    
                    e.Surface.Canvas.DrawText("Click-Drag to update bars.", new SKPoint(10, 80), paint);
                    e.Surface.Canvas.DrawText("Double-Click to show / hide grid.", new SKPoint(10, 95), paint);
                    e.Surface.Canvas.DrawText("Resize to update all.", new SKPoint(10, 110), paint);
                }
            }
    
    
            // -------------------------------------
            // --- Event - SkglControl1 - Resize ---
            // -------------------------------------
    
            private void SkglControl1_Resize(object sender, EventArgs e)
            {
                // Invalidate all of the Layers
                foreach (var layer in m_Layers)
                {
                    layer.Invalidate();
                }
    
                // Start a new rendering cycle to redraw all of the layers.
                UpdateDrawing();
            }
    
    
            // -----------------------------------------
            // --- Event - SkglControl1 - Mouse Move ---
            // -----------------------------------------
    
            private void SkglControl1_MouseMove(object sender, MouseEventArgs e)
            {
                // Save the mouse position
                m_MousePos = e.Location.ToSKPoint();
    
                // If Left-Click Drag, draw new bars
                if (e.Button == MouseButtons.Left)
                {
                    // Invalidate the Data Layer to draw a new random set of bars
                    m_Layer_Data.Invalidate();
                }
    
                // If Mouse Move, draw new mouse coordinates
                if (e.Location != m_PrevMouseLoc)
                {
                    // Remember the previous mouse location
                    m_PrevMouseLoc = e.Location;
    
                    // Invalidate the Overlay Layer to show the new mouse coordinates
                    m_Layer_Overlay.Invalidate();
                }
    
                // Start a new rendering cycle to redraw any invalidated layers.
                UpdateDrawing();
            }
    
    
            // -------------------------------------------------
            // --- Event - SkglControl1 - Mouse Double Click ---
            // -------------------------------------------------
    
            private void SkglControl1_MouseDoubleClick(object sender, MouseEventArgs e)
            {
                // Toggle the grid visibility
                m_ShowGrid = !m_ShowGrid;
    
                // Invalidate only the Grid Layer.  
                m_Layer_Grid.Invalidate();
    
                // Start a new rendering cycle to redraw any invalidated layers.
                UpdateDrawing();
            }
    
    
            // ----------------------
            // --- Update Drawing ---
            // ----------------------
    
            public void UpdateDrawing()
            {
                // Unblock the rendering thread to begin a render cycle.  Only the invalidated
                // Layers will be re-rendered, but all will be repainted onto the SKGLControl.
                m_ThreadGate.Set();
            }
    
    
            // --------------------------
            // --- Render Loop Method ---
            // --------------------------
    
            private void RenderLoopMethod()
            {
                while (m_KeepSwimming)
                {
                    // Draw any invalidated layers using this Render thread
                    DrawLayers();
    
                    // Invalidate the SKGLControl to run the PaintSurface event on the GUI thread
                    // The PaintSurface event will Paint the layer stack to the SKGLControl
                    skglControl1.Invalidate();
    
                    // DoEvents to ensure that the GUI has time to process
                    Application.DoEvents();
    
                    // Block and wait for the next rendering cycle
                    m_ThreadGate.WaitOne();
                }
            }
    
    
            // -------------------
            // --- Draw Layers ---
            // -------------------
    
            private void DrawLayers()
            {
                // Iterate through the collection of layers and raise the Draw event for each layer that is
                // invalidated.  Each event handler will receive a Canvas to draw on along with the Bounds for 
                // the Canvas, and can then draw the contents of that layer. The Draw commands are recorded and  
                // stored in an SKPicture for later playback to the SKGLControl.  This method can be called from
                // any thread.
    
                var clippingBounds = skglControl1.ClientRectangle.ToSKRect();
    
                foreach (var layer in m_Layers)
                {
                    layer.Render(clippingBounds);
                }
            }
    
    
            // -----------------------------------------
            // --- Event - Layer - Background - Draw ---
            // -----------------------------------------
    
            private void Layer_Background_Draw(object sender, EventArgs_Draw e)
            {
                // Create a diagonal gradient fill from Blue to Black to use as the background
                var topLeft = new SKPoint(e.Bounds.Left, e.Bounds.Top);
                var bottomRight = new SKPoint(e.Bounds.Right, e.Bounds.Bottom);
                var gradColors = new SKColor[2] { SKColors.DarkBlue, SKColors.Black };
    
                using (var paint = new SKPaint())
                using (var shader = SKShader.CreateLinearGradient(topLeft, bottomRight, gradColors, SKShaderTileMode.Clamp))
                {
                    paint.Shader = shader;
                    paint.Style = SKPaintStyle.Fill;
                    e.Canvas.DrawRect(e.Bounds, paint);
                }
            }
    
    
            // -----------------------------------
            // --- Event - Layer - Grid - Draw ---
            // -----------------------------------
    
            private void Layer_Grid_Draw(object sender, EventArgs_Draw e)
            {
                if (m_ShowGrid)
                {
                    // Draw a 25x25 grid of gray lines
    
                    using (var paint = new SKPaint())
                    {
                        paint.Color = new SKColor(64, 64, 64); // Very dark gray
                        paint.Style = SKPaintStyle.Stroke;
                        paint.StrokeWidth = 1;
    
                        // Draw the Horizontal Grid Lines
                        for (int i = 0; i < 50; i++)
                        {
                            var y = e.Bounds.Height * (i / 25f);
                            var leftPoint = new SKPoint(e.Bounds.Left, y);
                            var rightPoint = new SKPoint(e.Bounds.Right, y);
    
                            e.Canvas.DrawLine(leftPoint, rightPoint, paint);
                        }
    
                        // Draw the Vertical Grid Lines
                        for (int i = 0; i < 50; i++)
                        {
                            var x = e.Bounds.Width * (i / 25f);
                            var topPoint = new SKPoint(x, e.Bounds.Top);
                            var bottomPoint = new SKPoint(x, e.Bounds.Bottom);
    
                            e.Canvas.DrawLine(topPoint, bottomPoint, paint);
                        }
                    }
                }
            }
    
    
            // -----------------------------------
            // --- Event - Layer - Date - Draw ---
            // -----------------------------------
    
            private void Layer_Data_Draw(object sender, EventArgs_Draw e)
            {
                // Draw a simple bar graph
    
                // Flip the Y-Axis so that zero is on the bottom
                e.Canvas.Scale(1, -1);
                e.Canvas.Translate(0, -e.Bounds.Height);
    
                var rand = new Random();
    
                // Create 25 red / yellow gradient bars of random length
                for (int i = 0; i < 25; i++)
                {
                    var barWidth = e.Bounds.Width / 25f;
                    var barHeight = rand.Next((int)(e.Bounds.Height * 0.65d));
                    var barLeft = (i + 0) * barWidth;
                    var barRight = (i + 1) * barWidth;
                    var barTop = barHeight;
                    var barBottom = 0;
                    var topLeft = new SKPoint(barLeft, barTop);
                    var bottomRight = new SKPoint(barRight, barBottom);
                    var gradColors = new SKColor[2] { SKColors.Yellow, SKColors.Red };
    
                    // Draw each bar with a gradient fill
                    using (var paint = new SKPaint())
                    using (var shader = SKShader.CreateLinearGradient(topLeft, bottomRight, gradColors, SKShaderTileMode.Clamp))
                    {
                        paint.Style = SKPaintStyle.Fill;
                        paint.StrokeWidth = 1;
                        paint.Shader = shader;
    
                        e.Canvas.DrawRect(barLeft, barBottom, barWidth, barHeight, paint);
                    }
    
                    // Draw the border of each bar
                    using (var paint = new SKPaint())
                    {
                        paint.Color = SKColors.Blue;
                        paint.Style = SKPaintStyle.Stroke;
                        paint.StrokeWidth = 1;
    
                        e.Canvas.DrawRect(barLeft, barBottom, barWidth, barHeight, paint);
                    }
                }
            }
    
    
            // --------------------------------------
            // --- Event - Layer - Overlay - Draw ---
            // --------------------------------------
    
            private void Layer_Overlay_Draw(object sender, EventArgs_Draw e)
            {
                // Draw the mouse coordinate text next to the cursor
    
                using (var paint = new SKPaint())
                {
                    // Configure the Paint to draw a black rectangle behind the text
                    paint.Color = SKColors.Black;
                    paint.Style = SKPaintStyle.Fill;
    
                    // Measure the bounds of the text
                    var text = m_MousePos.ToString();
                    SKRect textBounds = new SKRect();
                    paint.MeasureText(text, ref textBounds);
    
                    // Fix the inverted height value from the MeaureText
                    textBounds = textBounds.Standardized;
                    textBounds.Location = new SKPoint(m_MousePos.X, m_MousePos.Y - textBounds.Height);
    
                    // Draw the black filled rectangle where the text will go
                    e.Canvas.DrawRect(textBounds, paint);
    
                    // Change the Paint to yellow
                    paint.Color = SKColors.Yellow;
    
                    // Draw the mouse coordinates text
                    e.Canvas.DrawText(m_MousePos.ToString(), m_MousePos, paint);
                }
            }
        }
    
    
    
        // ---------------------------------------------------------------------
        // ---------------------------------------------------------------------
        // -------                                                       -------
        // -------                     Class - Layer                     -------
        // -------                                                       -------
        // ---------------------------------------------------------------------
        // ---------------------------------------------------------------------
    
        public class Layer
        {
            // The Draw event that the background rendering thread will use to draw on the SKPicture Canvas.  
            public event EventHandler<EventArgs_Draw> Draw;
    
            // The finished recording - Used to play back the Draw commands to the SKGLControl from the GUI thread
            private SKPicture m_Picture = null;
    
            // A flag that indicates if the Layer is valid, or needs to be redrawn.
            private bool m_IsValid = false;
    
    
            // ---------------------------
            // --- Layer - Constructor ---
            // ---------------------------
    
            public Layer(string title)
            {
                this.Title = title;
            }
    
    
            // -------------
            // --- Title ---
            // -------------
    
            public string Title { get; set; }
    
    
            // --------------
            // --- Render ---
            // --------------
    
     
            // Raises the Draw event and records any drawing commands to an SKPicture for later playback.  
            // This can be called from any thread.
      
    
            public void Render(SKRect clippingBounds)
            {
                // Only redraw the Layer if it has been invalidated
                if (!m_IsValid)
                {
                    // Create an SKPictureRecorder to record the Canvas Draw commands to an SKPicture
                    using (var recorder = new SKPictureRecorder())
                    {
                        // Start recording 
                        recorder.BeginRecording(clippingBounds);
    
                        // Raise the Draw event.  The subscriber can then draw on the Canvas provided in the event
                        // and the commands will be recorded for later playback.
                        Draw?.Invoke(this, new EventArgs_Draw(recorder.RecordingCanvas, clippingBounds));
    
                        // Dispose of any previous Pictures
                        m_Picture?.Dispose();
    
                        // Create a new SKPicture with recorded Draw commands 
                        m_Picture = recorder.EndRecording();
    
                        this.RenderCount++;
    
                        m_IsValid = true;
                    }
                }
            }
    
    
            // --------------------
            // --- Render Count ---
            // --------------------
    
            // Gets the number of times that this Layer has been rendered
    
            public int RenderCount { get; private set; }
    
    
            // -------------
            // --- Paint ---
            // -------------
    
            // Paints the previously recorded SKPicture to the provided skglControlCanvas.  This basically plays 
            // back the draw commands from the last Render.  This should be called from the SKGLControl.PaintSurface
            // event using the GUI thread.
    
            public void Paint(SKCanvas skglControlCanvas)
            {
                if (m_Picture != null)
                {
                    // Play back the previously recorded Draw commands to the skglControlCanvas using the GUI thread
                    skglControlCanvas.DrawPicture(m_Picture);
    
                    this.PaintCount++;
                }
            }
    
    
            // --------------------
            // --- Render Count ---
            // --------------------
    
            // Gets the number of times that this Layer has been painted
    
            public int PaintCount { get; private set; }
    
    
            // ------------------
            // --- Invalidate ---
            // ------------------
    
            // Forces the Layer to be redrawn with the next rendering cycle
    
            public void Invalidate()
            {
                m_IsValid = false;
            }
        }
    
    
        // ---------------------------------------------------------------------
        // ---------------------------------------------------------------------
        // -------                                                       -------
        // -------                    EventArgs - Draw                   -------
        // -------                                                       -------
        // ---------------------------------------------------------------------
        // ---------------------------------------------------------------------
    
    
        public class EventArgs_Draw : EventArgs
        {
            public SKRect Bounds { get; set; }
            public SKCanvas Canvas { get; set; }
    
            public EventArgs_Draw(SKCanvas canvas, SKRect bounds)
            {
                this.Canvas = canvas;
                this.Bounds = bounds;
            }
        }
    
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM