diff --git a/Source/DirectShow/Controls/D3DRenderer.cs b/Source/DirectShow/Controls/D3DRenderer.cs index bc3a3d0..ec3add7 100644 --- a/Source/DirectShow/Controls/D3DRenderer.cs +++ b/Source/DirectShow/Controls/D3DRenderer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -57,6 +58,17 @@ public abstract class D3DRenderer : FrameworkElement /// This is used to remember the value for when the control is loaded and unloaded. /// private bool m_renderOnCompositionTargetRenderingTemp; + + /// + /// TryLock timeout for the invalidate video image. Low values means higher UI responsivity, but more video dropped frames. + /// + private Duration m_invalidateVideoImageLockDuration = new Duration(TimeSpan.FromMilliseconds(100)); + + /// + /// Flag to reduce redundant calls to the AddDirtyRect when the rendering thread is busy. + /// Int instead of bool for Interlocked support. + /// + private int m_videoImageInvalid = 1; #endregion #region Dependency Properties @@ -324,44 +336,13 @@ private void CompositionTargetRendering(object sender, EventArgs e) InternalInvalidateVideoImage(); } - /// - /// Cleans up any dead references we may have to any cloned renderers - /// - private void CleanZombieRenderers() - { - lock (m_clonedD3Drenderers) - { - var deadObjects = new List(); - - for (int i = 0; i < m_clonedD3Drenderers.Count; i++) - { - if (!m_clonedD3Drenderers[i].IsAlive) - deadObjects.Add(m_clonedD3Drenderers[i]); - } - - foreach (var deadGuy in deadObjects) - { - m_clonedD3Drenderers.Remove(deadGuy); - } - } - } - /// /// Sets the backbuffer for any cloned D3DRenderers /// private void SetBackBufferForClones() { - lock (m_clonedD3Drenderers) - { - CleanZombieRenderers(); - - foreach (var rendererRef in m_clonedD3Drenderers) - { - var renderer = rendererRef.Target as D3DRenderer; - if (renderer != null) - renderer.SetBackBuffer(m_pBackBuffer); - } - } + var backBuffer = m_pBackBuffer; + ForEachCloneD3DRenderer(r => r.SetBackBuffer(backBuffer)); } /// @@ -387,11 +368,15 @@ private void SetBackBufferInternal(IntPtr backBuffer) { D3DImage.Lock(); D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer); - D3DImage.Unlock(); - SetNaturalWidthHeight(); } catch { } + finally + { + D3DImage.Unlock(); + } + + SetNaturalWidthHeight(); /* Clear our flag, so this won't be ran again * until a new surface is sent */ @@ -404,21 +389,47 @@ private void SetNaturalWidthHeight() SetNaturalVideoWidth(m_d3dImage.PixelWidth); } + private bool GetSetVideoImageInvalid(bool value) + { + int oldValue = Interlocked.Exchange(ref m_videoImageInvalid, value ? 1 : 0); + return oldValue == 1; + } + /// /// Invalidates any possible cloned renderer we may have /// private void InvalidateClonedVideoImages() + { + ForEachCloneD3DRenderer(r => r.InvalidateVideoImage()); + } + + private void ForEachCloneD3DRenderer(Action action) { lock (m_clonedD3Drenderers) { - CleanZombieRenderers(); - + bool needClean = false; foreach (var rendererRef in m_clonedD3Drenderers) { var renderer = rendererRef.Target as D3DRenderer; if (renderer != null) - renderer.InvalidateVideoImage(); + action(renderer); + else + needClean = true; } + + if (needClean) + CleanZombieRenderers(); + } + } + + /// + /// Cleans up any dead references we may have to any cloned renderers + /// + private void CleanZombieRenderers() + { + lock (m_clonedD3Drenderers) + { + m_clonedD3Drenderers.RemoveAll(c => !c.IsAlive); } } @@ -533,6 +544,8 @@ protected void SetBackBuffer(IntPtr backBuffer) protected void InvalidateVideoImage() { + GetSetVideoImageInvalid(true); + if (!m_renderOnCompositionTargetRendering) InternalInvalidateVideoImage(); } @@ -545,7 +558,7 @@ protected void InternalInvalidateVideoImage() /* Ensure we run on the correct Dispatcher */ if(!D3DImage.Dispatcher.CheckAccess()) { - D3DImage.Dispatcher.Invoke((Action)(() => InvalidateVideoImage())); + D3DImage.Dispatcher.BeginInvoke((Action)(() => InternalInvalidateVideoImage())); return; } @@ -553,21 +566,31 @@ protected void InternalInvalidateVideoImage() * this method will do the trick */ SetBackBufferInternal(m_pBackBuffer); + // may save a few AddDirtyRect calls when the rendering thread is too busy + // or RenderOnCompositionTargetRendering is set but the video is not playing + bool invalid = GetSetVideoImageInvalid(false); + if (!invalid) + return; + /* Only render the video image if possible, or if IsRenderingEnabled is true */ if (D3DImage.IsFrontBufferAvailable && IsRenderingEnabled && m_pBackBuffer != IntPtr.Zero) { try { + if (!D3DImage.TryLock(InvalidateVideoImageLockDuration)) + return; /* Invalidate the entire image */ - D3DImage.Lock(); D3DImage.AddDirtyRect(new Int32Rect(0, /* Left */ 0, /* Top */ D3DImage.PixelWidth, /* Width */ D3DImage.PixelHeight /* Height */)); - D3DImage.Unlock(); } catch (Exception) { } + finally + { + D3DImage.Unlock(); + } } /* Invalidate all of our cloned D3DRenderers */ @@ -584,6 +607,19 @@ protected D3DRenderer() Unloaded += D3DRendererUnloaded; } + /// + /// TryLock timeout for the invalidate video image. Low values means higher UI responsivity, but more video dropped frames. + /// + public Duration InvalidateVideoImageLockDuration + { + get { return m_invalidateVideoImageLockDuration; } + set { + if (value == null) + throw new ArgumentNullException("InvalidateVideoImageLockDuration"); + m_invalidateVideoImageLockDuration = value; + } + } + /// /// Creates a clone of the D3DRenderer. This is a work for the visual /// brush not working cross-threaded