Skip to content

Repository files navigation

ProEssentials WinUI Real-Time Circular Buffer — 4 x 2 Million Points

A ProEssentials v11 WinUI 3 .NET 10 demonstration of the highest-performance real-time line charting pattern using PesgoWinUI — combining local-memory UseDataAtLocation, CircularBuffers, GPU ComputeShader with StagingBuffers, and Filter2D3D data reduction to sustain a continuous 8 million float real-time update at 15ms intervals.

Extends the base example 146 pattern with a custom right-click Zoom Mode menu that lets you switch live between two zooming behaviors — demonstrating the single-line timer tick difference between examples 145 and 146.

ProEssentials WinUI Real-Time Circular Buffer

➡️ gigasoft.com/examples/146

The chart-configuration code is identical to the WPF and WinForms versions of this example. The ProEssentials API does not change between interfaces; only the window plumbing differs.


What This Demonstrates

4 subsets × 2,000,000 points = 8,000,000 floats updated continuously at 150 new samples per subset every 15ms. The chart never allocates new memory, never shifts existing data, and the GPU never waits on a CPU copy — achieving the maximum throughput possible in a .NET WinUI application.


The Two Zoom Modes (right-click → Zoom Mode)

This repo combines examples 145 and 146 into a single app, toggling between their zoom behaviors via the custom right-click menu. The subtitle updates to reflect the active mode.

Stationary (default — example 146 behavior)

Zoom in → zoom window stays fixed
New data streams past behind it

When zoomed in, the zoom window does not advance. New data continuously appends to the ring buffer and updates the full axis extents, but the zoomed viewport is anchored in time. Use this to study a feature in detail while the live stream keeps running.

Scrolling (example 145 behavior)

Zoom in → zoom window follows the latest samples

When zoomed in, Zoom.MaxX and Zoom.MinX are each advanced by nNewSamplesPerTick every frame. The viewport "follows" the data stream, always showing the most recent 150 samples per tick.

The Entire Code Difference

// In Timer_Tick — the only difference between 145 and 146:

if (_zoomScrolls && Pesgo1.PeGrid.Zoom.Mode)  // Scrolling mode (145)
{
    Pesgo1.PeGrid.Zoom.MaxX += nNew;
    Pesgo1.PeGrid.Zoom.MinX += nNew;
}
// Comment out the block above = Stationary mode (146)

ProEssentials Features Demonstrated

UseDataAtLocation — Zero-Copy Local Memory

// App owns these arrays — chart holds a pointer, never copies them
private readonly float[] _xData = new float[2_000_000];
private readonly float[] _yData = new float[8_000_000];

// Give the chart direct access — no transfer, no allocation
Pesgo1.PeData.X.UseDataAtLocation(_xData, 2_000_000);
Pesgo1.PeData.Y.UseDataAtLocation(_yData, 8_000_000);

Compare to example 145 which uses FastCopyFrom — that copies 8M floats into chart-internal circular buffers at init time. UseDataAtLocation is faster because the GPU staging buffers read from your memory directly. No copy ever happens between app memory and chart memory.

CircularBuffers

Pesgo1.PeData.CircularBuffers = true;  // set before UseDataAtLocation

AppendData writes into a ring buffer rather than shifting all existing data left. With 2M points per subset, shifting without CircularBuffers would move 8M floats every 15ms. With it, append is O(1) — only the new samples are written. ComputeShader rendering reads the ring in-order with no penalty.

DuplicateDataX — Single X Array for All Subsets

Pesgo1.PeData.DuplicateDataX = DuplicateData.PointIncrement;
// ...
Pesgo1.PeData.X.UseDataAtLocation(_xData, 2_000_000);  // one array, not four

One shared X array instead of 4 × 2M = 8M floats of redundant X data. The chart uses _xData[p] for all subsets automatically. AppendData on X sends only 150 values per tick instead of 600.

GPU Pipeline — ComputeShader + StagingBuffers + Filter2D3D

Pesgo1.PeConfigure.RenderEngine = RenderEngine.Direct3D;
Pesgo1.PeData.ComputeShader     = true;  // geometry built on GPU
Pesgo1.PeData.StagingBufferX    = true;  // DX CPU→GPU transfer path for X
Pesgo1.PeData.StagingBufferY    = true;  // DX CPU→GPU transfer path for Y
Pesgo1.PeData.Filter2D3D        = true;  // lossless pixel-level reduction
Pesgo1.PeSpecial.AutoImageReset = false; // suppress intermediate redraws

At 2M points per subset, only ~1000 points are visible per pixel-column at any zoom level. Filter2D3D applies a lossless min/max reduction before GPU transfer — the GPU only processes the visible data density, not the full 2M. AutoImageReset = false suppresses the intermediate invalidation path, preventing tearing at the 15ms update rate.

Manual Axis Scaling — Skip the Min/Max Scan

Pesgo1.PeGrid.Configure.ManualScaleControlY = ManualScaleControl.MinMax;
Pesgo1.PeGrid.Configure.ManualMinY          = -110;
Pesgo1.PeGrid.Configure.ManualMaxY          =  220;

Pesgo1.PeGrid.Configure.ManualScaleControlX = ManualScaleControl.MinMax;
Pesgo1.PeGrid.Configure.ManualMinX          = 0;
Pesgo1.PeGrid.Configure.ManualMaxX          = 2_000_000;

Without manual scaling the chart scans all 8M floats every tick to find min/max. With manual scaling that scan is skipped entirely — the axis extents are updated manually in the timer tick as the counter advances.

Custom Right-Click Menu — PeCustomMenu Event (example 127 pattern)

// Define menu items — "|" prefix = separator, "Title|Sub1|Sub2" = submenu
Pesgo1.PeUserInterface.Menu.CustomMenuText[0] = "|";
Pesgo1.PeUserInterface.Menu.CustomMenuText[1] = "Zoom Mode|Stationary|Scrolling";

// Default: Stationary checked
Pesgo1.PeUserInterface.Menu.CustomMenuState[1, 1] = CustomMenuState.Checked;
Pesgo1.PeUserInterface.Menu.CustomMenuState[1, 2] = CustomMenuState.UnChecked;

// Both items placed at bottom of built-in menu
Pesgo1.PeUserInterface.Menu.CustomMenuLocation[0] = CustomMenuLocation.Bottom;
Pesgo1.PeUserInterface.Menu.CustomMenuLocation[1] = CustomMenuLocation.Bottom;
// Wire the event:
Pesgo1.PeCustomMenu += Pesgo1_PeCustomMenu;

// Handler — radio-style mutual exclusion:
private void Pesgo1_PeCustomMenu(object sender,
    Gigasoft.ProEssentials.EventArg.CustomMenuEventArgs e)
{
    if (e.MenuIndex != 1) return;

    Pesgo1.PeUserInterface.Menu.CustomMenuState[1, 1] = CustomMenuState.UnChecked;
    Pesgo1.PeUserInterface.Menu.CustomMenuState[1, 2] = CustomMenuState.UnChecked;
    Pesgo1.PeUserInterface.Menu.CustomMenuState[1, e.SubmenuIndex] = CustomMenuState.Checked;

    _zoomScrolls = (e.SubmenuIndex == 2);
}

Y Data Memory Layout

The _yData array is a flat interleaved block, subset-major order:

[0       .. 1999999]   subset 0  (red)
[2000000 .. 3999999]   subset 1  (green)
[4000000 .. 5999999]   subset 2  (yellow)
[6000000 .. 7999999]   subset 3  (cyan)

UseDataAtLocation(_yData, 8_000_000) passes the total element count. AppendData(_newY, 150) appends 150 samples per subset per tick, laid out the same way: [s0p0..s0p149 | s1p0..s1p149 | s2.. | s3..].


The Timer Tick

WinUI has no DispatcherTimer. The equivalent is a DispatcherQueueTimer created off the window's own dispatcher queue — it cannot be a field initializer, because the queue does not exist until the window is constructed:

_timer = DispatcherQueue.CreateTimer();     // in the constructor
_timer.Interval    = TimeSpan.FromMilliseconds(15);
_timer.IsRepeating = true;
_timer.Tick       += Timer_Tick;
_timer.Start();

// The Tick signature differs from WPF — the timer is the sender:
private void Timer_Tick(DispatcherQueueTimer sender, object e)

Everything inside the tick body is unchanged from the WPF version.


Controls

Input Action
Mouse wheel Horizontal zoom in / out
Right-click Context menu
Right-click → Zoom Mode → Stationary Zoom window fixed (default)
Right-click → Zoom Mode → Scrolling Zoom window follows data

Prerequisites

  • Visual Studio 2026 with the .NET desktop development and Windows application development workloads
  • .NET 10 SDK
  • Internet connection for NuGet restore
  • Discrete GPU recommended (integrated GPU will work but may throttle)

No XAML designer: Visual Studio has no XAML designer for WinUI 3 in any edition, so the chart is declared in markup (<pe:PesgoWinUI x:Name="Pesgo1" />) rather than dragged from the Toolbox. That is a Windows App SDK limitation, not a ProEssentials one.

Window sizing is code, not XAML: a WinUI Window is not a Control — it has no Height/Width/MinWidth, no Loaded event and no Closing. SizeAndCenterWindow() sets the size with a Win32 MoveWindow call, the chart is initialized from the chart's own Loaded event, and Closing becomes Closed.


How to Run

1. Clone this repository
2. Open RealTimeCircularBufferWinUI.sln in Visual Studio 2026
3. Build → Rebuild Solution (NuGet restore is automatic)
4. Press F5
5. Mouse-wheel to zoom in, then right-click → Zoom Mode to switch behaviors

NuGet Package

References ProEssentials.Chart.Net10.WinUI. Package restore is automatic on build.

The AnyCpu package embeds the native rendering engines inside the assembly and unpacks the one matching the running process at run time, so there is no native DLL to copy or deploy alongside your app. It also carries the control's .pri, whose resources are merged into your app's own .pri at build time.


Evaluation Watermark

The nuget.org package is the evaluation build and draws an "Evaluating ProEssentials" watermark across the chart. A licensed installation replaces the assembly and the watermark goes away — performance and behavior are identical.


Deploying a WinUI App

WinUI deploys differently than WPF and WinForms:

  • A .NET app is not a single exe — ship the entire build or publish output folder, never a hand-picked subset.
  • The target machine needs two runtimes: the .NET 10 Desktop Runtime and the Windows App SDK Runtime. Or publish self-contained with -p:WindowsAppSDKSelfContained=true.
  • Your development machine already has both, so it will run from an under-filled folder. Always validate on a clean machine.

Related Examples


License

Example code is MIT licensed. ProEssentials requires a commercial license for continued use.

About

WinUI 3 realtime line chart, .NET 10 C# - 4 subsets x 2M points via zero-copy UseDataAtLocation plus CircularBuffers and Direct3D compute shaders. Custom menu toggles stationary vs scrolling zoom behaviour. PesgoWinUI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages