+
+
+
diff --git a/Samples/BlazorSampleApp/BlazorSampleApp.csproj b/Samples/BlazorSampleApp/BlazorSampleApp.csproj
new file mode 100644
index 000000000..04c515bfd
--- /dev/null
+++ b/Samples/BlazorSampleApp/BlazorSampleApp.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/BlazorSampleApp/Components/BasicCanvas.razor b/Samples/BlazorSampleApp/Components/BasicCanvas.razor
new file mode 100644
index 000000000..03e7b46f8
--- /dev/null
+++ b/Samples/BlazorSampleApp/Components/BasicCanvas.razor
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Samples/BlazorSampleApp/Components/BasicCanvas.razor.cs b/Samples/BlazorSampleApp/Components/BasicCanvas.razor.cs
new file mode 100644
index 000000000..5777beded
--- /dev/null
+++ b/Samples/BlazorSampleApp/Components/BasicCanvas.razor.cs
@@ -0,0 +1,246 @@
+
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2021 ILGPU Project
+// www.ilgpu.net
+//
+// File: BasicCanvas.razor.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
+
+namespace BlazorSampleApp.Components
+{
+
+#nullable disable
+ public partial class BasicCanvas : ComponentBase, IAsyncDisposable
+ {
+
+ private IJSObjectReference asyncModule = null;
+
+ private IJSInProcessObjectReference module = null;
+
+ protected IJSRuntime _jsRuntime;
+
+ protected IJSInProcessRuntime _jsInProcessRuntime = null;
+
+ public event Action CanvasInitComplete = null;
+
+
+
+
+ [Parameter]
+ public bool IsTransparent { get; set; } = false;
+
+ [Parameter]
+ public bool IsDesyncronized { get; set; } = false;
+
+ [Parameter]
+ public int Height { get; set; } = 600;
+
+ [Parameter]
+ public int Width { get; set; } = 800;
+
+ [Parameter]
+ public bool IsFullScreen { get; set; } = false;
+
+
+ [Parameter]
+ public string CanvasId { get; set; } = Guid.NewGuid().ToString();
+
+ protected ElementReference _canvasRef;
+
+ public ElementReference CanvasReference => this._canvasRef;
+
+ public bool IsWebAssembley { get { return (_jsRuntime is IJSInProcessRuntime); } }
+
+ private bool IsDisposing = false;
+
+ [Inject]
+ public IJSRuntime JS_Runtime
+ {
+ get
+ {
+ return _jsRuntime;
+ }
+ set
+ {
+ _jsRuntime = value;
+
+ if (IsWebAssembley)
+ {
+ _jsInProcessRuntime = (IJSInProcessRuntime)value;
+
+ }
+ }
+ }
+
+
+
+
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ await base.OnAfterRenderAsync(firstRender);
+ if (firstRender)
+ {
+
+ try
+ {
+ if (IsWebAssembley)
+ {
+
+ module = await _jsInProcessRuntime.InvokeAsync("import", "./Scripts/BasicCanvas.js");
+
+ module.InvokeVoid("initializeBasicCanvas", CanvasId, IsWebAssembley, IsTransparent, IsDesyncronized);
+ }
+ else
+ {
+ asyncModule = await JS_Runtime.InvokeAsync("import", "./Scripts/BasicCanvas.js");
+
+ await asyncModule.InvokeVoidAsync("initializeBasicCanvas", CanvasId, IsWebAssembley, IsTransparent, IsDesyncronized);
+
+ }
+
+ if (CanvasInitComplete != null)
+ CanvasInitComplete(this);
+
+ }
+ catch (Exception ex)
+ {
+ var crap = ex.Message;
+ }
+
+ }
+
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ IsDisposing = true;
+
+ if (asyncModule != null)
+ {
+ await asyncModule.DisposeAsync();
+ }
+ module?.Dispose();
+ }
+
+ public async ValueTask InjectScript(string scriptText)
+ {
+ if (IsDisposing) return;
+
+ if (module != null)
+ {
+ module.InvokeVoid("InjectScript", scriptText);
+ }
+ else
+ {
+ await asyncModule.InvokeVoidAsync("InjectScript", scriptText);
+ }
+ }
+
+#nullable enable
+ public async ValueTask SetValueBasicContext(string ValueName, params object?[]? args)
+ {
+#nullable disable
+ if (IsDisposing) return;
+
+ if (module != null)
+ {
+ module.InvokeVoid("setValueBasicContext", CanvasReference, ValueName, args);
+ }
+ else
+ {
+ await asyncModule.InvokeVoidAsync("setValueBasicContext", CanvasReference, ValueName, args);
+ }
+ }
+
+
+#nullable enable
+ public async ValueTask GetValueBasicContext(string ValueName, params object?[]? args)
+ {
+#nullable disable
+ if (IsDisposing) return default(T);
+
+ if (module != null)
+ {
+ return module.Invoke("getValueBasicContext", CanvasReference, ValueName, args);
+ }
+ else
+ {
+ return await asyncModule.InvokeAsync("getValueBasicContext", CanvasReference, ValueName, args);
+ }
+ }
+
+
+#nullable enable
+ public async ValueTask SetFunctionBasicContext(string FunctionName, params object?[]? args)
+ {
+#nullable disable
+ if (IsDisposing) return;
+
+ if (module != null)
+ {
+ module.InvokeVoid("setFunctionBasicContext", CanvasReference, FunctionName, args);
+ }
+ else
+ {
+ await asyncModule.InvokeVoidAsync("setFunctionBasicContext", CanvasReference, FunctionName, args);
+ }
+ }
+
+#nullable enable
+ public async ValueTask GetFunctionBasicContext(string FunctionName, params object?[]? args)
+ {
+#nullable disable
+ if (IsDisposing) return default(T);
+
+ if (module != null)
+ {
+ return module.Invoke("getFunctionBasicContext", CanvasReference, FunctionName, args);
+ }
+ else
+ {
+ return await asyncModule.InvokeAsync("getFunctionBasicContext", CanvasReference, FunctionName, args);
+ }
+ }
+
+#nullable enable
+ public async ValueTask SetFunctionDrawingBasis(string FunctionName, params object?[]? args)
+ {
+#nullable disable
+ if (IsDisposing) return;
+
+ if (module != null)
+ {
+ module.InvokeVoid("setFunctionDrawingBasis", CanvasReference, FunctionName, args);
+ }
+ else
+ {
+ await asyncModule.InvokeVoidAsync("setFunctionDrawingBasis", CanvasReference, FunctionName, args);
+ }
+ }
+
+#nullable enable
+ public async ValueTask GetFunctionDrawingBasis(string FunctionName, params object?[]? args)
+ {
+#nullable disable
+ if (IsDisposing) return default(T);
+
+ if (module != null)
+ {
+ return module.Invoke("getFunctionDrawingBasis", CanvasReference, FunctionName, args);
+ }
+ else
+ {
+ return await asyncModule.InvokeAsync("getFunctionDrawingBasis", CanvasReference, FunctionName, args);
+ }
+ }
+
+ }
+}
diff --git a/Samples/BlazorSampleApp/Components/BasicCanvas.razor.css b/Samples/BlazorSampleApp/Components/BasicCanvas.razor.css
new file mode 100644
index 000000000..eb5a1f9c7
--- /dev/null
+++ b/Samples/BlazorSampleApp/Components/BasicCanvas.razor.css
@@ -0,0 +1,3 @@
+canvas {
+ border: 5px dotted blue;
+}
diff --git a/Samples/BlazorSampleApp/Components/BasicCanvasEnums.cs b/Samples/BlazorSampleApp/Components/BasicCanvasEnums.cs
new file mode 100644
index 000000000..af76994da
--- /dev/null
+++ b/Samples/BlazorSampleApp/Components/BasicCanvasEnums.cs
@@ -0,0 +1,27 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2021 ILGPU Project
+// www.ilgpu.net
+//
+// File: BasicCanvasEnums.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BlazorSampleApp.Components
+{
+ public enum CanvasLineCap { butt, round, square };
+ public enum CanvasLineJoin { round, bevel, miter };
+ public enum CanvasTextAlign { start, end, left, right, center };
+ public enum CanvasTextBaseline { top, hanging, middle, alphabetic, ideographic, bottom };
+ public enum CanvasDirection { ltr, rtl, inherit };
+
+
+}
diff --git a/Samples/BlazorSampleApp/Components/BasicCanvasExtensions.cs b/Samples/BlazorSampleApp/Components/BasicCanvasExtensions.cs
new file mode 100644
index 000000000..ff7302cc0
--- /dev/null
+++ b/Samples/BlazorSampleApp/Components/BasicCanvasExtensions.cs
@@ -0,0 +1,175 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2021 ILGPU Project
+// www.ilgpu.net
+//
+// File: BasicCanvasExtensions.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System.Text.Json.Nodes;
+
+
+namespace BlazorSampleApp.Components;
+
+///
+/// All webgl context methods are setup as extension methods to the BasicCanvas
+///
+/// This approach is taken for clarity of inter-operation with JavaScript.
+///
+/// Documentation about 2d webgl rendering methods can be Googled, though Mozilla
+/// documentation is fairly easy to read.
+/// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
+///
+
+#nullable enable
+
+public static class BasicCanvasExtensions
+{
+
+ public async static ValueTask GetContextAttributes(this BasicCanvas basicCanvas) => await basicCanvas.GetFunctionBasicContext("getContextAttributes");
+
+
+ // basic values
+ public async static ValueTask GlobalAlpha(this BasicCanvas basicCanvas, float alpha) => await basicCanvas.SetValueBasicContext("globalAlpha", alpha);
+
+ public async static ValueTask GlobalAlpha(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("globalAlpha");
+
+ public async static ValueTask Filter(this BasicCanvas basicCanvas, string filter) => await basicCanvas.SetValueBasicContext("filter", filter); // not supported by safari
+ public async static ValueTask Filter(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("filter");
+
+
+
+
+ // transformations
+
+ public async static ValueTask SetTransform(this BasicCanvas basicCanvas, float a, float b, float c, float d, float e, float f) => await basicCanvas.SetFunctionBasicContext("setTransform", a, b, c, d, e, f);
+
+ public async static ValueTask SetTransform(this BasicCanvas basicCanvas, double a, double b, double c, double d, double e, double f) => await basicCanvas.SetFunctionBasicContext("setTransform", a, b, c, d, e, f);
+
+
+ public async static ValueTask GetTransform(this BasicCanvas basicCanvas) => await basicCanvas.GetFunctionBasicContext("getTransform");
+ public async static ValueTask Transform(this BasicCanvas basicCanvas, float a, float b, float c, float d, float e, float f) => await basicCanvas.SetFunctionBasicContext("transform", a, b, c, d, e, f);
+ public async static ValueTask Transform(this BasicCanvas basicCanvas, double a, double b, double c, double d, double e, double f) => await basicCanvas.SetFunctionBasicContext("transform", a, b, c, d, e, f);
+
+ public async static ValueTask ResetTransform(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("resetTransform");
+ public async static ValueTask Translate(this BasicCanvas basicCanvas, float x, float y) => await basicCanvas.SetFunctionBasicContext("translate", x, y);
+
+ public async static ValueTask Rotate(this BasicCanvas basicCanvas, float angle) => await basicCanvas.SetFunctionBasicContext("rotate", angle);
+
+ public async static ValueTask Scale(this BasicCanvas basicCanvas, float x, float y) => await basicCanvas.SetFunctionBasicContext("scale", x, y);
+
+
+ // path methods
+ public async static ValueTask BeginPath(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("beginPath");
+ public async static ValueTask ClosePath(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("closePath");
+ public async static ValueTask Clip(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("clip");
+
+ public async static ValueTask MoveTo(this BasicCanvas basicCanvas, float x, float y) => await basicCanvas.SetFunctionBasicContext("moveTo", x, y);
+ public async static ValueTask LineTo(this BasicCanvas basicCanvas, float x, float y) => await basicCanvas.SetFunctionBasicContext("lineTo", x, y);
+
+ public async static ValueTask Arc(this BasicCanvas basicCanvas, int x, int y, float radius, float startAngle, float endAngle, bool counterclockwise = false) => await basicCanvas.SetFunctionBasicContext("arc", x, y, radius, startAngle, endAngle, counterclockwise);
+
+ public async static ValueTask ArcTo(this BasicCanvas basicCanvas, int x1, int y1, int x2, int y2, float radius) => await basicCanvas.SetFunctionBasicContext("arcTo", x1, y1, x2, y2, radius);
+
+ public async static ValueTask BezierCurveTo(this BasicCanvas basicCanvas, int cp1x, int cp1y, int cp2x, int cp2y, int x, int y) => await basicCanvas.SetFunctionBasicContext("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
+
+ public async static ValueTask Ellipse(this BasicCanvas basicCanvas, int x, int y, int radiusX, int radiusY, int rotation, float startAngle, float endAngle, bool counterclockwise = false) => await basicCanvas.SetFunctionBasicContext("ellipse", x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
+
+ public async static ValueTask LineWidth(this BasicCanvas basicCanvas, float pixels) => await basicCanvas.SetValueBasicContext("lineWidth", pixels);
+
+ public async static ValueTask LineWidth(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("lineWidth");
+
+ public async static ValueTask MiterLimit(this BasicCanvas basicCanvas, float limit) => await basicCanvas.SetValueBasicContext("miterLimit", limit);
+
+ public async static ValueTask MiterLimit(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("miterLimit");
+
+ public async static ValueTask LineCap(this BasicCanvas basicCanvas, CanvasLineCap value) => await basicCanvas.SetValueBasicContext("lineCap", value.ToString());
+
+ public async static ValueTask LineCap(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("lineCap");
+
+
+ public async static ValueTask LineJoin(this BasicCanvas basicCanvas, CanvasLineJoin joinType) => await basicCanvas.SetValueBasicContext("lineJoin", joinType.ToString());
+
+ public async static ValueTask LineDashOffset(this BasicCanvas basicCanvas, int lineDashOffset) => await basicCanvas.SetValueBasicContext("lineDashOffset", lineDashOffset);
+
+ public async static ValueTask Stroke(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("stroke");
+
+ public async static ValueTask StrokeStyle(this BasicCanvas basicCanvas, string strokeStyle) => await basicCanvas.SetValueBasicContext("strokeStyle", strokeStyle);
+
+ // draw methods
+
+ public async static ValueTask GlobalCompositeOperation(this BasicCanvas basicCanvas, string operation) => await basicCanvas.SetValueBasicContext("globalCompositeOperation", operation);
+
+ public async static ValueTask GlobalCompositeOperation(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("globalCompositeOperation");
+
+
+ public async static ValueTask Fill(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("fill");
+ public async static ValueTask FillStyle(this BasicCanvas basicCanvas, string style) => await basicCanvas.SetValueBasicContext("fillStyle", style);
+ public async static ValueTask FillStyle(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("fillStyle");
+
+
+ public async static ValueTask ShadowColor(this BasicCanvas basicCanvas, string color) => await basicCanvas.SetValueBasicContext("shadowColor", color);
+ public async static ValueTask ShadowColor(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("shadowColor");
+
+ public async static ValueTask ShadowBlur(this BasicCanvas basicCanvas, float blur) => await basicCanvas.SetValueBasicContext("shadowBlur", blur);
+ public async static ValueTask ShadowBlur(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("shadowBlur");
+
+
+ public async static ValueTask ShadowOffsetX(this BasicCanvas basicCanvas, int offset) => await basicCanvas.SetValueBasicContext("shadowOffsetX", offset);
+ public async static ValueTask ShadowOffsetX(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("shadowOffsetX");
+
+ public async static ValueTask ShadowOffsetY(this BasicCanvas basicCanvas, int offset) => await basicCanvas.SetValueBasicContext("shadowOffsetY", offset);
+ public async static ValueTask ShadowOffsetY(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("shadowOffsetY");
+
+
+ public async static ValueTask Save(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("save");
+ public async static ValueTask Restore(this BasicCanvas basicCanvas) => await basicCanvas.SetFunctionBasicContext("restore");
+
+
+
+ // rectangle methods
+ public async static ValueTask ClearRect(this BasicCanvas basicCanvas, int x, int y, int width, int height) => await basicCanvas.SetFunctionBasicContext("clearRect", x, y, width, height);
+ public async static ValueTask FillRect(this BasicCanvas basicCanvas, int x, int y, int width, int height) => await basicCanvas.SetFunctionBasicContext("fillRect", x, y, width, height);
+ public async static ValueTask StrokeRect(this BasicCanvas basicCanvas, int x, int y, int width, int height) => await basicCanvas.SetFunctionBasicContext("strokeRect", x, y, width, height);
+
+ // text methods
+ public async static ValueTask Font(this BasicCanvas basicCanvas, string fontName) => await basicCanvas.SetValueBasicContext("font", fontName);
+ public async static ValueTask Font(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("font");
+ public async static ValueTask TextAlign(this BasicCanvas basicCanvas, CanvasTextAlign align) => await basicCanvas.SetValueBasicContext("textAlign", align.ToString());
+ public async static ValueTask TextAlign(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("textAlign");
+ public async static ValueTask TextBaseline(this BasicCanvas basicCanvas, CanvasTextBaseline baseline) => await basicCanvas.SetValueBasicContext("textBaseline", baseline.ToString());
+ public async static ValueTask TextBaseline(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("textBaseline");
+ public async static ValueTask Direction(this BasicCanvas basicCanvas, CanvasDirection direction) => await basicCanvas.SetValueBasicContext("direction", direction.ToString());
+ public async static ValueTask Direction(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("direction");
+
+
+ public async static ValueTask FillText(this BasicCanvas basicCanvas, string text, int x, int y) => await basicCanvas.SetFunctionBasicContext("fillText", text, x, y);
+ public async static ValueTask FillText(this BasicCanvas basicCanvas, string text, int x, int y, int maxWidth) => await basicCanvas.SetFunctionBasicContext("fillText", text, x, y, maxWidth);
+ public async static ValueTask StrokeText(this BasicCanvas basicCanvas, string text, int x, int y) => await basicCanvas.SetFunctionBasicContext("StrokeText", text, x, y);
+ public async static ValueTask StrokeText(this BasicCanvas basicCanvas, string text, int x, int y, int maxWidth) => await basicCanvas.SetFunctionBasicContext("StrokeText", text, x, y, maxWidth);
+
+ public async static ValueTask MeasureText(this BasicCanvas basicCanvas, string text) => await basicCanvas.GetFunctionDrawingBasis("measureText", text);// is this a dictionary?
+
+
+ // image methods
+ public async static ValueTask ImageSmoothingEnabled(this BasicCanvas basicCanvas, bool smoothingEnabled) => await basicCanvas.SetValueBasicContext("imageSmoothingEnabled", smoothingEnabled);
+ public async static ValueTask ImageSmoothingEnabled(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("imageSmoothingEnabled");
+ public async static ValueTask ImageSmoothingQuality(this BasicCanvas basicCanvas, string quality) => await basicCanvas.SetValueBasicContext("imageSmoothingQuality", quality); /// not supported by firefox
+ public async static ValueTask ImageSmoothingQuality(this BasicCanvas basicCanvas) => await basicCanvas.GetValueBasicContext("imageSmoothingQuality");
+
+ // image pixel manipulation
+ public async static ValueTask CreateImageData(this BasicCanvas basicCanvas, string nameImageStorage, int width, int height, params object?[]? args) => await basicCanvas.SetFunctionDrawingBasis("createImageData", nameImageStorage, width, height, args);
+ public async static ValueTask CreateImageDataCopyByteArray(this BasicCanvas basicCanvas, string nameImageStorage, int width, int height, params object?[]? args) => await basicCanvas.SetFunctionDrawingBasis("createImageDataCopyByteArray", nameImageStorage, width, height, args);
+ public async static ValueTask GetImageData(this BasicCanvas basicCanvas, string nameImageStorage, int x, int y, int width, int height) => await basicCanvas.SetFunctionDrawingBasis("getImageData", nameImageStorage, x, y, width, height);
+ public async static ValueTask PutImageData(this BasicCanvas basicCanvas, string nameImageStorage, int x, int y) => await basicCanvas.SetFunctionDrawingBasis("putImageData", nameImageStorage, x, y);
+ public async static ValueTask PutImageData(this BasicCanvas basicCanvas, string nameImageStorage, int x, int y, int dx, int dy, int dWidth, int dHeight) => await basicCanvas.SetFunctionDrawingBasis("putImageDataPartial", nameImageStorage, x, y, dx, dy, dWidth, dHeight);
+
+ // image support
+ public async static ValueTask CreateImage(this BasicCanvas basicCanvas, string nameImageStorage, string source, string alt) => await basicCanvas.SetFunctionDrawingBasis("createImage", nameImageStorage, source, alt);
+ public async static ValueTask DrawImage(this BasicCanvas basicCanvas, string nameImageStorage, int x, int y) => await basicCanvas.SetFunctionDrawingBasis("drawImage", nameImageStorage, x, y);
+
+
+}
diff --git a/Samples/BlazorSampleApp/Components/Tooltip.razor b/Samples/BlazorSampleApp/Components/Tooltip.razor
new file mode 100644
index 000000000..0947c1f89
--- /dev/null
+++ b/Samples/BlazorSampleApp/Components/Tooltip.razor
@@ -0,0 +1,61 @@
+
+
+
+
+ @((MarkupString)Text)
+
+ @ChildContent
+
+
+
+@code {
+ ///
+ /// This is a simple way to show hints over buttons Blazor style.
+ ///
+#nullable disable
+ [Parameter] public RenderFragment ChildContent { get; set; }
+ [Parameter] public string Text { get; set; }
+
+ //similar to from https://bladeprogramming.blogspot.com/2021/03/blazor-tutorial-tooltip-component.html?m=1 by Blade Programming
+}
+
diff --git a/Samples/BlazorSampleApp/ILGPUWebHost/ComputeHost.cs b/Samples/BlazorSampleApp/ILGPUWebHost/ComputeHost.cs
new file mode 100644
index 000000000..839fb958e
--- /dev/null
+++ b/Samples/BlazorSampleApp/ILGPUWebHost/ComputeHost.cs
@@ -0,0 +1,252 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2021 ILGPU Project
+// www.ilgpu.net
+//
+// File: ComputeHost.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+
+namespace BlazorSampleApp.ILGPUWebHost
+{
+
+ ///
+ /// This is a singleton object for sharing all accelerators on a host server.
+ ///
+ ///
+ /// We assume a server can have multiple GPUs, this object's job would be to manage the load
+ /// across all GPUs on this server. Imagine a farm with 1000 server each with 4 GPUs,
+ /// how do we manage a GPU farm? At a single "blazor" server, sharing GPU's is not too hard to
+ /// imagine how we spread the load across the 4 GPUs but at a farm level; networking, routing and
+ /// bandwidth become important very important.
+ ///
+ ///
+ /// We could in theory re-establish streams between GPUs and clients to balance
+ /// the computational load. It is a trivial task counting accelerator streams per GPU
+ /// should we want to balance similar compute tasks.
+ ///
+ /// We will note the Mandelbrot set computed here uses approximately 100 MB of memory per session
+ /// therefor we should secure buffer allocation as part of the session.
+ ///
+
+#nullable disable
+ public class ComputeHost : IComputeHost, IDisposable
+ {
+ // the context for session and accelerator instantiation
+ private Context _context;
+
+ private bool _disposing;
+
+ // all current sessions
+ private List _computeSessions;
+
+ // all compliant accelerators
+ private List _accelerators;
+
+ // has this host been configured?
+ public bool HostConfigured { get { return !_disposing && _accelerators.Count>0; } }
+
+ // current active session list
+ public int SessionCount { get { return _computeSessions?.Count ?? 0; } }
+
+
+ ///
+ /// This is used to establish the host as a singleton object on the web server
+ ///
+ public ComputeHost()
+ {
+ _disposing = false;
+
+ _context = Context.Create(builder => builder.Default());
+
+ _computeSessions = new List();
+
+ _accelerators = new List();
+ }
+
+
+ ///
+ /// Each application must specify what are the requirements for accelerators on the host server
+ ///
+ ///
+ ///
+ ///
+ public bool ConfigureAcceleration(AcceleratorType[] allowedAcceleratorTypes, long minimumMemory, int multiProcessors)
+ {
+ bool result = false;
+
+ if (!HostConfigured)
+ {
+
+ List types = allowedAcceleratorTypes.ToList();
+
+ foreach (var device in _context)
+ {
+
+ if (types.Exists(x => x == device.AcceleratorType))
+ {
+
+ var accelerator = device.CreateAccelerator(_context);
+
+ if (accelerator.MemorySize >= minimumMemory && accelerator.NumMultiprocessors >= multiProcessors)
+ {
+
+ result = true;
+
+ _accelerators.Add(accelerator);
+ }
+ else
+ {
+ accelerator.Dispose();
+ }
+ }
+ }
+
+ if (_accelerators.Count > 1)
+ {
+ var ordered = from acc in _accelerators orderby acc.AcceleratorRank() descending
+ select acc;
+
+ _accelerators = ordered.ToList();
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// Here we assume only one accelerator on the system. If there were multiple accelerators we could do a
+ /// check of how many sessions were on each accelerator and return a new compute session on the leased
+ /// used accelerator.
+ ///
+ ///
+ ///
+ public ComputeSession NewComputeStream (string sessionID)
+ {
+ if (!_disposing)
+ {
+ ComputeSession computeSession = new ComputeSession(sessionID, _accelerators[0], this); // preform load balancing magic here is supporting multiple accelerators
+ _computeSessions.Add(computeSession);
+ return computeSession;
+ }
+ return null;
+ }
+
+
+ ///
+ /// If for some reason a blazor compute session end without notice, we can attempt to
+ /// connect back the our original session provided we stored the session ID in the
+ /// client browser. Note if we have a server farm, we may need to keep a server session map.
+ ///
+ ///
+ ///
+ public ComputeSession FindComputeSession(string sessionID)
+ {
+
+ ComputeSession result = null;
+
+ if (!_disposing)
+ {
+ result = _computeSessions?.Find(x => x.SessionID == sessionID);
+ }
+
+ return null;
+ }
+
+ ///
+ /// We are tracking all compute sessions on the host for GPU resource allocation. Sessions must be removed
+ /// from the host otherwise when disposed otherwise we will take the GPU down on all sessions by overallocation
+ /// or resources.
+ ///
+ ///
+
+ public void ReturnSession(ComputeSession session)
+ {
+ if (session != null)
+ {
+ session.InActivate();
+
+ _computeSessions.Remove(session);
+
+ if (!session.IsDisposing)
+ {
+ session.Dispose();
+ }
+ }
+ }
+
+
+ ///
+ /// "gracefully clean up sessions" If the host is disposing we need to clean up outstanding sessions
+ ///
+ public void CleanUpSessions()
+ {
+ foreach(var session in _computeSessions)
+ {
+ // stop all new compute
+ session.InActivate();
+ }
+
+ foreach (var session in _computeSessions)
+ {
+ while(session.IsComputing)
+ {
+ // this does not work if the current session is in the host thread.
+ Thread.Sleep(10);
+ }
+ if (!session.IsDisposing)
+ {
+ session?.Dispose();
+ }
+ }
+ _computeSessions.Clear();
+ }
+
+ ///
+ /// clean up accelerators.
+ ///
+ ///
+ private void CleanUpAcceleratorHosts()
+ {
+ foreach (var accelerator in _accelerators)
+ {
+ // we assume and accelerator could be active
+ accelerator?.Synchronize();
+
+ }
+ foreach (var accelerator in _accelerators)
+ {
+ accelerator?.Dispose();
+ }
+ _accelerators.Clear();
+ }
+
+
+ ///
+ /// clear up resources should the host be disposed.
+ ///
+
+ public void Dispose()
+ {
+ _disposing = true;
+
+ CleanUpSessions();
+
+ CleanUpAcceleratorHosts();
+
+ _context?.Dispose();
+
+ }
+ }
+}
diff --git a/Samples/BlazorSampleApp/ILGPUWebHost/ComputeHostExtension.cs b/Samples/BlazorSampleApp/ILGPUWebHost/ComputeHostExtension.cs
new file mode 100644
index 000000000..8bff1ddfe
--- /dev/null
+++ b/Samples/BlazorSampleApp/ILGPUWebHost/ComputeHostExtension.cs
@@ -0,0 +1,46 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: ComputeHostExtension.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+
+namespace BlazorSampleApp.ILGPUWebHost
+{
+
+ public static class ComputeHostExtension
+ {
+ ///
+ /// We rank accelerators by their maximum throughput.
+ ///
+ /// Rank is approximated by clock rate * threads per processor * multiprocessors, for an integrated GPU
+ /// we assume the clock rate is 2GHz to avoid operating specific calls
+ ///
+ ///
+ ///
+ public static long AcceleratorRank(this Accelerator accelerator)
+ {
+ long clockRate = Convert.ToInt64(int.MaxValue); // assume 2 GHz for a CPU.
+
+ clockRate = (accelerator as ILGPU.Runtime.OpenCL.CLAccelerator)?.ClockRate ?? clockRate;
+
+ clockRate = (accelerator as ILGPU.Runtime.Cuda.CudaAccelerator)?.ClockRate ?? clockRate;
+
+ return accelerator.NumMultiprocessors * accelerator.MaxNumThreadsPerMultiprocessor * clockRate;
+ }
+ }
+}
diff --git a/Samples/BlazorSampleApp/ILGPUWebHost/ComputeSession.cs b/Samples/BlazorSampleApp/ILGPUWebHost/ComputeSession.cs
new file mode 100644
index 000000000..3d9ddab9b
--- /dev/null
+++ b/Samples/BlazorSampleApp/ILGPUWebHost/ComputeSession.cs
@@ -0,0 +1,223 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: ComputeSession.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+using BlazorSampleApp.MandelbrotExplorer;
+
+
+
+namespace BlazorSampleApp.ILGPUWebHost
+{
+ ///
+ /// An individual compute session represents all compute resources for a single accelerator stream.
+ ///
+ /// This object must implement IDisposable as it allocates dedicated non-movable GPU accessible buffers.
+ ///
+#nullable disable
+ public class ComputeSession : IDisposable
+ {
+
+ // one or more kernels can be hosted in each compute session depending on the complexity of our calculation
+ private System.Action, ArrayView1D, int, ArrayView> mandelbrot_kernel;
+
+
+ // SessionID allows individual Blazor sessions to "find" their compute session
+
+ public string SessionID { get; set; }
+
+ public bool _isActive = true;
+ public bool IsActive { get { return _isActive; } }
+
+
+ // do we have an active process? If so we wait until the process is finished to avoid blowing up GPU
+ private bool _computing = false;
+ public bool IsComputing { get { return _computing; } }
+
+ private bool _disposing = false;
+ public bool IsDisposing { get { return _disposing; } }
+
+ private bool _kernelIsCompiled = false;
+ public bool KernelIsCompiled { get { return _kernelIsCompiled; } }
+
+
+
+ private AcceleratorStream _stream = null;
+
+
+ private ComputeHost _host;
+
+ public AcceleratorStream Stream
+ {
+ get
+ {
+ if (IsActive)
+ {
+ return _stream;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ public ComputeSession(string sessionID, Accelerator accelerator, ComputeHost host)
+ {
+ SessionID = sessionID;
+
+ _stream = accelerator.CreateStream();
+ _host = host;
+
+ }
+
+ public void CompileKernel()
+ {
+
+ if (IsActive && !_kernelIsCompiled)
+ {
+ _computing = true;
+ mandelbrot_kernel = _stream.Accelerator.LoadAutoGroupedKernel, ArrayView1D, int, ArrayView>(MandelbrotExtensions.MandelbrotKernel);
+ _kernelIsCompiled = true;
+ _computing = false;
+ }
+
+
+ }
+
+
+ ///
+ /// We're going to allow up to 10 seconds for any compute to complete
+ ///
+ ///
+ private async Task ComputeFinished()
+ {
+ int iCount = 0;
+ while ((iCount < 1000) && _computing)
+ {
+ await Task.Delay(10);
+ iCount += 1;
+ }
+ return (iCount < 1000);
+ }
+
+ public void InActivate()
+ {
+ _isActive = false;
+ }
+
+ ///
+ /// Clean up all compute resources
+ ///
+ public async void Dispose()
+ {
+ _disposing = true;
+ _isActive = false; // do not allow any new computation.
+
+ // the host will dispose of this stream
+ _host?.ReturnSession(this);
+ _host = null;
+
+ bool computeFinished = await ComputeFinished();
+
+ _stream?.Dispose();
+ _stream = null;
+ _display?.Dispose();
+ _display = null;
+
+ _area?.Dispose();
+ _area = null;
+ _output?.Dispose();
+ _output = null;
+ mandelbrot_kernel = null;
+ }
+
+
+ int _buffersize;
+ int _iterations;
+ MemoryBuffer1D _display = null;
+ MemoryBuffer1D _area = null;
+ MemoryBuffer1D _output = null;
+
+ public void InitGPU(ref int[] buffer, int[] displayPort, float[] viewArea, int max_iterations)
+ {
+ if (IsActive && !_disposing)
+ {
+ // release current buffers if they exist
+ _display?.Dispose();
+ _area?.Dispose();
+ _output?.Dispose();
+
+ _buffersize = buffer.Length;
+
+ _iterations = max_iterations;
+
+ _area = _stream.Accelerator.Allocate1D(viewArea);
+
+ _display = _stream.Accelerator.Allocate1D(displayPort);
+
+ _output = _stream.Accelerator.Allocate1D(_buffersize);
+
+ }
+ }
+
+
+ ///
+ /// calculate a new result set based on the viewArea of interest
+ ///
+ ///
+ ///
+ ///
+ public async Task CalcGPU(int[] buffer, float[] viewArea)
+ {
+ bool result = false;
+ if (buffer.Length != _buffersize)
+ {
+ return false;
+ }
+
+ // copy parameters to GPU inbound buffer
+ ArrayViewExtensions.CopyFromCPU(_area, viewArea);
+
+ if (IsActive && !_computing && !_disposing)
+ {
+ // Launch kernel
+ _computing = true;
+
+ mandelbrot_kernel(_stream, _buffersize, _display, _area, _iterations, _output.View);
+ await _stream.SynchronizeAsync(); // wait for the stream to synchronize
+ result = IsActive;
+ _computing = false;
+ }
+
+
+ // Reads data from the GPU buffer into a CPU array.
+ // that the kernel and memory copy are completed first.
+ if (IsActive && !_disposing)
+ {
+ _computing = true;
+ _output.CopyToCPU(buffer);
+ result = IsActive;
+ _computing = false;
+ }
+
+ return result && !_disposing;
+ }
+
+ }
+}
diff --git a/Samples/BlazorSampleApp/ILGPUWebHost/IComputeHost.cs b/Samples/BlazorSampleApp/ILGPUWebHost/IComputeHost.cs
new file mode 100644
index 000000000..7a5559487
--- /dev/null
+++ b/Samples/BlazorSampleApp/ILGPUWebHost/IComputeHost.cs
@@ -0,0 +1,63 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: IComputeHost.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+
+namespace BlazorSampleApp.ILGPUWebHost
+{
+ ///
+ /// represents the type of a host
+ ///
+ public interface IComputeHost
+ {
+ ///
+ /// host this host been configured?
+ ///
+ bool HostConfigured { get; }
+
+ ///
+ /// number of active compute sessions
+ ///
+ int SessionCount { get; }
+
+ ///
+ /// determine the minium specifications for an accelerator
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool ConfigureAcceleration(AcceleratorType[] allowedAcceleratorTypes, long minimumMemory,int multiProcessors);
+
+ ///
+ /// request a new compute session
+ ///
+ ///
+ ///
+ ComputeSession NewComputeStream(string sessionID);
+
+
+ ///
+ /// return a compute session
+ ///
+ ///
+ void ReturnSession(ComputeSession session);
+
+ }
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/IMandelbrotBasic.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/IMandelbrotBasic.cs
new file mode 100644
index 000000000..464dc6860
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/IMandelbrotBasic.cs
@@ -0,0 +1,39 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: IMandelbrotBasic.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using ILGPU;
+using ILGPU.Runtime;
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+
+ ///
+ /// For Blazor scoped dependency injection of the full ILGPU client into a razor page.
+ ///
+ public interface IMandelbrotBasic
+ {
+ bool IsDisposing { get; }
+
+ Context ContextInstance { get; }
+
+ Accelerator AcceleratorInstance { get; }
+
+ void CompileKernel(Device device);
+
+ void CalcGPU(ref int[] buffer, int[] dispayPort, float[] viewArea, int maxIterations);
+
+ void InitGPURepeat(ref int[] buffer, int[] displayPort, float[] viewArea, int max_iterations);
+ void CalcGPURepeat(ref int[] buffer, int[] dispayPort, float[] viewArea, int max_iterations);
+
+ void CleanupGPURepeat();
+ }
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/IMandelbrotClient.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/IMandelbrotClient.cs
new file mode 100644
index 000000000..3b4dde99a
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/IMandelbrotClient.cs
@@ -0,0 +1,36 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: IMandelbrotClient.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+ ///
+ /// This interface is to allow Blazor's dependency injection to generate scoped access to a server hosted singleton compute session.
+ ///
+ public interface IMandelbrotClient
+ {
+ string AcceleratorName();
+
+ public bool IsActive { get; }
+
+ void SetDisplay(int width, int height);
+
+ Task CalculateMandelbrot(float left, float right, float top, float bottom);
+
+ }
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotBasic.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotBasic.cs
new file mode 100644
index 000000000..de466d3f7
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotBasic.cs
@@ -0,0 +1,241 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: MandelbrotBasic.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+using ILGPU.Runtime.CPU;
+using ILGPU.Runtime.Cuda;
+
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+ ///
+ /// This loads the scoped dependency injected instance of ILGPU per razor page which is similar coding to a command line application.
+ ///
+ /// Blazor has difference levels of dependency injection, this should be scoped
+ /// "services.AddScoped();"
+ /// in the Blazor server startup file.
+ ///
+
+#nullable disable
+ public class MandelbrotBasic : IMandelbrotBasic, IDisposable
+ {
+ private Context _context = null;
+ private Accelerator _accelerator = null;
+ private System.Action, ArrayView1D, int, ArrayView> mandelbrot_kernel;
+
+
+ // If we are disposing do not start new processes
+ private bool _disposing = false;
+
+ // Are we actively computing? If so we wait until the active compute process is finished to avoid blowing
+ // up the GPU and all shared ILGPU sessions on the GPU by disposing a buffer while a kernel is active.
+ private bool _computing = true;
+
+
+ public bool IsDisposing { get { return _disposing; } }
+
+ public Context ContextInstance {
+ get
+ {
+ return _context;
+ }
+ }
+
+
+ public Accelerator AcceleratorInstance
+ {
+ get
+ {
+ return _accelerator;
+ }
+ }
+
+ public MandelbrotBasic()
+ {
+ _context = Context.CreateDefault();
+ _accelerator = null;
+ }
+
+
+ ///
+ /// Compile the Mandelbrot kernel based on the device selected.
+ ///
+ ///
+ public void CompileKernel(Device device)
+ {
+ _accelerator?.Dispose();
+ _accelerator = device.CreateAccelerator(_context);
+
+ mandelbrot_kernel = _accelerator.LoadAutoGroupedStreamKernel<
+ Index1D, ArrayView1D, ArrayView1D, int, ArrayView>(MandelbrotExtensions.MandelbrotKernel);
+ }
+
+ ///
+ /// We're going to allow up to 10 seconds for any compute to complete. This should be an appsettings parameter, or passed as a compute timeout.
+ ///
+ ///
+ private async Task ComputeFinished()
+ {
+ int iCount = 0;
+ while ((iCount < 1000) && _computing)
+ {
+ await Task.Delay(10);
+ iCount += 1;
+ }
+ return (iCount < 1000);
+ }
+
+
+ ///
+ /// Dispose accelerator and main ILGPU context, not to be called except when this component is disposed by dependency injection.
+ ///
+ public async void Dispose()
+ {
+
+ _disposing = true;
+
+ bool computeFinished = await ComputeFinished();
+
+ CleanupGPURepeat();
+ mandelbrot_kernel = null;
+ _accelerator?.Dispose();
+ _context?.Dispose();
+
+ }
+
+ ///
+ /// Calculate the Mandelbrot set a single time on the GPU.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CalcGPU(ref int[] buffer, int[] dispayPort, float[] viewArea, int max_iterations)
+ {
+ _computing = true;
+ int num_values = buffer.Length;
+ var dev_out = _accelerator.Allocate1D(num_values);
+ var displayParams = _accelerator.Allocate1D(dispayPort);
+ var viewAreaParams = _accelerator.Allocate1D(viewArea);
+
+
+ if (!_disposing)
+ {
+ // Launch kernel
+ mandelbrot_kernel(num_values, displayParams, viewAreaParams, max_iterations, dev_out.View);
+ }
+
+ // Reads data from the GPU buffer into a new CPU array.
+ // Implicitly calls accelerator.DefaultStream.Synchronize() to ensure
+ // the kernel computation is complete.
+
+ displayParams.Dispose();
+ viewAreaParams.Dispose();
+ if (!_disposing)
+ {
+ dev_out.CopyToCPU(buffer);
+ }
+ dev_out.Dispose();
+ _computing = false;
+
+ return;
+ }
+
+ int _buffersize;
+ MemoryBuffer1D _display = null;
+ MemoryBuffer1D _area = null;
+ MemoryBuffer1D _output = null;
+
+ ///
+ /// Initialize resources for repetitive computing
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void InitGPURepeat(ref int[] buffer, int[] displayPort, float[] viewArea, int max_iterations)
+ {
+ if (!_disposing)
+ {
+ _computing = true;
+ _buffersize = buffer.Length;
+ _area = _accelerator.Allocate1D(viewArea);
+ _output = _accelerator.Allocate1D(_buffersize);
+ _display = _accelerator.Allocate1D(displayPort);
+ _computing = false;
+ }
+
+ }
+
+
+ ///
+ /// Calculate a new Mandelbrot set.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CalcGPURepeat(ref int[] buffer, int[] dispayPort, float[] viewArea, int max_iterations)
+ {
+ _computing = true;
+
+ if (!_disposing)
+ {
+ ArrayViewExtensions.CopyFromCPU(_area, viewArea);
+ }
+
+
+ if (!_disposing)
+ {
+ // Launch kernel
+ mandelbrot_kernel(_buffersize, _display, _area, max_iterations, _output.View);
+ }
+
+
+ // Reads data from the GPU buffer into a new CPU array.
+ // Implicitly calls accelerator.DefaultStream.Synchronize() to ensure
+ // that the kernel and memory copy are completed first.
+ if (!_disposing)
+ {
+ _output.CopyToCPU(buffer);
+ }
+
+ _computing = false;
+
+ return;
+ }
+
+ ///
+ /// Clean up compute resources.
+ ///
+ public void CleanupGPURepeat()
+ {
+ _display?.Dispose();
+ _display = null;
+ _area?.Dispose();
+ _area = null;
+ _output?.Dispose();
+ _output = null;
+
+ }
+
+
+
+ }
+
+
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotClient.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotClient.cs
new file mode 100644
index 000000000..d7a2ab752
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotClient.cs
@@ -0,0 +1,150 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: MandelbrotClient.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ILGPU;
+using ILGPU.Runtime;
+using BlazorSampleApp.ILGPUWebHost;
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+ ///
+ /// This object's job is to provide access/state/parameters to a compute session, this object is loaded via
+ /// Blazor "scoped" dependency injected in a razor page.
+ ///
+ /// IDisposable is implemented to notify the a "hosted" compute session is no longer needed a user's razor page.
+ ///
+ ///
+#nullable disable
+ public class MandelbrotClient : IMandelbrotClient, IDisposable
+ {
+
+ private ComputeSession _session;
+
+ public bool IsActive { get { return _session?.IsActive ?? false; } }
+
+ // In Blazor server app or any web a
+ private string SessionId = Guid.NewGuid().ToString();
+
+ private bool _disposing = false;
+
+ public int ViewWidth { get; private set; }
+
+ public int ViewHeight { get; private set; }
+
+ public float Left { get; private set; } = -2.0f;
+ public float Right { get; private set; } = 1.0f;
+ public float Top { get; private set; } = 1.0f;
+
+ public float Bottom { get; private set; } = -1.0f;
+
+ public int MaxIterations { get; set; } = 1000;
+
+ private int[] _buffer = null;
+
+ ///
+ /// set display size and compile the GPU kernel
+ ///
+ ///
+ ///
+ public void SetDisplay(int width, int height)
+ {
+ ViewWidth = width;
+ ViewHeight = height;
+
+ if (_disposing)
+ {
+ return;
+ }
+
+ if ((_buffer?.Length ?? 0) != ViewWidth * ViewHeight)
+ {
+ _buffer = new int[ViewWidth * ViewHeight];
+ }
+
+ if (!_session.KernelIsCompiled && _session.IsActive)
+ {
+ _session.InitGPU(ref _buffer, new int[2] { ViewWidth, ViewHeight }, new float[4] { Left, Right, Bottom, Top }, MaxIterations);
+ _session.CompileKernel();
+ }
+ }
+
+
+
+ ///
+ /// Generate a Mandelbrot set
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task CalculateMandelbrot(float left, float right, float bottom, float top)
+ {
+ Left = left;
+ Right = right;
+ Top = top;
+ Bottom = bottom;
+
+
+
+ if (_session.IsActive && !_session.IsComputing && !_disposing)
+ {
+ if (await _session.CalcGPU(_buffer, new float[] { Left, Right, Bottom, Top }))
+ {
+ return _buffer;
+ }
+
+ }
+
+ return null;
+ }
+
+ public MandelbrotClient(IComputeHost computeHost)
+ {
+ // method for dependency injection otherwise this blows up on a web server.
+
+ if (!computeHost.HostConfigured)
+ {
+ computeHost.ConfigureAcceleration(new AcceleratorType[2] { AcceleratorType.OpenCL, AcceleratorType.Cuda }, int.MaxValue, 20);
+ }
+
+ _session = computeHost.NewComputeStream(SessionId);
+
+ }
+
+ public string AcceleratorName()
+ {
+ return _session?.Stream?.Accelerator?.Name ?? "n/a";
+ }
+
+
+ public void Dispose()
+ {
+ _disposing = true;
+
+
+ if (!_session?.IsDisposing ?? false)
+ {
+ _session.Dispose();
+ }
+
+ _session = null;
+
+ }
+
+
+ }
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotExtensions.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotExtensions.cs
new file mode 100644
index 000000000..fcf07ad68
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotExtensions.cs
@@ -0,0 +1,195 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: MandelbrotExtensions.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Drawing;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using BlazorSampleApp.Components;
+using ILGPU;
+using ILGPU.Runtime;
+
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+ public static class MandelbrotExtensions
+ {
+
+ ///
+ /// This "kernel" function will compile to IL code which ILGPU will ingest and convert to GPU compute shader code.
+ ///
+ ///
+ ///
+ /// displayPort int[] {width, height }
+ /// displayView float[] {h_a, h_b, v_a, v_b}
+ ///
+ ///
+ public static void MandelbrotKernel(
+ Index1D index,
+ ArrayView1D displayParams, ArrayView1D viewAreaParams, int maxIterations,
+ ArrayView output)
+ {
+
+ if (index >= output.Length)
+ return;
+
+ int img_x = index % displayParams[0];
+ int img_y = index / displayParams[0];
+
+ float x0 = viewAreaParams[0] + img_x * (viewAreaParams[1] - viewAreaParams[0]) / displayParams[0];
+ float y0 = viewAreaParams[2] + img_y * (viewAreaParams[3] - viewAreaParams[2]) / displayParams[1];
+ float x = 0.0f;
+ float y = 0.0f;
+ int iteration = 0;
+ while ((x * x + y * y < 2 * 2) && (iteration < maxIterations))
+ {
+ float xtemp = x * x - y * y + x0;
+ y = 2 * x * y + y0;
+ x = xtemp;
+ iteration += 1;
+ }
+ output[index] = iteration;
+ }
+
+
+ ///
+ /// Calculate the Mandelbrot set single threaded on the CPU.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void CalcCPUSingle(int[] buffer, int[] display, float[] view, int max_iterations)
+ {
+
+ for (int i=0; i < display[0]; i++ )
+ {
+ for (int j = 0; j < display[1]; j++)
+ {
+ int index = i + j * display[0]; // ILGPU-like index
+ int img_x = index % display[0];
+ int img_y = index / display[0];
+
+ float x0 = view[0] + img_x * (view[1] - view[0]) / display[0];
+ float y0 = view[2] + img_y * (view[3] - view[2]) / display[1];
+ float x = 0.0f;
+ float y = 0.0f;
+ int iteration = 0;
+ while ((x * x + y * y < 2 * 2) && (iteration < max_iterations))
+ {
+ float xtemp = x * x - y * y + x0;
+ y = 2 * x * y + y0;
+ x = xtemp;
+ iteration += 1;
+ }
+ buffer[index] = iteration;
+ }
+ }
+ }
+
+
+ ///
+ /// Calculate the Mandelbrot set using multiple parallel threads on the CPU.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void CalcCPUParallel(int[] buffer, int[] display, float[] view, int max_iterations)
+ {
+ int icnt = display[0];
+
+ Parallel.For( 0, icnt, i =>
+ {
+ for (int j = 0; j < display[1]; j++)
+ {
+ int index = i + j * display[0]; // ILGPU-like index
+ int img_x = index % display[0];
+ int img_y = index / display[0];
+
+ float x0 = view[0] + img_x * (view[1] - view[0]) / display[0];
+ float y0 = view[2] + img_y * (view[3] - view[2]) / display[1];
+ float x = 0.0f;
+ float y = 0.0f;
+ int iteration = 0;
+ while ((x * x + y * y < 2 * 2) && (iteration < max_iterations))
+ {
+ float xtemp = x * x - y * y + x0;
+ y = 2 * x * y + y0;
+ x = xtemp;
+ iteration += 1;
+ }
+ buffer[index] = iteration;
+ }
+ });
+ }
+
+
+ ///
+ /// This creates and passes an array to webgl for rendering to the canvas using "2D" webgl interface
+ ///
+ /// There are two possibilities for showing our result:
+ ///
+ /// First is the "direct" draw approach where we pass a color map and create an ImageData object
+ /// in JavaScript, copying each pixels color to the image data object.
+ ///
+ /// Second is we generate a compressed PNG image in memory and tell the webgl context
+ /// to download the compressed PNG image as a file like any other web page process.
+ ///
+ /// While not implemented this approach would reduce the server bandwidth consumed
+ /// per render by 80% or more as the resulting color map is large when uncompressed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task Draw(BasicCanvas basicCanvas, int[] data, int width, int height, int iterations, Color color)
+ {
+
+ byte[] result = new byte[width * height * 4];
+
+ for (int i = 0; i < width * height; i++)
+ {
+ Color fillColor = color;
+
+
+ if (data[i] >= iterations)
+ {
+ fillColor = color;
+ }
+ else
+ {
+
+ int red = data[i] * 30 % 256;
+ int green = data[i] * 20 % 256;
+ int blue = data[i] * 50 % 256;
+
+ fillColor = Color.FromArgb(255, red, green, blue);
+
+ }
+
+
+ result[i * 4] = fillColor.R;
+ result[i * 4 + 1] = fillColor.G;
+ result[i * 4 + 2] = fillColor.B;
+ result[i * 4 + 3] = fillColor.A;
+
+ }
+
+ await basicCanvas.CreateImageDataCopyByteArray("Mandelbrot", width, height, result);
+ await basicCanvas.PutImageData("Mandelbrot", 0, 0);
+ }
+ }
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageBasic.razor b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageBasic.razor
new file mode 100644
index 000000000..d82e0877b
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageBasic.razor
@@ -0,0 +1,44 @@
+@page "/mandelbrotPageBasic"
+
+
Basic Mandelbrot ILGPU Example
+
+
+
Blazor Demo of Mandelbrot set with ILGPU objects injected in current scope.
+
+
+
+
@ExecutionsDetails1
+
@ExecutionsDetails2
+
@ExecutionsDetails3
+
+
+
+
+ In this example we inject ILGPU objects scoped to a razor page session. Individual ILGPU compute sessions
+ are unaware of other ILGPU compute sessions hosted in a Blazor server application. Every kernel will have to
+ be recompiled in every session resulting in lower productivity.
+
+ This demonstrates ILGPU objects work in complex multi-threaded environments.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageBasic.razor.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageBasic.razor.cs
new file mode 100644
index 000000000..15c6e8854
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageBasic.razor.cs
@@ -0,0 +1,297 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: MandelbrotPageBasic.razor.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using System.IO;
+using System.Drawing;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.JSInterop;
+using ILGPU;
+using ILGPU.Runtime;
+using BlazorSampleApp.Components;
+
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+#nullable disable
+ public partial class MandelbrotPageBasic : IDisposable
+ {
+ public BasicCanvas Canvas2D { get; set; }
+
+ public bool DisabledButtons { get; set; } = true;
+
+
+ [Inject]
+ IJSRuntime JSRuntime { get; set; }
+
+ [Inject]
+ IMandelbrotBasic MandelbrotInstance { get; set; }
+
+ [Inject]
+ NavigationManager NavManager { get; set; }
+
+
+ public string ExecutionsDetails1 { get; set; }
+
+
+ public string ExecutionsDetails2 { get; set; }
+
+ public string ExecutionsDetails3 { get; set; }
+
+ private bool _disposing = false;
+
+ protected override void OnInitialized()
+ {
+ NavManager.LocationChanged += LocationChanged;
+ base.OnInitialized();
+ }
+
+ void LocationChanged(object sender, LocationChangedEventArgs e)
+ {
+ // assume we're leaving this page
+ _disposing = true;
+ }
+
+ protected override void OnAfterRender(bool firstRender)
+ {
+ if (firstRender)
+ {
+ // we can't call any webgl functions until the page is fully rendered and the canvas is complete.
+ Canvas2D.CanvasInitComplete += CanvasInitComplete;
+ _stopWatch = new Stopwatch();
+ GetAvailableDevices();
+ }
+ base.OnAfterRender(firstRender);
+ }
+
+ private async void CanvasInitComplete(BasicCanvas supercanvas)
+ {
+
+
+ displayPort[0] = Canvas2D.Width;
+ displayPort[1] = Canvas2D.Height;
+ areaView[0] = -2.0f;
+ areaView[1] = 1.0f;
+ areaView[2] = -1.0f;
+ areaView[3] = 1.0f;
+ maxIterations = 1000;
+
+ int[] data = Crunch();
+ await MandelbrotExtensions.Draw(Canvas2D, data, displayPort[0], displayPort[1], maxIterations, Color.Blue);
+
+
+ // we could start the rendering process here rather than having a button click to start rendering.
+ DisabledButtons = false;
+
+ StateHasChanged();
+ }
+
+
+ private Device _lastDevice = null;
+
+
+
+ List SystemDevices = new List();
+
+ ///
+ /// Create a non-CPU accelerator device list
+ ///
+ private void GetAvailableDevices()
+ {
+ int icnt = 0;
+ foreach (Device device in MandelbrotInstance.ContextInstance.Devices)
+ {
+ if (device.AcceleratorType != AcceleratorType.CPU)
+ {
+
+ if (icnt == 0)
+ {
+ DeviceName = device.Name;
+ }
+ icnt += 1;
+ SystemDevices.Add(device.Name);
+ }
+ }
+
+
+ }
+
+ private static Stopwatch _stopWatch;
+
+ private void RestartWatch()
+ {
+ _stopWatch.Reset();
+ _stopWatch.Start();
+ }
+
+ private string ElapsedTime(string title = "Elapsed Time")
+ {
+ _stopWatch.Stop();
+ return title + " " + $"{_stopWatch.Elapsed.Minutes:00}:{_stopWatch.Elapsed.Seconds:00}.{_stopWatch.Elapsed.Milliseconds:000} ";
+ }
+
+
+
+
+ public string DeviceName { get; set; } = "CPUOnly";
+
+ protected async void UpdateSelected(ChangeEventArgs e)
+ {
+ DeviceName = e.Value.ToString();
+ await RenderDevice(DeviceName);
+ }
+
+ public async Task RenderDevice(string deviceName)
+ {
+
+ areaView[0] = -2.0f;
+ areaView[1] = 1.0f;
+ areaView[2] = -1.0f;
+ areaView[3] = 1.0f;
+ maxIterations = 1000;
+
+ int[] data = Crunch();
+ await MandelbrotExtensions.Draw(Canvas2D, data, displayPort[0], displayPort[1], maxIterations, Color.Blue);
+ StateHasChanged();
+ }
+
+
+
+ float[] areaView = new float[4];
+ int[] displayPort = new int[2];
+ int maxIterations = 1000;
+
+ ///
+ /// Generate a Mandelbrot set.
+ ///
+ ///
+ protected int[] Crunch()
+ {
+ int[] data = new int[displayPort[0] * displayPort[1]];
+
+ if (DeviceName == "CPUOnly")
+ {
+ RestartWatch();
+ //page resource
+ MandelbrotExtensions.CalcCPUParallel(data, displayPort, areaView, maxIterations); // Single thread CPU
+ ExecutionsDetails1 = ElapsedTime("CPU Only Mandelbrot");
+
+
+ }
+ else
+ {
+ Device device = MandelbrotInstance.ContextInstance.Devices.First(x => x.Name == DeviceName);
+
+ if (_lastDevice != device)
+ {
+ RestartWatch();
+ MandelbrotInstance.CompileKernel(device);
+ ExecutionsDetails2 = ElapsedTime("IL Compile - " + device.Name);
+ _lastDevice = device;
+ }
+
+
+ RestartWatch();
+
+ MandelbrotInstance.CalcGPU(ref data, displayPort, areaView, maxIterations); // ILGPU-CPU-Mode
+ ExecutionsDetails3 = ElapsedTime("IL Run - " + DeviceName);
+
+
+ }
+
+
+ return data;
+ }
+
+
+ ///
+ /// Animate 500 frames while narrowing view to small subsection of the Mandelbrot set.
+ ///
+ public async void AnimateMandelbrot()
+ {
+ int[] data = new int[displayPort[0] * displayPort[1]];
+
+ Device device = MandelbrotInstance.ContextInstance.Devices.First(x => x.Name == DeviceName);
+
+ if (_lastDevice != device)
+ {
+ RestartWatch();
+ MandelbrotInstance.CompileKernel(device);
+ ExecutionsDetails2 = ElapsedTime("IL Compile - " + device.Name);
+ _lastDevice = device;
+
+ }
+
+
+ MandelbrotInstance.InitGPURepeat(ref data, displayPort, areaView, maxIterations);
+ StateHasChanged();
+ float offsetX = -0.02f;
+ float offsetY = 0.00562f;
+
+ for (int i = 0; i < 500; i++)
+ {
+ // here we are in a long running loop, a user can navigate away or close a window which will
+ // eventually result in an exception as this loop will continue to run
+
+
+ if (_disposing || MandelbrotInstance.IsDisposing)
+ {
+
+ break;
+ }
+ RestartWatch();
+ MandelbrotInstance.CalcGPURepeat(ref data, displayPort, areaView, maxIterations); // ILGPU-CPU-Mode
+ ExecutionsDetails2 = ElapsedTime($"IL Run - {DeviceName}");
+
+ areaView[0] = areaView[0] * 0.98f + offsetX;
+ areaView[1] = areaView[1] * 0.98f + offsetX;
+ areaView[2] = areaView[2] * 0.98f + offsetY;
+ areaView[3] = areaView[3] * 0.98f + offsetY;
+
+
+ if (_disposing || MandelbrotInstance.IsDisposing)
+ {
+ break;
+ }
+ RestartWatch();
+ await MandelbrotExtensions.Draw(Canvas2D, data, displayPort[0], displayPort[1], maxIterations, Color.Blue);
+ ExecutionsDetails3 = ElapsedTime("Web Server Render");
+ StateHasChanged();
+
+
+
+ }
+
+ MandelbrotInstance.CleanupGPURepeat();
+
+
+ }
+
+
+
+ public void Dispose()
+ {
+ _disposing = true;
+ NavManager.LocationChanged -= LocationChanged;
+
+
+ }
+
+
+ }
+
+}
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageCPU.razor b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageCPU.razor
new file mode 100644
index 000000000..201a8815f
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageCPU.razor
@@ -0,0 +1,39 @@
+@page "/mandelbrotPageCPU"
+
+
Mandelbrot CPU Demo
+
+
+
Example of Mandelbrot set using Blazor and CPU resources.
+
+
+
+
@ExecutionsDetails1
+
@ExecutionsDetails2
+
@ExecutionsDetails3
+
@ExecutionsDetails4
+
+
+
+
+
+ Currently the CPU accelerator is intended for GPU kernel development/debugging rather than CPU kernel performance.
+ Optimized CPU kernel performance will likely be added back in an upcoming release of ILGPU.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Example of Mandelbrot set on Blazor server using ILGPU accelerator streams.
+
+
+
+
@ExecutionsDetails1
+
@ExecutionsDetails2
+
@ExecutionsDetails3
+
+
+
+
+
+ ILGPU Accelerator Streams are great in multi-threading environments. We dependency inject a singleton
+ compute host in Blazor server and share ILGPU resources via accelerator streams. Kernels are compiled once
+ per singleton, improving user response times.
+ In this case we track individual accelerator streams within a host singleton thus providing a
+ starting point for GPU resource management.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageStream.razor.cs b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageStream.razor.cs
new file mode 100644
index 000000000..9e3b9617c
--- /dev/null
+++ b/Samples/BlazorSampleApp/MandelbrotExplorer/MandelbrotPageStream.razor.cs
@@ -0,0 +1,217 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: MandelbrotPageStream.razor.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using System.Drawing;
+using BlazorSampleApp.Components;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.JSInterop;
+
+namespace BlazorSampleApp.MandelbrotExplorer
+{
+#nullable disable
+
+ ///
+ /// An example razor page calling a streamed accelerator via an injected IMandelbrotClient.
+ ///
+ public partial class MandelbrotPageStream: IDisposable
+ {
+ public BasicCanvas Canvas2D { get; set; }
+
+ [Inject]
+ IJSRuntime JSRuntime { get; set; }
+
+ [Inject]
+ IMandelbrotClient MandelbrotInstance { get; set; }
+
+ [Inject]
+ NavigationManager NavManager { get; set; }
+
+ // current details
+ public string ExecutionsDetails1 { get; set; }
+
+ public string ExecutionsDetails2 { get; set; }
+
+ public string ExecutionsDetails3 { get; set; }
+
+ public bool DisabledButtons { get; set; } = true;
+
+ string DeviceName { get; set; } = "n/a";
+
+ // Mandelbrot parameters
+ float[] areaView = new float[4];
+
+ // Mandelbrot render depth
+ int maxIterations = 1000;
+
+
+ // Canvas size
+ int[] displayPort = new int[2];
+
+
+ private bool _disposing = false;
+
+ private Stopwatch _stopWatch;
+
+
+ private void RestartWatch()
+ {
+ _stopWatch.Reset();
+ _stopWatch.Start();
+ }
+
+ private string ElapsedTime(string title = "Elapsed Time")
+ {
+ _stopWatch.Stop();
+ return title + " " + $"{_stopWatch.Elapsed.Minutes:00}:{_stopWatch.Elapsed.Seconds:00}.{_stopWatch.Elapsed.Milliseconds:000} ";
+ }
+
+
+ // Initialize razor page
+ protected override void OnInitialized()
+ {
+ _stopWatch = new Stopwatch();
+
+ NavManager.LocationChanged += LocationChanged;
+
+ base.OnInitialized();
+ }
+
+
+ // Remove navigation detection if this page is ending.
+ public void Dispose()
+ {
+ _disposing = true;
+ NavManager.LocationChanged -= LocationChanged;
+ }
+
+
+ // This only gets called when the user has navigated elsewhere, this prempts the.
+ void LocationChanged(object sender, LocationChangedEventArgs e)
+ {
+ // assume we're leaving this page for good, preempt new computation
+ _disposing = true;
+
+ }
+
+
+ // Once the razor page/component is render complete we can interact with the browser
+ protected override void OnAfterRender(bool firstRender)
+ {
+ if (firstRender)
+ {
+ // We can't call any webgl functions until the page is fully rendered and the HTML canvas and JavaScript are fully loaded.
+ Canvas2D.CanvasInitComplete += CanvasInitComplete;
+
+ }
+ base.OnAfterRender(firstRender);
+ }
+
+
+ ///
+ /// Render an initial Mandelbrot graph once this razor page/component loads
+ ///
+ ///
+ private async void CanvasInitComplete(BasicCanvas basicCanvas)
+ {
+
+ DeviceName = MandelbrotInstance.AcceleratorName();
+
+ displayPort[0] = Canvas2D.Width;
+ displayPort[1] = Canvas2D.Height;
+ areaView[0] = -2.0f;
+ areaView[1] = 1.0f;
+ areaView[2] = -1.0f;
+ areaView[3] = 1.0f;
+ maxIterations = 1000;
+
+ if (_disposing || !MandelbrotInstance.IsActive)
+ {
+ return;
+ }
+ RestartWatch();
+ MandelbrotInstance.SetDisplay(Canvas2D.Width, Canvas2D.Height);
+ ExecutionsDetails1 = ElapsedTime($"IL Compile - {DeviceName}");
+
+ if (_disposing || !MandelbrotInstance.IsActive)
+ {
+ return;
+ }
+ RestartWatch();
+ int[] data = await MandelbrotInstance.CalculateMandelbrot(areaView[0], areaView[1], areaView[2], areaView[3]); // ILGPU-CPU-Mode
+ ExecutionsDetails2 = ElapsedTime($"IL Run - {DeviceName}");
+
+ if (data == null || _disposing)
+ {
+ return;
+ }
+
+ await MandelbrotExtensions.Draw(Canvas2D, data, displayPort[0], displayPort[1], maxIterations, Color.Blue);
+
+ DisabledButtons = false;
+ StateHasChanged(); // note StateHasChanged will force an update of controls on our razor page.
+ }
+
+
+
+
+ ///
+ /// Animate 500 frames while narrowing view to small subsection of the Mandelbrot set.
+ ///
+ public async void AnimateMandelbrot()
+ {
+
+ // Target information
+ float offsetX = -0.02f;
+ float offsetY = 0.00562f;
+
+ for (int i = 0; i < 500; i++)
+ {
+ if (_disposing || !MandelbrotInstance.IsActive)
+ {
+ break;
+ }
+
+ // Set the next areaView size
+ areaView[0] = areaView[0] * 0.98f + offsetX;
+ areaView[1] = areaView[1] * 0.98f + offsetX;
+ areaView[2] = areaView[2] * 0.98f + offsetY;
+ areaView[3] = areaView[3] * 0.98f + offsetY;
+
+ // Generate the next frame of this animation.
+ RestartWatch();
+ int[] data = await MandelbrotInstance.CalculateMandelbrot(areaView[0], areaView[1], areaView[2], areaView[3]); // ILGPU-CPU-Mode
+ if (data==null || _disposing)
+ {
+ break;
+ }
+ ExecutionsDetails2 = ElapsedTime($"IL Run - {DeviceName}");
+
+
+
+
+ // Render the generated frame to the canvas.
+ RestartWatch();
+ await MandelbrotExtensions.Draw(Canvas2D, data, displayPort[0], displayPort[1], maxIterations, Color.Blue);
+ StateHasChanged();
+
+ ExecutionsDetails3 = ElapsedTime("Web Server Render");
+
+ }
+ }
+ }
+
+}
diff --git a/Samples/BlazorSampleApp/Pages/Error.cshtml b/Samples/BlazorSampleApp/Pages/Error.cshtml
new file mode 100644
index 000000000..24b825f82
--- /dev/null
+++ b/Samples/BlazorSampleApp/Pages/Error.cshtml
@@ -0,0 +1,42 @@
+@page
+@model BlazorSampleApp.Pages.ErrorModel
+
+
+
+
+
+
+
+ Error
+
+
+
+
+
+
+
+
Error.
+
An error occurred while processing your request.
+
+ @if (Model.ShowRequestId)
+ {
+
+ Request ID:@Model.RequestId
+
+ }
+
+
Development Mode
+
+ Swapping to the Development environment displays detailed information about the error that occurred.
+
+
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
+ and restarting the app.
+
+
+
+
+
+
diff --git a/Samples/BlazorSampleApp/Pages/Error.cshtml.cs b/Samples/BlazorSampleApp/Pages/Error.cshtml.cs
new file mode 100644
index 000000000..9255384b9
--- /dev/null
+++ b/Samples/BlazorSampleApp/Pages/Error.cshtml.cs
@@ -0,0 +1,27 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using System.Diagnostics;
+
+namespace BlazorSampleApp.Pages
+{
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ [IgnoreAntiforgeryToken]
+ public class ErrorModel : PageModel
+ {
+ public string? RequestId { get; set; }
+
+ public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+ private readonly ILogger _logger;
+
+ public ErrorModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void OnGet()
+ {
+ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/BlazorSampleApp/Pages/Index.razor b/Samples/BlazorSampleApp/Pages/Index.razor
new file mode 100644
index 000000000..329ec04b5
--- /dev/null
+++ b/Samples/BlazorSampleApp/Pages/Index.razor
@@ -0,0 +1,40 @@
+@page "/"
+
+
Welcome to ILGPU samples with Blazor
+
+
+
+
+
+ This sample demonstrates ILGPU GPU compute in a Blazor server application.
+
+
Sharing a GPU resources across Blazor server sessions is a demonstration of ILGPU multi-threaded prowess.
+ Two approaches can be taken; one is instantiating an accelerator context in each Blazor page session,
+ second is to instantiate ILGPU as a singleton in Blazor server and share accelerator streams.
+
+
+
Example Applications Ideas
+
+
Intelligent Machine Control
One could use low cost tablets to operate a machine tool with a centralized Blazor server.
+
Compute Studio Service
Proprietary code is sometimes best as a service. For example thermal or electrodynamic analysis as a service.
+
Shared Rendering Studio
Loading workstations with cutting edge GPUs may not be the optimal use of capital.
+
+
+
+
+
+
Notes
+
+
OpenCL or Cuda compatible accelerator required.
+
This sample includes two custom Blazor component; these give flavor to Blazor coding as compare to traditional
+ ASP.Net coding. First is a Blazor wrapper around HTML canvas WebGL 2D context with JavaScript inter-operation.
+ Second is a simple "tool tip" flyover component.
+
This animation pushes the limits with 32 bit floats while displaying progressively smaller subsections of the
+ Mandelbrot set. Pushing the animation button a second time will block up the display as the limit of 32 bit floating
+ point is reached.
+
+
No attempt is made to operate in WebAssembly as shader compute support is experimental or not supported in all browsers.
+
+ An error has occurred. This application may no longer respond until reloaded.
+
+
+ An unhandled exception has occurred. See browser dev tools for details.
+
+ Reload
+ 🗙
+
+
+
+
+
diff --git a/Samples/BlazorSampleApp/Program.cs b/Samples/BlazorSampleApp/Program.cs
new file mode 100644
index 000000000..7ecbe9619
--- /dev/null
+++ b/Samples/BlazorSampleApp/Program.cs
@@ -0,0 +1,55 @@
+// ---------------------------------------------------------------------------------------
+// ILGPU Samples
+// Copyright (c) 2022 ILGPU Project
+// www.ilgpu.net
+//
+// File: Program.cs
+//
+// This file is part of ILGPU and is distributed under the University of Illinois Open
+// Source License. See LICENSE.txt for details.
+// ---------------------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Web;
+
+
+var builder = WebApplication.CreateBuilder(args);
+
+
+// In process host without streams, note this will get loaded by each web "session" without awareness of other users
+builder.Services.AddScoped();
+
+// Out of process host singleton instance of ILGPU Host
+builder.Services.AddSingleton();
+
+// In process compute session access for multiple accelerator streams
+builder.Services.AddScoped();
+
+
+
+// Add services to the container.
+builder.Services.AddRazorPages();
+builder.Services.AddServerSideBlazor();
+
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (!app.Environment.IsDevelopment())
+{
+ app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+}
+
+// to enable https: support uncomment the following line.
+//app.UseHttpsRedirection();
+
+app.UseStaticFiles();
+
+app.UseRouting();
+
+app.MapBlazorHub();
+app.MapFallbackToPage("/_Host");
+
+app.Run();
diff --git a/Samples/BlazorSampleApp/Properties/launchSettings.json b/Samples/BlazorSampleApp/Properties/launchSettings.json
new file mode 100644
index 000000000..804faa6f8
--- /dev/null
+++ b/Samples/BlazorSampleApp/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:8413",
+ "sslPort": 44311
+ }
+ },
+ "profiles": {
+ "BlazorSampleApp": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5034",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Samples/BlazorSampleApp/Shared/MainLayout.razor b/Samples/BlazorSampleApp/Shared/MainLayout.razor
new file mode 100644
index 000000000..56f1d8b91
--- /dev/null
+++ b/Samples/BlazorSampleApp/Shared/MainLayout.razor
@@ -0,0 +1,19 @@
+@inherits LayoutComponentBase
+
+BlazorSampleApp
+
+