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();