diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index d47b11f3..0cd4e23e 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,3 +1,6 @@
+### 5.5.5
+- OpenGL/WPF control uses tasks for rendering (avoiding stack-inlining due to STAThread)
+
### 5.5.4
- OpenGL/WPF control uses `OnPainRender` again
diff --git a/src/Application/Aardvark.Application.WPF.GL/Aardvark.Application.WPF.GL.fsproj b/src/Application/Aardvark.Application.WPF.GL/Aardvark.Application.WPF.GL.fsproj
index c809e5ce..9f416836 100644
--- a/src/Application/Aardvark.Application.WPF.GL/Aardvark.Application.WPF.GL.fsproj
+++ b/src/Application/Aardvark.Application.WPF.GL/Aardvark.Application.WPF.GL.fsproj
@@ -18,6 +18,7 @@
..\..\..\bin\Release\
+
diff --git a/src/Application/Aardvark.Application.WPF.GL/RenderControl.fs b/src/Application/Aardvark.Application.WPF.GL/RenderControl.fs
index 43084f22..b46aac28 100644
--- a/src/Application/Aardvark.Application.WPF.GL/RenderControl.fs
+++ b/src/Application/Aardvark.Application.WPF.GL/RenderControl.fs
@@ -6,7 +6,7 @@ open System.Windows.Forms.Integration
open Aardvark.Application
open System.Windows.Threading
-type private WinFormsControl = Aardvark.Application.WinForms.OpenGlRenderControl
+type private WinFormsControl = Aardvark.Application.WinForms.ThreadedRenderControl
type OpenGlRenderControl(runtime : Runtime, debug : IDebugConfig, samples : int) as this =
inherit WindowsFormsHost()
@@ -27,7 +27,6 @@ type OpenGlRenderControl(runtime : Runtime, debug : IDebugConfig, samples : int)
// do ctrl.AutoInvalidate <- false
do this.Child <- ctrl
- //ctrl.OnPaintRender <- false
this.Loaded.Add(fun e -> this.Focusable <- false)
//override x.OnDpiChanged(oldDpi : DpiScale, newDpi : DpiScale) =
diff --git a/src/Application/Aardvark.Application.WPF.GL/ThreadedRenderControl.fs b/src/Application/Aardvark.Application.WPF.GL/ThreadedRenderControl.fs
new file mode 100644
index 00000000..863b1f7b
--- /dev/null
+++ b/src/Application/Aardvark.Application.WPF.GL/ThreadedRenderControl.fs
@@ -0,0 +1,379 @@
+namespace Aardvark.Application.WinForms
+
+
+open OpenTK
+open OpenTK.Graphics.OpenGL4
+
+open System
+open System.Threading.Tasks
+open System.Windows.Forms
+
+open Aardvark.Base
+open Aardvark.Rendering
+open FSharp.Data.Adaptive
+open Aardvark.Rendering.GL
+open Aardvark.Application
+
+type ThreadedRenderControl(runtime : Runtime, debug : IDebugConfig, samples : int) =
+ inherit GLControl(
+ Graphics.GraphicsMode(
+ OpenTK.Graphics.ColorFormat(Config.BitsPerPixel),
+ Config.DepthBits,
+ Config.StencilBits,
+ 0,
+ OpenTK.Graphics.ColorFormat.Empty,
+ Config.Buffers,
+ false
+ ),
+ Config.MajorVersion,
+ Config.MinorVersion,
+ Config.ContextFlags,
+ VSync = false
+ )
+// static let messageLoop = MessageLoop()
+// static do messageLoop.Start()
+
+ let ctx = runtime.Context
+ let mutable loaded = false
+ let transaction = new Transaction()
+
+ let mutable task : Option = None
+ let mutable taskSubscription : IDisposable = null
+
+ let depthStencilFormat =
+ match Config.DepthBits, Config.StencilBits with
+ | 0, 0 -> None
+ | 16, 0 -> Some TextureFormat.DepthComponent16
+ | 24, 0 -> Some TextureFormat.DepthComponent24
+ | 32, 0 -> Some TextureFormat.DepthComponent32
+ | 24, 8 -> Some TextureFormat.Depth24Stencil8
+ | 32, 8 -> Some TextureFormat.Depth32fStencil8
+ | 0, 8 -> Some TextureFormat.StencilIndex8
+ | _ -> failwith "invalid depth-stencil mode"
+
+ let fboSignature =
+ let depthStencilAtt =
+ depthStencilFormat |> Option.map (fun f -> DefaultSemantic.DepthStencil, f) |> Option.toList
+
+ runtime.CreateFramebufferSignature([DefaultSemantic.Colors, TextureFormat.Rgba8] @ depthStencilAtt, samples)
+
+ let mutable contextHandle : ContextHandle = null
+ let defaultFramebuffer =
+ new Framebuffer(
+ ctx, fboSignature,
+ (fun _ -> 0),
+ ignore,
+ [0, DefaultSemantic.Colors, new Renderbuffer(ctx, 0, V2i.Zero, TextureFormat.Rgba8, samples, 0L) :> IFramebufferOutput], None
+ )
+
+ let mutable framebuffer : option> = None
+
+ let getFramebuffer (realSize : V2i) (size : V2i) (samples : int) =
+ let subsampled, resolved =
+ match framebuffer with
+ | Some (c, _, f, f0) when f.Size = realSize && c.Multisamples = samples ->
+ f, f0
+ | _ ->
+ match framebuffer with
+ | Some (c, d, f, f0) ->
+ ctx.Delete f
+ ctx.Delete c
+ ctx.Delete d
+ match f0 with
+ | Some (c0, f0) ->
+ ctx.Delete f0
+ ctx.Delete c0
+ | None ->
+ ()
+ | _ ->
+ ()
+
+ let c = ctx.CreateTexture2D(realSize, 1, TextureFormat.Rgba8, samples)
+ let d = ctx.CreateTexture2D(realSize, 1, TextureFormat.Depth24Stencil8, samples)
+ let f =
+ ctx.CreateFramebuffer(
+ fboSignature,
+ [ 0, DefaultSemantic.Colors, c.GetOutputView() ],
+ Some ( d.GetOutputView())
+ )
+
+ let f0 =
+ if samples > 1 then
+ let c0 = ctx.CreateTexture2D(realSize, 1, TextureFormat.Rgba8, 1)
+ let f0 =
+ ctx.CreateFramebuffer(
+ fboSignature,
+ [ 0, DefaultSemantic.Colors, c0.GetOutputView() ],
+ None
+ )
+ Some (c0, f0)
+ else
+ None
+
+ framebuffer <- Some (c, d, f, f0)
+ f, f0
+
+ let blit() =
+ let s = realSize
+ match resolved with
+ | Some(_, resolved) ->
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, subsampled.Handle)
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, resolved.Handle)
+ GL.DrawBuffer(DrawBufferMode.ColorAttachment0)
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0)
+ GL.BlitFramebuffer(0, 0, s.X, s.Y, 0, 0, s.X, s.Y, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest)
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, 0)
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0)
+
+
+ GL.DrawBuffer(DrawBufferMode.Back)
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, resolved.Handle)
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0)
+ GL.BlitFramebuffer(0, 0, s.X, s.Y, 0, 0, size.X, size.Y, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear)
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, 0)
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0)
+ | None ->
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, subsampled.Handle)
+ GL.ReadBuffer(ReadBufferMode.ColorAttachment0)
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0)
+ GL.DrawBuffer(DrawBufferMode.Back)
+ GL.BlitFramebuffer(0, 0, s.X, s.Y, 0, 0, size.X, size.Y, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear)
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, 0)
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0)
+
+ subsampled, blit
+
+ //let mutable defaultOutput = OutputDescription.ofFramebuffer defaultFramebuffer
+
+ // NOTE: previously the size was forced to > 0 in Render, but now is only updated if the ClientSize is > 0
+ // not sure if lying about the size is ideal, but a size of 0 area might crash client the application on the client size if not handled there
+ // earlier we seem to have followed the OnResize events and the "size" represented the true RenderControl size
+ // as we seem to have decided at some point to implement this behavior the initial size also needs to be > 0 here:
+ let sizes = AVal.init (V2i(max 1 base.ClientSize.Width, max 1 base.ClientSize.Height))
+
+ let frameTime = AverageWindow(10)
+ let frameWatch = System.Diagnostics.Stopwatch()
+
+ let timeWatch = System.Diagnostics.Stopwatch()
+ let baseTime = DateTime.Now.Ticks
+ do timeWatch.Start()
+
+ let now() = DateTime(timeWatch.Elapsed.Ticks + baseTime)
+ let nextFrameTime() =
+ if frameTime.Count >= 10 then
+ now() + TimeSpan.FromSeconds frameTime.Value
+ else
+ now()
+ let time = AVal.init (now())
+
+ let mutable samples = samples
+ let mutable subsampling = 1.0
+
+ let mutable needsRedraw = false
+ let mutable first = true
+
+ let mutable renderContinuously = false
+ let mutable autoInvalidate = true
+ let mutable threadStealing : StopStealing =
+ { new StopStealing with member x.StopStealing () = Disposable.empty }
+
+ let beforeRender = Event()
+ let afterRender = Event()
+
+ member x.ContextHandle = contextHandle
+
+ member x.DisableThreadStealing
+ with get () = threadStealing
+ and set v = threadStealing <- v
+
+ // automatically invalidates after OnPaint if renderTask is out of date again
+ member x.AutoInvalidate
+ with get () = autoInvalidate
+ and set v = autoInvalidate <- v
+
+ /// Returns true if the control has been fully initialized.
+ member x.IsLoaded
+ with get () = loaded
+
+ interface IInvalidateControl with
+ member x.IsInvalid = needsRedraw
+
+ member private x.ForceRedraw() =
+ if not renderContinuously then
+ MessageLoop.Invalidate x |> ignore
+
+ member x.RenderContinuously
+ with get() = renderContinuously
+ and set v =
+ renderContinuously <- v
+ // if continuous rendering is enabled make sure rendering is initiated
+ if v then // -> only makes sense with onPaintRender
+ x.Invalidate()
+
+
+ member x.RenderTask
+ with get() = task.Value
+ and set t =
+ match task with
+ | Some old ->
+ if taskSubscription <> null then taskSubscription.Dispose()
+ old.Dispose()
+ | None -> ()
+
+ task <- Some t
+ taskSubscription <- t.AddMarkingCallback x.ForceRedraw
+
+ member x.Sizes = sizes :> aval<_>
+ member x.Samples
+ with get() = samples
+ and set s =
+ if samples <> s then
+ samples <- s
+ x.ForceRedraw()
+
+ member x.SubSampling
+ with get() = subsampling
+ and set v =
+ if subsampling <> v then
+ subsampling <- v
+ x.ForceRedraw()
+
+
+ override x.OnHandleCreated(e) =
+
+ base.OnHandleCreated(e) // creates the graphics context of the control and performs MakeCurrent -> NOTE: during this call rendering in other threads can break resource sharing
+
+ GL.SetDefaultStates()
+
+
+ member x.Render() =
+
+ let mutable initial = false
+ if loaded then
+ if isNull contextHandle || contextHandle.Handle.IsDisposed then
+ contextHandle <- new ContextHandle(base.Context, base.WindowInfo)
+ contextHandle.Initialize(debug, setDefaultStates = false)
+ initial <- true
+
+ let task =
+ Task.Factory.StartNew(new System.Action(fun () ->
+ beforeRender.Trigger()
+
+ let screenSize = V2i(x.ClientSize.Width, x.ClientSize.Height)
+ if Vec.allGreater screenSize 0 then
+
+ let fboSize = V2i(max 1 (int (round (float screenSize.X * subsampling))), (int (round (float screenSize.Y * subsampling))))
+ match task with
+ | Some t ->
+ use __ = ctx.RenderingLock contextHandle
+ let fbo, blit = getFramebuffer fboSize screenSize samples
+
+ if initial then
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0)
+ GL.Disable(EnableCap.Multisample)
+ //let ms = GL.IsEnabled(EnableCap.Multisample)
+ //if ms then Log.warn "multisample enabled"
+ //else Log.warn "multisample disabled"
+ //let samples = Array.zeroCreate 1
+ //GL.GetFramebufferParameter(FramebufferTarget.Framebuffer, unbox (int All.Samples), samples)
+ //Log.warn "effective samples: %A" samples.[0]
+
+ let stopDispatcherProcessing = threadStealing.StopStealing()
+
+ frameWatch.Restart()
+ useTransaction transaction (fun () -> time.Value <- nextFrameTime())
+
+ if fboSize <> sizes.Value then
+ useTransaction transaction (fun () -> sizes.Value <- fboSize)
+
+ transaction.Commit()
+ transaction.Dispose()
+
+ defaultFramebuffer.Size <- screenSize
+ //defaultOutput <- { defaultOutput with viewport = Box2i(V2i.OO, defaultFramebuffer.Size - V2i.II) }
+
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, fbo.Handle)
+ GL.ColorMask(true, true, true, true)
+ GL.DepthMask(true)
+ GL.StencilMask(0xFFFFFFFFu)
+ GL.Viewport(0,0,screenSize.X, screenSize.Y)
+ GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f)
+ GL.ClearDepth(1.0)
+ GL.Clear(ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit ||| ClearBufferMask.StencilBufferBit)
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0)
+
+ //EvaluationUtilities.evaluateTopLevel(fun () ->
+ t.Run(AdaptiveToken.Top, RenderToken.Empty, OutputDescription.ofFramebuffer fbo)
+ blit()
+
+ x.SwapBuffers()
+ //System.Threading.Thread.Sleep(200)
+ frameWatch.Stop()
+ if not first then
+ frameTime.Insert frameWatch.Elapsed.TotalSeconds |> ignore
+
+ useTransaction transaction (fun () -> time.MarkOutdated())
+ transaction.Commit()
+ transaction.Dispose()
+
+ stopDispatcherProcessing.Dispose()
+ if t.OutOfDate then
+ needsRedraw <- true
+ if autoInvalidate then x.ForceRedraw()
+ else
+ needsRedraw <- false
+
+ first <- false
+
+ | None ->
+ if fboSize <> sizes.Value then
+ useTransaction transaction (fun () -> sizes.Value <- fboSize)
+ transaction.Commit()
+ transaction.Dispose()
+
+ needsRedraw <- false
+
+ afterRender.Trigger()
+
+ needsRedraw <- renderContinuously
+ if renderContinuously then
+ x.ForceRedraw() // why not use ForceRedraw ?
+ ))
+ task.Wait()
+
+ override x.OnPaint(e) =
+ base.OnPaint(e)
+ loaded <- true
+ x.Render()
+
+// override x.OnResize(e) =
+// base.OnResize(e)
+// sizes.Emit <| V2i(base.ClientSize.Width, base.ClientSize.Height)
+
+ member x.Time = time :> aval<_>
+ member x.FramebufferSignature = fboSignature
+
+ member x.BeforeRender = beforeRender.Publish
+ member x.AfterRender = afterRender.Publish
+
+ interface IRenderTarget with
+ member x.FramebufferSignature = fboSignature
+ member x.Runtime = runtime :> IRuntime
+ member x.Time = time :> aval<_>
+ member x.RenderTask
+ with get() = x.RenderTask
+ and set t = x.RenderTask <- t
+
+ member x.SubSampling
+ with get() = x.SubSampling
+ and set v = x.SubSampling <- v
+
+ member x.Sizes = sizes :> aval<_>
+ member x.Samples = samples
+ member x.BeforeRender = beforeRender.Publish
+ member x.AfterRender = afterRender.Publish
+
+ new(runtime : Runtime, debug : bool, samples : int) = new ThreadedRenderControl(runtime, DebugLevel.ofBool debug, samples)
+ new(runtime : Runtime, debug : IDebugConfig) = new ThreadedRenderControl(runtime, debug, 1)
+ new(runtime : Runtime, debug : bool) = new ThreadedRenderControl(runtime, debug, 1)
+
diff --git a/src/Examples (netcore)/01 - Hello Wpf/MainWindow.xaml.cs b/src/Examples (netcore)/01 - Hello Wpf/MainWindow.xaml.cs
index 792c25e7..fd49556d 100644
--- a/src/Examples (netcore)/01 - Hello Wpf/MainWindow.xaml.cs
+++ b/src/Examples (netcore)/01 - Hello Wpf/MainWindow.xaml.cs
@@ -36,7 +36,7 @@ public MainWindow()
{
Aardvark.Base.Aardvark.Init(); // initialize aardvark base modules
- Config.useSharingControl = true;
+ Config.useSharingControl = false;
var app = new Aardvark.Application.WPF.OpenGlApplication(true);
InitializeComponent();