Saturday, 26 March 2022

Pixel Graphics Winforms

In an older post I illustrated how one can achieve decent pixel-based graphics. Following further research, I built upon the original idea. In this post, I achieve render times of about 11 milliseconds. For 60 FPS one must render each frame in about 16.7 milliseconds.

Top

Background

In the early days of Windows, an application could fully control the graphics display. Early games took advantage of this and essentially bypassed the operating system. Modern Windows due to its multitasking nature, no longer allows an application to "own" any resources outright. Resources are shared, including graphical data. Personally, I think the operating system should have a "Game Mode" setting, where only critical processes, threads, etc are run. I mean, does anyone want to run multiple apps whilst running a game?

Modern games use the graphics card(GPU) to output graphics. Data is sent to the GPU, leaving the CPU to perform CPU-bounds tasks (updating player position, score and so on). This is great if your app has access to DirectX, Direct 3D, OpenGL. However, if your app is unable to access such technology, and you are bound by simple Winforms technology...Read on!

Top

Winforms Graphics Basics

Each Window is assigned a graphics context. This usually arises in a Window's OnPaint event. Paint messages are classed as low priority messages, and as such are posted rather than sent. A sent message yields an immediate return, posted messages will yield a result in the future. The above is based upon ealier versions of Windows, though I doubt much has changed. Regardless, the OnPaint and supplied Graphics context is what is of interest here.

Top

Memory

C# as a rule manages memory. One can allocate memory (not directly) by creating new classes, structures etc. The memory required for such items will be released when the class/struct instance is no longer required. The CLR runtime can also relocate memory as and when required. When dealing with bitmaps, the latter is not ideal. Relocating memory for large bitmaps is expensive in terms of CPU time.

It is possible to allocate memory outside of the CLR. Doing so ensures that the CLR will never try to reclaim the memory. Put another way, you will be responsible for memory management. Almost in C++ land!

Manual memory management is a must for fast pixel graphics.

Top

Image Processing Pipeline

The basic pipeline is as follows...

  • Create an array to hold image information
  • Create a bitmap that uses memory from 1st step
  • Update array to reflect image information (Bitmap points to array memory so is automatically updated)
  • Blit bitmap to main form or relevant window
  • Repeat until app ends
Top

Multithreading/async programming

>My own research proved that adding threads or using asynchronous programming did not yield better results.

A Windows message loop generally doesn't play well with other threads. Typically, additional threads, at some point must communicate results with the main UI thread. This requires thread synchronisation techniques, which tend to be quite expensive. Performing most operations on the same thread seems to give best results.

That said, updating non UI elements may be worthwhile performing on separate thread(s). For example, whilst the UI thread is blitting current content, another thread might update values for the next scene.

Top

Achieving Optimal Paint Results

As mentioned, Windows posts a WM_PAINT message to a window, when an application window needs re-painting. One can invoke the WM_PAINT, and hence cause window repainting by issuing an Invalidate command. This may be followed by an Update command to perform immediate repainting.

I found that the best way to do this is to override the Application.OnIdle event. In the handler, one can just call invalidate. This will post the WM_PAINT message to the window. Once painting has occurred, the idle event will be raised again. This essentially causes a forever painting loop whilst still being responsive to other events.

The Code

The project is a .NET Windows Application targeting .NET Framework 4.8. No 3rd party libraries are used.


  using System;
  using System.Drawing;
  using System.Drawing.Imaging;
  using System.Runtime.InteropServices;
  
  namespace FastGraphics
  {
    /// <summary>
    /// Uses pinned memory and direct array access to manipulate bitmap bits.
    /// Pinned memory is not owned by the CLR, so, it is important to free when done.
    /// </summary>
    public sealed class FastBitmap : IDisposable
    {
      private GCHandle _pinnedMemoryHandle;
      private IntPtr _pinnedMemoryAddress;    
  
      public int Width { get; private set; }
      public int Height { get; private set; }
      public uint[] Pixels { get; private set; }
      public Bitmap Image { get; private set; }
  
      public FastBitmap(int width, int height)
      {
        // Pixels is an array that may be used to modify pixel data.
        // Typical formula is ((y*width)+x) = Alpha | Colour;
        // Uses 4 bytes per pixel (Alpha, Red, Green and Blue channels, each 8 bits)
        Pixels = new uint[width * height];
  
        // Inform OS that pixel allocated array is pinned and not subject to normal garbage collection.
        _pinnedMemoryHandle = GCHandle.Alloc(Pixels, GCHandleType.Pinned);
  
        // Obtain the pinned memory address, required to create bitmap for use by .NET app.
        _pinnedMemoryAddress = _pinnedMemoryHandle.AddrOfPinnedObject();
  
        // Set pixel format and calculate scan line stride.
        PixelFormat format = PixelFormat.Format32bppArgb;
        int bitsPerPixel = ((int)format & 65280) >> 8;
        int bytesPerPixel = (bitsPerPixel + 7) / 8;
        int stride = 4 * ((width * bytesPerPixel + 3) / 4);
        Image = new Bitmap(width, height, stride, PixelFormat.Format32bppArgb, _pinnedMemoryAddress);
        Width = width;
        Height = height;      
      }
  
      public void Draw(Graphics target) =>
        target.DrawImage(Image, 0, 0);
  
      public void Dispose()
      {
        Image.Dispose();
  
        if (_pinnedMemoryHandle.IsAllocated)
          _pinnedMemoryHandle.Free();
  
        _pinnedMemoryAddress = IntPtr.Zero;
        Pixels = Array.Empty<uint>();
        Width = 0;
        Height = 0;
      }
    }
  }
  

    using System;
    using System.Windows.Forms;
    
    namespace FastGraphics
    {
      internal static class Program
      {
        [STAThread]
        static void Main()
        {
          Application.EnableVisualStyles();
          Application.SetCompatibleTextRenderingDefault(false);
    
          try
          {
            Form1 form = new Form1();
            Application.Idle += (s, e) => form.OnIdle();
            Application.Run(form);
          }
          catch (Exception ex)
          {
            MessageBox.Show(ex.Message);
          }
        }
      }
    }

      using System;
      using System.Drawing.Drawing2D;
      using System.Windows.Forms;
      
      namespace FastGraphics
      {
        public partial class Form1 : Form
        {
          private FastBitmap _fastBitmap;
          private float _offset = 0;
      
          private uint _renderTimeInMilliseconds;
      
          public Form1()
          {
            InitializeComponent();
          }
      
          /// <summary>
          /// Update on-screen text that displays render time in miliiseconds.
          /// </summary>
          public void UpdateRenderTimeText()
          {
            label1.Text = _renderTimeInMilliseconds.ToString();
          }
      
          /// <summary>
          /// Dispose of existing fast bitmap (if exists)
          /// Create new fast bitmap based upon client rectangle.
          /// </summary>
          private void CreateFastBitmap()
          {
            _fastBitmap?.Dispose();
            _fastBitmap = new FastBitmap(ClientRectangle.Width, ClientRectangle.Height);
          }
      
          public void OnIdle()
          {
            Invalidate();
            UpdateRenderTimeText();
          }
      
          // Resizing form changes drawing area.
          // Resize fast bitmap accordingly.
          protected override void OnResize(EventArgs e)
          {
            CreateFastBitmap();
          }
      
          /// <summary>
          /// Override OnPaintBackground to do nothing.
          /// The OnPaint will perform all drawing.
          /// </summary>
          /// <param name="e"></param>
          protected override void OnPaintBackground(PaintEventArgs e)
          {
          }
      
          /// <summary>
          /// Magic happens in OnPaint.
          /// 1. Render raw pixel data (Fast bitmap)
          /// 2. Due to the nature of fast bitmap, its image will reflect pixel data.
          /// 3. Draw fast bitmap image to this form's window.
          /// 4. Time all of this so we can determine time taken to render a frame.
          /// Optional - set interpolation mode.
          /// </summary>
          /// <param name="e"></param>
          protected override void OnPaint(PaintEventArgs e)
          {
            DateTime startTime = DateTime.UtcNow;
            e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
            RenderRawPixels();
            _fastBitmap.Draw(e.Graphics);
            this.BackgroundImage = _fastBitmap.Image;
            DateTime endTime = DateTime.UtcNow;
            _renderTimeInMilliseconds = (uint)(endTime - startTime).Milliseconds;
          }
      
          /// <summary>
          /// RenderRawPixels is called for each onPaint.    
          /// </summary>
          private void RenderRawPixels()
          {
            int rowAddress = 0;
            int width = _fastBitmap.Width;
            int height = _fastBitmap.Height;
            uint[] pixels = _fastBitmap.Pixels;
      
            for (int y = 0; y < height; y++)
            {
              float yF = (float)y;
              for (int x = 0; x < width; x++)
              {
                float xF = (float)x;
      
                float f =
                  ((xF * xF * xF * yF) / 16384) +
                  ((xF * yF * yF * yF) / 16384) +
                  ((xF * yF * _offset) / 16384);
                f *= _offset;
                f /= 65536;
                f /= 8192;
      
                uint alpha = 4278190080;
                // Alpha component tends to double rendering time
                // Setting to 0xFF000000 will ensure the above renders solid colours.
                // Uncomment the code below to introduce alpha component.
                //uint alpha = (uint)(
                //  (x * y) -
                //  (y * _offset)) /
                //  1024;
                //alpha <<= 24;
                //alpha &= 0xff000000;
      
      
                pixels[rowAddress + x] = alpha | (uint)(f * 16777215);
              }
              rowAddress += width;
            }
            BackgroundImage = _fastBitmap.Image;
            _offset += 0.005;
          }
      
          /// <summary>
          /// upon closing this form, we must dispose the fast bitmap.
          /// </summary>
          /// <param name="e"></param>
          protected override void OnClosed(EventArgs e)
          {
            _fastBitmap.Dispose();
            base.OnClosed(e);
          }
        }
      }

        namespace FastGraphics
        {
          partial class Form1
          {
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;
        
            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
              if (disposing && (components != null))
              {
                components.Dispose();
              }
              base.Dispose(disposing);
            }
        
            #region Windows Form Designer generated code
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
              this.label1 = new System.Windows.Forms.Label();
              this.SuspendLayout();
              // 
              // label1
              // 
              this.label1.BackColor = System.Drawing.Color.Black;
              this.label1.ForeColor = System.Drawing.Color.White;
              this.label1.Location = new System.Drawing.Point(-2, -1);
              this.label1.Name = "label1";
              this.label1.Size = new System.Drawing.Size(43, 18);
              this.label1.TabIndex = 0;
              this.label1.Text = "Time";
              this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
              // 
              // Form1
              // 
              this.AutoScaleDimensions = new System.Drawing.SizeF(6, 13);
              this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
              this.ClientSize = new System.Drawing.Size(800, 450);
              this.Controls.Add(this.label1);
              this.DoubleBuffered = true;
              this.Name = "Form1";
              this.Text = "Form1";
              this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
              this.ResumeLayout(false);
        
            }
        
            #endregion
            public System.Windows.Forms.Label label1;
          }
        

Saturday, 12 February 2022

C# To HTML

In this post I use Rosyln to covert C# code to HTML. Took me best part of a day to understand the Rosyln code. Not exactly straightforward.

The Code

The code is split into two parts, syntax tree walker, code renderer. There is then the code to compile c# code and convert to HTML. To date, comments, keywords and document comments (e.g. ///) are catered for.

IRenderCode interface

To keep things simple, this interface renders code based upon a supplied Token and string. Another way is to have a method for each token to handle. However, I tend to like smaller interfaces. The IRenderInterface is defined as follows...

Top

using CSharpToHtml.CodeRenderers;
namespace CSharpToHtml
{
  /// <summary>
  /// The Token enum lists all tokens we are interested in for C# to HTML conversion.
  /// </summary>
  public enum Token
  {
    Comment,
    DocumentComment,
    LiteralChar,
    LiteralString,
    Keyword,
    Text,
  }

  /// <summary>
  /// The IRenderCode interface renders token and supplied text.
  /// The Completed method is called once tokenisation is completed.
  /// </summary>
  public interface IRenderCode
  {
    void Render(Token token, string text);
    void Completed();
  }

  /// <summary>
  /// The FactoryRenderCode class creates specific renderer instances.
  /// </summary>
  public static class FactoryRenderCode
  {
    public static IRenderCode ToConsole() =>
      new RenderCodeConsole();

    public static IRenderCode ToHtml(TextWriter writer) =>
      new RenderCodeHtml(writer);
  }
}
Top

Rendering HTML

The HTML render class is as follows...


using System.Web;
namespace CSharpToHtml.CodeRenderers
{
  class RenderCodeHtml : IRenderCode
  {
    private readonly TextWriter _writer;
    private readonly Dictionary<Token, Action<string>> _handlers;    

    public RenderCodeHtml(TextWriter writer)
    {
      _writer = writer;
      _writer.Write("<pre class="code"><code>
");
      _handlers = new Dictionary<Token, Action<string>>
      {
        { Token.Comment, s => Write("Green", s) },
        { Token.DocumentComment, s => Write("Green", s) },
        { Token.Keyword, s => Write("Blue", s) },
        { Token.LiteralChar, s => Write("Red", s) },
        { Token.LiteralString, s => Write("Red", s) },
        { Token.Text, s => Write(s) },
      };
    }

    public void Render(Token type, string text)
    {
      _handlers[type](text);
    }

    public void Completed()
    {
      _writer.Write("</code></pre>");
    }

    private void Write(string text)
    {
      _writer.Write(HttpUtility.HtmlEncode(text));
    }

    private void Write(string color, string text)
    {
      _writer.Write($"<span style="color:{color};">");
      Write(text);
      _writer.Write("</span>");
    }
  }
}
Top

Syntax Tree Walker

To make all of this work, one needs a syntax tree walker. Using the CSharpSyntaxWalker was a good start. The first thing I wanted to do, for test purposes, was to traverse the syntax tree and print values so that the resultant content would look like my original code. This is what one might call a sanity check. The syntax walker class is as follows...


using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace CSharpToHtml
{
  /// <summary>
  /// The SyntaxWalker parses supplied code (RenderCode) and walks tokens.
  /// The base class, CSharpSyntaxWalker, allows one to walk trivia as well.
  /// Trivia includes tokens such as whitespace, comments, etc.
  /// As such, token depth walking should suffice for the IRenderCode interface.
  /// </summary>
  public class SyntaxWalker : CSharpSyntaxWalker
  {
    // _model is not currently used
    private readonly SemanticModel _model;
    private readonly IRenderCode _render;


    /// <summary>
    /// Take the code to parse and the code renderer to render tokens.
    /// </summary>
    public static void RenderCode(string code, IRenderCode renderer)
    {
      // Create syntax tree from supplied code.
      var tree = CSharpSyntaxTree.ParseText(code);

      // Create a new compilation unit, gives access to the semantic model.
      var compilation = CSharpCompilation.Create(
        "MyCompilation",
        new[] { tree },
        new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });

      // Create semantic model, a walker and node visitor.
      // Visit nodes then call renderer completed.
      var semanticModel = compilation.GetSemanticModel(tree);
      var walker = new SyntaxWalker(semanticModel, renderer);
      walker.Visit(semanticModel.SyntaxTree.GetRoot());
      renderer.Completed();
    }    

    /// <summary>
    /// Visit all tokens.
    /// A token may have leading or trailing trivia.
    /// Trivia is defined as whitespace or comments.
    /// </summary>
    /// <param name="token"></param>
    public override void VisitToken(SyntaxToken token)
    {
      // Process trivia that may lead the supplied token.
      if (token.HasLeadingTrivia)
        ProcessTrivia(token.LeadingTrivia);

      // Special case - token is a keyword.
      if (token.IsKeyword())
        _render.Render(Token.Keyword, token.ValueText);
      else
      {
        // get token type.
        var kind = token.Kind();
        switch (kind)
        {
          // token is a character.
          case SyntaxKind.CharacterLiteralToken:
            _render.Render(Token.LiteralChar, $"'{token.ValueText}'");
            break;

          // token is a string.
          case SyntaxKind.StringLiteralToken:
            _render.Render(Token.LiteralString, $""{token.ValueText}"");
            break;

          // don't care what the token is at this point.
          default:
            _render.Render(Token.Text, token.ValueText);
            break;
        }        
      }

      // Process trailing trivia (typically whitespace).
      if (token.HasTrailingTrivia)
        ProcessTrivia(token.TrailingTrivia);

      base.VisitToken(token);
    }

    private SyntaxWalker(
      SemanticModel model,
      IRenderCode walker) : base(SyntaxWalkerDepth.Token)
    {
      _model = model;
      _render = walker;
    }

    /// <summary>
    /// Process trivia (comments, whitespace or text.
    /// </summary>
    /// <param name="triviaCollection"></param>
    private void ProcessTrivia(SyntaxTriviaList triviaCollection)
    {
      foreach (var trivia in triviaCollection)
      {
        var kind = trivia.Kind();

        switch (kind)
        {
          // Single or multiline comments are rendered as a Token.Comment.
          case SyntaxKind.SingleLineCommentTrivia:
          case SyntaxKind.MultiLineCommentTrivia:          
            _render.Render(Token.Comment, trivia.ToString());
            break;

          // Document comments are rendered as a Token.DocumentComment.
          case SyntaxKind.SingleLineDocumentationCommentTrivia:
          case SyntaxKind.MultiLineDocumentationCommentTrivia:
            string text = "///" + trivia.ToString();
            _render.Render(Token.DocumentComment, text);
            break;
          default:
            _render.Render(Token.Text, trivia.ToString());
            break;
        }        
      }
    }
  }
}

Using the code

To generate HTML output the following code should be used.


using CSharpToHtml;
var code = File.ReadAllText("myfile");
using var writer = new StringWriter();
SyntaxWalker.RenderCode(code, FactoryRenderCode.ToHtml(writer));
string html = writer.ToString();
}

One can also pass C# code as a string to the SyntaxWalker.RenderCode method.

Summary

I showed how to use the C# syntax walker to pick out tokens for use when converting code. It should be noted that syntax highlighting for code in this post was genererated using the above code.