Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Core / CSharp / System / Windows / Media / MediaContext.cs / 1 / MediaContext.cs
//------------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description:
// The MediaContext class controls the media layer.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Composition;
using System.Security;
using System.Windows.Media.Effects;
using MS.Internal;
using MS.Internal.PresentationCore;
using MS.Utility;
using MS.Win32;
using Microsoft.Win32.SafeHandles;
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;
namespace System.Windows.Media
{
///
/// The MediaContext class controls the media layer.
///
///
/// Use to start up the media system and to
/// shut down the media system.
///
///
internal partial class MediaContext : DispatcherObject, IDisposable, IClock
{
///
/// Initializes the MediaContext's clock service.
///
static MediaContext()
{
long qpcCurrentTime;
SafeNativeMethods.QueryPerformanceFrequency(out _perfCounterFreq);
if (IsClockSupported)
{
SafeNativeMethods.QueryPerformanceCounter(out qpcCurrentTime);
}
else
{
qpcCurrentTime = 0;
}
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.PERFFREQUENCYGUID),
MS.Utility.EventType.Info,
_perfCounterFreq,
qpcCurrentTime
);
}
}
///
/// Returns true if the MediaContext can return current time values,
/// false otherwise.
///
internal static bool IsClockSupported
{
get
{
return _perfCounterFreq != 0;
}
}
///
/// Converts a time value expressed in "counts" (as returned by a call
/// to QueryPerformanceCounter) to "ticks". A Tick is the smallest
/// time unit expressable by a TimeSpan and is equal to 100ns
///
///
///
private static long CountsToTicks(long counts)
{
// The following expression retains precision while avoiding overflow:
return (long)(TimeSpan.TicksPerSecond * (counts / _perfCounterFreq) + (TimeSpan.TicksPerSecond * (counts % _perfCounterFreq)) / _perfCounterFreq);
}
///
/// Converts a time value expressed in "ticks" to an estimate of a count
/// (as returned by a call to QueryPerformanceCounter)
///
///
///
private static long TicksToCounts(long ticks)
{
return (long)(_perfCounterFreq * (ticks / TimeSpan.TicksPerSecond) + (_perfCounterFreq * (ticks % TimeSpan.TicksPerSecond)) / TimeSpan.TicksPerSecond);
}
///
/// Finds out whether a number is prime or not
///
///
/// Fails on 2 by saying that it is not prime but we won't call the
/// method with 2 as input
///
private static bool IsPrime(int number)
{
// If the number is even then it's not prime.
// This is WRONG for 2 but we don't get called with 2.
if ((number & 1) == 0)
return false;
int sqrt = (int) Math.Sqrt(number);
for (int i = 3; i <= sqrt; i += 2)
{
if (number % i == 0)
{
return false;
}
}
return true;
}
///
/// Find the next prime number greater than number
///
private static int FindNextPrime(int number)
{
while (!IsPrime(++number))
{
// Nothing to do
}
return number;
}
///
/// The MediaContext lives in the Dispatcher and is the MediaSystem's class that keeps
/// per Dispatcher state.
///
internal MediaContext(Dispatcher dispatcher)
{
// We create exactly one MediaContext per thread. This is the one
// for this thread
Debug.Assert(dispatcher.Reserved0 == null);
// Initialize frame time information
if (IsClockSupported)
{
SafeNativeMethods.QueryPerformanceCounter(out _lastPresentationTime);
_estimatedNextPresentationTime = TimeSpan.FromTicks(CountsToTicks(_lastPresentationTime));
}
// Generate a unique id for our context so that we can pass this along to
// CreateHWNDRenderTarget
_contextGuid = Guid.NewGuid();
_connectMessage = new DispatcherOperationCallback(ConnectHandler);
_disconnectMessage = new DispatcherOperationCallback(DisconnectHandler);
// Create a hashtable in which we manage the CompositionTargets.
_registeredICompositionTargets = new System.Collections.Hashtable();
_registeredLayeredWindows = new System.Collections.Hashtable();
// Connect to the MediaSystem.
if (MediaSystem.Startup(this))
{
_isConnected = MediaSystem.ConnectChannels(this);
}
// Subscribe to the OnDestroyContext event so that we can cleanup our state.
_destroyHandler = new EventHandler(this.OnDestroyContext);
Dispatcher.ShutdownFinished += _destroyHandler;
_renderMessage = new DispatcherOperationCallback(RenderMessageHandler);
_animRenderMessage = new DispatcherOperationCallback(AnimatedRenderMessageHandler);
_inputMarkerMessage = new DispatcherOperationCallback(InputMarkerMessageHandler);
// We hold off connecting ourselves to the dispatcher until we are sure that
// initialization will complete successfully. In rare cases, function calls
// earlier in this constructor throw exceptions, resulting in the MediaContext
// being left in an uninitialized state; however, the Dispatcher could call methods
// on the MediaContext, resulting in unpredictable behaviour (see bug 1630647).
//
// NOTE: We must attach to the Dispatcher before creating a TimeManager,
// otherwise we will create a circular function loop where TimeManager attempts
// to create a Clock, which attempts to locate a MediaContext, which attempts to
// create a TimeManager, resulting in a stack overflow.
dispatcher.Reserved0 = this;
// Create a notification window to listen for broadcast window messages
_notificationWindow.CreateNotificationWindow(this);
_timeManager = new TimeManager();
_timeManager.Start();
_timeManager.NeedTickSooner += new EventHandler(OnNeedTickSooner);
_promoteRenderOpToInput = new DispatcherTimer(DispatcherPriority.Render);
_promoteRenderOpToInput.Tick += new EventHandler(PromoteRenderOpToInput);
_promoteRenderOpToRender = new DispatcherTimer(DispatcherPriority.Render);
_promoteRenderOpToRender.Tick += new EventHandler(PromoteRenderOpToRender);
_estimatedNextVSyncTimer = new DispatcherTimer(DispatcherPriority.Render);
_estimatedNextVSyncTimer.Tick += new EventHandler(EstimatedNextVSyncTimeExpired);
_bitmapEffectsUsed = false;
_commitPendingAfterRender = false;
}
///
/// Called by the notification window when WM_DWMCOMPOSITIONCHANGED is broadcasted.
///
///
/// Critical -- calls into an unsafe native method.
/// TreatAsSafe -- the call to DwmIsCompositionEnabled simply returns a boolean
/// through an out parameter, it is safe to be called in this context
///
[SecurityCritical, SecurityTreatAsSafe]
internal void OnDWMCompositionChanged()
{
// Check if the desktop composition is enabled.
Int32 isDesktopCompositionEnabled = 0;
UnsafeNativeMethods.DwmIsCompositionEnabled(out isDesktopCompositionEnabled);
// Notify the mediasystem that it needs to re evaluate the transport generation id.
System.Windows.Media.MediaSystem.NotifyRedirectionEnvironmentChanged();
// Iterate through the ICompositionTargets and let them know about
// the DWM's composition changed broadcast.
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
HwndTarget hwndTarget = (ICompositionTarget)entry.Key as HwndTarget;
if (hwndTarget != null)
{
hwndTarget.OnDWMCompositionChanged(isDesktopCompositionEnabled != 0);
}
}
}
///
/// Called by a message processor to notify us that our asynchronous
/// channel has outstanding messages that need to be pumped.
///
///
/// Critical - Calls a critical method: PeekNextMessage.
/// TreatAsSafe - Retrieving and processing a message from the back
/// channel is safe -- the channel is owned by this
/// media context.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void NotifySyncChannelMessage(DUCE.Channel channel)
{
// empty the channel messages.
DUCE.MilMessage.Message message;
while (channel.PeekNextMessage(out message))
{
switch (message.Type)
{
case DUCE.MilMessage.Type.Caps:
case DUCE.MilMessage.Type.SyncModeStatus:
case DUCE.MilMessage.Type.Presented:
break;
case DUCE.MilMessage.Type.PartitionIsZombie:
// we remove the [....] channels so that if the app handles the exception
// it will get a new partition on the next [....] render request.
_channelManager.RemoveSyncChannels();
NotifyPartitionIsZombie(message.HRESULTFailure.HRESULTFailureCode);
break;
default:
HandleInvalidPacketNotification();
break;
}
}
}
///
/// Called by a message processor to notify us that our asynchronous
/// channel has outstanding messages that need to be pumped.
///
///
/// Critical - Calls a critical method: PeekNextMessage.
/// TreatAsSafe - Retrieving and processing a message from the back
/// channel is safe -- the channel is owned by this
/// media context.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void NotifyChannelMessage()
{
// Since a notification message may sit in the queue while we
// disconnect, we need to check that we actually have a channel
// when we receive this notification. If not, there's no harm;
// just skip the operation
if (Channel != null)
{
DUCE.MilMessage.Message message;
while (Channel.PeekNextMessage(out message))
{
switch (message.Type)
{
case DUCE.MilMessage.Type.Caps:
NotifySetCaps(message.Caps.Caps);
break;
case DUCE.MilMessage.Type.SyncModeStatus:
NotifySyncModeStatus(message.SyncModeStatus.Enabled);
break;
case DUCE.MilMessage.Type.Presented:
NotifyPresented(
message.Presented.PresentationResults,
message.Presented.PresentationTime,
message.Presented.RefreshRate
);
break;
case DUCE.MilMessage.Type.PartitionIsZombie:
NotifyPartitionIsZombie(message.HRESULTFailure.HRESULTFailureCode);
break;
case DUCE.MilMessage.Type.BadPixelShader:
NotifyBadPixelShader();
break;
default:
HandleInvalidPacketNotification();
break;
}
}
}
}
///
/// NotifySetCaps - this method is called to update the graphics caps
/// If the new render tier is different from the previous render tier,
/// this method will notify all TierChanged listeners.
///
/// int - the new render tier
private void NotifySetCaps(MilGraphicsAccelerationCaps caps)
{
PixelShaderVersion = caps.PixelShaderVersion;
HasSSE2Support = caps.HasSSE2Support;
int tier = caps.TierValue;
if (_tier != tier)
{
_tier = tier;
if (TierChanged != null)
{
TierChanged(null, null);
}
}
}
///
/// Internal event which is raised when a bad pixel shader is detected on this MediaContext.
///
internal event EventHandler InvalidPixelShaderEncountered;
///
/// NotifyBadPixelShader - this method is called when the render
/// thread has detected a bad pixel shader. The render thread continues
/// to raise this until the problem's been corrected. This method
/// invokes the listeners on the static event in PixelShader.
///
private void NotifyBadPixelShader()
{
if (InvalidPixelShaderEncountered != null)
{
InvalidPixelShaderEncountered(null, null);
}
else
{
// It's never correct to not have an event handler hooked up in
// the case when an invalid shader is encountered. Raise an
// exception directing the app to hook up an event handler.
throw new InvalidOperationException(SR.Get(SRID.MediaContext_NoBadShaderHandler));
}
}
///
/// The partition this media context is connected to went into
/// zombie state. This means either an unhandled batch processing,
/// rendering or presentation error and will require us to reconnect.
///
private void NotifyPartitionIsZombie(int failureCode)
{
//
// We only get back these kinds of notification:-
// For all OOM cases, we get E_OUTOFMEMORY.
// For all OOVM cases, we get D3DERR_OUTOFVIDEOMEMORY and
// for all other errors we get WGXERR_UCE_RENDERTHREADFAILURE.
//
switch (failureCode)
{
case HRESULT.E_OUTOFMEMORY:
throw new System.OutOfMemoryException();
case HRESULT.D3DERR_OUTOFVIDEOMEMORY:
throw new System.OutOfMemoryException(SR.Get(SRID.MediaContext_OutOfVideoMemory));
default:
throw new System.InvalidOperationException(SR.Get(SRID.MediaContext_RenderThreadError));
}
}
///
/// The back channel processed a malformed packet and so gives
/// the notification of invalid packet.
///
private void HandleInvalidPacketNotification()
{
//
// NTRAID#Longhorn-2006/05/02-pravirg -
// For now we ignore the packet and continue processing
// other packets. In future, we could also close this channel.
//
}
///
/// Tier Property - returns the current render tier for this MediaContext.
///
internal int Tier
{
get
{
return _tier;
}
}
///
/// PixelShaderVersion Property - returns the current PixelShader
/// (major<<16|minor) version
///
internal UInt32 PixelShaderVersion
{
get;
private set;
}
///
/// HasSSE2Support Property - returns true if the processor supports SSE2 instructions
///
internal Boolean HasSSE2Support
{
get;
private set;
}
///
/// Internal event which is raised when the Tier changes on this MediaContext.
///
internal event EventHandler TierChanged;
///
/// Asks the composition engine to retrieve the current hardware tier.
/// This tier will be sent back via NotifyChannelMessage.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
/// Sending a message to a channel is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
private void RequestTier(DUCE.Channel channel)
{
unsafe
{
DUCE.MILCMD_CHANNEL_REQUESTTIER data;
//
// Ask for the hardware tier information for the primary display
//
data.Type = MILCMD.MilCmdChannelRequestTier;
data.ReturnCommonMinimum = 0; // false
channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_CHANNEL_REQUESTTIER)
);
}
}
///
/// Schedule the next rendering operation based on our presentation
/// mode and the next time we will have active animations.
///
///
/// Specifies the minimum time before making the next rendering operation
/// active
///
private void ScheduleNextRenderOp(TimeSpan minimumDelay)
{
//
// If _needToCommitChannel is true, then we are in a waiting state and we've
// already rendered a new frame. We don't want to render again until we've
// hit the next VSync at which point we'll commit the rendered frame.
// Note that we will still render again if someone explicitely forces a
// render (PostRender). When we do hit the next VSync and commit the channel
// we will schedule the next render operation.
//
if (!_isDisconnecting && !_needToCommitChannel)
{
//
// This is the time at which the next animation will be active
// in ms. (a negative value represents no animations are active)
//
TimeSpan nextTickNeeded = TimeSpan.Zero;
//
// If we have one or more active Rendering events it's the same
// as having an active animation so we know that we'll need to
// render another frame.
//
if (Rendering == null)
{
nextTickNeeded = _timeManager.GetNextTickNeeded();
}
//Dispatcher.WriteLineWithIndent("nextTickNeeded = {0}ms", nextTickNeeded.TotalMilliseconds);
//
// If we have a tick in the future, make sure that it's not before
// the minimum delay requested.
//
if (nextTickNeeded >= TimeSpan.Zero)
{
nextTickNeeded = TimeSpan.FromTicks(Math.Max(nextTickNeeded.Ticks, minimumDelay.Ticks));
EnterInterlockedPresentation();
}
else
{
LeaveInterlockedPresentation();
}
// We need to tick in the distant future, schedule a far way message
if (nextTickNeeded > TimeSpan.FromSeconds(1))
{
if (_currentRenderOp == null)
{
//Console.WriteLine("RenderMessageHandlerCore posting a new inactive queue item {0}", queueItemID++);
//Dispatcher.WriteLineWithIndent("BeginInvoke at Inactive, set one timer");
//Dispatcher.Indent+=2;
_currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Inactive, _animRenderMessage, null);
_promoteRenderOpToRender.Interval = nextTickNeeded;
_promoteRenderOpToRender.Start();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
}
// We need to tick soon (< 1 second)
else if (nextTickNeeded >= TimeSpan.Zero)
{
//Dispatcher.WriteLineWithIndent("BeginInvoke at Inactive, set two timers");
//Dispatcher.Indent+=2;
// We need to tick in the future
if (nextTickNeeded > TimeSpan.Zero)
{
// Only create a new render op if we don't have one
// scheduled
if (_currentRenderOp == null)
{
_currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Inactive, _animRenderMessage, null);
_promoteRenderOpToInput.Interval = nextTickNeeded;
_promoteRenderOpToInput.Start();
_promoteRenderOpToRender.Interval = TimeSpan.FromSeconds(1);
_promoteRenderOpToRender.Start();
// Debug.WriteLine(string.Format("{1}ms: ScheduleNextRenderOp: Posting a new queue item active in {0}ms", nextTickNeeded.TotalMilliseconds, CurrentTimeInMs);
}
}
else
{
Debug.Assert(InterlockIsEnabled,
"If we are not in Interlocked Mode, we should always have a delay");
DispatcherPriority priority = DispatcherPriority.Render;
//
// We normally want to schedule rendering at Render priority, however if something at
// render priority takes more than a frame it will block input from ever being processed.
// To prevent this we create an operation at input priority which lets us know how long
// input has been blocked for. If it's blocked for too long we should schedule our render
// at Input priority so that input can flush before we start another render.
//
if (_inputMarkerOp == null)
{
_inputMarkerOp = Dispatcher.BeginInvoke(DispatcherPriority.Input, _inputMarkerMessage, null);
_lastInputMarkerTime = CurrentTicks;
}
else if (CurrentTicks - _lastInputMarkerTime > MaxTicksWithoutInput)
{
priority = DispatcherPriority.Input;
}
// Schedule an operation to happen immediately.
if (_currentRenderOp == null)
{
_currentRenderOp = Dispatcher.BeginInvoke(priority, _animRenderMessage, null);
}
else
{
_currentRenderOp.Priority = priority;
}
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
}
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Console.WriteLine("ScheduleNextRenderOp not posting new queue item");
//Dispatcher.WriteLineWithIndent("Nothing to do");
}
//
// Trace the scheduling of the render
//
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.SCHEDULERENDERGUID),
MS.Utility.EventType.Info,
nextTickNeeded.TotalMilliseconds
);
}
}
//else if (_needToCommitChannel)
//{
// Debug.WriteLine(string.Format("{0}: ScheduleNextRenderOp waiting to commit - didn't schedule next render", CurrentTimeInMs));
//}
}
///
/// Commits the channel, but only after the next vblank has occured.
///
private void CommitChannelAfterNextVSync()
{
if (_animationRenderRate != 0)
{
//
// estimate the next vblank interval and set our timer interval to wake up at this time.
// we add 1ms to the estimated time because are estimated time make not be perfectly accurate
// and its more important that wake up after the vblank than right on it.
//
long currentTicks = CurrentTicks;
long earliestWakeupTicks = currentTicks + TicksUntilNextVsync(currentTicks) + TimeSpan.TicksPerMillisecond;
_estimatedNextVSyncTimer.Interval = TimeSpan.FromTicks(earliestWakeupTicks - currentTicks);
_estimatedNextVSyncTimer.Tag = earliestWakeupTicks;
}
else
{
// It's possible our first notification from the UCE didn't give us the
// refresh rate. We can't estimate when vsync will be - try again in about a vblank
_estimatedNextVSyncTimer.Interval = TimeSpan.FromMilliseconds(17);
}
_estimatedNextVSyncTimer.Start();
//
// We are waiting for the next VBlank to occur
//
_interlockState = InterlockState.WaitingForNextFrame;
_lastPresentationResults = MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT;
}
///
/// Processes the Presented composition engine notification.
///
///
/// The results of the last presentation.
///
///
/// The timestamp of the last presentation.
///
///
/// The current display refresh rate.
///
private void NotifyPresented(
MIL_PRESENTATION_RESULTS presentationResults,
long presentationTime,
int displayRefreshRate
)
{
if (InterlockIsEnabled)
{
Debug.Assert(_interlockState == InterlockState.WaitingForResponse,
"We should not be getting a notification unless we asked for one");
//
// The composition engine has presented, so we are ready to start
// another frame, if necessary. Also remember the presentation
// time to use as the basis for estimating the time for the next
// frame.
//
//Console.WriteLine("Got a Presentation Notification {0}", presentationResults.ToString());
//
// presentationDelay represents the time we want to wait until
// we activate a new render operation.
//
TimeSpan presentationDelay = TimeSpan.Zero;
_lastPresentationResults = presentationResults;
//
// The UCE has processed our frame. So we are not waiting on it
// anymore. Set our state to idle.
//
_interlockState = InterlockState.Idle;
// Debug.WriteLine(String.Format("{0}: UCE Presented", CurrentTimeInMs));
switch (presentationResults)
{
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC:
{
// Adjust the refresh rate to prevent constant tearing
// on the screen. We've chosen NextPrime(RefreshRate+5)
// as a function that seems to look good at all
// popular refresh rates.
// Only update the adjusted refresh rate when the
// display refresh rate changes. If changing this
// make sure to look at the performance of the lookup
// of the adjusted refresh rate.
if (displayRefreshRate != _displayRefreshRate)
{
_displayRefreshRate = displayRefreshRate;
_adjustedRefreshRate = FindNextPrime(displayRefreshRate + 5);
}
// VSync means that the UCE has presented at the time we
// requested, but it can still tear, so if the user has
// requested a high framerate through DFR, override the
// monitor refresh rate with this DFR.
_animationRenderRate = Math.Max(_adjustedRefreshRate, _timeManager.GetMaxDesiredFrameRate());
_lastPresentationTime = presentationTime;
}
break;
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED:
{
//
// If we don't support VSync then wait a small delay so that we don't
// just overrun the UCE
//
presentationDelay = _timeDelay;
}
break;
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT:
{
//
// We didn't present because the scene didn't change.
// Since the UCE returned early, the vblank at which
// we were hoping to present the last frame has not
// occurred yet. We will set a timer to trigger at
// the time at which we think that vblank will occur
// (with a fudge factor of 1 ms to ensure we don't
// wake up before)
//
// Until the timer expires we will not send any updates
// to the UCE because we don't know if we'll be able to
// hit the vblank. We will update the Visual tree and
// queue the UCE changes, but we will not commit yet.
//
Debug.Assert(!InterlockIsWaiting,
"We should not be waiting at this point");
CommitChannelAfterNextVSync();
// Debug.WriteLine({1}: String.Format("UCE returned early; waiting {0}ms until committing the channel", TicksUntilNextVsync(CurrentTicks) / TimeSpan.TicksPerMillisecond + 1, CurrentTimeInMs));
}
break;
// This return code represents that we've presented with
// the DWM, so there is no tearing, we don't need to
// override the refresh rate in this case.
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_DWM:
{
// In the DWM case these values are actually correct, so we update them here
_animationRenderRate = displayRefreshRate;
_lastPresentationTime = presentationTime;
//if (_animationRenderRate > 0)
//{
// Debug.WriteLine(String.Format("{1}: DWM presented for us and we waited till Vsync. Last present time:, {0}ms", CountsToTicks(_lastPresentationTime) / TimeSpan.TicksPerMillisecond, CurrentTimeInMs));
//}
}
break;
}
// Cap our Animation RenderRate to 1000 fps.
_animationRenderRate = Math.Min(_animationRenderRate, 1000);
//
// Trace the notification from the UCE
//
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UCENOTIFYPRESENTGUID),
MS.Utility.EventType.Info,
_lastPresentationTime, // The last presentation time
(Int64)presentationResults // The presentation results
);
}
if (presentationResults == MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT)
{
// dont commit or schedule because we've created a timer to do this.
// the only reason we're waiting until here to do this is because we want the ETW event to fire
return;
}
else
{
// if we get any result other than a NOPRESENT then we should stop the _estimatedNextVSyncTimer timer
// so we dont end up commiting twice in 1 vblank interval
_estimatedNextVSyncTimer.Stop();
}
//
// We want to schedule the next render operation before commiting the
// channel so that we can send the command right away.
//
// If we already had a render occur then we've ticked the
// time manager and layout has been updated send this frame
// to the UCE.
//
if (!InterlockIsWaiting && _needToCommitChannel)
{
//
// if we've already commit during this vblank interval, dont do it again
// until the following vblank
//
if (HasCommittedThisVBlankInterval)
{
CommitChannelAfterNextVSync();
return;
}
CommitChannel();
Debug.Assert(InterlockIsWaiting,
"We had something to commit, we should be waiting for that"+
"notification to come back");
// Debug.WriteLine(String.Format("{0}: UCE returned from Vsync; committing now. Scheduling next render with a delay of {1}ms", CurrentTimeInMs, presentationDelay.TotalMilliseconds));
}
ScheduleNextRenderOp(presentationDelay);
}
}
///
/// Return true if the current time is within the same vblank interval as our last commit time
///
private bool HasCommittedThisVBlankInterval
{
get
{
if (_animationRenderRate == 0)
return false;
// is our last commit within 1 refresh period of our current time?
//
if (CurrentTicks - _lastCommitTime < RefreshPeriod)
{
// if the last commit is later than the last presentation, then we
// have committed a frame and haven't been notified of it yet.
// in this case, we dont want to commit the channel again
if (_lastCommitTime > CountsToTicks(_lastPresentationTime))
return true;
}
return false;
}
}
///
/// Returns the current time in Ticks (100 ns intervals).
///
private long CurrentTicks
{
get
{
long counts;
SafeNativeMethods.QueryPerformanceCounter(out counts);
return CountsToTicks(counts);
}
}
//
//private long CurrentTimeInMs
//{
// get
// {
// return CurrentTicks / TimeSpan.TicksPerMillisecond;
// }
//}
//
///
/// Returns the time in Ticks (100 ns intervals) between vsyncs.
/// It's up to the caller to ensure that _animationRenderRate is valid.
///
private long RefreshPeriod
{
get
{
return TimeSpan.TicksPerSecond / _animationRenderRate;
}
}
///
/// Computes the number of ticks since the last UCE present
///
///
///
private long TicksSinceLastPresent(long currentTime)
{
return currentTime - CountsToTicks(_lastPresentationTime);
}
///
/// Estimates the time in Ticks (100 ns intervals) since the
/// last vsync. It starts with the last presentation time
/// and extrapolates based on the display refresh rate.
///
private long TicksSinceLastVsync(long currentTime)
{
return TicksSinceLastPresent(currentTime) % RefreshPeriod;
}
///
/// Estimates the number of ticks until the next vsync
///
///
///
private long TicksUntilNextVsync(long currentTime)
{
return RefreshPeriod - TicksSinceLastVsync(currentTime);
}
///
/// Processes the SyncMode composition engine notification.
///
///
/// The HRESULT of enabling [....] mode.
///
private void NotifySyncModeStatus(int enabledResult)
{
//
// Only process the notification if we asked to start interlocked
// presentation mode
//
if (_interlockState == InterlockState.RequestedStart)
{
if (enabledResult >= 0)
{
//
// We succeded in entering the interlocked mode in the
// composition engine.
//
//Console.WriteLine("Entered interlockedMode");
_interlockState = InterlockState.Idle;
if (Channel != null)
{
Channel.SyncFlush();
}
}
else
{
_interlockState = InterlockState.Disabled;
}
}
}
///
/// Estimates the timestamp of the next frame to be presented
///
///
///
/// How the current time is computed
/// ================================
///
/// The MediaContext keeps track of when frames are presented to the
/// display. The "current time" for the MediaContext is actually the
/// time at which we estimate the next frame to be presented. This
/// allows the system to produce a frame that is correct at the time
/// it is seen by the user, leading to smooth animations. The main
/// assumption at this time is that a frame can be compiled, composed
/// and presented before the next vertical refresh, whenever that may
/// be. A future refinement of this algorithm will take historical
/// profiling data into account to compute a more accurate target
/// presentation frame.
///
///
/// The next frame time is estimated by taking the current system
/// time and rounding that up to the next refresh time, based on the
/// last refresh time and the current display refresh rate.
///
/// This is the situation when we are ready to submit a new frame:
///
/// refresh: -| F |-
/// --+----[+]----+-----+-----+--*-(+)----+-----+ t
/// L C N
/// last frame now next frame
///
///
/// In order to pipeline the UI thread and the composition thread we
/// will try to start rendering the next frame while the composition
/// is presenting the last frame that we've sent it. This allows us to
/// make use of 2 CPUs more efficiently (UI thread can renders 1 frame
/// while the composition thread is presenting another). So when
/// calculating the current time, if we are waiting on a frame already
/// then we will produce the frame for the VSync after. That means if we
/// are in the wait state then the next presentation time is not the
/// next refresh time, but at least *two* refresh times in the future,
/// because the next refresh is when the *last* frame will be presented.
///
/// This is the situation when we are in a wait state:
///
/// refresh: -| F |-
/// --+----[+]----+-----+-----+--*--+----(+)----+ t
/// L C N
/// last frame now next frame
///
///
/// The computation of the next frame time "N", then, involves four
/// variables:
///
/// Variable Units
/// -------------------------------------------------------------
/// L The last presentation time Ticks
/// C The current actual time Ticks
/// R The display refresh rate frames per second
/// W The wait state boolean -- true if waiting
///
/// The computation is fairly straightforward. We take the time since
/// we've last presented (C - L) and mod it with the refresh rate (R) to get
/// the time since the last vsync. We can then get the time until the next
/// vsync by subtracting the refresh rate (R) from that value.
/// TicksSinceLastVsync() and TicksUntilNextVsync() implement this.
///
/// If we're not waiting we can render at the next vsync. If we are waiting,
/// we'll wait until the vsync after. This computation can get slightly
/// hairy if we're exactly on a frame boundary; the comments inside
/// IClock.CurrentTime explain this a bit more.
///
/// Each time the composition engine notifies us that a frame
/// was presented it also tells us the timestamp for that frame. At
/// that time we also leave the wait state. If we successfully present
/// on every refresh then two consecutive computations should give the
/// same result. However, the values of L we get from the composition
/// engine are subject to fluctuations (due to thread scheduling issues),
/// so we may in fact compute different time values, either earlier or later.
/// To avoid thrashing the Tick and Layout processes we keep the previous
/// estimate if the new estimate is within half a frame of it. If the
/// new estimate is later than that then it means the previous estimate
/// was too inaccurate, potentially because we actually took longer
/// than a single refresh to compile, compose and present the last
/// frame. In that case we abandon the previous estimated value.
///
///
TimeSpan IClock.CurrentTime
{
get
{
Debug.Assert(IsClockSupported, "MediaContext.CurrentTime called when QueryPerformaceCounter is not supported");
long counts;
SafeNativeMethods.QueryPerformanceCounter(out counts);
long countsTicks = CountsToTicks(counts);
if (_interlockState != InterlockState.Disabled)
{
//
// On the first frame we haven't yet received information about
// the display refresh rate from the compositor. In that case
// we can't snap to frame times, so we simply use the current
// time.
//
if ( _animationRenderRate != 0
&& _lastPresentationResults != MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED)
{
//
// Figure out where we are in the vsync period. The entire computation
// is done in TimeSpan Ticks (100ns intervals)
//
long nextVsyncTicks; // Absolute time in ticks at which the next vsync will occur
long vsyncAdvance; // We expect to present this many vsyncs in the future
long nextPresentationTicks; // Absolute time in ticks at which we expect to present
// Future: eventually we should actually keep track of how long it takes
// the UCE to present. For now we assume it's one vsync
_averagePresentationInterval = RefreshPeriod;
//
// Compute how many frames in the future we expect to present to.
//
//
// We have to be very careful about the computation when we're on a frame boundary.
//
// If the time since last vsync is very small it means that we, by computation, think a vsync
// just happened. In reality, it may have just happened, is happening, or will happen very soon
// Since the UCE is on a different thread, it's possible in all three cases that the UCE is about
// to notify us of the vsync.
//
// This can get us into a situation where, though the computed time since last vsync is small,
// the timeSinceLastPresent is one frame ago. Either the UCE is currently presenting and
// timeSinceLastPresent is stale (i.e. we just haven't been notified yet), or the UCE will be
// missing this particular vsync and will return from present on a subsequent vsync.
//
// This is a problem when we're in a wait state (we've previously committed a frame and are
// waiting for the UCE to return from presenting it). If the UCE is about to return, we
// can tick the current frame to the next vsync. If the UCE is not about to return, we
// must tick the current frame in the future.
//
// The best way to disambiguate this is to look up the last time a frame was committed.
// This is how long the UCE has been working on it. By comparing that with how long the UCE
// takes on average to present a frame we'll be able to determine if it is finishing it now.
vsyncAdvance = 0;
if (InterlockIsWaiting)
{
// If we're waiting for the UCE to finish presenting a frame then
// in most cases it'll come back at the next vsync and we'll set our
// render time to the one after.
// We once (v1 shipping code) attempted to limit vsyncAdvance to 0
// based on heuristics involving where in the frame we are. This was
// removed after it was discovered that the logic was flawed and it made
// analyzing timing graphs more difficult.
vsyncAdvance = 1;
}
nextVsyncTicks = countsTicks + TicksUntilNextVsync(countsTicks);
nextPresentationTicks = (nextVsyncTicks + (vsyncAdvance * RefreshPeriod));
//
// If we had previously estimated the next presentation time
// and that estimate still seems reasonable then use the
// previous estimate rather than the newly computed value.
// This is a good performance win because it means we will
// tick animations and thus run layout to the same value as
// last time, which saves a lot of computation. For this
// purpose, we will consider the previous estimate "reasonable"
// if it falls within 1/2 frame of the new value.
//
if ((nextPresentationTicks - _estimatedNextPresentationTime.Ticks) * _animationRenderRate > TimeSpan.FromMilliseconds(500).Ticks)
{
// Establish a new estimate
_estimatedNextPresentationTime = TimeSpan.FromTicks(nextPresentationTicks);
}
else
{
// Debug.WriteLine(String.Format("{0}, CurrentTime kept the old frame estimate, {1}", CurrentTimeInMs, _estimatedNextPresentationTime));
}
}
else
{
_estimatedNextPresentationTime = TimeSpan.FromTicks(countsTicks);
}
}
else
{
_estimatedNextPresentationTime = TimeSpan.FromTicks(countsTicks);
}
return _estimatedNextPresentationTime;
}
}
///
/// Starts up the media system and creates needed channels used by the media context
///
internal void CreateChannels()
{
bool layeredWindows = false;
if (_registeredLayeredWindows != null)
{
layeredWindows = _registeredLayeredWindows.Count != 0;
}
_channelManager.CreateChannels(layeredWindows);
if (_notificationHandler != null)
{
HookNotifications();
}
// Create an ETW Event Resource for performance tracing
// GSchneid: It might be good enough to put this in the current batch without
// submitting it.
_uceEtwEvent.CreateOrAddRefOnChannel(Channel, DUCE.ResourceType.TYPE_ETWEVENTRESOURCE);
// Send a request for an updated render tier value
RequestTier(Channel);
Channel.Commit();
// We now call CompleteRender, which calls SyncFlush, to ensure that all commands have
// been processed, including the tier request.
CompleteRender();
// Since all of the commands have now been processed, we can go ahead and manually call
// NotifyChannelMessage to pick up any back channel messages which have been sent.
NotifyChannelMessage();
_primaryChannelRemote = (Channel.MarshalType == ChannelMarshalType.ChannelMarshalTypeCrossMachine);
}
///
/// Starts releases channels and shuts down the media system. When the last visual manager has
/// disconnected the transport will shut down.
///
private void RemoveChannels()
{
// This test is needed because this method is called by Dispose which is called
// on shutdown which can happen in a disconnected state. We can replace this
// test with an assert by moving
// the management of transport connectedness state to the media system.
if (Channel != null)
{
_uceEtwEvent.ReleaseOnChannel(Channel);
//
// With no channels left open, we cannot be in an interlocked
// presentation mode because we don't have a connection to the
// composition engine.
//
LeaveInterlockedPresentation();
}
_channelManager.RemoveChannels();
}
///
/// Start interlocked presentation mode and resquest
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
///
[SecurityCritical, SecurityTreatAsSafe]
private void EnterInterlockedPresentation()
{
if (!InterlockIsEnabled)
{
if (MediaSystem.AnimationSmoothing
&& Channel.MarshalType == ChannelMarshalType.ChannelMarshalTypeCrossThread
&& IsClockSupported)
{
unsafe
{
//
// Ask the UCE to get into VSync mode
//
DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE data;
data.Type = MILCMD.MilCmdPartitionSetVBlankSyncMode;
data.Enable = 1; /* true */
Channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE));
_interlockState = InterlockState.RequestedStart;
}
}
}
}
///
/// Leaves interlocked presentation mode and cleans up state so we can
/// continue to present in non-interlocked mode.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
///
[SecurityCritical, SecurityTreatAsSafe]
private void LeaveInterlockedPresentation()
{
bool interlockDisabled = (_interlockState == InterlockState.Disabled);
if (_interlockState == InterlockState.WaitingForResponse)
{
// Process messages until we get a response for the outstanding frame.
// This is necessary because we are unsure whether the UCE has already
// posted a notification in our message queue
CompleteRender();
}
// If we are waiting for the next frame stop the timer since we are
// leaving interlocked presentation mode
_estimatedNextVSyncTimer.Stop();
//
// If we are not disabled then request to stop the mode.
// If we had already asked to start but haven't gotten the response
// still request to stop
//
if (!interlockDisabled)
{
_interlockState = InterlockState.Disabled;
unsafe
{
//
// Tell the UCE that we are not in VSync mode anymore
//
DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE data;
data.Type = MILCMD.MilCmdPartitionSetVBlankSyncMode;
data.Enable = 0; /* false */
Channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE));
// We need to send this notification now otherwise, we don't
// know when we'll send the next packet. This will clear the
// channel and render the last frame (if necessary).
_needToCommitChannel = true;
CommitChannel();
}
}
Debug.Assert(_interlockState == InterlockState.Disabled,
"LeaveInterlockedPresentationMode should set the InterlockedState to Disabled");
}
///
/// Hooks the async channel so we get notifications.
///
private void HookNotifications()
{
Debug.Assert(Channel != null);
Debug.Assert(_notificationHandler != null);
//
// This associates this channel with the given notification
// window so that we can receive a window message whenever
// there is a new message posted.
//
HwndTarget.SetChannelNotificationWindow(
Channel,
_notificationHandler
);
//
// This actually populates the channel into the composition
// engine so that it can receive notifications.
//
RegisterForNotifications(Channel);
EnterInterlockedPresentation();
}
///
/// Gets the MediaContext from the context passed in as argument.
///
internal static MediaContext From(Dispatcher dispatcher)
{
Debug.Assert(dispatcher != null, "Dispatcher required");
MediaContext cm = (MediaContext)dispatcher.Reserved0;
if (cm == null)
{
cm = new MediaContext(dispatcher);
Debug.Assert(dispatcher.Reserved0 == cm);
}
return cm;
}
///
/// Gets the MediaContext from the current UI context.
///
internal static MediaContext CurrentMediaContext
{
get
{
return From(Dispatcher.CurrentDispatcher);
}
}
///
/// Called by the Dispatcher to let us know that we are going away.
///
void OnDestroyContext(object sender, EventArgs e)
{
Debug.Assert(CheckAccess());
Dispose();
}
///
/// Disposes the MediaContext.
///
///
/// Critical - Shuts down the queue item promoter, effectively disabling rendering
/// when animation smoothing is turned on.
/// TreatAsSafe - At the time the media context object gets disposed, no rendering
/// is supposed to be happening anymore, so it is safe to shut down
/// the queue item promoter.
///
[SecurityCritical, SecurityTreatAsSafe]
public virtual void Dispose()
{
Debug.Assert(CheckAccess());
if (!_isDisposed)
{
//
// Dispose all still registered ICompositionTargets ----------------
// Note that disposing the CompositionTargets should be the first thing we do here.
// First make a copy of the Hashtable contents, because ICompositionTarget.Dispose modifies this Hashtable.
ICompositionTarget[] registeredVTs = new ICompositionTarget[_registeredICompositionTargets.Count];
_registeredICompositionTargets.Keys.CopyTo(registeredVTs, 0);
// Iterate through the ICompositionTargets and dispose them. Be careful, ICompositionTarget.Dispose
// removes the ICompositionTargets from the HashTable. This is why we don't iterate the HashTable directly.
foreach (ICompositionTarget iv in registeredVTs)
{
iv.Dispose();
}
_registeredICompositionTargets = null;
// Dispose the notification window
_notificationWindow.DisposeNotificationWindow();
// Unhook the context destroy event handler -------------------
Dispatcher.ShutdownFinished -= _destroyHandler;
_destroyHandler = null;
// Dispose the time manager ----------------------------------
Debug.Assert(_timeManager != null);
_timeManager.NeedTickSooner -= new EventHandler(OnNeedTickSooner);
_timeManager.Stop();
_timeManager = null;
// From now on we are disposed -------------------------------
_isDisposed = true;
RemoveChannels();
// if we set the Dispatcher.Reserved0 field to null, we end
// creating another media context on the shutdown pass when the
// HwndSrc class sets its visual root to null. In a disconnected
// state this attempts to re open the transport.
// Disconnect from MediaSystem -------------------------------
MediaSystem.Shutdown(this);
}
}
///
/// Registers a new ICompositionTarget with the MediaSystem.
///
/// Dispatcher with which the ICompositionTarget should be registered.
/// The ICompositionTarget to register with the MediaSystem.
internal static void RegisterICompositionTarget(Dispatcher dispatcher, ICompositionTarget iv)
{
Debug.Assert(dispatcher != null);
Debug.Assert(iv != null);
MediaContext current = From(dispatcher);
current.RegisterICompositionTargetInternal(iv);
}
///
/// Registers the ICompositionTarget from this MediaContext.
///
///
private void RegisterICompositionTargetInternal(ICompositionTarget iv)
{
Debug.Assert(!_isDisposed);
Debug.Assert(iv != null);
// If channel is not available, we are in a disconnected state.
// When connect handler is invoked for this media context, all
// registered targets will be visited and AddRefChannel will be
// called for them, so here we just skip the operation.
if (Channel != null)
{
// if _currentRenderingChannel is nonempty, we're registering this ICompositionTarget
// from within a render walk and it is thus a visualbrush, we need to add it to the
// channel which we are currently rendering. If _currentRenderingChannel
// is null, we just get the target channels for this ICompositionTarget and add
// there.
DUCE.ChannelSet channelSet = (_currentRenderingChannel == null) ? GetChannels() : _currentRenderingChannel.Value;
iv.AddRefOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
}
// if we have _tokensForRegistration we are registering a visual brush from
// within a render call. We need to defer adding this ICompositionTarget.
if ( _tokensForRegistration != null)
{
int count = _tokensForUnRegistration.Count;
Debug.Assert(!_tokensForRegistration.Contains(iv));
_tokensForUnRegistration.Remove(iv);
// if we had to remove the target from the unregister array we already have
// it registered so do nothing. If the count before and after the remove are
// the same we did not first un register so we need to add it in.
if (count == _tokensForUnRegistration.Count)
{
_tokensForRegistration.Add(iv);
}
}
else
{
_registeredICompositionTargets.Add(iv, null); // We use the hashtable just as a set.
}
// When we get our first HwndTarget we may be able to start
// processing notifications
if (_notificationHandler == null)
{
HwndTarget target = iv as HwndTarget;
if (target != null)
{
_notificationHandler = target;
if (Channel != null)
{
HookNotifications();
}
}
}
}
///
/// Unregisters the ICompositionTarget from the Dispatcher.
///
///
///
internal static void UnregisterICompositionTarget(Dispatcher dispatcher, ICompositionTarget iv)
{
Debug.Assert(dispatcher != null);
Debug.Assert(iv != null);
MediaContext.From(dispatcher).UnregisterICompositionTargetInternal(iv);
}
///
/// Removes the ICompositionTarget from this MediaContext.
///
/// ICompositionTarget to unregister.
///
/// Critical - Calls a security critical method: SetNotificationWindow.
/// TreatAsSafe - It is ok for the MediaContext to call SetNotificationWindow on
/// the channel because the MediaContext owns the channel. In addition
/// no user data is passed along.
///
[SecurityCritical, SecurityTreatAsSafe]
private void UnregisterICompositionTargetInternal(ICompositionTarget iv)
{
Debug.Assert(iv != null);
// this test is needed because we always unregister the target when the ReleaseUCEResources
// is called on the target and Dispose is called from both the media context and the
// hwnd source, so when shutting down in a disconnected state we end up calling here
// after a Dispose.
if (_isDisposed)
{
return;
}
// If channel is not available, we are in a disconnected state, which means
// that all resources have been released and we can just skip the operation.
if (Channel != null)
{
// if _currentRenderingChannel is nonempty, we're unregistering this ICompositionTarget
// from within a render walk and it is thus a visualbrush, we need to remove it from the
// channel which we are currently rendering. If _currentRenderingChannel
// is null, we just get the target channels for this ICompositionTarget and release
// there.
DUCE.ChannelSet channelSet = (_currentRenderingChannel == null) ? GetChannels() : _currentRenderingChannel.Value;
iv.ReleaseOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
}
// if we have _tokensForUnRegistration we are unregistering a visual brush from
// within a render call. We need to defer removing this ICompositionTarget.
if ( _tokensForUnRegistration != null)
{
Debug.Assert(!_tokensForUnRegistration.Contains(iv));
_tokensForUnRegistration.Add(iv);
_tokensForRegistration.Remove(iv);
}
else
{
_registeredICompositionTargets.Remove(iv);
}
//
// If we remove the HwndTarget that we were using to handle
// notifications then we may not be able to process them anymore.
// We need to release this one and look for a new one.
//
if (_notificationHandler != null)
{
if (iv == _notificationHandler)
{
_notificationHandler = null;
FindNotificationHandler();
if (Channel != null)
{
if (_notificationHandler != null)
{
HookNotifications();
}
else
{
Channel.SetNotificationWindow(IntPtr.Zero, 0);
//
// If we can't get notifications then we can't be in
// interlocked presentation mode.
//
LeaveInterlockedPresentation();
}
}
}
}
}
///
/// Ensures that we have a handler for back-channel message notifications
/// if possible.
///
private void FindNotificationHandler()
{
Debug.Assert(_notificationHandler == null);
if (_registeredICompositionTargets != null)
{
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
_notificationHandler = (ICompositionTarget)entry.Key as HwndTarget;
if (_notificationHandler != null)
{
// Found it.
break;
}
}
}
}
private class InvokeOnRenderCallback
{
private DispatcherOperationCallback _callback;
private object _arg;
public InvokeOnRenderCallback(
DispatcherOperationCallback callback,
object arg)
{
_callback = callback;
_arg = arg;
}
public void DoWork()
{
_callback(_arg);
}
}
internal void BeginInvokeOnRender(
DispatcherOperationCallback callback,
object arg)
{
Debug.Assert(callback != null);
// While technically it could be OK for the arg to be null, for now
// I know that arg represents the this reference for the layout
// process and should never be null.
Debug.Assert(arg != null);
if (_invokeOnRenderCallbacks == null)
{
_invokeOnRenderCallbacks = new ArrayList();
}
_invokeOnRenderCallbacks.Add(new InvokeOnRenderCallback(callback, arg));
if (!_isRendering)
{
PostRender();
}
}
///
/// Add a pending loaded or unloaded callback
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal LoadedOrUnloadedOperation AddLoadedOrUnloadedCallback(
DispatcherOperationCallback callback,
DependencyObject target)
{
LoadedOrUnloadedOperation op = new LoadedOrUnloadedOperation(callback, target);
if (_loadedOrUnloadedPendingOperations == null)
{
_loadedOrUnloadedPendingOperations = new FrugalObjectList(1);
}
_loadedOrUnloadedPendingOperations.Add(op);
return op;
}
///
/// Remove a pending loaded or unloaded callback
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal void RemoveLoadedOrUnloadedCallback(LoadedOrUnloadedOperation op)
{
Debug.Assert(op != null);
// cancel the operation - this prevents it from running even if it has
// already been copied into the local array in FireLoadedPendingCallbacks
op.Cancel();
if (_loadedOrUnloadedPendingOperations != null)
{
for (int i=0; i<_loadedOrUnloadedPendingOperations.Count; i++)
{
LoadedOrUnloadedOperation operation = _loadedOrUnloadedPendingOperations[i];
if (operation == op)
{
_loadedOrUnloadedPendingOperations.RemoveAt(i);
break;
}
}
}
}
///
/// If there is already a render operation in the Dispatcher queue, this
/// method will bump it up to render priority. If not, it will add a
/// render operation at render priority.
///
///
/// This method should only be called when a render is necessary "right
/// now." Events such as a change to the visual tree would result in
/// this method being called.
///
internal void PostRender()
{
// this is now needed because we no longer set Dispatcher.Reserved0 to null
// in the Dispose method. See comment in the Dispose method.
if (_isDisposed)
{
return;
}
Debug.Assert(CheckAccess());
//if (_waitingForPresent)
//{
// Console.WriteLine("PostRender called while waiting for a previous present");
//}
//Dispatcher.WriteLineWithIndent("PostRender...");
//Dispatcher.Indent+=2;
if (!_isRendering)
{
EventTrace.NormalTraceEvent(EventTraceGuidId.POSTRENDERGUID, MS.Utility.EventType.Info);
if (_currentRenderOp != null)
{
//Console.WriteLine("PostRender promoting existing queue item");
//Dispatcher.WriteLineWithIndent("Promoting to Render...");
//Dispatcher.Indent+=2;
// If we already have a render operation in the queue, we should
// change its priority to render priority so it happens sooner.
_currentRenderOp.Priority = DispatcherPriority.Render;
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Console.WriteLine("PostRender issuing new queue item");
//Dispatcher.WriteLineWithIndent("BeginInvoke at Render...");
//Dispatcher.Indent+=2;
// If we don't have a render operation in the queue, add one at
// render priority.
_currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Render, _renderMessage, null);
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
//Dispatcher.WriteLineWithIndent("Killing the timers...");
//Dispatcher.Indent+=2;
// We don't need to keep our promotion timers around.
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Console.WriteLine("PostRender called while rendering");
//Dispatcher.WriteLineWithIndent("Already rendering!");
}
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
///
/// This method is invoked from the HwndTarget when the window is resize.
/// It will cancel pending render queue items and then run the dispatch for the
/// render queue item by hand.
///
internal void Resize(ICompositionTarget resizedCompositionTarget)
{
// Cancel pending render queue items so that we don't dispatch them later
// causing a double render during Resize. (Note that RenderMessage will schedule a
// new RenderQueueItem).
if (_currentRenderOp != null)
{
_currentRenderOp.Abort();
_currentRenderOp = null;
}
// We don't need to keep our promotion timers around.
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
// Now render manually directly from the resize handler.
// Alternatively we could pump the message queue here with a filter that only allows
// RenderQueueItems to get dispatched.
RenderMessageHandler(resizedCompositionTarget);
}
internal void PostConnect()
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, _connectMessage, null);
}
internal void PostDisconnect()
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, _disconnectMessage, null);
}
internal object ConnectHandler(
object obj
)
{
// create all media context channels
if (MediaSystem.ConnectChannels(this))
{
_isConnected = true;
ArrayList targets = new ArrayList();
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
ICompositionTarget iv = ((ICompositionTarget)(entry.Key));
targets.Add(iv);
}
foreach (ICompositionTarget iv in targets)
{
DUCE.ChannelSet channelSet = GetChannels();
iv.AddRefOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
Resize(iv);
}
}
return null;
}
internal object DisconnectHandler(
object obj
)
{
//
// When we disconnect we need to exit interlocked mode
//
LeaveInterlockedPresentation();
ArrayList targets = new ArrayList();
_isDisconnecting = true;
_isConnected = false;
//
// In order to ensure correctly ordered processing of all queued
// RemoveChild and Release commands, we need to flush these queues
// for all channels here. If we don't, we can arrive at a situation
// in which there are RemoveAndReleaseCommands queued for the root visual
// of a CompositionTarget which has already been released from the channel
// the below calls to ICompositionTarget.ReleaseOnChannel(). This can
// occur when we get a style changing message followed by a disconnect
// message, but could potentially occur in other situations where
// we modify the tree and then don't render before processing a
// disconnect message.
//
_channelManager.FlushAllRemoveReleaseQueues();
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
ICompositionTarget iv = ((ICompositionTarget)(entry.Key));
targets.Add(iv);
}
DUCE.ChannelSet channelSet = GetChannels();
foreach (ICompositionTarget iv in targets)
{
iv.ReleaseOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
}
_isDisconnecting = false;
// shutdown all media context channels.
RemoveChannels();
return null;
}
///
/// This is the standard RenderMessageHandler callback, posted via PostRender()
/// and Resize(). This wraps RenderMessageHandlerCore and emits an ETW events
/// to trace its execution.
///
internal object RenderMessageHandler(
object resizedCompositionTarget /* can be null if we are not resizing*/
)
{
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.RENDERHANDLERGUID),
MS.Utility.EventType.StartEvent,
Dispatcher.GetHashCode());
}
//Console.WriteLine(string.Format("RenderMessageHandler {0}", queueItemID++));
RenderMessageHandlerCore(resizedCompositionTarget);
EventTrace.NormalTraceEvent(EventTraceGuidId.RENDERHANDLERGUID, MS.Utility.EventType.EndEvent);
return null;
}
///
/// This is the RenderMessageHandler callback posted by RenderMessageHandlerCore
/// when animations are active
/// This wraps RenderMessageHandlerCore and emits an ETW event to signify that
/// the Render Message being handled is processing an animation.
///
internal object AnimatedRenderMessageHandler(
object resizedCompositionTarget /* can be null if we are not resizing*/
)
{
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.ANIMRENDERHANDLERGUID),
MS.Utility.EventType.StartEvent,
Dispatcher.GetHashCode());
}
RenderMessageHandlerCore(resizedCompositionTarget);
EventTrace.NormalTraceEvent(EventTraceGuidId.ANIMRENDERHANDLERGUID, MS.Utility.EventType.EndEvent);
return null;
}
///
/// This handles the _inputMarkerOp message. We're using
/// _inputMarkerOp to determine if input priority dispatcher ops
/// have been processes.
///
internal object InputMarkerMessageHandler(object arg)
{
//set the marker to null so we know that input priority has been processed
_inputMarkerOp = null;
return null;
}
//static int queueItemID;
///
/// The ol' RenderQueueItem.
///
///
/// Critical: Since it calls to InputManager.UnsecureCurrent
/// TreatAsSafe: Since it does not expose the InputManager
///
[SecurityCritical,SecurityTreatAsSafe]
private void RenderMessageHandlerCore(
object resizedCompositionTarget /* can be null if we are not resizing*/
)
{
// if the media system is disconnected bail.
if (Channel == null)
{
return;
}
Debug.Assert(CheckAccess());
Debug.Assert(
(resizedCompositionTarget == null) ||
(resizedCompositionTarget is ICompositionTarget));
_isRendering = true;
// We don't need our promotion timers anymore.
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
bool gotException = true;
try
{
int tickLoopCount = 0;
do
{
tickLoopCount++;
if (tickLoopCount > 153)
{
throw new InvalidOperationException(SR.Get(SRID.MediaContext_InfiniteTickLoop));
}
_timeManager.Tick();
// Although the timing tree is now clean, during layout
// more animations may be added, in which case we must tick
// again to prevent "first frame" problems. If we do tick
// again we want to do so at the same time as before, until
// we are done. To that end, lock the tick time to the
// first tick. Note that Lock/Unlock aren't counted, so we
// can call Lock inside the loop and still safely call
// Unlock just once after we are done.
_timeManager.LockTickTime();
// call all render callbacks
FireInvokeOnRenderCallbacks();
// signal that the frame has been updated and we are ready to render.
// only fire on the first iteration
if (Rendering != null && tickLoopCount==1)
{
// The RenderingEventArgs class stores the next estimated presentation time.
// Since the TimeManager has just ticked, LastTickTime is exactly this time.
// (TimeManager gets its tick time from MediaContext's IClock implementation).
// In the case where we can't query QPC or aren't doing interlocked presents,
// this will be equal to the current time, which is a good enough approximation.
Rendering(this.Dispatcher, new RenderingEventArgs(_timeManager.LastTickTime));
// call all render callbacks again in case the Rendering event affects layout
// this will enable layout effecting changes to get triggered this frame
FireInvokeOnRenderCallbacks();
}
}
while (_timeManager.IsDirty);
_timeManager.UnlockTickTime();
// Invalidate the input devices on the InputManager
InputManager.UnsecureCurrent.InvalidateInputDevices();
//
// before we call Render we want to save the in Interlock state so we know
// if we need to schedule another render or not. This is because we want to render
// while the UCE is working on rendering the previous frame. To do this we would like to get into
// a NotifyPresent, CommitChannel, Render pattern. If the interlock state is not
// "Waiting" at this point, then we must be in a Render, CommitChannel pattern
// and we will need to do something in order to get us back in parallel operation
//
bool interlockWasNotWaiting = !InterlockIsWaiting;
//
// This is the big Render!
//
// We've now updated timing and layout and the updated scene will be sent
// to the UCE.
//
Render((ICompositionTarget)resizedCompositionTarget);
//
// We've processed the currentRenderOp so clear it
//
if (_currentRenderOp != null)
{
_currentRenderOp.Abort();
_currentRenderOp = null;
}
if (!InterlockIsEnabled)
{
//
// Schedule our next rendering operation. We want to introduce
// a minimum delay, so that we don't overrun the composition
// thread
//
ScheduleNextRenderOp(_timeDelay);
}
else if (interlockWasNotWaiting)
{
//
// schedule another render because we were in the Render, CommitChannel
// pattern, and this will get us back in the CommitChannel, Render pattern so
// we will have the channel full when notification comes back from the UCE.
//
ScheduleNextRenderOp(TimeSpan.Zero);
}
gotException = false;
}
finally
{
// Reset current operation so it can be re-queued by layout
// This is needed when exception happens in the midst of layout/TemplateExpansion
// and it unwinds from the stack. If we don't clean this field here, the subsequent
// PostRender won't queue new render operation and the window gets stuck. Bug 1355561.
if (gotException
&& _currentRenderOp != null)
{
_currentRenderOp.Abort();
_currentRenderOp = null;
}
_isRendering = false;
}
}
///
/// Calls all _invokeOnRenderCallbacks until no more are added
///
private void FireInvokeOnRenderCallbacks()
{
int callbackLoopCount = 0;
int count = _invokeOnRenderCallbacks != null ? _invokeOnRenderCallbacks.Count : 0;
// This outer loop is to re-run layout in case the app causes a layout to get enqueued in response
// to a Loaded event. In this case we would like to re-run layout before we allow render.
do
{
while (count > 0)
{
callbackLoopCount++;
if (callbackLoopCount > 153)
{
throw new InvalidOperationException(SR.Get(SRID.MediaContext_InfiniteLayoutLoop));
}
InvokeOnRenderCallback[] callbacks = new InvokeOnRenderCallback[count];
_invokeOnRenderCallbacks.CopyTo(callbacks);
_invokeOnRenderCallbacks.Clear();
for (int i = 0; i < count; i++)
{
callbacks[i].DoWork();
}
count = _invokeOnRenderCallbacks.Count;
}
// Fire all the pending Loaded events before Render happens
// but after the layout storm has subsided
FireLoadedPendingCallbacks();
count = _invokeOnRenderCallbacks != null ? _invokeOnRenderCallbacks.Count : 0;
}
while (count > 0);
}
///
/// Fire all the pending Loaded callbacks before Render happens
///
private void FireLoadedPendingCallbacks()
{
// Fire all the pending Loaded events before Render happens but after layout
if (_loadedOrUnloadedPendingOperations != null && _loadedOrUnloadedPendingOperations.Count > 0)
{
// Create a copy of the _loadedOrUnloadedPendingOperations
LoadedOrUnloadedOperation[] copyOfPendingCallbacks = new LoadedOrUnloadedOperation[_loadedOrUnloadedPendingOperations.Count];
_loadedOrUnloadedPendingOperations.CopyTo(copyOfPendingCallbacks, 0);
// Clear up the _loadedOrUnloadedPendingOperations in case the broadcast of Loaded causes
// more of the pending operations to get posted.
_loadedOrUnloadedPendingOperations.Clear();
// Iterate and fire all the pending loaded operations
for (int i=0; i
/// Render all registered ICompositionTargets.
///
///
/// * We have to render all visual targets on the same context at once. The reason for this is that
/// we batch per Dispatcher (we use the context to get to the batch all over the place).
/// * On a WM_SIZE we also need to render because USER32 is sitting in a tight loop sending us messages
/// continously. Hence we need to render all visual trees attached to visual targets otherwise we
/// would submit a batch that has only part of the changes for some visual trees. This would cause
/// structural tearing.
///
internal void Render(ICompositionTarget resizedCompositionTarget)
{
// resizedCompositionTarget is the HwndTarget that is currently being resized.
//
// Disable reentrancy during the Render pass. This is because much work is done
// during Render and we cannot survive reentrancy in these code paths.
// Disabling processing will prevent the lock() statement from pumping messages,
// so we don’t run the risk of having to process an unrelated message in the middle
// of this code. Message pumping will resume sometime after we return.
//
// Note: The possible downside of DisableProcessing is
// 1) Cross-Apartment COM calls may deadlock.
// 2) We restrict what people can do in callbacks ie, they can’t display a message box.
//
using (Dispatcher.DisableProcessing())
{
Debug.Assert(CheckAccess());
Debug.Assert(!_isDisposed);
Debug.Assert(_registeredICompositionTargets != null);
Debug.Assert(_tokensForRegistration == null);
bool anyTreeContainsGraphness = false;
RemoveChannel(_channelManager.Channel);
_tokensForRegistration = new ArrayList();
_tokensForUnRegistration = new ArrayList();
// ETW event tracing
bool etwTracingEnabled = false;
uint renderID = (uint)Interlocked.Increment(ref _contextRenderID);
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
etwTracingEnabled = true;
DUCE.ETWEvent.RaiseEvent(
_uceEtwEvent.Handle,
renderID,
Channel);
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.MEDIARENDERGUID),
MS.Utility.EventType.StartEvent,
renderID,
TicksToCounts(_estimatedNextPresentationTime.Ticks)
);
}
// ---------------------------------------------------------------
// 1) Render each registered ICompositionTarget to finish up the batch.
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
ICompositionTarget registeredTarget = ((ICompositionTarget)(entry.Key));
DUCE.ChannelSet channelSet;
channelSet.Channel = _channelManager.Channel;
channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel;
_currentRenderingChannel = channelSet;
registeredTarget.Render((registeredTarget == resizedCompositionTarget), channelSet.Channel);
_currentRenderingChannel = null;
anyTreeContainsGraphness |= registeredTarget.VisualTreeContainsGraphness();
}
// process all the visual brush registration requests we picked up during the render walk.
foreach (ICompositionTarget iv in _tokensForRegistration)
{
_registeredICompositionTargets.Add(iv, null); // We use the hashtable just as a set.
}
// process all the visual brush unregistration requests we picked up during the render walk.
foreach (ICompositionTarget iv in _tokensForUnRegistration)
{
_registeredICompositionTargets.Remove(iv);
}
_tokensForRegistration = null;
_tokensForUnRegistration = null;
if (IsConnected)
{
// ----------------------------------------------------------------
// 2) Get the cached realization context if possible, otherwise
// create a new one. Note that we null out the cached context
// field. This means that in failure cases we will always
// recreate the realization context.
//
//
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UPDATEREALIZATIONSGUID),
MS.Utility.EventType.StartEvent);
}
RealizationContext realizationContext = null;
if (_cachedRealizationContext != null)
{
realizationContext = _cachedRealizationContext;
_cachedRealizationContext = null;
}
else
{
realizationContext = new RealizationContext();
}
// ---------------------------------------------------------------
// 3) Prepare the realization context.
//
// Can only walk incrementally if we haven't encountered graphness in the tree
// and don't have any transform hints. Transform hints can be considered another
// form of graphness for text because they produce multiple realizations for
// each glyphrun
//
anyTreeContainsGraphness |= (_transformHints.Count > 0);
realizationContext.BeginFrame(!anyTreeContainsGraphness /* full walk */,
false /* walk for BitmapRenderTarget */);
// ----------------------------------------------------------------
// 4) Mark visible realizations for top-level visual targets.
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
CompositionTarget vt = ((ICompositionTarget)(entry.Key)) as CompositionTarget;
if (vt != null)
{
DUCE.ChannelSet targetChannels = GetChannels();
realizationContext.ChannelSet = targetChannels;
realizationContext.WindowClip = vt.WorldClipBounds;
vt.MarkVisibleRealizations(realizationContext);
}
}
// ----------------------------------------------------------------
// 5) Execute the scheduled realization updates.
realizationContext.ExecuteRealizationsUpdateSchedule();
// -----------------------------------------------------------
// 6) Now cache the realization context.
// Note that EndFrame call is required for caching the
// realization context. However, before calling endframe all
// realization context stacks must be empty. If they are not
// empty at this point our code has a serious bug.
realizationContext.EndFrame();
Debug.Assert(_cachedRealizationContext == null);
_cachedRealizationContext = realizationContext;
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UPDATEREALIZATIONSGUID),
MS.Utility.EventType.EndEvent);
}
}
// ----------------------------------------------------------------
// 7) Update any resources that need to be updated for this render.
RaiseResourcesUpdated();
//
// 8) Commit the channel.
//
// if we are not already waiting for a present then commit the
// channel at this time. If we are waiting for a present then we
// will wait until we have presented before committing this channel
//
_needToCommitChannel = true;
_commitPendingAfterRender = true;
if (!InterlockIsWaiting)
{
// Debug.WriteLine(string.Format("{0}, Committing the channel after render", CurrentTimeInMs));
//if we've already commit during this vblank interval, dont do it again
// because it will cause the DWM to stall
if (HasCommittedThisVBlankInterval)
{
CommitChannelAfterNextVSync();
}
else
{
CommitChannel();
}
}
// ---------------------------------------------------------------
// 9) Raise RenderComplete event.
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.MEDIARENDERGUID),
MS.Utility.EventType.EndEvent
);
// trace the UI Response event
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UIRESPONSEGUID),
MS.Utility.EventType.Info,
GetHashCode(),
renderID);
}
}
}
///
/// Commit the current channel to the composition thread.
///
///
/// This allows us to separate updating the visual tree from sending
/// data to the UCE. When in InterlockedPresentation mode, we'll always
/// have layout properly updated but we'll only commit 1 render per
/// frame to the composition.
///
private void CommitChannel()
{
// if we get render messages posted while we are disconnected we don't have a channel.
if (Channel != null)
{
Debug.Assert(_needToCommitChannel, "CommitChannel called with nothing on the channel");
if (InterlockIsEnabled)
{
//Console.WriteLine("Requesting Presentation Notification");
Debug.Assert(!InterlockIsWaiting,
"We can't be committing the channel while waiting for a notification");
long currentTicks = CurrentTicks;
long presentationTime = _estimatedNextPresentationTime.Ticks;
//
// it is possible that presentationTime is in the past, if we request this time
// we will get an immediate NotifyPresent instead of getting one after the next
// vblank. To prevent this we ensure the presentaitonTime is no earlier than the
// next VBlank time.
//
if (_animationRenderRate > 0)
{
long nextVBlank = currentTicks + TicksUntilNextVsync(currentTicks);
if (nextVBlank > presentationTime)
{
presentationTime = nextVBlank;
}
}
RequestPresentedNotification(Channel, TicksToCounts(presentationTime));
//
// If we are in interlocked presentation mode then we enter
// a wait state once we commit the channel.
//
_interlockState = InterlockState.WaitingForResponse;
_lastCommitTime = currentTicks;
}
//Console.WriteLine("Committing Channel");
if (CommittingBatch != null)
{
CommittingBatch(Channel, new EventArgs());
}
Channel.Commit();
if (_commitPendingAfterRender)
{
//
// Raise Render Complete event since a Render happened and
// the commit for that render happened.
//
if (_renderCompleteHandlers != null)
{
_renderCompleteHandlers(this, null);
}
_commitPendingAfterRender = false;
}
//
// The channel has just been commited. There's nothing more in it.
//
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
// The payload data for this event is the render ID of the frame we're committing.
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.COMMITCHANNELGUID),
MS.Utility.EventType.Info,
_contextRenderID
);
}
}
_needToCommitChannel = false;
}
///
/// Asks the composition engine to notify us once the frame we are
/// submitted has been presented to the screen.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
/// Sending a message to a channel is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
private void RequestPresentedNotification(DUCE.Channel channel, long estimatedFrameTime)
{
Debug.Assert(InterlockIsEnabled,
"Cannot request presentation notification unless interlock mode is enabled");
unsafe
{
DUCE.MILCMD_PARTITION_NOTIFYPRESENT data;
data.Type = MILCMD.MilCmdPartitionNotifyPresent;
data.FrameTime = (ulong) estimatedFrameTime;
channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_PARTITION_NOTIFYPRESENT)
);
}
}
///
/// CompleteRender
///
///
/// Wait for the rendering loop to finish any pending instructions.
///
///
/// Critical - Calls one of two critical methods: WaitForNextMessage
/// or MilComposition_SyncFlush.
/// TreatAsSafe - Net effect is to wait until render completes. Waiting
/// is safe because we only block the thread if we know
/// we are waiting for a message. Flushing the channel is
/// safe because we own the channel and we know there
/// aren't unfinished batches at this point.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void CompleteRender()
{
// for now just bail if we are not connected.
if (Channel != null)
{
//
// In intelocked mode in order to make sure that frames are
// fully updated to the screen we need to do the folloing:
// 1. If we are waiting for a response from the UCE, then wait
// until we get that response.
// 2. If we had pending operations then flush the channel and
// wait for the notification that this new frame has been
// processed by the composition.
//
if (InterlockIsEnabled)
{
//Console.WriteLine("CompleteRender: We are in InterlockedMode: {0}", _interlockState.ToString());
if (_interlockState == InterlockState.WaitingForResponse)
{
//Console.WriteLine("CompleteRender waiting for previous present");
do
{
Channel.WaitForNextMessage();
NotifyChannelMessage();
} while (_interlockState == InterlockState.WaitingForResponse);
}
//
// We might have started a timer to wait for the next frame.
// stop it now and go back to idle state
//
_estimatedNextVSyncTimer.Stop();
_interlockState = InterlockState.Idle;
if (_needToCommitChannel)
{
//Console.WriteLine("CompleteRender: committing channel");
CommitChannel();
if (_interlockState == InterlockState.WaitingForResponse)
{
//Console.WriteLine("CompleteRender waiting for present");
do
{
Channel.WaitForNextMessage();
NotifyChannelMessage();
} while (_interlockState == InterlockState.WaitingForResponse);
//
// We might have started a timer to wait for the next frame.
// stop it now and go back to idle state
//
_estimatedNextVSyncTimer.Stop();
_interlockState = InterlockState.Idle;
}
}
}
else
{
//Console.WriteLine("CompleteRender waiting for [....] flush");
//
// Issue a [....] flush, which will only return after
// the last frame is presented
//
Channel.SyncFlush();
}
}
}
///
/// This function is registered with the MediaContext's TimeManager. It is called whenever
/// a clock managed by the TimeManager goes active, but only if there hasn't been already an
/// active clock. For now we start the animation thread in there.
///
private void OnNeedTickSooner(object sender, EventArgs e)
{
PostRender();
}
///
/// Checks if the current context can request the specified permissions.
///
internal void VerifyWriteAccess()
{
if (!WriteAccessEnabled)
{
throw new InvalidOperationException(SR.Get(SRID.MediaContext_APINotAllowed));
}
}
///
/// Returns false if the MediaContext is currently read-only
///
internal bool WriteAccessEnabled
{
get { return _readOnlyAccessCounter <= 0; }
}
///
/// Methods to lock down the Visual tree for write access.
///
internal void PushReadOnlyAccess()
{
_readOnlyAccessCounter++;
}
internal void PopReadOnlyAccess()
{
_readOnlyAccessCounter--;
}
///
/// Each MediaContext is associated with a TimeManager. The TimeManager is shared by all ICompositionTargets.
///
private TimeManager _timeManager;
internal TimeManager TimeManager
{
get
{
return _timeManager;
}
}
///
/// RenderComplete event is fired when Render method commits the channel.
/// This is used for ink transition. Currently this event is internal and will
/// be accessed using reflection until proper object model is defined.
///
internal event EventHandler RenderComplete
{
add
{
//
// If the Render happened (i.e. flag is true) and corresponding Commit
// has not happened, then set the flag to false, since the next
// Commit will not have the changes after Render and before +RenderComplete.
// In other words, consider this event sequence:-
// 1. Render
// 2. Some resource property changes (eg. visual.Clip = null)
// 3. +RenderComplete
// 4. Commit
// Then the 4th Commit will not have 2's changes(as it requires a
// new Render pass) and so raising the event is wrong.
//
// Note: If the event is added every time in the middle of Render
// and Commit, then RenderComplete will starve.
//
if (_commitPendingAfterRender)
{
_commitPendingAfterRender = false;
}
_renderCompleteHandlers += value;
}
remove
{
_renderCompleteHandlers -= value;
}
}
///
/// ResourcesUpdatedHandler - This event handler prototype defines the callback
/// for our async update callback in the ResourcesUpdated Event.
/// The method which implements this prototype is also often called in situations where
/// the resource is known to be "on channel" - in those cases, "true" is passed for the second
/// parameter (allowing the implementation to skip the check).
///
internal delegate void ResourcesUpdatedHandler(DUCE.Channel channel, bool skipOnChannelCheck);
internal event ResourcesUpdatedHandler ResourcesUpdated
{
add
{
_resourcesUpdatedHandlers += value;
}
remove
{
_resourcesUpdatedHandlers -= value;
}
}
private void RaiseResourcesUpdated()
{
if (_resourcesUpdatedHandlers != null)
{
DUCE.ChannelSet channelSet = GetChannels();
_resourcesUpdatedHandlers(channelSet.Channel, false /* do not skip the "on channel" check */);
_resourcesUpdatedHandlers = null;
}
}
///
/// Create a fresh or fetch one from the pool synchronous channel.
///
internal DUCE.Channel AllocateSyncChannel()
{
return _channelManager.AllocateSyncChannel();
}
///
/// Returns a [....] channel back to the pool.
///
internal void ReleaseSyncChannel(DUCE.Channel channel)
{
_channelManager.ReleaseSyncChannel(channel);
}
///
/// Returns the asynchronous channel for this media context.
///
///
/// This property is deprecated and scheduled to be removed as per task #26681.
/// Please do not create additional dependencies on it.
///
internal DUCE.Channel Channel
{
get
{
return _channelManager.Channel;
}
}
///
/// Returns the asynchronous out-of-band channel for this media context.
///
internal DUCE.Channel OutOfBandChannel
{
get
{
return _channelManager.OutOfBandChannel;
}
}
internal bool IsConnected
{
get
{
return _isConnected;
}
}
///
/// Returns the BoundsDrawingContextWalker for this media context.
/// To handle reentrance we want to make sure that
/// no one else on the same thread gets the same context.
///
internal BoundsDrawingContextWalker AcquireBoundsDrawingContextWalker()
{
if (_cachedBoundsDrawingContextWalker == null)
{
return new BoundsDrawingContextWalker();
}
BoundsDrawingContextWalker ctx = _cachedBoundsDrawingContextWalker;
_cachedBoundsDrawingContextWalker = null;
ctx.ClearState();
return ctx;
}
///
/// Set the BoundsDrawingContextWalker for next use
/// To handle reentrance we want to make sure that
/// no one else on the same thread gets the same context.
///
internal void ReleaseBoundsDrawingContextWalker(BoundsDrawingContextWalker ctx)
{
_cachedBoundsDrawingContextWalker = ctx;
}
///
/// Describes whether any bitmap effects have been
/// used in the scene. This value can not be unset,
/// and is used to propagate realization updates when
/// offset changes occur.
///
internal bool BitmapEffectsUsed
{
get
{
return _bitmapEffectsUsed;
}
set
{
_bitmapEffectsUsed |= value;
}
}
private void PromoteRenderOpToInput(object sender, EventArgs e)
{
//Dispatcher.WriteLineWithIndent("PromoteRenderOpToInput...");
//Dispatcher.Indent+=2;
// Debug.WriteLine(String.Format("{0}, Promoting render operation to input", CurrentTimeInMs));
if(_currentRenderOp != null)
{
//Dispatcher.WriteLineWithIndent("Promoting to Input...");
//Dispatcher.Indent+=2;
_currentRenderOp.Priority = DispatcherPriority.Input;
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Dispatcher.WriteLineWithIndent("Nothing to promote");
}
((DispatcherTimer)sender).Stop();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
private void PromoteRenderOpToRender(object sender, EventArgs e)
{
//Dispatcher.WriteLineWithIndent("PromoteRenderOpToRender...");
//Dispatcher.Indent+=2;
if(_currentRenderOp != null)
{
//Dispatcher.WriteLineWithIndent("Promoting to Render...");
//Dispatcher.Indent+=2;
_currentRenderOp.Priority = DispatcherPriority.Render;
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Dispatcher.WriteLineWithIndent("Nothing to promote");
}
((DispatcherTimer)sender).Stop();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
///
/// We setup a timer when the UCE doesn't present a frame because the
/// scene hasn't changed. When this timer triggers, we have passed the
/// estimated time at which the predicted VSync should have occured.
/// We can now commit our accumulated changes to the channel. This
/// timer should only be active if we got a NoPresent notification from
/// the composition thread.
///
private void EstimatedNextVSyncTimeExpired(object sender, EventArgs e)
{
//Dispatcher.WriteLineWithIndent("CommitRenderChannel...");
//Dispatcher.Indent+=2;
Debug.Assert(_interlockState == InterlockState.WaitingForNextFrame
&& _lastPresentationResults == MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT,
"CommitRenderChannel timer should only be trigger while waiting for the frame to expire");
//
// if we wake up before our earliest wakup time, we run the risk of commiting twice in one
// vblank interval. This is mitigated by detecting the early wakup and creating a new timer
// for the remaining time. This could cause us to skip a vblank if our new timer wakes up too
// late, but this is seens as a better alternative to commiting twice in one vblank interval.
// A better solution would be to have some form of high resolution timer.
//
long currentTicks = CurrentTicks;
DispatcherTimer timer = ((DispatcherTimer)sender);
long earliestWakeupTicks = 0;
if(timer.Tag != null)
earliestWakeupTicks = (long)timer.Tag;
if (earliestWakeupTicks > currentTicks)
{
timer.Stop();
timer.Interval = TimeSpan.FromTicks(earliestWakeupTicks - currentTicks);
timer.Start();
return;
}
_interlockState = InterlockState.Idle;
if (_needToCommitChannel)
{
//Dispatcher.WriteLineWithIndent("Committing the current channel...");
//Dispatcher.Indent+=2;
CommitChannel();
//schedule the next render so we're back on the np-commit-render pattern
ScheduleNextRenderOp(TimeSpan.Zero);
// Debug.WriteLine(String.Format("{0}, Next Vsync timer expired; commit the channel", CurrentTimeInMs));
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
timer.Stop();
// Console.WriteLine("Timer expired. Commited Render");
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
internal ArrayList TransformHints
{
get { return _transformHints; }
}
//+---------------------------------------------------------------------
//
// Private Methods
//
//---------------------------------------------------------------------
#region Private Methods
///
/// Tells the composition engine that we want to receive asynchronous
/// notifications on this channel.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
/// Sending a message to a channel is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
private void RegisterForNotifications(DUCE.Channel channel)
{
DUCE.MILCMD_PARTITION_REGISTERFORNOTIFICATIONS registerCmd;
registerCmd.Type = MILCMD.MilCmdPartitionRegisterForNotifications;
registerCmd.Enable = 1; // Enable notifications.
unsafe
{
channel.SendCommand(
(byte*)®isterCmd,
sizeof(DUCE.MILCMD_PARTITION_REGISTERFORNOTIFICATIONS)
);
}
}
#endregion Private Methods
//+----------------------------------------------------------------------
//
// Private Fields
//
//---------------------------------------------------------------------
#region Private Fields
///
/// Returns the current channel set for this MediaContext.
///
internal DUCE.ChannelSet GetChannels()
{
DUCE.ChannelSet channelSet;
channelSet.Channel = _channelManager.Channel;
channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel;
return channelSet;
}
///
/// The disposed flag indicates if the object got disposed.
///
private bool _isDisposed;
///
/// Event handler that is called when the context is destroyed.
///
private EventHandler _destroyHandler;
///
/// This event is raised when the MediaContext.Render has
/// committed the channel.
///
private event EventHandler _renderCompleteHandlers;
///
/// This event is raised when the MediaContext is ready for all of the
/// invalid resources to update their values.
///
private event ResourcesUpdatedHandler _resourcesUpdatedHandlers;
///
/// Use a guid to uniquely identify the current context. Note that
/// we can't use static data to generate a unique id since static
/// data is not shared across app domains and this id must be
/// truly unique.
///
private Guid _contextGuid;
///
/// Message delegate.
///
private DispatcherOperation _currentRenderOp;
private DispatcherOperation _inputMarkerOp;
private DispatcherOperationCallback _connectMessage;
private DispatcherOperationCallback _disconnectMessage;
private DispatcherOperationCallback _renderMessage;
private DispatcherOperationCallback _animRenderMessage;
private DispatcherOperationCallback _inputMarkerMessage;
private DispatcherTimer _promoteRenderOpToInput;
private DispatcherTimer _promoteRenderOpToRender;
///
/// This timer is used to keep track of when we should commit our
/// accumulated renders to the composition thread when the composition
/// thread hasn't presented our frame. If we didn't wait for this time
/// we might present a frame one vsync too early.
///
private DispatcherTimer _estimatedNextVSyncTimer;
///
/// The channel manager is a security wrapper for channel operations.
///
private ChannelManager _channelManager;
///
/// ETW Event Resource handle for performance tracing
///
private DUCE.Resource _uceEtwEvent = new DUCE.Resource();
///
/// Indicates that we are in the middle of processing a render message.
///
private bool _isRendering;
///
/// Indicates we are in the process of disconnecting.
/// This flag is set so that we know not to schedule an animation
/// render after the render pass we use to unmarshall resources.
///
private bool _isDisconnecting = false;
///
/// Indicates we are in a disconnected state. This flag is used by the
/// composition targets to determine if they need to DeleteCobsInSubgraph
/// (i.e. unmarshall the visual tree)
///
private bool _isConnected = false;
private ArrayList _invokeOnRenderCallbacks;
///
/// ArrayList of ICompositionTargets that are currently registered with the MediaSystem;
///
private System.Collections.Hashtable _registeredICompositionTargets;
private System.Collections.Hashtable _registeredLayeredWindows;
private ArrayList _tokensForRegistration;
private ArrayList _tokensForUnRegistration;
///
/// This are the the permissions the Context has to access Visual APIs.
///
private int _readOnlyAccessCounter;
// the cached realization context
private RealizationContext _cachedRealizationContext;
private BoundsDrawingContextWalker _cachedBoundsDrawingContextWalker = new BoundsDrawingContextWalker();
//
// The ID associated with a render dispatch. This is used to track
// renders, and is used by the realization cache to determine
// uniqueness of realizations, and across the UI and UCE threads
// in ETW trace events.
//
private static int _contextRenderID = 0;
//
// The HwndTarget that is handling channel notifications, if any
//
private HwndTarget _notificationHandler;
// The render tier associated with this MediaContext. This is updated
// when channels are created.
private int _tier;
///
/// Rendering event. Registers a delegate to be notified after animation and layout but before rendering
/// Its EventArgs parameter can be cast to RenderingEventArgs to get the last presentation time.
///
internal event EventHandler Rendering;
///
/// CommittingBatch event. Registers a delegate to be notified when a batch is
/// about to be committed to MIL.
///
internal event EventHandler CommittingBatch;
private ArrayList _transformHints = new ArrayList();
// List of pending loaded event dispatcher operations
private FrugalObjectList _loadedOrUnloadedPendingOperations;
// Time to wait for unthrottled renders
private TimeSpan _timeDelay = TimeSpan.FromMilliseconds(10);
// Describes if we currently do or have ever had targets containing any bitmap effects
private bool _bitmapEffectsUsed;
// A flag to determine if RenderComplete event is raised. We only
// raise the event if Render + Commit happens.
//
// Note: If the event is added every time in the middle of Render
// and Commit, then RenderComplete will starve.
private bool _commitPendingAfterRender;
// The top-level hidden notification window that is used to receive
// and forward broadcast messages
private MediaContextNotificationWindow _notificationWindow;
private bool _primaryChannelRemote;
private DUCE.ChannelSet? _currentRenderingChannel = null;
#endregion Private Fields
//+----------------------------------------------------------------------
//
// Animation Smoothing
//
//----------------------------------------------------------------------
#region Animation Smoothing
///
/// This enum indicates that we are in an interlocked presentation mode.
/// We render a frame. send it to be presented and the composition comes
/// to tell us that the frame was presented. Otherwise we just keep giving
/// frames to the composition thread and assume that they get presented
/// as requested.
///
private enum InterlockState
{
///
/// Interlock presentation mode is disabled. We send a frame to the
/// composition thread and simply schedule our next one when we know
/// that we have something to render
///
Disabled = 0,
///
/// Interlock presentation mode has requested a roundtrip message to
/// before enabling the mode. This state indicates that we've
/// requested the UCE enter the interlocked presentation mode but we
/// haven't received the response yet.
///
RequestedStart,
///
/// We are in interlocked presentation mode and are not waiting for
/// anything. If we get a render request we will process it
/// immediately
///
Idle,
///
/// We are in interlocked presentation mode and have sumitted a
/// frame to be presented. We are waiting for the notification from
/// the UCE thread.
///
WaitingForResponse,
///
/// We are in interlocked presentation mode but the last frame we
/// submitted to be presented wasn't presented. We are waiting until
/// the next VSync before submitting another frame for presentation.
///
WaitingForNextFrame
};
///
/// The current state of the interlocked presentation mode
///
private InterlockState _interlockState;
///
/// This indicates that we are waiting for something (either the next
/// frame time to occur or for a response from the UCE).
///
private bool InterlockIsWaiting
{
get
{
return (_interlockState == InterlockState.WaitingForNextFrame ||
_interlockState == InterlockState.WaitingForResponse);
}
}
///
/// We are currently in an interlocked presentation mode and the UCE
/// has acknolwedged that it is also in that mode.
///
private bool InterlockIsEnabled
{
get
{
return ( _interlockState != InterlockState.Disabled
&& _interlockState != InterlockState.RequestedStart);
}
}
///
/// This is used in interlocked presentation mode. This flag indicates
/// that we've put something on the channel but that we haven't commited
/// it yet. This occurs when we know that the composition thread is already
/// processing a batch for a frame. We only want to give the UCE 1 frame
/// per VSync so that we try to make it present the frame at the time
/// that we have estimated. At this point we have rendered 1 frame in
/// advance and will wait until we reach the next frame boundary before
/// committing the channel to have the information sent to the
/// composition thread on the right frame.
///
private bool _needToCommitChannel;
///
/// Last time the composition presented a frame
/// Units: counts
///
private long _lastPresentationTime;
///
/// Last time the UI thread committed a frame
/// Units: Ticks
///
private long _lastCommitTime;
///
/// Last time the the input marker was added to the queue
/// Units: Ticks
///
private long _lastInputMarkerTime;
///
/// Average time it takes the UCE to present a frame
/// Units: Ticks
///
private long _averagePresentationInterval;
///
/// Estimation of the next time we want a frame to appear on screen. We
/// will set the TimeManager's time to this to have the animations look
/// smooth
///
private TimeSpan _estimatedNextPresentationTime;
///
/// The refresh rate of the monitor that we are displaying to
///
private int _displayRefreshRate;
///
/// The rate at which we try to display content if no special throttling
/// mechanism is used
///
private int _adjustedRefreshRate;
///
/// The rate at which we are rendering animations. This can be the
/// refresh rate of the monitor that we are presenting on or can be
/// overridden with a DesiredFrameRate on the Timeline. This is
/// used to estimate the time of the next frame that we want to present.
///
private int _animationRenderRate;
///
/// The results of the last present call.
///
private MIL_PRESENTATION_RESULTS _lastPresentationResults = MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED;
static private long _perfCounterFreq;
private const long MaxTicksWithoutInput = TimeSpan.TicksPerSecond / 2;
#endregion Animation Smoothing
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description:
// The MediaContext class controls the media layer.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Composition;
using System.Security;
using System.Windows.Media.Effects;
using MS.Internal;
using MS.Internal.PresentationCore;
using MS.Utility;
using MS.Win32;
using Microsoft.Win32.SafeHandles;
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;
namespace System.Windows.Media
{
///
/// The MediaContext class controls the media layer.
///
///
/// Use to start up the media system and to
/// shut down the media system.
///
///
internal partial class MediaContext : DispatcherObject, IDisposable, IClock
{
///
/// Initializes the MediaContext's clock service.
///
static MediaContext()
{
long qpcCurrentTime;
SafeNativeMethods.QueryPerformanceFrequency(out _perfCounterFreq);
if (IsClockSupported)
{
SafeNativeMethods.QueryPerformanceCounter(out qpcCurrentTime);
}
else
{
qpcCurrentTime = 0;
}
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.PERFFREQUENCYGUID),
MS.Utility.EventType.Info,
_perfCounterFreq,
qpcCurrentTime
);
}
}
///
/// Returns true if the MediaContext can return current time values,
/// false otherwise.
///
internal static bool IsClockSupported
{
get
{
return _perfCounterFreq != 0;
}
}
///
/// Converts a time value expressed in "counts" (as returned by a call
/// to QueryPerformanceCounter) to "ticks". A Tick is the smallest
/// time unit expressable by a TimeSpan and is equal to 100ns
///
///
///
private static long CountsToTicks(long counts)
{
// The following expression retains precision while avoiding overflow:
return (long)(TimeSpan.TicksPerSecond * (counts / _perfCounterFreq) + (TimeSpan.TicksPerSecond * (counts % _perfCounterFreq)) / _perfCounterFreq);
}
///
/// Converts a time value expressed in "ticks" to an estimate of a count
/// (as returned by a call to QueryPerformanceCounter)
///
///
///
private static long TicksToCounts(long ticks)
{
return (long)(_perfCounterFreq * (ticks / TimeSpan.TicksPerSecond) + (_perfCounterFreq * (ticks % TimeSpan.TicksPerSecond)) / TimeSpan.TicksPerSecond);
}
///
/// Finds out whether a number is prime or not
///
///
/// Fails on 2 by saying that it is not prime but we won't call the
/// method with 2 as input
///
private static bool IsPrime(int number)
{
// If the number is even then it's not prime.
// This is WRONG for 2 but we don't get called with 2.
if ((number & 1) == 0)
return false;
int sqrt = (int) Math.Sqrt(number);
for (int i = 3; i <= sqrt; i += 2)
{
if (number % i == 0)
{
return false;
}
}
return true;
}
///
/// Find the next prime number greater than number
///
private static int FindNextPrime(int number)
{
while (!IsPrime(++number))
{
// Nothing to do
}
return number;
}
///
/// The MediaContext lives in the Dispatcher and is the MediaSystem's class that keeps
/// per Dispatcher state.
///
internal MediaContext(Dispatcher dispatcher)
{
// We create exactly one MediaContext per thread. This is the one
// for this thread
Debug.Assert(dispatcher.Reserved0 == null);
// Initialize frame time information
if (IsClockSupported)
{
SafeNativeMethods.QueryPerformanceCounter(out _lastPresentationTime);
_estimatedNextPresentationTime = TimeSpan.FromTicks(CountsToTicks(_lastPresentationTime));
}
// Generate a unique id for our context so that we can pass this along to
// CreateHWNDRenderTarget
_contextGuid = Guid.NewGuid();
_connectMessage = new DispatcherOperationCallback(ConnectHandler);
_disconnectMessage = new DispatcherOperationCallback(DisconnectHandler);
// Create a hashtable in which we manage the CompositionTargets.
_registeredICompositionTargets = new System.Collections.Hashtable();
_registeredLayeredWindows = new System.Collections.Hashtable();
// Connect to the MediaSystem.
if (MediaSystem.Startup(this))
{
_isConnected = MediaSystem.ConnectChannels(this);
}
// Subscribe to the OnDestroyContext event so that we can cleanup our state.
_destroyHandler = new EventHandler(this.OnDestroyContext);
Dispatcher.ShutdownFinished += _destroyHandler;
_renderMessage = new DispatcherOperationCallback(RenderMessageHandler);
_animRenderMessage = new DispatcherOperationCallback(AnimatedRenderMessageHandler);
_inputMarkerMessage = new DispatcherOperationCallback(InputMarkerMessageHandler);
// We hold off connecting ourselves to the dispatcher until we are sure that
// initialization will complete successfully. In rare cases, function calls
// earlier in this constructor throw exceptions, resulting in the MediaContext
// being left in an uninitialized state; however, the Dispatcher could call methods
// on the MediaContext, resulting in unpredictable behaviour (see bug 1630647).
//
// NOTE: We must attach to the Dispatcher before creating a TimeManager,
// otherwise we will create a circular function loop where TimeManager attempts
// to create a Clock, which attempts to locate a MediaContext, which attempts to
// create a TimeManager, resulting in a stack overflow.
dispatcher.Reserved0 = this;
// Create a notification window to listen for broadcast window messages
_notificationWindow.CreateNotificationWindow(this);
_timeManager = new TimeManager();
_timeManager.Start();
_timeManager.NeedTickSooner += new EventHandler(OnNeedTickSooner);
_promoteRenderOpToInput = new DispatcherTimer(DispatcherPriority.Render);
_promoteRenderOpToInput.Tick += new EventHandler(PromoteRenderOpToInput);
_promoteRenderOpToRender = new DispatcherTimer(DispatcherPriority.Render);
_promoteRenderOpToRender.Tick += new EventHandler(PromoteRenderOpToRender);
_estimatedNextVSyncTimer = new DispatcherTimer(DispatcherPriority.Render);
_estimatedNextVSyncTimer.Tick += new EventHandler(EstimatedNextVSyncTimeExpired);
_bitmapEffectsUsed = false;
_commitPendingAfterRender = false;
}
///
/// Called by the notification window when WM_DWMCOMPOSITIONCHANGED is broadcasted.
///
///
/// Critical -- calls into an unsafe native method.
/// TreatAsSafe -- the call to DwmIsCompositionEnabled simply returns a boolean
/// through an out parameter, it is safe to be called in this context
///
[SecurityCritical, SecurityTreatAsSafe]
internal void OnDWMCompositionChanged()
{
// Check if the desktop composition is enabled.
Int32 isDesktopCompositionEnabled = 0;
UnsafeNativeMethods.DwmIsCompositionEnabled(out isDesktopCompositionEnabled);
// Notify the mediasystem that it needs to re evaluate the transport generation id.
System.Windows.Media.MediaSystem.NotifyRedirectionEnvironmentChanged();
// Iterate through the ICompositionTargets and let them know about
// the DWM's composition changed broadcast.
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
HwndTarget hwndTarget = (ICompositionTarget)entry.Key as HwndTarget;
if (hwndTarget != null)
{
hwndTarget.OnDWMCompositionChanged(isDesktopCompositionEnabled != 0);
}
}
}
///
/// Called by a message processor to notify us that our asynchronous
/// channel has outstanding messages that need to be pumped.
///
///
/// Critical - Calls a critical method: PeekNextMessage.
/// TreatAsSafe - Retrieving and processing a message from the back
/// channel is safe -- the channel is owned by this
/// media context.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void NotifySyncChannelMessage(DUCE.Channel channel)
{
// empty the channel messages.
DUCE.MilMessage.Message message;
while (channel.PeekNextMessage(out message))
{
switch (message.Type)
{
case DUCE.MilMessage.Type.Caps:
case DUCE.MilMessage.Type.SyncModeStatus:
case DUCE.MilMessage.Type.Presented:
break;
case DUCE.MilMessage.Type.PartitionIsZombie:
// we remove the [....] channels so that if the app handles the exception
// it will get a new partition on the next [....] render request.
_channelManager.RemoveSyncChannels();
NotifyPartitionIsZombie(message.HRESULTFailure.HRESULTFailureCode);
break;
default:
HandleInvalidPacketNotification();
break;
}
}
}
///
/// Called by a message processor to notify us that our asynchronous
/// channel has outstanding messages that need to be pumped.
///
///
/// Critical - Calls a critical method: PeekNextMessage.
/// TreatAsSafe - Retrieving and processing a message from the back
/// channel is safe -- the channel is owned by this
/// media context.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void NotifyChannelMessage()
{
// Since a notification message may sit in the queue while we
// disconnect, we need to check that we actually have a channel
// when we receive this notification. If not, there's no harm;
// just skip the operation
if (Channel != null)
{
DUCE.MilMessage.Message message;
while (Channel.PeekNextMessage(out message))
{
switch (message.Type)
{
case DUCE.MilMessage.Type.Caps:
NotifySetCaps(message.Caps.Caps);
break;
case DUCE.MilMessage.Type.SyncModeStatus:
NotifySyncModeStatus(message.SyncModeStatus.Enabled);
break;
case DUCE.MilMessage.Type.Presented:
NotifyPresented(
message.Presented.PresentationResults,
message.Presented.PresentationTime,
message.Presented.RefreshRate
);
break;
case DUCE.MilMessage.Type.PartitionIsZombie:
NotifyPartitionIsZombie(message.HRESULTFailure.HRESULTFailureCode);
break;
case DUCE.MilMessage.Type.BadPixelShader:
NotifyBadPixelShader();
break;
default:
HandleInvalidPacketNotification();
break;
}
}
}
}
///
/// NotifySetCaps - this method is called to update the graphics caps
/// If the new render tier is different from the previous render tier,
/// this method will notify all TierChanged listeners.
///
/// int - the new render tier
private void NotifySetCaps(MilGraphicsAccelerationCaps caps)
{
PixelShaderVersion = caps.PixelShaderVersion;
HasSSE2Support = caps.HasSSE2Support;
int tier = caps.TierValue;
if (_tier != tier)
{
_tier = tier;
if (TierChanged != null)
{
TierChanged(null, null);
}
}
}
///
/// Internal event which is raised when a bad pixel shader is detected on this MediaContext.
///
internal event EventHandler InvalidPixelShaderEncountered;
///
/// NotifyBadPixelShader - this method is called when the render
/// thread has detected a bad pixel shader. The render thread continues
/// to raise this until the problem's been corrected. This method
/// invokes the listeners on the static event in PixelShader.
///
private void NotifyBadPixelShader()
{
if (InvalidPixelShaderEncountered != null)
{
InvalidPixelShaderEncountered(null, null);
}
else
{
// It's never correct to not have an event handler hooked up in
// the case when an invalid shader is encountered. Raise an
// exception directing the app to hook up an event handler.
throw new InvalidOperationException(SR.Get(SRID.MediaContext_NoBadShaderHandler));
}
}
///
/// The partition this media context is connected to went into
/// zombie state. This means either an unhandled batch processing,
/// rendering or presentation error and will require us to reconnect.
///
private void NotifyPartitionIsZombie(int failureCode)
{
//
// We only get back these kinds of notification:-
// For all OOM cases, we get E_OUTOFMEMORY.
// For all OOVM cases, we get D3DERR_OUTOFVIDEOMEMORY and
// for all other errors we get WGXERR_UCE_RENDERTHREADFAILURE.
//
switch (failureCode)
{
case HRESULT.E_OUTOFMEMORY:
throw new System.OutOfMemoryException();
case HRESULT.D3DERR_OUTOFVIDEOMEMORY:
throw new System.OutOfMemoryException(SR.Get(SRID.MediaContext_OutOfVideoMemory));
default:
throw new System.InvalidOperationException(SR.Get(SRID.MediaContext_RenderThreadError));
}
}
///
/// The back channel processed a malformed packet and so gives
/// the notification of invalid packet.
///
private void HandleInvalidPacketNotification()
{
//
// NTRAID#Longhorn-2006/05/02-pravirg -
// For now we ignore the packet and continue processing
// other packets. In future, we could also close this channel.
//
}
///
/// Tier Property - returns the current render tier for this MediaContext.
///
internal int Tier
{
get
{
return _tier;
}
}
///
/// PixelShaderVersion Property - returns the current PixelShader
/// (major<<16|minor) version
///
internal UInt32 PixelShaderVersion
{
get;
private set;
}
///
/// HasSSE2Support Property - returns true if the processor supports SSE2 instructions
///
internal Boolean HasSSE2Support
{
get;
private set;
}
///
/// Internal event which is raised when the Tier changes on this MediaContext.
///
internal event EventHandler TierChanged;
///
/// Asks the composition engine to retrieve the current hardware tier.
/// This tier will be sent back via NotifyChannelMessage.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
/// Sending a message to a channel is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
private void RequestTier(DUCE.Channel channel)
{
unsafe
{
DUCE.MILCMD_CHANNEL_REQUESTTIER data;
//
// Ask for the hardware tier information for the primary display
//
data.Type = MILCMD.MilCmdChannelRequestTier;
data.ReturnCommonMinimum = 0; // false
channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_CHANNEL_REQUESTTIER)
);
}
}
///
/// Schedule the next rendering operation based on our presentation
/// mode and the next time we will have active animations.
///
///
/// Specifies the minimum time before making the next rendering operation
/// active
///
private void ScheduleNextRenderOp(TimeSpan minimumDelay)
{
//
// If _needToCommitChannel is true, then we are in a waiting state and we've
// already rendered a new frame. We don't want to render again until we've
// hit the next VSync at which point we'll commit the rendered frame.
// Note that we will still render again if someone explicitely forces a
// render (PostRender). When we do hit the next VSync and commit the channel
// we will schedule the next render operation.
//
if (!_isDisconnecting && !_needToCommitChannel)
{
//
// This is the time at which the next animation will be active
// in ms. (a negative value represents no animations are active)
//
TimeSpan nextTickNeeded = TimeSpan.Zero;
//
// If we have one or more active Rendering events it's the same
// as having an active animation so we know that we'll need to
// render another frame.
//
if (Rendering == null)
{
nextTickNeeded = _timeManager.GetNextTickNeeded();
}
//Dispatcher.WriteLineWithIndent("nextTickNeeded = {0}ms", nextTickNeeded.TotalMilliseconds);
//
// If we have a tick in the future, make sure that it's not before
// the minimum delay requested.
//
if (nextTickNeeded >= TimeSpan.Zero)
{
nextTickNeeded = TimeSpan.FromTicks(Math.Max(nextTickNeeded.Ticks, minimumDelay.Ticks));
EnterInterlockedPresentation();
}
else
{
LeaveInterlockedPresentation();
}
// We need to tick in the distant future, schedule a far way message
if (nextTickNeeded > TimeSpan.FromSeconds(1))
{
if (_currentRenderOp == null)
{
//Console.WriteLine("RenderMessageHandlerCore posting a new inactive queue item {0}", queueItemID++);
//Dispatcher.WriteLineWithIndent("BeginInvoke at Inactive, set one timer");
//Dispatcher.Indent+=2;
_currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Inactive, _animRenderMessage, null);
_promoteRenderOpToRender.Interval = nextTickNeeded;
_promoteRenderOpToRender.Start();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
}
// We need to tick soon (< 1 second)
else if (nextTickNeeded >= TimeSpan.Zero)
{
//Dispatcher.WriteLineWithIndent("BeginInvoke at Inactive, set two timers");
//Dispatcher.Indent+=2;
// We need to tick in the future
if (nextTickNeeded > TimeSpan.Zero)
{
// Only create a new render op if we don't have one
// scheduled
if (_currentRenderOp == null)
{
_currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Inactive, _animRenderMessage, null);
_promoteRenderOpToInput.Interval = nextTickNeeded;
_promoteRenderOpToInput.Start();
_promoteRenderOpToRender.Interval = TimeSpan.FromSeconds(1);
_promoteRenderOpToRender.Start();
// Debug.WriteLine(string.Format("{1}ms: ScheduleNextRenderOp: Posting a new queue item active in {0}ms", nextTickNeeded.TotalMilliseconds, CurrentTimeInMs);
}
}
else
{
Debug.Assert(InterlockIsEnabled,
"If we are not in Interlocked Mode, we should always have a delay");
DispatcherPriority priority = DispatcherPriority.Render;
//
// We normally want to schedule rendering at Render priority, however if something at
// render priority takes more than a frame it will block input from ever being processed.
// To prevent this we create an operation at input priority which lets us know how long
// input has been blocked for. If it's blocked for too long we should schedule our render
// at Input priority so that input can flush before we start another render.
//
if (_inputMarkerOp == null)
{
_inputMarkerOp = Dispatcher.BeginInvoke(DispatcherPriority.Input, _inputMarkerMessage, null);
_lastInputMarkerTime = CurrentTicks;
}
else if (CurrentTicks - _lastInputMarkerTime > MaxTicksWithoutInput)
{
priority = DispatcherPriority.Input;
}
// Schedule an operation to happen immediately.
if (_currentRenderOp == null)
{
_currentRenderOp = Dispatcher.BeginInvoke(priority, _animRenderMessage, null);
}
else
{
_currentRenderOp.Priority = priority;
}
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
}
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Console.WriteLine("ScheduleNextRenderOp not posting new queue item");
//Dispatcher.WriteLineWithIndent("Nothing to do");
}
//
// Trace the scheduling of the render
//
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.SCHEDULERENDERGUID),
MS.Utility.EventType.Info,
nextTickNeeded.TotalMilliseconds
);
}
}
//else if (_needToCommitChannel)
//{
// Debug.WriteLine(string.Format("{0}: ScheduleNextRenderOp waiting to commit - didn't schedule next render", CurrentTimeInMs));
//}
}
///
/// Commits the channel, but only after the next vblank has occured.
///
private void CommitChannelAfterNextVSync()
{
if (_animationRenderRate != 0)
{
//
// estimate the next vblank interval and set our timer interval to wake up at this time.
// we add 1ms to the estimated time because are estimated time make not be perfectly accurate
// and its more important that wake up after the vblank than right on it.
//
long currentTicks = CurrentTicks;
long earliestWakeupTicks = currentTicks + TicksUntilNextVsync(currentTicks) + TimeSpan.TicksPerMillisecond;
_estimatedNextVSyncTimer.Interval = TimeSpan.FromTicks(earliestWakeupTicks - currentTicks);
_estimatedNextVSyncTimer.Tag = earliestWakeupTicks;
}
else
{
// It's possible our first notification from the UCE didn't give us the
// refresh rate. We can't estimate when vsync will be - try again in about a vblank
_estimatedNextVSyncTimer.Interval = TimeSpan.FromMilliseconds(17);
}
_estimatedNextVSyncTimer.Start();
//
// We are waiting for the next VBlank to occur
//
_interlockState = InterlockState.WaitingForNextFrame;
_lastPresentationResults = MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT;
}
///
/// Processes the Presented composition engine notification.
///
///
/// The results of the last presentation.
///
///
/// The timestamp of the last presentation.
///
///
/// The current display refresh rate.
///
private void NotifyPresented(
MIL_PRESENTATION_RESULTS presentationResults,
long presentationTime,
int displayRefreshRate
)
{
if (InterlockIsEnabled)
{
Debug.Assert(_interlockState == InterlockState.WaitingForResponse,
"We should not be getting a notification unless we asked for one");
//
// The composition engine has presented, so we are ready to start
// another frame, if necessary. Also remember the presentation
// time to use as the basis for estimating the time for the next
// frame.
//
//Console.WriteLine("Got a Presentation Notification {0}", presentationResults.ToString());
//
// presentationDelay represents the time we want to wait until
// we activate a new render operation.
//
TimeSpan presentationDelay = TimeSpan.Zero;
_lastPresentationResults = presentationResults;
//
// The UCE has processed our frame. So we are not waiting on it
// anymore. Set our state to idle.
//
_interlockState = InterlockState.Idle;
// Debug.WriteLine(String.Format("{0}: UCE Presented", CurrentTimeInMs));
switch (presentationResults)
{
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC:
{
// Adjust the refresh rate to prevent constant tearing
// on the screen. We've chosen NextPrime(RefreshRate+5)
// as a function that seems to look good at all
// popular refresh rates.
// Only update the adjusted refresh rate when the
// display refresh rate changes. If changing this
// make sure to look at the performance of the lookup
// of the adjusted refresh rate.
if (displayRefreshRate != _displayRefreshRate)
{
_displayRefreshRate = displayRefreshRate;
_adjustedRefreshRate = FindNextPrime(displayRefreshRate + 5);
}
// VSync means that the UCE has presented at the time we
// requested, but it can still tear, so if the user has
// requested a high framerate through DFR, override the
// monitor refresh rate with this DFR.
_animationRenderRate = Math.Max(_adjustedRefreshRate, _timeManager.GetMaxDesiredFrameRate());
_lastPresentationTime = presentationTime;
}
break;
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED:
{
//
// If we don't support VSync then wait a small delay so that we don't
// just overrun the UCE
//
presentationDelay = _timeDelay;
}
break;
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT:
{
//
// We didn't present because the scene didn't change.
// Since the UCE returned early, the vblank at which
// we were hoping to present the last frame has not
// occurred yet. We will set a timer to trigger at
// the time at which we think that vblank will occur
// (with a fudge factor of 1 ms to ensure we don't
// wake up before)
//
// Until the timer expires we will not send any updates
// to the UCE because we don't know if we'll be able to
// hit the vblank. We will update the Visual tree and
// queue the UCE changes, but we will not commit yet.
//
Debug.Assert(!InterlockIsWaiting,
"We should not be waiting at this point");
CommitChannelAfterNextVSync();
// Debug.WriteLine({1}: String.Format("UCE returned early; waiting {0}ms until committing the channel", TicksUntilNextVsync(CurrentTicks) / TimeSpan.TicksPerMillisecond + 1, CurrentTimeInMs));
}
break;
// This return code represents that we've presented with
// the DWM, so there is no tearing, we don't need to
// override the refresh rate in this case.
case MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_DWM:
{
// In the DWM case these values are actually correct, so we update them here
_animationRenderRate = displayRefreshRate;
_lastPresentationTime = presentationTime;
//if (_animationRenderRate > 0)
//{
// Debug.WriteLine(String.Format("{1}: DWM presented for us and we waited till Vsync. Last present time:, {0}ms", CountsToTicks(_lastPresentationTime) / TimeSpan.TicksPerMillisecond, CurrentTimeInMs));
//}
}
break;
}
// Cap our Animation RenderRate to 1000 fps.
_animationRenderRate = Math.Min(_animationRenderRate, 1000);
//
// Trace the notification from the UCE
//
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UCENOTIFYPRESENTGUID),
MS.Utility.EventType.Info,
_lastPresentationTime, // The last presentation time
(Int64)presentationResults // The presentation results
);
}
if (presentationResults == MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT)
{
// dont commit or schedule because we've created a timer to do this.
// the only reason we're waiting until here to do this is because we want the ETW event to fire
return;
}
else
{
// if we get any result other than a NOPRESENT then we should stop the _estimatedNextVSyncTimer timer
// so we dont end up commiting twice in 1 vblank interval
_estimatedNextVSyncTimer.Stop();
}
//
// We want to schedule the next render operation before commiting the
// channel so that we can send the command right away.
//
// If we already had a render occur then we've ticked the
// time manager and layout has been updated send this frame
// to the UCE.
//
if (!InterlockIsWaiting && _needToCommitChannel)
{
//
// if we've already commit during this vblank interval, dont do it again
// until the following vblank
//
if (HasCommittedThisVBlankInterval)
{
CommitChannelAfterNextVSync();
return;
}
CommitChannel();
Debug.Assert(InterlockIsWaiting,
"We had something to commit, we should be waiting for that"+
"notification to come back");
// Debug.WriteLine(String.Format("{0}: UCE returned from Vsync; committing now. Scheduling next render with a delay of {1}ms", CurrentTimeInMs, presentationDelay.TotalMilliseconds));
}
ScheduleNextRenderOp(presentationDelay);
}
}
///
/// Return true if the current time is within the same vblank interval as our last commit time
///
private bool HasCommittedThisVBlankInterval
{
get
{
if (_animationRenderRate == 0)
return false;
// is our last commit within 1 refresh period of our current time?
//
if (CurrentTicks - _lastCommitTime < RefreshPeriod)
{
// if the last commit is later than the last presentation, then we
// have committed a frame and haven't been notified of it yet.
// in this case, we dont want to commit the channel again
if (_lastCommitTime > CountsToTicks(_lastPresentationTime))
return true;
}
return false;
}
}
///
/// Returns the current time in Ticks (100 ns intervals).
///
private long CurrentTicks
{
get
{
long counts;
SafeNativeMethods.QueryPerformanceCounter(out counts);
return CountsToTicks(counts);
}
}
//
//private long CurrentTimeInMs
//{
// get
// {
// return CurrentTicks / TimeSpan.TicksPerMillisecond;
// }
//}
//
///
/// Returns the time in Ticks (100 ns intervals) between vsyncs.
/// It's up to the caller to ensure that _animationRenderRate is valid.
///
private long RefreshPeriod
{
get
{
return TimeSpan.TicksPerSecond / _animationRenderRate;
}
}
///
/// Computes the number of ticks since the last UCE present
///
///
///
private long TicksSinceLastPresent(long currentTime)
{
return currentTime - CountsToTicks(_lastPresentationTime);
}
///
/// Estimates the time in Ticks (100 ns intervals) since the
/// last vsync. It starts with the last presentation time
/// and extrapolates based on the display refresh rate.
///
private long TicksSinceLastVsync(long currentTime)
{
return TicksSinceLastPresent(currentTime) % RefreshPeriod;
}
///
/// Estimates the number of ticks until the next vsync
///
///
///
private long TicksUntilNextVsync(long currentTime)
{
return RefreshPeriod - TicksSinceLastVsync(currentTime);
}
///
/// Processes the SyncMode composition engine notification.
///
///
/// The HRESULT of enabling [....] mode.
///
private void NotifySyncModeStatus(int enabledResult)
{
//
// Only process the notification if we asked to start interlocked
// presentation mode
//
if (_interlockState == InterlockState.RequestedStart)
{
if (enabledResult >= 0)
{
//
// We succeded in entering the interlocked mode in the
// composition engine.
//
//Console.WriteLine("Entered interlockedMode");
_interlockState = InterlockState.Idle;
if (Channel != null)
{
Channel.SyncFlush();
}
}
else
{
_interlockState = InterlockState.Disabled;
}
}
}
///
/// Estimates the timestamp of the next frame to be presented
///
///
///
/// How the current time is computed
/// ================================
///
/// The MediaContext keeps track of when frames are presented to the
/// display. The "current time" for the MediaContext is actually the
/// time at which we estimate the next frame to be presented. This
/// allows the system to produce a frame that is correct at the time
/// it is seen by the user, leading to smooth animations. The main
/// assumption at this time is that a frame can be compiled, composed
/// and presented before the next vertical refresh, whenever that may
/// be. A future refinement of this algorithm will take historical
/// profiling data into account to compute a more accurate target
/// presentation frame.
///
///
/// The next frame time is estimated by taking the current system
/// time and rounding that up to the next refresh time, based on the
/// last refresh time and the current display refresh rate.
///
/// This is the situation when we are ready to submit a new frame:
///
/// refresh: -| F |-
/// --+----[+]----+-----+-----+--*-(+)----+-----+ t
/// L C N
/// last frame now next frame
///
///
/// In order to pipeline the UI thread and the composition thread we
/// will try to start rendering the next frame while the composition
/// is presenting the last frame that we've sent it. This allows us to
/// make use of 2 CPUs more efficiently (UI thread can renders 1 frame
/// while the composition thread is presenting another). So when
/// calculating the current time, if we are waiting on a frame already
/// then we will produce the frame for the VSync after. That means if we
/// are in the wait state then the next presentation time is not the
/// next refresh time, but at least *two* refresh times in the future,
/// because the next refresh is when the *last* frame will be presented.
///
/// This is the situation when we are in a wait state:
///
/// refresh: -| F |-
/// --+----[+]----+-----+-----+--*--+----(+)----+ t
/// L C N
/// last frame now next frame
///
///
/// The computation of the next frame time "N", then, involves four
/// variables:
///
/// Variable Units
/// -------------------------------------------------------------
/// L The last presentation time Ticks
/// C The current actual time Ticks
/// R The display refresh rate frames per second
/// W The wait state boolean -- true if waiting
///
/// The computation is fairly straightforward. We take the time since
/// we've last presented (C - L) and mod it with the refresh rate (R) to get
/// the time since the last vsync. We can then get the time until the next
/// vsync by subtracting the refresh rate (R) from that value.
/// TicksSinceLastVsync() and TicksUntilNextVsync() implement this.
///
/// If we're not waiting we can render at the next vsync. If we are waiting,
/// we'll wait until the vsync after. This computation can get slightly
/// hairy if we're exactly on a frame boundary; the comments inside
/// IClock.CurrentTime explain this a bit more.
///
/// Each time the composition engine notifies us that a frame
/// was presented it also tells us the timestamp for that frame. At
/// that time we also leave the wait state. If we successfully present
/// on every refresh then two consecutive computations should give the
/// same result. However, the values of L we get from the composition
/// engine are subject to fluctuations (due to thread scheduling issues),
/// so we may in fact compute different time values, either earlier or later.
/// To avoid thrashing the Tick and Layout processes we keep the previous
/// estimate if the new estimate is within half a frame of it. If the
/// new estimate is later than that then it means the previous estimate
/// was too inaccurate, potentially because we actually took longer
/// than a single refresh to compile, compose and present the last
/// frame. In that case we abandon the previous estimated value.
///
///
TimeSpan IClock.CurrentTime
{
get
{
Debug.Assert(IsClockSupported, "MediaContext.CurrentTime called when QueryPerformaceCounter is not supported");
long counts;
SafeNativeMethods.QueryPerformanceCounter(out counts);
long countsTicks = CountsToTicks(counts);
if (_interlockState != InterlockState.Disabled)
{
//
// On the first frame we haven't yet received information about
// the display refresh rate from the compositor. In that case
// we can't snap to frame times, so we simply use the current
// time.
//
if ( _animationRenderRate != 0
&& _lastPresentationResults != MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED)
{
//
// Figure out where we are in the vsync period. The entire computation
// is done in TimeSpan Ticks (100ns intervals)
//
long nextVsyncTicks; // Absolute time in ticks at which the next vsync will occur
long vsyncAdvance; // We expect to present this many vsyncs in the future
long nextPresentationTicks; // Absolute time in ticks at which we expect to present
// Future: eventually we should actually keep track of how long it takes
// the UCE to present. For now we assume it's one vsync
_averagePresentationInterval = RefreshPeriod;
//
// Compute how many frames in the future we expect to present to.
//
//
// We have to be very careful about the computation when we're on a frame boundary.
//
// If the time since last vsync is very small it means that we, by computation, think a vsync
// just happened. In reality, it may have just happened, is happening, or will happen very soon
// Since the UCE is on a different thread, it's possible in all three cases that the UCE is about
// to notify us of the vsync.
//
// This can get us into a situation where, though the computed time since last vsync is small,
// the timeSinceLastPresent is one frame ago. Either the UCE is currently presenting and
// timeSinceLastPresent is stale (i.e. we just haven't been notified yet), or the UCE will be
// missing this particular vsync and will return from present on a subsequent vsync.
//
// This is a problem when we're in a wait state (we've previously committed a frame and are
// waiting for the UCE to return from presenting it). If the UCE is about to return, we
// can tick the current frame to the next vsync. If the UCE is not about to return, we
// must tick the current frame in the future.
//
// The best way to disambiguate this is to look up the last time a frame was committed.
// This is how long the UCE has been working on it. By comparing that with how long the UCE
// takes on average to present a frame we'll be able to determine if it is finishing it now.
vsyncAdvance = 0;
if (InterlockIsWaiting)
{
// If we're waiting for the UCE to finish presenting a frame then
// in most cases it'll come back at the next vsync and we'll set our
// render time to the one after.
// We once (v1 shipping code) attempted to limit vsyncAdvance to 0
// based on heuristics involving where in the frame we are. This was
// removed after it was discovered that the logic was flawed and it made
// analyzing timing graphs more difficult.
vsyncAdvance = 1;
}
nextVsyncTicks = countsTicks + TicksUntilNextVsync(countsTicks);
nextPresentationTicks = (nextVsyncTicks + (vsyncAdvance * RefreshPeriod));
//
// If we had previously estimated the next presentation time
// and that estimate still seems reasonable then use the
// previous estimate rather than the newly computed value.
// This is a good performance win because it means we will
// tick animations and thus run layout to the same value as
// last time, which saves a lot of computation. For this
// purpose, we will consider the previous estimate "reasonable"
// if it falls within 1/2 frame of the new value.
//
if ((nextPresentationTicks - _estimatedNextPresentationTime.Ticks) * _animationRenderRate > TimeSpan.FromMilliseconds(500).Ticks)
{
// Establish a new estimate
_estimatedNextPresentationTime = TimeSpan.FromTicks(nextPresentationTicks);
}
else
{
// Debug.WriteLine(String.Format("{0}, CurrentTime kept the old frame estimate, {1}", CurrentTimeInMs, _estimatedNextPresentationTime));
}
}
else
{
_estimatedNextPresentationTime = TimeSpan.FromTicks(countsTicks);
}
}
else
{
_estimatedNextPresentationTime = TimeSpan.FromTicks(countsTicks);
}
return _estimatedNextPresentationTime;
}
}
///
/// Starts up the media system and creates needed channels used by the media context
///
internal void CreateChannels()
{
bool layeredWindows = false;
if (_registeredLayeredWindows != null)
{
layeredWindows = _registeredLayeredWindows.Count != 0;
}
_channelManager.CreateChannels(layeredWindows);
if (_notificationHandler != null)
{
HookNotifications();
}
// Create an ETW Event Resource for performance tracing
// GSchneid: It might be good enough to put this in the current batch without
// submitting it.
_uceEtwEvent.CreateOrAddRefOnChannel(Channel, DUCE.ResourceType.TYPE_ETWEVENTRESOURCE);
// Send a request for an updated render tier value
RequestTier(Channel);
Channel.Commit();
// We now call CompleteRender, which calls SyncFlush, to ensure that all commands have
// been processed, including the tier request.
CompleteRender();
// Since all of the commands have now been processed, we can go ahead and manually call
// NotifyChannelMessage to pick up any back channel messages which have been sent.
NotifyChannelMessage();
_primaryChannelRemote = (Channel.MarshalType == ChannelMarshalType.ChannelMarshalTypeCrossMachine);
}
///
/// Starts releases channels and shuts down the media system. When the last visual manager has
/// disconnected the transport will shut down.
///
private void RemoveChannels()
{
// This test is needed because this method is called by Dispose which is called
// on shutdown which can happen in a disconnected state. We can replace this
// test with an assert by moving
// the management of transport connectedness state to the media system.
if (Channel != null)
{
_uceEtwEvent.ReleaseOnChannel(Channel);
//
// With no channels left open, we cannot be in an interlocked
// presentation mode because we don't have a connection to the
// composition engine.
//
LeaveInterlockedPresentation();
}
_channelManager.RemoveChannels();
}
///
/// Start interlocked presentation mode and resquest
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
///
[SecurityCritical, SecurityTreatAsSafe]
private void EnterInterlockedPresentation()
{
if (!InterlockIsEnabled)
{
if (MediaSystem.AnimationSmoothing
&& Channel.MarshalType == ChannelMarshalType.ChannelMarshalTypeCrossThread
&& IsClockSupported)
{
unsafe
{
//
// Ask the UCE to get into VSync mode
//
DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE data;
data.Type = MILCMD.MilCmdPartitionSetVBlankSyncMode;
data.Enable = 1; /* true */
Channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE));
_interlockState = InterlockState.RequestedStart;
}
}
}
}
///
/// Leaves interlocked presentation mode and cleans up state so we can
/// continue to present in non-interlocked mode.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
///
[SecurityCritical, SecurityTreatAsSafe]
private void LeaveInterlockedPresentation()
{
bool interlockDisabled = (_interlockState == InterlockState.Disabled);
if (_interlockState == InterlockState.WaitingForResponse)
{
// Process messages until we get a response for the outstanding frame.
// This is necessary because we are unsure whether the UCE has already
// posted a notification in our message queue
CompleteRender();
}
// If we are waiting for the next frame stop the timer since we are
// leaving interlocked presentation mode
_estimatedNextVSyncTimer.Stop();
//
// If we are not disabled then request to stop the mode.
// If we had already asked to start but haven't gotten the response
// still request to stop
//
if (!interlockDisabled)
{
_interlockState = InterlockState.Disabled;
unsafe
{
//
// Tell the UCE that we are not in VSync mode anymore
//
DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE data;
data.Type = MILCMD.MilCmdPartitionSetVBlankSyncMode;
data.Enable = 0; /* false */
Channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_PARTITION_SETVBLANKSYNCMODE));
// We need to send this notification now otherwise, we don't
// know when we'll send the next packet. This will clear the
// channel and render the last frame (if necessary).
_needToCommitChannel = true;
CommitChannel();
}
}
Debug.Assert(_interlockState == InterlockState.Disabled,
"LeaveInterlockedPresentationMode should set the InterlockedState to Disabled");
}
///
/// Hooks the async channel so we get notifications.
///
private void HookNotifications()
{
Debug.Assert(Channel != null);
Debug.Assert(_notificationHandler != null);
//
// This associates this channel with the given notification
// window so that we can receive a window message whenever
// there is a new message posted.
//
HwndTarget.SetChannelNotificationWindow(
Channel,
_notificationHandler
);
//
// This actually populates the channel into the composition
// engine so that it can receive notifications.
//
RegisterForNotifications(Channel);
EnterInterlockedPresentation();
}
///
/// Gets the MediaContext from the context passed in as argument.
///
internal static MediaContext From(Dispatcher dispatcher)
{
Debug.Assert(dispatcher != null, "Dispatcher required");
MediaContext cm = (MediaContext)dispatcher.Reserved0;
if (cm == null)
{
cm = new MediaContext(dispatcher);
Debug.Assert(dispatcher.Reserved0 == cm);
}
return cm;
}
///
/// Gets the MediaContext from the current UI context.
///
internal static MediaContext CurrentMediaContext
{
get
{
return From(Dispatcher.CurrentDispatcher);
}
}
///
/// Called by the Dispatcher to let us know that we are going away.
///
void OnDestroyContext(object sender, EventArgs e)
{
Debug.Assert(CheckAccess());
Dispose();
}
///
/// Disposes the MediaContext.
///
///
/// Critical - Shuts down the queue item promoter, effectively disabling rendering
/// when animation smoothing is turned on.
/// TreatAsSafe - At the time the media context object gets disposed, no rendering
/// is supposed to be happening anymore, so it is safe to shut down
/// the queue item promoter.
///
[SecurityCritical, SecurityTreatAsSafe]
public virtual void Dispose()
{
Debug.Assert(CheckAccess());
if (!_isDisposed)
{
//
// Dispose all still registered ICompositionTargets ----------------
// Note that disposing the CompositionTargets should be the first thing we do here.
// First make a copy of the Hashtable contents, because ICompositionTarget.Dispose modifies this Hashtable.
ICompositionTarget[] registeredVTs = new ICompositionTarget[_registeredICompositionTargets.Count];
_registeredICompositionTargets.Keys.CopyTo(registeredVTs, 0);
// Iterate through the ICompositionTargets and dispose them. Be careful, ICompositionTarget.Dispose
// removes the ICompositionTargets from the HashTable. This is why we don't iterate the HashTable directly.
foreach (ICompositionTarget iv in registeredVTs)
{
iv.Dispose();
}
_registeredICompositionTargets = null;
// Dispose the notification window
_notificationWindow.DisposeNotificationWindow();
// Unhook the context destroy event handler -------------------
Dispatcher.ShutdownFinished -= _destroyHandler;
_destroyHandler = null;
// Dispose the time manager ----------------------------------
Debug.Assert(_timeManager != null);
_timeManager.NeedTickSooner -= new EventHandler(OnNeedTickSooner);
_timeManager.Stop();
_timeManager = null;
// From now on we are disposed -------------------------------
_isDisposed = true;
RemoveChannels();
// if we set the Dispatcher.Reserved0 field to null, we end
// creating another media context on the shutdown pass when the
// HwndSrc class sets its visual root to null. In a disconnected
// state this attempts to re open the transport.
// Disconnect from MediaSystem -------------------------------
MediaSystem.Shutdown(this);
}
}
///
/// Registers a new ICompositionTarget with the MediaSystem.
///
/// Dispatcher with which the ICompositionTarget should be registered.
/// The ICompositionTarget to register with the MediaSystem.
internal static void RegisterICompositionTarget(Dispatcher dispatcher, ICompositionTarget iv)
{
Debug.Assert(dispatcher != null);
Debug.Assert(iv != null);
MediaContext current = From(dispatcher);
current.RegisterICompositionTargetInternal(iv);
}
///
/// Registers the ICompositionTarget from this MediaContext.
///
///
private void RegisterICompositionTargetInternal(ICompositionTarget iv)
{
Debug.Assert(!_isDisposed);
Debug.Assert(iv != null);
// If channel is not available, we are in a disconnected state.
// When connect handler is invoked for this media context, all
// registered targets will be visited and AddRefChannel will be
// called for them, so here we just skip the operation.
if (Channel != null)
{
// if _currentRenderingChannel is nonempty, we're registering this ICompositionTarget
// from within a render walk and it is thus a visualbrush, we need to add it to the
// channel which we are currently rendering. If _currentRenderingChannel
// is null, we just get the target channels for this ICompositionTarget and add
// there.
DUCE.ChannelSet channelSet = (_currentRenderingChannel == null) ? GetChannels() : _currentRenderingChannel.Value;
iv.AddRefOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
}
// if we have _tokensForRegistration we are registering a visual brush from
// within a render call. We need to defer adding this ICompositionTarget.
if ( _tokensForRegistration != null)
{
int count = _tokensForUnRegistration.Count;
Debug.Assert(!_tokensForRegistration.Contains(iv));
_tokensForUnRegistration.Remove(iv);
// if we had to remove the target from the unregister array we already have
// it registered so do nothing. If the count before and after the remove are
// the same we did not first un register so we need to add it in.
if (count == _tokensForUnRegistration.Count)
{
_tokensForRegistration.Add(iv);
}
}
else
{
_registeredICompositionTargets.Add(iv, null); // We use the hashtable just as a set.
}
// When we get our first HwndTarget we may be able to start
// processing notifications
if (_notificationHandler == null)
{
HwndTarget target = iv as HwndTarget;
if (target != null)
{
_notificationHandler = target;
if (Channel != null)
{
HookNotifications();
}
}
}
}
///
/// Unregisters the ICompositionTarget from the Dispatcher.
///
///
///
internal static void UnregisterICompositionTarget(Dispatcher dispatcher, ICompositionTarget iv)
{
Debug.Assert(dispatcher != null);
Debug.Assert(iv != null);
MediaContext.From(dispatcher).UnregisterICompositionTargetInternal(iv);
}
///
/// Removes the ICompositionTarget from this MediaContext.
///
/// ICompositionTarget to unregister.
///
/// Critical - Calls a security critical method: SetNotificationWindow.
/// TreatAsSafe - It is ok for the MediaContext to call SetNotificationWindow on
/// the channel because the MediaContext owns the channel. In addition
/// no user data is passed along.
///
[SecurityCritical, SecurityTreatAsSafe]
private void UnregisterICompositionTargetInternal(ICompositionTarget iv)
{
Debug.Assert(iv != null);
// this test is needed because we always unregister the target when the ReleaseUCEResources
// is called on the target and Dispose is called from both the media context and the
// hwnd source, so when shutting down in a disconnected state we end up calling here
// after a Dispose.
if (_isDisposed)
{
return;
}
// If channel is not available, we are in a disconnected state, which means
// that all resources have been released and we can just skip the operation.
if (Channel != null)
{
// if _currentRenderingChannel is nonempty, we're unregistering this ICompositionTarget
// from within a render walk and it is thus a visualbrush, we need to remove it from the
// channel which we are currently rendering. If _currentRenderingChannel
// is null, we just get the target channels for this ICompositionTarget and release
// there.
DUCE.ChannelSet channelSet = (_currentRenderingChannel == null) ? GetChannels() : _currentRenderingChannel.Value;
iv.ReleaseOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
}
// if we have _tokensForUnRegistration we are unregistering a visual brush from
// within a render call. We need to defer removing this ICompositionTarget.
if ( _tokensForUnRegistration != null)
{
Debug.Assert(!_tokensForUnRegistration.Contains(iv));
_tokensForUnRegistration.Add(iv);
_tokensForRegistration.Remove(iv);
}
else
{
_registeredICompositionTargets.Remove(iv);
}
//
// If we remove the HwndTarget that we were using to handle
// notifications then we may not be able to process them anymore.
// We need to release this one and look for a new one.
//
if (_notificationHandler != null)
{
if (iv == _notificationHandler)
{
_notificationHandler = null;
FindNotificationHandler();
if (Channel != null)
{
if (_notificationHandler != null)
{
HookNotifications();
}
else
{
Channel.SetNotificationWindow(IntPtr.Zero, 0);
//
// If we can't get notifications then we can't be in
// interlocked presentation mode.
//
LeaveInterlockedPresentation();
}
}
}
}
}
///
/// Ensures that we have a handler for back-channel message notifications
/// if possible.
///
private void FindNotificationHandler()
{
Debug.Assert(_notificationHandler == null);
if (_registeredICompositionTargets != null)
{
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
_notificationHandler = (ICompositionTarget)entry.Key as HwndTarget;
if (_notificationHandler != null)
{
// Found it.
break;
}
}
}
}
private class InvokeOnRenderCallback
{
private DispatcherOperationCallback _callback;
private object _arg;
public InvokeOnRenderCallback(
DispatcherOperationCallback callback,
object arg)
{
_callback = callback;
_arg = arg;
}
public void DoWork()
{
_callback(_arg);
}
}
internal void BeginInvokeOnRender(
DispatcherOperationCallback callback,
object arg)
{
Debug.Assert(callback != null);
// While technically it could be OK for the arg to be null, for now
// I know that arg represents the this reference for the layout
// process and should never be null.
Debug.Assert(arg != null);
if (_invokeOnRenderCallbacks == null)
{
_invokeOnRenderCallbacks = new ArrayList();
}
_invokeOnRenderCallbacks.Add(new InvokeOnRenderCallback(callback, arg));
if (!_isRendering)
{
PostRender();
}
}
///
/// Add a pending loaded or unloaded callback
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal LoadedOrUnloadedOperation AddLoadedOrUnloadedCallback(
DispatcherOperationCallback callback,
DependencyObject target)
{
LoadedOrUnloadedOperation op = new LoadedOrUnloadedOperation(callback, target);
if (_loadedOrUnloadedPendingOperations == null)
{
_loadedOrUnloadedPendingOperations = new FrugalObjectList(1);
}
_loadedOrUnloadedPendingOperations.Add(op);
return op;
}
///
/// Remove a pending loaded or unloaded callback
///
[FriendAccessAllowed] // Built into Core, also used by Framework.
internal void RemoveLoadedOrUnloadedCallback(LoadedOrUnloadedOperation op)
{
Debug.Assert(op != null);
// cancel the operation - this prevents it from running even if it has
// already been copied into the local array in FireLoadedPendingCallbacks
op.Cancel();
if (_loadedOrUnloadedPendingOperations != null)
{
for (int i=0; i<_loadedOrUnloadedPendingOperations.Count; i++)
{
LoadedOrUnloadedOperation operation = _loadedOrUnloadedPendingOperations[i];
if (operation == op)
{
_loadedOrUnloadedPendingOperations.RemoveAt(i);
break;
}
}
}
}
///
/// If there is already a render operation in the Dispatcher queue, this
/// method will bump it up to render priority. If not, it will add a
/// render operation at render priority.
///
///
/// This method should only be called when a render is necessary "right
/// now." Events such as a change to the visual tree would result in
/// this method being called.
///
internal void PostRender()
{
// this is now needed because we no longer set Dispatcher.Reserved0 to null
// in the Dispose method. See comment in the Dispose method.
if (_isDisposed)
{
return;
}
Debug.Assert(CheckAccess());
//if (_waitingForPresent)
//{
// Console.WriteLine("PostRender called while waiting for a previous present");
//}
//Dispatcher.WriteLineWithIndent("PostRender...");
//Dispatcher.Indent+=2;
if (!_isRendering)
{
EventTrace.NormalTraceEvent(EventTraceGuidId.POSTRENDERGUID, MS.Utility.EventType.Info);
if (_currentRenderOp != null)
{
//Console.WriteLine("PostRender promoting existing queue item");
//Dispatcher.WriteLineWithIndent("Promoting to Render...");
//Dispatcher.Indent+=2;
// If we already have a render operation in the queue, we should
// change its priority to render priority so it happens sooner.
_currentRenderOp.Priority = DispatcherPriority.Render;
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Console.WriteLine("PostRender issuing new queue item");
//Dispatcher.WriteLineWithIndent("BeginInvoke at Render...");
//Dispatcher.Indent+=2;
// If we don't have a render operation in the queue, add one at
// render priority.
_currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Render, _renderMessage, null);
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
//Dispatcher.WriteLineWithIndent("Killing the timers...");
//Dispatcher.Indent+=2;
// We don't need to keep our promotion timers around.
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Console.WriteLine("PostRender called while rendering");
//Dispatcher.WriteLineWithIndent("Already rendering!");
}
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
///
/// This method is invoked from the HwndTarget when the window is resize.
/// It will cancel pending render queue items and then run the dispatch for the
/// render queue item by hand.
///
internal void Resize(ICompositionTarget resizedCompositionTarget)
{
// Cancel pending render queue items so that we don't dispatch them later
// causing a double render during Resize. (Note that RenderMessage will schedule a
// new RenderQueueItem).
if (_currentRenderOp != null)
{
_currentRenderOp.Abort();
_currentRenderOp = null;
}
// We don't need to keep our promotion timers around.
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
// Now render manually directly from the resize handler.
// Alternatively we could pump the message queue here with a filter that only allows
// RenderQueueItems to get dispatched.
RenderMessageHandler(resizedCompositionTarget);
}
internal void PostConnect()
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, _connectMessage, null);
}
internal void PostDisconnect()
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, _disconnectMessage, null);
}
internal object ConnectHandler(
object obj
)
{
// create all media context channels
if (MediaSystem.ConnectChannels(this))
{
_isConnected = true;
ArrayList targets = new ArrayList();
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
ICompositionTarget iv = ((ICompositionTarget)(entry.Key));
targets.Add(iv);
}
foreach (ICompositionTarget iv in targets)
{
DUCE.ChannelSet channelSet = GetChannels();
iv.AddRefOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
Resize(iv);
}
}
return null;
}
internal object DisconnectHandler(
object obj
)
{
//
// When we disconnect we need to exit interlocked mode
//
LeaveInterlockedPresentation();
ArrayList targets = new ArrayList();
_isDisconnecting = true;
_isConnected = false;
//
// In order to ensure correctly ordered processing of all queued
// RemoveChild and Release commands, we need to flush these queues
// for all channels here. If we don't, we can arrive at a situation
// in which there are RemoveAndReleaseCommands queued for the root visual
// of a CompositionTarget which has already been released from the channel
// the below calls to ICompositionTarget.ReleaseOnChannel(). This can
// occur when we get a style changing message followed by a disconnect
// message, but could potentially occur in other situations where
// we modify the tree and then don't render before processing a
// disconnect message.
//
_channelManager.FlushAllRemoveReleaseQueues();
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
ICompositionTarget iv = ((ICompositionTarget)(entry.Key));
targets.Add(iv);
}
DUCE.ChannelSet channelSet = GetChannels();
foreach (ICompositionTarget iv in targets)
{
iv.ReleaseOnChannel(channelSet.Channel, channelSet.OutOfBandChannel);
}
_isDisconnecting = false;
// shutdown all media context channels.
RemoveChannels();
return null;
}
///
/// This is the standard RenderMessageHandler callback, posted via PostRender()
/// and Resize(). This wraps RenderMessageHandlerCore and emits an ETW events
/// to trace its execution.
///
internal object RenderMessageHandler(
object resizedCompositionTarget /* can be null if we are not resizing*/
)
{
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.RENDERHANDLERGUID),
MS.Utility.EventType.StartEvent,
Dispatcher.GetHashCode());
}
//Console.WriteLine(string.Format("RenderMessageHandler {0}", queueItemID++));
RenderMessageHandlerCore(resizedCompositionTarget);
EventTrace.NormalTraceEvent(EventTraceGuidId.RENDERHANDLERGUID, MS.Utility.EventType.EndEvent);
return null;
}
///
/// This is the RenderMessageHandler callback posted by RenderMessageHandlerCore
/// when animations are active
/// This wraps RenderMessageHandlerCore and emits an ETW event to signify that
/// the Render Message being handled is processing an animation.
///
internal object AnimatedRenderMessageHandler(
object resizedCompositionTarget /* can be null if we are not resizing*/
)
{
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.ANIMRENDERHANDLERGUID),
MS.Utility.EventType.StartEvent,
Dispatcher.GetHashCode());
}
RenderMessageHandlerCore(resizedCompositionTarget);
EventTrace.NormalTraceEvent(EventTraceGuidId.ANIMRENDERHANDLERGUID, MS.Utility.EventType.EndEvent);
return null;
}
///
/// This handles the _inputMarkerOp message. We're using
/// _inputMarkerOp to determine if input priority dispatcher ops
/// have been processes.
///
internal object InputMarkerMessageHandler(object arg)
{
//set the marker to null so we know that input priority has been processed
_inputMarkerOp = null;
return null;
}
//static int queueItemID;
///
/// The ol' RenderQueueItem.
///
///
/// Critical: Since it calls to InputManager.UnsecureCurrent
/// TreatAsSafe: Since it does not expose the InputManager
///
[SecurityCritical,SecurityTreatAsSafe]
private void RenderMessageHandlerCore(
object resizedCompositionTarget /* can be null if we are not resizing*/
)
{
// if the media system is disconnected bail.
if (Channel == null)
{
return;
}
Debug.Assert(CheckAccess());
Debug.Assert(
(resizedCompositionTarget == null) ||
(resizedCompositionTarget is ICompositionTarget));
_isRendering = true;
// We don't need our promotion timers anymore.
_promoteRenderOpToInput.Stop();
_promoteRenderOpToRender.Stop();
bool gotException = true;
try
{
int tickLoopCount = 0;
do
{
tickLoopCount++;
if (tickLoopCount > 153)
{
throw new InvalidOperationException(SR.Get(SRID.MediaContext_InfiniteTickLoop));
}
_timeManager.Tick();
// Although the timing tree is now clean, during layout
// more animations may be added, in which case we must tick
// again to prevent "first frame" problems. If we do tick
// again we want to do so at the same time as before, until
// we are done. To that end, lock the tick time to the
// first tick. Note that Lock/Unlock aren't counted, so we
// can call Lock inside the loop and still safely call
// Unlock just once after we are done.
_timeManager.LockTickTime();
// call all render callbacks
FireInvokeOnRenderCallbacks();
// signal that the frame has been updated and we are ready to render.
// only fire on the first iteration
if (Rendering != null && tickLoopCount==1)
{
// The RenderingEventArgs class stores the next estimated presentation time.
// Since the TimeManager has just ticked, LastTickTime is exactly this time.
// (TimeManager gets its tick time from MediaContext's IClock implementation).
// In the case where we can't query QPC or aren't doing interlocked presents,
// this will be equal to the current time, which is a good enough approximation.
Rendering(this.Dispatcher, new RenderingEventArgs(_timeManager.LastTickTime));
// call all render callbacks again in case the Rendering event affects layout
// this will enable layout effecting changes to get triggered this frame
FireInvokeOnRenderCallbacks();
}
}
while (_timeManager.IsDirty);
_timeManager.UnlockTickTime();
// Invalidate the input devices on the InputManager
InputManager.UnsecureCurrent.InvalidateInputDevices();
//
// before we call Render we want to save the in Interlock state so we know
// if we need to schedule another render or not. This is because we want to render
// while the UCE is working on rendering the previous frame. To do this we would like to get into
// a NotifyPresent, CommitChannel, Render pattern. If the interlock state is not
// "Waiting" at this point, then we must be in a Render, CommitChannel pattern
// and we will need to do something in order to get us back in parallel operation
//
bool interlockWasNotWaiting = !InterlockIsWaiting;
//
// This is the big Render!
//
// We've now updated timing and layout and the updated scene will be sent
// to the UCE.
//
Render((ICompositionTarget)resizedCompositionTarget);
//
// We've processed the currentRenderOp so clear it
//
if (_currentRenderOp != null)
{
_currentRenderOp.Abort();
_currentRenderOp = null;
}
if (!InterlockIsEnabled)
{
//
// Schedule our next rendering operation. We want to introduce
// a minimum delay, so that we don't overrun the composition
// thread
//
ScheduleNextRenderOp(_timeDelay);
}
else if (interlockWasNotWaiting)
{
//
// schedule another render because we were in the Render, CommitChannel
// pattern, and this will get us back in the CommitChannel, Render pattern so
// we will have the channel full when notification comes back from the UCE.
//
ScheduleNextRenderOp(TimeSpan.Zero);
}
gotException = false;
}
finally
{
// Reset current operation so it can be re-queued by layout
// This is needed when exception happens in the midst of layout/TemplateExpansion
// and it unwinds from the stack. If we don't clean this field here, the subsequent
// PostRender won't queue new render operation and the window gets stuck. Bug 1355561.
if (gotException
&& _currentRenderOp != null)
{
_currentRenderOp.Abort();
_currentRenderOp = null;
}
_isRendering = false;
}
}
///
/// Calls all _invokeOnRenderCallbacks until no more are added
///
private void FireInvokeOnRenderCallbacks()
{
int callbackLoopCount = 0;
int count = _invokeOnRenderCallbacks != null ? _invokeOnRenderCallbacks.Count : 0;
// This outer loop is to re-run layout in case the app causes a layout to get enqueued in response
// to a Loaded event. In this case we would like to re-run layout before we allow render.
do
{
while (count > 0)
{
callbackLoopCount++;
if (callbackLoopCount > 153)
{
throw new InvalidOperationException(SR.Get(SRID.MediaContext_InfiniteLayoutLoop));
}
InvokeOnRenderCallback[] callbacks = new InvokeOnRenderCallback[count];
_invokeOnRenderCallbacks.CopyTo(callbacks);
_invokeOnRenderCallbacks.Clear();
for (int i = 0; i < count; i++)
{
callbacks[i].DoWork();
}
count = _invokeOnRenderCallbacks.Count;
}
// Fire all the pending Loaded events before Render happens
// but after the layout storm has subsided
FireLoadedPendingCallbacks();
count = _invokeOnRenderCallbacks != null ? _invokeOnRenderCallbacks.Count : 0;
}
while (count > 0);
}
///
/// Fire all the pending Loaded callbacks before Render happens
///
private void FireLoadedPendingCallbacks()
{
// Fire all the pending Loaded events before Render happens but after layout
if (_loadedOrUnloadedPendingOperations != null && _loadedOrUnloadedPendingOperations.Count > 0)
{
// Create a copy of the _loadedOrUnloadedPendingOperations
LoadedOrUnloadedOperation[] copyOfPendingCallbacks = new LoadedOrUnloadedOperation[_loadedOrUnloadedPendingOperations.Count];
_loadedOrUnloadedPendingOperations.CopyTo(copyOfPendingCallbacks, 0);
// Clear up the _loadedOrUnloadedPendingOperations in case the broadcast of Loaded causes
// more of the pending operations to get posted.
_loadedOrUnloadedPendingOperations.Clear();
// Iterate and fire all the pending loaded operations
for (int i=0; i
/// Render all registered ICompositionTargets.
///
///
/// * We have to render all visual targets on the same context at once. The reason for this is that
/// we batch per Dispatcher (we use the context to get to the batch all over the place).
/// * On a WM_SIZE we also need to render because USER32 is sitting in a tight loop sending us messages
/// continously. Hence we need to render all visual trees attached to visual targets otherwise we
/// would submit a batch that has only part of the changes for some visual trees. This would cause
/// structural tearing.
///
internal void Render(ICompositionTarget resizedCompositionTarget)
{
// resizedCompositionTarget is the HwndTarget that is currently being resized.
//
// Disable reentrancy during the Render pass. This is because much work is done
// during Render and we cannot survive reentrancy in these code paths.
// Disabling processing will prevent the lock() statement from pumping messages,
// so we don’t run the risk of having to process an unrelated message in the middle
// of this code. Message pumping will resume sometime after we return.
//
// Note: The possible downside of DisableProcessing is
// 1) Cross-Apartment COM calls may deadlock.
// 2) We restrict what people can do in callbacks ie, they can’t display a message box.
//
using (Dispatcher.DisableProcessing())
{
Debug.Assert(CheckAccess());
Debug.Assert(!_isDisposed);
Debug.Assert(_registeredICompositionTargets != null);
Debug.Assert(_tokensForRegistration == null);
bool anyTreeContainsGraphness = false;
RemoveChannel(_channelManager.Channel);
_tokensForRegistration = new ArrayList();
_tokensForUnRegistration = new ArrayList();
// ETW event tracing
bool etwTracingEnabled = false;
uint renderID = (uint)Interlocked.Increment(ref _contextRenderID);
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
etwTracingEnabled = true;
DUCE.ETWEvent.RaiseEvent(
_uceEtwEvent.Handle,
renderID,
Channel);
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.MEDIARENDERGUID),
MS.Utility.EventType.StartEvent,
renderID,
TicksToCounts(_estimatedNextPresentationTime.Ticks)
);
}
// ---------------------------------------------------------------
// 1) Render each registered ICompositionTarget to finish up the batch.
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
ICompositionTarget registeredTarget = ((ICompositionTarget)(entry.Key));
DUCE.ChannelSet channelSet;
channelSet.Channel = _channelManager.Channel;
channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel;
_currentRenderingChannel = channelSet;
registeredTarget.Render((registeredTarget == resizedCompositionTarget), channelSet.Channel);
_currentRenderingChannel = null;
anyTreeContainsGraphness |= registeredTarget.VisualTreeContainsGraphness();
}
// process all the visual brush registration requests we picked up during the render walk.
foreach (ICompositionTarget iv in _tokensForRegistration)
{
_registeredICompositionTargets.Add(iv, null); // We use the hashtable just as a set.
}
// process all the visual brush unregistration requests we picked up during the render walk.
foreach (ICompositionTarget iv in _tokensForUnRegistration)
{
_registeredICompositionTargets.Remove(iv);
}
_tokensForRegistration = null;
_tokensForUnRegistration = null;
if (IsConnected)
{
// ----------------------------------------------------------------
// 2) Get the cached realization context if possible, otherwise
// create a new one. Note that we null out the cached context
// field. This means that in failure cases we will always
// recreate the realization context.
//
//
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UPDATEREALIZATIONSGUID),
MS.Utility.EventType.StartEvent);
}
RealizationContext realizationContext = null;
if (_cachedRealizationContext != null)
{
realizationContext = _cachedRealizationContext;
_cachedRealizationContext = null;
}
else
{
realizationContext = new RealizationContext();
}
// ---------------------------------------------------------------
// 3) Prepare the realization context.
//
// Can only walk incrementally if we haven't encountered graphness in the tree
// and don't have any transform hints. Transform hints can be considered another
// form of graphness for text because they produce multiple realizations for
// each glyphrun
//
anyTreeContainsGraphness |= (_transformHints.Count > 0);
realizationContext.BeginFrame(!anyTreeContainsGraphness /* full walk */,
false /* walk for BitmapRenderTarget */);
// ----------------------------------------------------------------
// 4) Mark visible realizations for top-level visual targets.
foreach (DictionaryEntry entry in _registeredICompositionTargets)
{
CompositionTarget vt = ((ICompositionTarget)(entry.Key)) as CompositionTarget;
if (vt != null)
{
DUCE.ChannelSet targetChannels = GetChannels();
realizationContext.ChannelSet = targetChannels;
realizationContext.WindowClip = vt.WorldClipBounds;
vt.MarkVisibleRealizations(realizationContext);
}
}
// ----------------------------------------------------------------
// 5) Execute the scheduled realization updates.
realizationContext.ExecuteRealizationsUpdateSchedule();
// -----------------------------------------------------------
// 6) Now cache the realization context.
// Note that EndFrame call is required for caching the
// realization context. However, before calling endframe all
// realization context stacks must be empty. If they are not
// empty at this point our code has a serious bug.
realizationContext.EndFrame();
Debug.Assert(_cachedRealizationContext == null);
_cachedRealizationContext = realizationContext;
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UPDATEREALIZATIONSGUID),
MS.Utility.EventType.EndEvent);
}
}
// ----------------------------------------------------------------
// 7) Update any resources that need to be updated for this render.
RaiseResourcesUpdated();
//
// 8) Commit the channel.
//
// if we are not already waiting for a present then commit the
// channel at this time. If we are waiting for a present then we
// will wait until we have presented before committing this channel
//
_needToCommitChannel = true;
_commitPendingAfterRender = true;
if (!InterlockIsWaiting)
{
// Debug.WriteLine(string.Format("{0}, Committing the channel after render", CurrentTimeInMs));
//if we've already commit during this vblank interval, dont do it again
// because it will cause the DWM to stall
if (HasCommittedThisVBlankInterval)
{
CommitChannelAfterNextVSync();
}
else
{
CommitChannel();
}
}
// ---------------------------------------------------------------
// 9) Raise RenderComplete event.
if (etwTracingEnabled)
{
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.MEDIARENDERGUID),
MS.Utility.EventType.EndEvent
);
// trace the UI Response event
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.UIRESPONSEGUID),
MS.Utility.EventType.Info,
GetHashCode(),
renderID);
}
}
}
///
/// Commit the current channel to the composition thread.
///
///
/// This allows us to separate updating the visual tree from sending
/// data to the UCE. When in InterlockedPresentation mode, we'll always
/// have layout properly updated but we'll only commit 1 render per
/// frame to the composition.
///
private void CommitChannel()
{
// if we get render messages posted while we are disconnected we don't have a channel.
if (Channel != null)
{
Debug.Assert(_needToCommitChannel, "CommitChannel called with nothing on the channel");
if (InterlockIsEnabled)
{
//Console.WriteLine("Requesting Presentation Notification");
Debug.Assert(!InterlockIsWaiting,
"We can't be committing the channel while waiting for a notification");
long currentTicks = CurrentTicks;
long presentationTime = _estimatedNextPresentationTime.Ticks;
//
// it is possible that presentationTime is in the past, if we request this time
// we will get an immediate NotifyPresent instead of getting one after the next
// vblank. To prevent this we ensure the presentaitonTime is no earlier than the
// next VBlank time.
//
if (_animationRenderRate > 0)
{
long nextVBlank = currentTicks + TicksUntilNextVsync(currentTicks);
if (nextVBlank > presentationTime)
{
presentationTime = nextVBlank;
}
}
RequestPresentedNotification(Channel, TicksToCounts(presentationTime));
//
// If we are in interlocked presentation mode then we enter
// a wait state once we commit the channel.
//
_interlockState = InterlockState.WaitingForResponse;
_lastCommitTime = currentTicks;
}
//Console.WriteLine("Committing Channel");
if (CommittingBatch != null)
{
CommittingBatch(Channel, new EventArgs());
}
Channel.Commit();
if (_commitPendingAfterRender)
{
//
// Raise Render Complete event since a Render happened and
// the commit for that render happened.
//
if (_renderCompleteHandlers != null)
{
_renderCompleteHandlers(this, null);
}
_commitPendingAfterRender = false;
}
//
// The channel has just been commited. There's nothing more in it.
//
if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
{
// The payload data for this event is the render ID of the frame we're committing.
EventTrace.EventProvider.TraceEvent(
EventTrace.GuidFromId(EventTraceGuidId.COMMITCHANNELGUID),
MS.Utility.EventType.Info,
_contextRenderID
);
}
}
_needToCommitChannel = false;
}
///
/// Asks the composition engine to notify us once the frame we are
/// submitted has been presented to the screen.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
/// Sending a message to a channel is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
private void RequestPresentedNotification(DUCE.Channel channel, long estimatedFrameTime)
{
Debug.Assert(InterlockIsEnabled,
"Cannot request presentation notification unless interlock mode is enabled");
unsafe
{
DUCE.MILCMD_PARTITION_NOTIFYPRESENT data;
data.Type = MILCMD.MilCmdPartitionNotifyPresent;
data.FrameTime = (ulong) estimatedFrameTime;
channel.SendCommand(
(byte*)&data,
sizeof(DUCE.MILCMD_PARTITION_NOTIFYPRESENT)
);
}
}
///
/// CompleteRender
///
///
/// Wait for the rendering loop to finish any pending instructions.
///
///
/// Critical - Calls one of two critical methods: WaitForNextMessage
/// or MilComposition_SyncFlush.
/// TreatAsSafe - Net effect is to wait until render completes. Waiting
/// is safe because we only block the thread if we know
/// we are waiting for a message. Flushing the channel is
/// safe because we own the channel and we know there
/// aren't unfinished batches at this point.
///
[SecurityCritical, SecurityTreatAsSafe]
internal void CompleteRender()
{
// for now just bail if we are not connected.
if (Channel != null)
{
//
// In intelocked mode in order to make sure that frames are
// fully updated to the screen we need to do the folloing:
// 1. If we are waiting for a response from the UCE, then wait
// until we get that response.
// 2. If we had pending operations then flush the channel and
// wait for the notification that this new frame has been
// processed by the composition.
//
if (InterlockIsEnabled)
{
//Console.WriteLine("CompleteRender: We are in InterlockedMode: {0}", _interlockState.ToString());
if (_interlockState == InterlockState.WaitingForResponse)
{
//Console.WriteLine("CompleteRender waiting for previous present");
do
{
Channel.WaitForNextMessage();
NotifyChannelMessage();
} while (_interlockState == InterlockState.WaitingForResponse);
}
//
// We might have started a timer to wait for the next frame.
// stop it now and go back to idle state
//
_estimatedNextVSyncTimer.Stop();
_interlockState = InterlockState.Idle;
if (_needToCommitChannel)
{
//Console.WriteLine("CompleteRender: committing channel");
CommitChannel();
if (_interlockState == InterlockState.WaitingForResponse)
{
//Console.WriteLine("CompleteRender waiting for present");
do
{
Channel.WaitForNextMessage();
NotifyChannelMessage();
} while (_interlockState == InterlockState.WaitingForResponse);
//
// We might have started a timer to wait for the next frame.
// stop it now and go back to idle state
//
_estimatedNextVSyncTimer.Stop();
_interlockState = InterlockState.Idle;
}
}
}
else
{
//Console.WriteLine("CompleteRender waiting for [....] flush");
//
// Issue a [....] flush, which will only return after
// the last frame is presented
//
Channel.SyncFlush();
}
}
}
///
/// This function is registered with the MediaContext's TimeManager. It is called whenever
/// a clock managed by the TimeManager goes active, but only if there hasn't been already an
/// active clock. For now we start the animation thread in there.
///
private void OnNeedTickSooner(object sender, EventArgs e)
{
PostRender();
}
///
/// Checks if the current context can request the specified permissions.
///
internal void VerifyWriteAccess()
{
if (!WriteAccessEnabled)
{
throw new InvalidOperationException(SR.Get(SRID.MediaContext_APINotAllowed));
}
}
///
/// Returns false if the MediaContext is currently read-only
///
internal bool WriteAccessEnabled
{
get { return _readOnlyAccessCounter <= 0; }
}
///
/// Methods to lock down the Visual tree for write access.
///
internal void PushReadOnlyAccess()
{
_readOnlyAccessCounter++;
}
internal void PopReadOnlyAccess()
{
_readOnlyAccessCounter--;
}
///
/// Each MediaContext is associated with a TimeManager. The TimeManager is shared by all ICompositionTargets.
///
private TimeManager _timeManager;
internal TimeManager TimeManager
{
get
{
return _timeManager;
}
}
///
/// RenderComplete event is fired when Render method commits the channel.
/// This is used for ink transition. Currently this event is internal and will
/// be accessed using reflection until proper object model is defined.
///
internal event EventHandler RenderComplete
{
add
{
//
// If the Render happened (i.e. flag is true) and corresponding Commit
// has not happened, then set the flag to false, since the next
// Commit will not have the changes after Render and before +RenderComplete.
// In other words, consider this event sequence:-
// 1. Render
// 2. Some resource property changes (eg. visual.Clip = null)
// 3. +RenderComplete
// 4. Commit
// Then the 4th Commit will not have 2's changes(as it requires a
// new Render pass) and so raising the event is wrong.
//
// Note: If the event is added every time in the middle of Render
// and Commit, then RenderComplete will starve.
//
if (_commitPendingAfterRender)
{
_commitPendingAfterRender = false;
}
_renderCompleteHandlers += value;
}
remove
{
_renderCompleteHandlers -= value;
}
}
///
/// ResourcesUpdatedHandler - This event handler prototype defines the callback
/// for our async update callback in the ResourcesUpdated Event.
/// The method which implements this prototype is also often called in situations where
/// the resource is known to be "on channel" - in those cases, "true" is passed for the second
/// parameter (allowing the implementation to skip the check).
///
internal delegate void ResourcesUpdatedHandler(DUCE.Channel channel, bool skipOnChannelCheck);
internal event ResourcesUpdatedHandler ResourcesUpdated
{
add
{
_resourcesUpdatedHandlers += value;
}
remove
{
_resourcesUpdatedHandlers -= value;
}
}
private void RaiseResourcesUpdated()
{
if (_resourcesUpdatedHandlers != null)
{
DUCE.ChannelSet channelSet = GetChannels();
_resourcesUpdatedHandlers(channelSet.Channel, false /* do not skip the "on channel" check */);
_resourcesUpdatedHandlers = null;
}
}
///
/// Create a fresh or fetch one from the pool synchronous channel.
///
internal DUCE.Channel AllocateSyncChannel()
{
return _channelManager.AllocateSyncChannel();
}
///
/// Returns a [....] channel back to the pool.
///
internal void ReleaseSyncChannel(DUCE.Channel channel)
{
_channelManager.ReleaseSyncChannel(channel);
}
///
/// Returns the asynchronous channel for this media context.
///
///
/// This property is deprecated and scheduled to be removed as per task #26681.
/// Please do not create additional dependencies on it.
///
internal DUCE.Channel Channel
{
get
{
return _channelManager.Channel;
}
}
///
/// Returns the asynchronous out-of-band channel for this media context.
///
internal DUCE.Channel OutOfBandChannel
{
get
{
return _channelManager.OutOfBandChannel;
}
}
internal bool IsConnected
{
get
{
return _isConnected;
}
}
///
/// Returns the BoundsDrawingContextWalker for this media context.
/// To handle reentrance we want to make sure that
/// no one else on the same thread gets the same context.
///
internal BoundsDrawingContextWalker AcquireBoundsDrawingContextWalker()
{
if (_cachedBoundsDrawingContextWalker == null)
{
return new BoundsDrawingContextWalker();
}
BoundsDrawingContextWalker ctx = _cachedBoundsDrawingContextWalker;
_cachedBoundsDrawingContextWalker = null;
ctx.ClearState();
return ctx;
}
///
/// Set the BoundsDrawingContextWalker for next use
/// To handle reentrance we want to make sure that
/// no one else on the same thread gets the same context.
///
internal void ReleaseBoundsDrawingContextWalker(BoundsDrawingContextWalker ctx)
{
_cachedBoundsDrawingContextWalker = ctx;
}
///
/// Describes whether any bitmap effects have been
/// used in the scene. This value can not be unset,
/// and is used to propagate realization updates when
/// offset changes occur.
///
internal bool BitmapEffectsUsed
{
get
{
return _bitmapEffectsUsed;
}
set
{
_bitmapEffectsUsed |= value;
}
}
private void PromoteRenderOpToInput(object sender, EventArgs e)
{
//Dispatcher.WriteLineWithIndent("PromoteRenderOpToInput...");
//Dispatcher.Indent+=2;
// Debug.WriteLine(String.Format("{0}, Promoting render operation to input", CurrentTimeInMs));
if(_currentRenderOp != null)
{
//Dispatcher.WriteLineWithIndent("Promoting to Input...");
//Dispatcher.Indent+=2;
_currentRenderOp.Priority = DispatcherPriority.Input;
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Dispatcher.WriteLineWithIndent("Nothing to promote");
}
((DispatcherTimer)sender).Stop();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
private void PromoteRenderOpToRender(object sender, EventArgs e)
{
//Dispatcher.WriteLineWithIndent("PromoteRenderOpToRender...");
//Dispatcher.Indent+=2;
if(_currentRenderOp != null)
{
//Dispatcher.WriteLineWithIndent("Promoting to Render...");
//Dispatcher.Indent+=2;
_currentRenderOp.Priority = DispatcherPriority.Render;
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
else
{
//Dispatcher.WriteLineWithIndent("Nothing to promote");
}
((DispatcherTimer)sender).Stop();
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
///
/// We setup a timer when the UCE doesn't present a frame because the
/// scene hasn't changed. When this timer triggers, we have passed the
/// estimated time at which the predicted VSync should have occured.
/// We can now commit our accumulated changes to the channel. This
/// timer should only be active if we got a NoPresent notification from
/// the composition thread.
///
private void EstimatedNextVSyncTimeExpired(object sender, EventArgs e)
{
//Dispatcher.WriteLineWithIndent("CommitRenderChannel...");
//Dispatcher.Indent+=2;
Debug.Assert(_interlockState == InterlockState.WaitingForNextFrame
&& _lastPresentationResults == MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_NOPRESENT,
"CommitRenderChannel timer should only be trigger while waiting for the frame to expire");
//
// if we wake up before our earliest wakup time, we run the risk of commiting twice in one
// vblank interval. This is mitigated by detecting the early wakup and creating a new timer
// for the remaining time. This could cause us to skip a vblank if our new timer wakes up too
// late, but this is seens as a better alternative to commiting twice in one vblank interval.
// A better solution would be to have some form of high resolution timer.
//
long currentTicks = CurrentTicks;
DispatcherTimer timer = ((DispatcherTimer)sender);
long earliestWakeupTicks = 0;
if(timer.Tag != null)
earliestWakeupTicks = (long)timer.Tag;
if (earliestWakeupTicks > currentTicks)
{
timer.Stop();
timer.Interval = TimeSpan.FromTicks(earliestWakeupTicks - currentTicks);
timer.Start();
return;
}
_interlockState = InterlockState.Idle;
if (_needToCommitChannel)
{
//Dispatcher.WriteLineWithIndent("Committing the current channel...");
//Dispatcher.Indent+=2;
CommitChannel();
//schedule the next render so we're back on the np-commit-render pattern
ScheduleNextRenderOp(TimeSpan.Zero);
// Debug.WriteLine(String.Format("{0}, Next Vsync timer expired; commit the channel", CurrentTimeInMs));
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
timer.Stop();
// Console.WriteLine("Timer expired. Commited Render");
//Dispatcher.WriteLineWithIndent(".");
//Dispatcher.Indent-=2;
}
internal ArrayList TransformHints
{
get { return _transformHints; }
}
//+---------------------------------------------------------------------
//
// Private Methods
//
//---------------------------------------------------------------------
#region Private Methods
///
/// Tells the composition engine that we want to receive asynchronous
/// notifications on this channel.
///
///
/// Critical - Contains an unsafe code block.
/// TreatAsSafe - Unsafe block just uses the sizeof operator.
/// Sending a message to a channel is safe.
///
[SecurityCritical, SecurityTreatAsSafe]
private void RegisterForNotifications(DUCE.Channel channel)
{
DUCE.MILCMD_PARTITION_REGISTERFORNOTIFICATIONS registerCmd;
registerCmd.Type = MILCMD.MilCmdPartitionRegisterForNotifications;
registerCmd.Enable = 1; // Enable notifications.
unsafe
{
channel.SendCommand(
(byte*)®isterCmd,
sizeof(DUCE.MILCMD_PARTITION_REGISTERFORNOTIFICATIONS)
);
}
}
#endregion Private Methods
//+----------------------------------------------------------------------
//
// Private Fields
//
//---------------------------------------------------------------------
#region Private Fields
///
/// Returns the current channel set for this MediaContext.
///
internal DUCE.ChannelSet GetChannels()
{
DUCE.ChannelSet channelSet;
channelSet.Channel = _channelManager.Channel;
channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel;
return channelSet;
}
///
/// The disposed flag indicates if the object got disposed.
///
private bool _isDisposed;
///
/// Event handler that is called when the context is destroyed.
///
private EventHandler _destroyHandler;
///
/// This event is raised when the MediaContext.Render has
/// committed the channel.
///
private event EventHandler _renderCompleteHandlers;
///
/// This event is raised when the MediaContext is ready for all of the
/// invalid resources to update their values.
///
private event ResourcesUpdatedHandler _resourcesUpdatedHandlers;
///
/// Use a guid to uniquely identify the current context. Note that
/// we can't use static data to generate a unique id since static
/// data is not shared across app domains and this id must be
/// truly unique.
///
private Guid _contextGuid;
///
/// Message delegate.
///
private DispatcherOperation _currentRenderOp;
private DispatcherOperation _inputMarkerOp;
private DispatcherOperationCallback _connectMessage;
private DispatcherOperationCallback _disconnectMessage;
private DispatcherOperationCallback _renderMessage;
private DispatcherOperationCallback _animRenderMessage;
private DispatcherOperationCallback _inputMarkerMessage;
private DispatcherTimer _promoteRenderOpToInput;
private DispatcherTimer _promoteRenderOpToRender;
///
/// This timer is used to keep track of when we should commit our
/// accumulated renders to the composition thread when the composition
/// thread hasn't presented our frame. If we didn't wait for this time
/// we might present a frame one vsync too early.
///
private DispatcherTimer _estimatedNextVSyncTimer;
///
/// The channel manager is a security wrapper for channel operations.
///
private ChannelManager _channelManager;
///
/// ETW Event Resource handle for performance tracing
///
private DUCE.Resource _uceEtwEvent = new DUCE.Resource();
///
/// Indicates that we are in the middle of processing a render message.
///
private bool _isRendering;
///
/// Indicates we are in the process of disconnecting.
/// This flag is set so that we know not to schedule an animation
/// render after the render pass we use to unmarshall resources.
///
private bool _isDisconnecting = false;
///
/// Indicates we are in a disconnected state. This flag is used by the
/// composition targets to determine if they need to DeleteCobsInSubgraph
/// (i.e. unmarshall the visual tree)
///
private bool _isConnected = false;
private ArrayList _invokeOnRenderCallbacks;
///
/// ArrayList of ICompositionTargets that are currently registered with the MediaSystem;
///
private System.Collections.Hashtable _registeredICompositionTargets;
private System.Collections.Hashtable _registeredLayeredWindows;
private ArrayList _tokensForRegistration;
private ArrayList _tokensForUnRegistration;
///
/// This are the the permissions the Context has to access Visual APIs.
///
private int _readOnlyAccessCounter;
// the cached realization context
private RealizationContext _cachedRealizationContext;
private BoundsDrawingContextWalker _cachedBoundsDrawingContextWalker = new BoundsDrawingContextWalker();
//
// The ID associated with a render dispatch. This is used to track
// renders, and is used by the realization cache to determine
// uniqueness of realizations, and across the UI and UCE threads
// in ETW trace events.
//
private static int _contextRenderID = 0;
//
// The HwndTarget that is handling channel notifications, if any
//
private HwndTarget _notificationHandler;
// The render tier associated with this MediaContext. This is updated
// when channels are created.
private int _tier;
///
/// Rendering event. Registers a delegate to be notified after animation and layout but before rendering
/// Its EventArgs parameter can be cast to RenderingEventArgs to get the last presentation time.
///
internal event EventHandler Rendering;
///
/// CommittingBatch event. Registers a delegate to be notified when a batch is
/// about to be committed to MIL.
///
internal event EventHandler CommittingBatch;
private ArrayList _transformHints = new ArrayList();
// List of pending loaded event dispatcher operations
private FrugalObjectList _loadedOrUnloadedPendingOperations;
// Time to wait for unthrottled renders
private TimeSpan _timeDelay = TimeSpan.FromMilliseconds(10);
// Describes if we currently do or have ever had targets containing any bitmap effects
private bool _bitmapEffectsUsed;
// A flag to determine if RenderComplete event is raised. We only
// raise the event if Render + Commit happens.
//
// Note: If the event is added every time in the middle of Render
// and Commit, then RenderComplete will starve.
private bool _commitPendingAfterRender;
// The top-level hidden notification window that is used to receive
// and forward broadcast messages
private MediaContextNotificationWindow _notificationWindow;
private bool _primaryChannelRemote;
private DUCE.ChannelSet? _currentRenderingChannel = null;
#endregion Private Fields
//+----------------------------------------------------------------------
//
// Animation Smoothing
//
//----------------------------------------------------------------------
#region Animation Smoothing
///
/// This enum indicates that we are in an interlocked presentation mode.
/// We render a frame. send it to be presented and the composition comes
/// to tell us that the frame was presented. Otherwise we just keep giving
/// frames to the composition thread and assume that they get presented
/// as requested.
///
private enum InterlockState
{
///
/// Interlock presentation mode is disabled. We send a frame to the
/// composition thread and simply schedule our next one when we know
/// that we have something to render
///
Disabled = 0,
///
/// Interlock presentation mode has requested a roundtrip message to
/// before enabling the mode. This state indicates that we've
/// requested the UCE enter the interlocked presentation mode but we
/// haven't received the response yet.
///
RequestedStart,
///
/// We are in interlocked presentation mode and are not waiting for
/// anything. If we get a render request we will process it
/// immediately
///
Idle,
///
/// We are in interlocked presentation mode and have sumitted a
/// frame to be presented. We are waiting for the notification from
/// the UCE thread.
///
WaitingForResponse,
///
/// We are in interlocked presentation mode but the last frame we
/// submitted to be presented wasn't presented. We are waiting until
/// the next VSync before submitting another frame for presentation.
///
WaitingForNextFrame
};
///
/// The current state of the interlocked presentation mode
///
private InterlockState _interlockState;
///
/// This indicates that we are waiting for something (either the next
/// frame time to occur or for a response from the UCE).
///
private bool InterlockIsWaiting
{
get
{
return (_interlockState == InterlockState.WaitingForNextFrame ||
_interlockState == InterlockState.WaitingForResponse);
}
}
///
/// We are currently in an interlocked presentation mode and the UCE
/// has acknolwedged that it is also in that mode.
///
private bool InterlockIsEnabled
{
get
{
return ( _interlockState != InterlockState.Disabled
&& _interlockState != InterlockState.RequestedStart);
}
}
///
/// This is used in interlocked presentation mode. This flag indicates
/// that we've put something on the channel but that we haven't commited
/// it yet. This occurs when we know that the composition thread is already
/// processing a batch for a frame. We only want to give the UCE 1 frame
/// per VSync so that we try to make it present the frame at the time
/// that we have estimated. At this point we have rendered 1 frame in
/// advance and will wait until we reach the next frame boundary before
/// committing the channel to have the information sent to the
/// composition thread on the right frame.
///
private bool _needToCommitChannel;
///
/// Last time the composition presented a frame
/// Units: counts
///
private long _lastPresentationTime;
///
/// Last time the UI thread committed a frame
/// Units: Ticks
///
private long _lastCommitTime;
///
/// Last time the the input marker was added to the queue
/// Units: Ticks
///
private long _lastInputMarkerTime;
///
/// Average time it takes the UCE to present a frame
/// Units: Ticks
///
private long _averagePresentationInterval;
///
/// Estimation of the next time we want a frame to appear on screen. We
/// will set the TimeManager's time to this to have the animations look
/// smooth
///
private TimeSpan _estimatedNextPresentationTime;
///
/// The refresh rate of the monitor that we are displaying to
///
private int _displayRefreshRate;
///
/// The rate at which we try to display content if no special throttling
/// mechanism is used
///
private int _adjustedRefreshRate;
///
/// The rate at which we are rendering animations. This can be the
/// refresh rate of the monitor that we are presenting on or can be
/// overridden with a DesiredFrameRate on the Timeline. This is
/// used to estimate the time of the next frame that we want to present.
///
private int _animationRenderRate;
///
/// The results of the last present call.
///
private MIL_PRESENTATION_RESULTS _lastPresentationResults = MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED;
static private long _perfCounterFreq;
private const long MaxTicksWithoutInput = TimeSpan.TicksPerSecond / 2;
#endregion Animation Smoothing
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- KeyConverter.cs
- Matrix3D.cs
- AssemblyInfo.cs
- RemotingConfiguration.cs
- SQLInt64Storage.cs
- ArrayListCollectionBase.cs
- MexHttpBindingCollectionElement.cs
- HttpSysSettings.cs
- MailWriter.cs
- PeerNameRecordCollection.cs
- ContentPresenter.cs
- UIElementPropertyUndoUnit.cs
- QueuePropertyVariants.cs
- CustomErrorsSectionWrapper.cs
- SmiGettersStream.cs
- HitTestResult.cs
- LeftCellWrapper.cs
- PolicyLevel.cs
- ConnectionStringsSection.cs
- CatalogPartChrome.cs
- StylusPointPropertyInfo.cs
- TreeChangeInfo.cs
- HyperlinkAutomationPeer.cs
- DEREncoding.cs
- FloaterBaseParagraph.cs
- SystemNetHelpers.cs
- TypeDescriptionProviderAttribute.cs
- DynamicResourceExtensionConverter.cs
- QilInvokeLateBound.cs
- BamlTreeNode.cs
- BezierSegment.cs
- InternalPermissions.cs
- DataGridViewAutoSizeModeEventArgs.cs
- HttpGetProtocolReflector.cs
- ExceptQueryOperator.cs
- DataControlLinkButton.cs
- CustomError.cs
- EmulateRecognizeCompletedEventArgs.cs
- PolicyVersion.cs
- CLRBindingWorker.cs
- XmlSchemaAttributeGroup.cs
- TextPattern.cs
- XamlInterfaces.cs
- DnsPermission.cs
- HttpDictionary.cs
- AppDomainEvidenceFactory.cs
- FlowStep.cs
- ExceptionRoutedEventArgs.cs
- HttpListenerException.cs
- RemoveStoryboard.cs
- TextBoxRenderer.cs
- SiteIdentityPermission.cs
- XmlSchemaIdentityConstraint.cs
- ShapingWorkspace.cs
- TextDpi.cs
- WebPartDisplayModeEventArgs.cs
- SiteMapNodeCollection.cs
- StrongNameMembershipCondition.cs
- AssemblyCacheEntry.cs
- BamlBinaryReader.cs
- PenContexts.cs
- PersonalizationEntry.cs
- _ContextAwareResult.cs
- XmlDataDocument.cs
- TransformValueSerializer.cs
- XhtmlBasicObjectListAdapter.cs
- CompositionAdorner.cs
- Rotation3D.cs
- SafeCryptContextHandle.cs
- XmlBoundElement.cs
- MemberHolder.cs
- DrawToolTipEventArgs.cs
- TextBoxBase.cs
- ObjectCache.cs
- ListViewGroupItemCollection.cs
- Schema.cs
- Literal.cs
- SimpleWorkerRequest.cs
- SR.cs
- UpDownBaseDesigner.cs
- SetterBaseCollection.cs
- BitmapEffectDrawingContextWalker.cs
- InstanceCreationEditor.cs
- TableSectionStyle.cs
- ActivityStateRecord.cs
- PersonalizationAdministration.cs
- HashStream.cs
- LineServicesCallbacks.cs
- KnownTypeHelper.cs
- DescendentsWalker.cs
- DbResourceAllocator.cs
- StateMachineSubscription.cs
- MatrixStack.cs
- VirtualPathUtility.cs
- CommunicationObjectManager.cs
- DrawingGroup.cs
- HttpListenerContext.cs
- EntityParameter.cs
- TextEndOfSegment.cs
- CodeTypeOfExpression.cs