首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C# SkiaSharp OpenTK Winform -如何从后台线程中绘制?

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

Stack Overflow用户
提问于 2020-11-08 11:37:39
回答 1查看 1.9K关注 0票数 7

我正试图用GDI+代替SkiaSharp,使用一个数据可视化框架来呈现具有实时不断变化的工程数据的多层pannable可缩放图。

在GDI+中,应用程序执行以下操作:

  • 创建了一个具有透明背景的绘图层集合,通常是一个网格层、一个或多个数据层,以及一个用于光标信息和高亮显示的覆盖层,每个图层都由一个单独的位图支持。在呈现循环背景线程中的
  • ,只使用GDI+重新绘制每个呈现周期需要更新的层(位图)。这可能需要成千上万的计算和转换的线条、矩形和文本来创建热图、波形、柱状图、数据标签等。然后,
  • 堆栈中的每个绘图层将被背景线程
  • 绘制成一个复合位图,最后的复合位图将被绘制到图形用户界面线程中的WinForm PictureBox中,长度可达30 GUI。

直到最终图像呈现的所有内容都是在一个或多个后台线程中完成的。GUI线程仅用于将完成的映像绘制到PictureBox。这一点很重要,因为还有许多其他GUI控件需要保持响应性。这是很好的工作,但它都是基于CPU的。小窗口是没有问题的,但最大化的4K屏幕将减缓渲染足够多的程序无法使用。

I想用GPU加速的SkiaSharp.重新创建这个概念

我试着创建了几十个不同的测试程序,我不断地获得交叉线程访问冲突,或者屏幕上没有显示,或者硬崩溃。让我来问一些基本问题,而不是发布代码:

问题:

  • ,您将如何创建这个框架?SkiaSharp甚至能做到这一点吗?我的图层类中的每个类都应该维护一个SKSurface、SKCanvas、SKImage或SKBitmap吗?--再说一遍,如果一个层不需要为当前的周期重新绘制,那么该层需要维护以前绘制的内容,以便在下一个复合映像中使用。在GUI线程上需要使用
  • A GLControl和GRContext来显示最终的复合图像,但背景呈现线程应该使用另一个单独的GRContext吗?--如何用GPU加速创建?
  • 是否有类似概念的工作示例?( GPU加速从后台线程呈现到GLControl )
  • 是否应该使用隐藏在后台的SkiaSharp,以及GDI+ BitBlt与PictureBox一起在屏幕上显示复合图像?-这会解决一些线程问题吗?

将非常感谢您提供任何帮助来定义该方法以及所要做的和不需要做的事情!!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-11-29 02:50:23

我想出了如何使用SKPicture对象来使用背景呈现线程记录每个层的绘制命令,然后使用GUI线程将它们绘制回SKGLControl。这满足了我所有的要求:它允许多个绘图层,用一个后台线程呈现,只呈现需要更新的层,使用GPU加速绘制,并且对于最大化的4K窗口非常快速。

汲取的经验教训

我在路上学到的一些教训给我带来了很多困惑……

  1. 有在线使用带GPU加速的OpenTK.GLControl的例子,也有使用内置于GPU加速的SkiaSharp.Views.Desktop.SKGLControl的例子。SKGLControl绝对是此任务的正确控件。GLControl为DrawCircle创建了方块,并且由于FramebufferBinding和StencilBits的问题拒绝渲染任何曲线?!-我放弃了。它也比SKGLControl for SKPicture对象慢。

  1. SKGLControl不需要也不喜欢使用SwapBuffers或Canvas.Flush,这是GLControl所必需的。这导致了SKGLControl图纸的碰撞和故障,这就是为什么我在与GLControl搏斗的杂草中消失的原因。当我用SKGLControl重建这个项目时,摆脱了SwapBuffers和Canvas.Flush,一切都开始运转起来。

  1. 对表面和画布的引用不应超过一个PaintSurface周期。SKPicture是一个神奇的对象,它允许您存储每一层的绘图命令,并一次又一次地播放它们。这与SKBitmap或SKImage不同,后者生成像素光栅,而不只是记录绘制命令。我无法让SKBitmap或SKImage在多线程环境中运行,而且仍然被GPU加速。SKPicture在这方面做得很好。

  1. 在PaintSurface事件和PaintSurface事件之间有区别。accelerated.

事件是应该使用的,默认情况下是GPU PaintSurface。

工作示例代码

下面的是一个多层、多线程、GPU加速SkiaSharp绘图的全功能演示。

此示例创建4个绘图层:

  • 背景层
  • 网格层
  • 数据层

这些层使用后台线程绘制(呈现),然后使用GUI线程绘制到SKGLControl。每个层只在需要时才呈现,但所有层都使用每个PaintSurface事件进行绘制。

要尝试该代码:

  1. 在Visual中创建一个新的C# WinForms项目。
  2. 添加NuGet包:"SkiaSharp.Views.WindowsForms“。这将自动添加"SkiaSharp",并将"SkiaSharp.Views.Desktop.Common".
  3. Add a SkiaSharp.Views.Desktop.SKGLControl添加到Form1中。为"skglControl1"
  4. Set命名为skglControl1的“填充”,以便填充Form1.
  5. 将下面的代码复制到Form1:

代码语言:javascript
运行
复制
    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;
            }
        }
    
    }
票数 14
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64737621

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档