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