Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / System / Windows / Media / MediaContext.cs / 7 / 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 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 internal partial class MediaContext : DispatcherObject, IDisposable, IClock { ///to start up the media system and to /// shut down the media system. /// /// /// 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); _transformHintUpdateMessage = new DispatcherOperationCallback(TransformHintUpdateHandler); // 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 sync channels so that if the app handles the exception // it will get a new partition on the next sync 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: NotifySetTier(message.Caps.Caps.TierValue); 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; default: HandleInvalidPacketNotification(); break; } } } } ////// NotifySetTier - this method is called to update the render tier. /// If the new render tier is different from the previous render tier, /// this method will notify all listeners. /// /// int - the new render tier private void NotifySetTier(int tier) { if (_tier != tier) { _tier = tier; if (TierChanged != null) { TierChanged(null, null); } } } ////// 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 three 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 UCEERR_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() { // // } ////// Tier Property - returns the current render tier for this MediaContext. /// internal int Tier { get { return _tier; } } ////// 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 sync 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 // [....]: 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. ListtargetChannels = (_currentRenderingChannel.Count == 0) ? GetChannels(iv) : _currentRenderingChannel; foreach (DUCE.ChannelSet channelSet in targetChannels) { 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. ListtargetChannels = (_currentRenderingChannel.Count == 0) ? GetChannels(iv) : _currentRenderingChannel; foreach (DUCE.ChannelSet channelSet in targetChannels) { 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 void PostTransformHintUpdate() { Dispatcher.BeginInvoke(DispatcherPriority.Render, _transformHintUpdateMessage, 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) { ListtargetChannels = GetChannels(iv); foreach (DUCE.ChannelSet channelSet in targetChannels) { 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); } foreach (ICompositionTarget iv in targets) { List targetChannels = GetChannels(iv); foreach (DUCE.ChannelSet channelSet in targetChannels) { iv.ReleaseOnChannel(channelSet.Channel, channelSet.OutOfBandChannel); } } _isDisconnecting = false; // shutdown all media context channels. RemoveChannels(); return null; } internal object TransformHintUpdateHandler( object obj ) { // Copy the updated list of transfrom hints from MediaSystem MediaSystem.UpdateTransformHints(this); // Force realization update on each composition target registered // for this media context. ArrayList targets = new ArrayList(); foreach (DictionaryEntry entry in _registeredICompositionTargets) { ICompositionTarget iv = ((ICompositionTarget)(entry.Key)); targets.Add(iv); } foreach (ICompositionTarget iv in targets) { Resize(iv); } 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); RemoveChannel(_channelManager.LocalAsynchronousChannel); _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.Add(channelSet); registeredTarget.Render((registeredTarget == resizedCompositionTarget), channelSet.Channel); _currentRenderingChannel.Clear(); if (_registeredLayeredWindows.Contains(registeredTarget) && _channelManager.LocalAsynchronousChannel != null) { channelSet.Channel = _channelManager.LocalAsynchronousChannel; channelSet.OutOfBandChannel = _channelManager.LocalAsynchronousOutOfBandChannel; _currentRenderingChannel.Add(channelSet); registeredTarget.Render((registeredTarget == resizedCompositionTarget), channelSet.Channel); _currentRenderingChannel.Clear(); } 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) { ListtargetChannels = GetChannels(vt); realizationContext.Channels = 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"); Channel.Commit(); DUCE.Channel localAsyncChannel = _channelManager.LocalAsynchronousChannel; if (localAsyncChannel != null) { localAsyncChannel.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 sync flush"); // // Issue a sync 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) { // // We must do RaiseResourceUpdated for all channels // since we don't know here which channels the specific resources // are on, we just iterate for all channels, and the resource // must check which channels it is on (thus pass false for // skip on channel check flag) // Listchannels = GetAllChannels(); foreach (DUCE.ChannelSet channelSet in channels) { DUCE.Channel channel = channelSet.Channel; _resourcesUpdatedHandlers(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 sync 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 ////// Registers an ICompositionTarget as a layered window or being displayed /// on another ICompositionTarget which is layered. This information is used /// to correctly marshal the window and its content to the local asynchronous /// compositor in the remote session case. /// /// The ICompositionTarget to register internal bool AddLayeredWindow(ICompositionTarget ct) { // // If running in a remote session, need to AddRef the // composition target // if (_primaryChannelRemote) { // // May need to request a local channel if one doesn't exist yet // (eg this is the first time a layered window has been added // if (_channelManager.LocalAsynchronousChannel == null) { _channelManager.CreateLocalAsynchronousChannels(); } ct.AddRefOnChannel(_channelManager.LocalAsynchronousChannel, _channelManager.LocalAsynchronousOutOfBandChannel); } // // Regardless of whether this is a remote session, // register the window in our list as layered, because // someone could connect from a remote session at any time. // if (!_registeredLayeredWindows.Contains(ct)) { _registeredLayeredWindows.Add(ct, null); } return true; } ////// Unregisters an ICompositionTarget as a layered window or being displayed /// on another ICompositionTarget which is layered. This information is used /// to correctly marshal the window and its content to the local asynchronous /// compositor in the remote session case. /// /// The ICompositionTarget to unregister internal bool RemoveLayeredWindow(ICompositionTarget ct) { if (_registeredLayeredWindows.Contains(ct)) { _registeredLayeredWindows.Remove(ct); // // Found the ICompositionTarget // Release on channel if we're connected in a remote // session // if (_primaryChannelRemote) { ct.ReleaseOnChannel(_channelManager.LocalAsynchronousChannel, _channelManager.LocalAsynchronousOutOfBandChannel); // // Note we don't check to see if the channel is no longer // used and delete it, and this is deliberate. Keeping the channel // around is not expensive, and we may use it again if another // layered window is created. // } return true; } else { // // Couldn't find the ICompositionTarget, this is // not a serious error in itself, but could indicate // failure to correctly call AddLayeredWindow // return false; } } ////// Helper to create the ChannelSets /// /// Specifies whether to add the local asynchronous channel set (if it exists for that ICompositionTarget) private ListGetChannelsHelper(bool addLocalChannels) { List channels = new List (); DUCE.ChannelSet channelSet; channelSet.Channel = _channelManager.Channel; channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel; channels.Add(channelSet); if (addLocalChannels && (_channelManager.LocalAsynchronousChannel != null)) { DUCE.ChannelSet localChannels; localChannels.Channel = _channelManager.LocalAsynchronousChannel; localChannels.OutOfBandChannel = _channelManager.LocalAsynchronousOutOfBandChannel; channels.Add(localChannels); } return channels; } /// /// Gets the current sets of channels which a particular ICompositionTarget is connected to /// /// ICompositionTarget to get channels for. internal ListGetChannels(ICompositionTarget ct) { return GetChannelsHelper(_registeredLayeredWindows.Contains(ct)); } /// /// Gets all the current sets of channels in use by this MediaContext /// private ListGetAllChannels() { return GetChannelsHelper(true); } /// /// 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 _transformHintUpdateMessage; 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; 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 List _currentRenderingChannel = new List (); #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
- TreeViewImageIndexConverter.cs
- DataControlField.cs
- WeakReadOnlyCollection.cs
- PreloadedPackages.cs
- WebPartDeleteVerb.cs
- ColorConvertedBitmap.cs
- GZipDecoder.cs
- KoreanLunisolarCalendar.cs
- OperationPickerDialog.cs
- PersonalizationState.cs
- WebContext.cs
- ArcSegment.cs
- TreeView.cs
- TextComposition.cs
- NetSectionGroup.cs
- TransactionProtocolConverter.cs
- XmlTextEncoder.cs
- XmlUtf8RawTextWriter.cs
- TypeDelegator.cs
- WinHttpWebProxyFinder.cs
- JsonQNameDataContract.cs
- ProviderSettingsCollection.cs
- BinHexEncoder.cs
- ProcessModule.cs
- DataGridView.cs
- DataGridViewRowStateChangedEventArgs.cs
- SoapSchemaExporter.cs
- UnsafeNativeMethodsPenimc.cs
- GPRECT.cs
- ErrorTableItemStyle.cs
- ObjectConverter.cs
- DoubleUtil.cs
- TraceSwitch.cs
- ArrayHelper.cs
- TransformGroup.cs
- SurrogateEncoder.cs
- DelegatingConfigHost.cs
- GenericQueueSurrogate.cs
- RestClientProxyHandler.cs
- ParameterToken.cs
- CollectionsUtil.cs
- BoolExpr.cs
- EventMetadata.cs
- TemplateControlParser.cs
- InstanceContext.cs
- ImageMapEventArgs.cs
- SoapInteropTypes.cs
- IgnoreFlushAndCloseStream.cs
- LocalBuilder.cs
- NativeCompoundFileAPIs.cs
- ExpressionEditorAttribute.cs
- PeerSecurityManager.cs
- TrustLevel.cs
- DesigntimeLicenseContextSerializer.cs
- UriSection.cs
- WindowProviderWrapper.cs
- Geometry.cs
- ThemeDirectoryCompiler.cs
- ProgressBarBrushConverter.cs
- grammarelement.cs
- ColorMatrix.cs
- FontSource.cs
- PassportAuthenticationEventArgs.cs
- AbstractSvcMapFileLoader.cs
- PenLineCapValidation.cs
- IncrementalReadDecoders.cs
- SQLBytesStorage.cs
- TransformerInfoCollection.cs
- SortQuery.cs
- DescriptionAttribute.cs
- XmlElementAttributes.cs
- MemoryMappedViewStream.cs
- RegexNode.cs
- GridView.cs
- ConfigXmlWhitespace.cs
- XmlIncludeAttribute.cs
- AnonymousIdentificationModule.cs
- TreeNodeCollection.cs
- FileCodeGroup.cs
- EDesignUtil.cs
- ConfigurationStrings.cs
- CustomErrorsSection.cs
- SmtpFailedRecipientsException.cs
- ChineseLunisolarCalendar.cs
- DataGrid.cs
- dtdvalidator.cs
- ImageButton.cs
- PageStatePersister.cs
- KeyedCollection.cs
- XmlILAnnotation.cs
- PropertyOverridesDialog.cs
- QilScopedVisitor.cs
- BindingNavigator.cs
- ArraySet.cs
- AccessDataSource.cs
- ToolStripPanelRow.cs
- Descriptor.cs
- SchemaType.cs
- TypedElement.cs
- FormatVersion.cs