Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / System / Windows / Media / MediaContext.cs / 1477649 / MediaContext.cs
//------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // The MediaContext class controls the media layer. // //----------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Threading; using System.Windows; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Composition; using System.Security; using System.Windows.Media.Effects; using MS.Internal; using MS.Internal.PresentationCore; using MS.Utility; using MS.Win32; using Microsoft.Win32.SafeHandles; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Media { ////// The MediaContext class controls the media layer. /// ////// Use 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; } EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics, EventTrace.Event.WClientQPCFrequency, _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(); // Create a dictionary in which we manage the CompositionTargets. _registeredICompositionTargets = new Dictionary(); _renderModeMessage = new DispatcherOperationCallback(InvalidateRenderMode); // Create a notification window to listen for broadcast window messages _notificationWindow = new MediaContextNotificationWindow(this); // 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; _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); _commitPendingAfterRender = false; } /// /// Called by a message processor to notify us that our asynchronous /// channel has outstanding messages that need to be pumped. /// ////// Critical - Calls a critical method: PeekNextMessage. /// TreatAsSafe - Retrieving and processing a message from the back /// channel is safe -- the channel is owned by this /// media context. /// [SecurityCritical, SecurityTreatAsSafe] internal void NotifySyncChannelMessage(DUCE.Channel channel) { // empty the channel messages. DUCE.MilMessage.Message message; while (channel.PeekNextMessage(out message)) { switch (message.Type) { case DUCE.MilMessage.Type.Caps: case DUCE.MilMessage.Type.SyncModeStatus: case DUCE.MilMessage.Type.Presented: break; case DUCE.MilMessage.Type.PartitionIsZombie: // we remove the [....] channels so that if the app handles the exception // it will get a new partition on the next [....] render request. _channelManager.RemoveSyncChannels(); NotifyPartitionIsZombie(message.HRESULTFailure.HRESULTFailureCode); break; default: HandleInvalidPacketNotification(); break; } } } ////// Called by a message processor to notify us that our asynchronous /// channel has outstanding messages that need to be pumped. /// ////// Critical - Calls a critical method: PeekNextMessage. /// TreatAsSafe - Retrieving and processing a message from the back /// channel is safe -- the channel is owned by this /// media context. /// [SecurityCritical, SecurityTreatAsSafe] internal void NotifyChannelMessage() { // Since a notification message may sit in the queue while we // disconnect, we need to check that we actually have a channel // when we receive this notification. If not, there's no harm; // just skip the operation if (Channel != null) { DUCE.MilMessage.Message message; while (Channel.PeekNextMessage(out message)) { switch (message.Type) { case DUCE.MilMessage.Type.Caps: NotifySetCaps(message.Caps.Caps); break; case DUCE.MilMessage.Type.SyncModeStatus: NotifySyncModeStatus(message.SyncModeStatus.Enabled); break; case DUCE.MilMessage.Type.Presented: NotifyPresented( message.Presented.PresentationResults, message.Presented.PresentationTime, message.Presented.RefreshRate ); break; case DUCE.MilMessage.Type.PartitionIsZombie: NotifyPartitionIsZombie(message.HRESULTFailure.HRESULTFailureCode); break; case DUCE.MilMessage.Type.BadPixelShader: NotifyBadPixelShader(); break; default: HandleInvalidPacketNotification(); break; } } } } // MediaSystem is per-AppDomain and so it uses this to ensure that InvalidateRenderMode() // is called by the right thread. internal void PostInvalidateRenderMode() { Dispatcher.BeginInvoke(DispatcherPriority.Normal, _renderModeMessage, null); } ////// Tells all of the HwndTargets to InvalidateRenderMode() /// private object InvalidateRenderMode(object dontCare) { Debug.Assert(CheckAccess()); foreach (ICompositionTarget target in _registeredICompositionTargets.Keys) { HwndTarget hwndTarget = target as HwndTarget; if (hwndTarget != null) { hwndTarget.InvalidateRenderMode(); } } return null; } ////// NotifySetCaps - this method is called to update the graphics caps /// If the new render tier is different from the previous render tier, /// this method will notify all TierChanged listeners. /// /// int - the new render tier private void NotifySetCaps(MilGraphicsAccelerationCaps caps) { PixelShaderVersion = caps.PixelShaderVersion; MaxPixelShader30InstructionSlots = caps.MaxPixelShader30InstructionSlots; HasSSE2Support = Convert.ToBoolean(caps.HasSSE2Support); MaxTextureSize = new Size(caps.MaxTextureWidth, caps.MaxTextureHeight); int tier = caps.TierValue; if (_tier != tier) { _tier = tier; if (TierChanged != null) { TierChanged(null, null); } } } ////// Internal event which is raised when a bad pixel shader is detected on this MediaContext. /// internal event EventHandler InvalidPixelShaderEncountered; ////// NotifyBadPixelShader - this method is called when the render /// thread has detected a bad pixel shader. The render thread continues /// to raise this until the problem's been corrected. This method /// invokes the listeners on the static event in PixelShader. /// private void NotifyBadPixelShader() { if (InvalidPixelShaderEncountered != null) { InvalidPixelShaderEncountered(null, null); } else { // It's never correct to not have an event handler hooked up in // the case when an invalid shader is encountered. Raise an // exception directing the app to hook up an event handler. throw new InvalidOperationException(SR.Get(SRID.MediaContext_NoBadShaderHandler)); } } ////// The partition this media context is connected to went into /// zombie state. This means either an unhandled batch processing, /// rendering or presentation error and will require us to reconnect. /// private void NotifyPartitionIsZombie(int failureCode) { // // We only get back these kinds of notification:- // For all OOM cases, we get E_OUTOFMEMORY. // For all OOVM cases, we get D3DERR_OUTOFVIDEOMEMORY and // for all other errors we get WGXERR_UCE_RENDERTHREADFAILURE. // switch (failureCode) { case HRESULT.E_OUTOFMEMORY: throw new System.OutOfMemoryException(); case HRESULT.D3DERR_OUTOFVIDEOMEMORY: throw new System.OutOfMemoryException(SR.Get(SRID.MediaContext_OutOfVideoMemory)); default: throw new System.InvalidOperationException(SR.Get(SRID.MediaContext_RenderThreadError)); } } ////// The back channel processed a malformed packet and so gives /// the notification of invalid packet. /// private void HandleInvalidPacketNotification() { // // NTRAID#Longhorn-2006/05/02-pravirg - // For now we ignore the packet and continue processing // other packets. In future, we could also close this channel. // } ////// Tier Property - returns the current render tier for this MediaContext. /// internal int Tier { get { return _tier; } } ////// PixelShaderVersion Property - returns the current PixelShader /// (major<<16|minor) version /// internal UInt32 PixelShaderVersion { get; private set; } ////// MaxPixelShader30InstructionSlots Property - returns the max number of instruction /// slots for PS 3.0 /// internal UInt32 MaxPixelShader30InstructionSlots { get; private set; } ////// HasSSE2Support Property - returns true if the processor supports SSE2 instructions /// internal Boolean HasSSE2Support { get; private set; } ////// MaxTextureSize Property - returns the max texture width and height creatable by the /// underlying hardware. The API returns the minimum values across all available hardware devices. /// internal Size MaxTextureSize { get; private set; } ////// Internal event which is raised when the Tier changes on this MediaContext. /// internal event EventHandler TierChanged; ////// Asks the composition engine to retrieve the current hardware tier. /// This tier will be sent back via NotifyChannelMessage. /// ////// Critical - Contains an unsafe code block. /// TreatAsSafe - Unsafe block just uses the sizeof operator. /// Sending a message to a channel is safe. /// [SecurityCritical, SecurityTreatAsSafe] private void RequestTier(DUCE.Channel channel) { unsafe { DUCE.MILCMD_CHANNEL_REQUESTTIER data; // // Ask for the hardware tier information for the primary display // data.Type = MILCMD.MilCmdChannelRequestTier; data.ReturnCommonMinimum = 0; // false channel.SendCommand( (byte*)&data, sizeof(DUCE.MILCMD_CHANNEL_REQUESTTIER) ); } } ////// Schedule the next rendering operation based on our presentation /// mode and the next time we will have active animations. /// /// /// Specifies the minimum time before making the next rendering operation /// active /// private void ScheduleNextRenderOp(TimeSpan minimumDelay) { // // If _needToCommitChannel is true, then we are in a waiting state and we've // already rendered a new frame. We don't want to render again until we've // hit the next VSync at which point we'll commit the rendered frame. // Note that we will still render again if someone explicitely forces a // render (PostRender). When we do hit the next VSync and commit the channel // we will schedule the next render operation. // if (!_isDisconnecting && !_needToCommitChannel) { // // This is the time at which the next animation will be active // in ms. (a negative value represents no animations are active) // TimeSpan nextTickNeeded = TimeSpan.Zero; // // If we have one or more active Rendering events it's the same // as having an active animation so we know that we'll need to // render another frame. // if (Rendering == null) { nextTickNeeded = _timeManager.GetNextTickNeeded(); } // // 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) { _currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Inactive, _animRenderMessage, null); _promoteRenderOpToRender.Interval = nextTickNeeded; _promoteRenderOpToRender.Start(); } } // We need to tick soon (< 1 second) else 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(); } } else if (nextTickNeeded == TimeSpan.Zero) { 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(); } // // Trace the scheduling of the render // EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics, EventTrace.Event.WClientScheduleRender, nextTickNeeded.TotalMilliseconds); } } ////// 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. // // // 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; 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(); } 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; } break; } // Cap our Animation RenderRate to 1000 fps. _animationRenderRate = Math.Min(_animationRenderRate, 1000); // // Trace the notification from the UCE // EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientUceNotifyPresent, _lastPresentationTime, (Int64)presentationResults); 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"); } 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). /// internal static long CurrentTicks { get { long counts; SafeNativeMethods.QueryPerformanceCounter(out counts); return CountsToTicks(counts); } } // //private long CurrentTimeInMs //{ // get // { // return CurrentTicks / TimeSpan.TicksPerMillisecond; // } //} // ////// Returns the time in Ticks (100 ns intervals) between vsyncs. /// It's up to the caller to ensure that _animationRenderRate is valid. /// private long RefreshPeriod { get { return TimeSpan.TicksPerSecond / _animationRenderRate; } } ////// Computes the number of ticks since the last UCE present /// /// ///private long TicksSinceLastPresent(long currentTime) { return currentTime - CountsToTicks(_lastPresentationTime); } /// /// Estimates the time in Ticks (100 ns intervals) since the /// last vsync. It starts with the last presentation time /// and extrapolates based on the display refresh rate. /// private long TicksSinceLastVsync(long currentTime) { return TicksSinceLastPresent(currentTime) % RefreshPeriod; } ////// Estimates the number of ticks until the next vsync /// /// ///private long TicksUntilNextVsync(long currentTime) { return RefreshPeriod - TicksSinceLastVsync(currentTime); } /// /// Processes the SyncMode composition engine notification. /// /// /// The HRESULT of enabling [....] mode. /// private void NotifySyncModeStatus(int enabledResult) { // // Only process the notification if we asked to start interlocked // presentation mode // if (_interlockState == InterlockState.RequestedStart) { if (enabledResult >= 0) { // // We succeded in entering the interlocked mode in the // composition engine. // _interlockState = InterlockState.Idle; if (Channel != null) { // SyncFlush will Commit() if (CommittingBatch != null) { CommittingBatch(Channel, new EventArgs()); } 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 { _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() { _channelManager.CreateChannels(); 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(this, Channel, DUCE.ResourceType.TYPE_ETWEVENTRESOURCE); // Send a request for an updated render tier value RequestTier(Channel); Channel.CloseBatch(); 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(); } ////// 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), true); _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), true); // 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); // // This associates this channel with the given notification // window so that we can receive a window message whenever // there is a new message posted. // _notificationWindow.SetAsChannelNotificationWindow(); // // This actually populates the channel into the composition // engine so that it can receive notifications. // RegisterForNotifications(Channel); } ////// 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 dictionarys contents, because ICompositionTarget.Dispose modifies this collection. 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 Dictionary. This is why we don't iterate the Dictionary directly. foreach (ICompositionTarget iv in registeredVTs) { iv.Dispose(); } _registeredICompositionTargets = null; // Dispose the notification window _notificationWindow.Dispose(); // 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(); // 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); _timeManager = null; GC.SuppressFinalize(this); } } ////// Registers a new ICompositionTarget with the MediaSystem. /// /// Dispatcher with which the ICompositionTarget should be registered. /// The ICompositionTarget to register with the MediaSystem. internal static void RegisterICompositionTarget(Dispatcher dispatcher, ICompositionTarget iv) { Debug.Assert(dispatcher != null); Debug.Assert(iv != null); MediaContext current = From(dispatcher); current.RegisterICompositionTargetInternal(iv); } ////// Registers the ICompositionTarget from this MediaContext. /// /// private void RegisterICompositionTargetInternal(ICompositionTarget iv) { Debug.Assert(!_isDisposed); Debug.Assert(iv != null); // If channel is not available, we are in a disconnected state. // When connect handler is invoked for this media context, all // registered targets will be visited and AddRefChannel will be // called for them, so here we just skip the operation. if (Channel != null) { // if _currentRenderingChannel is nonempty, we're registering this ICompositionTarget // from within a render walk and it is thus a visualbrush, we need to add it to the // channel which we are currently rendering. If _currentRenderingChannel // is null, we just get the target channels for this ICompositionTarget and add // there. DUCE.ChannelSet channelSet = (_currentRenderingChannel == null) ? GetChannels() : _currentRenderingChannel.Value; iv.AddRefOnChannel(channelSet.Channel, channelSet.OutOfBandChannel); } _registeredICompositionTargets.Add(iv, null); // We use the dictionary just as a set. } ////// Unregisters the ICompositionTarget from the Dispatcher. /// /// /// internal static void UnregisterICompositionTarget(Dispatcher dispatcher, ICompositionTarget iv) { Debug.Assert(dispatcher != null); Debug.Assert(iv != null); MediaContext.From(dispatcher).UnregisterICompositionTargetInternal(iv); } ////// Removes the ICompositionTarget from this MediaContext. /// /// ICompositionTarget to unregister. ////// Critical - Calls a security critical method: SetNotificationWindow. /// TreatAsSafe - It is ok for the MediaContext to call SetNotificationWindow on /// the channel because the MediaContext owns the channel. In addition /// no user data is passed along. /// [SecurityCritical, SecurityTreatAsSafe] private void UnregisterICompositionTargetInternal(ICompositionTarget iv) { Debug.Assert(iv != null); // this test is needed because we always unregister the target when the ReleaseUCEResources // is called on the target and Dispose is called from both the media context and the // hwnd source, so when shutting down in a disconnected state we end up calling here // after a Dispose. if (_isDisposed) { return; } // If channel is not available, we are in a disconnected state, which means // that all resources have been released and we can just skip the operation. if (Channel != null) { // if _currentRenderingChannel is nonempty, we're unregistering this ICompositionTarget // from within a render walk and it is thus a visualbrush, we need to remove it from the // channel which we are currently rendering. If _currentRenderingChannel // is null, we just get the target channels for this ICompositionTarget and release // there. DUCE.ChannelSet channelSet = (_currentRenderingChannel == null) ? GetChannels() : _currentRenderingChannel.Value; iv.ReleaseOnChannel(channelSet.Channel, channelSet.OutOfBandChannel); } _registeredICompositionTargets.Remove(iv); } 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 FrugalObjectList(); } _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 (!_isRendering) { EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientPostRender); if (_currentRenderOp != null) { // 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; } else { // If we don't have a render operation in the queue, add one at // render priority. _currentRenderOp = Dispatcher.BeginInvoke(DispatcherPriority.Render, _renderMessage, null); } // We don't need to keep our promotion timers around. _promoteRenderOpToInput.Stop(); _promoteRenderOpToRender.Stop(); } } ////// 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); } ////// This is the standard RenderMessageHandler callback, posted via PostRender() /// and Resize(). This wraps RenderMessageHandlerCore and emits an ETW events /// to trace its execution. /// private object RenderMessageHandler( object resizedCompositionTarget /* can be null if we are not resizing*/ ) { if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info)) { EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientRenderHandlerBegin, EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, PerfService.GetPerfElementID(this)); } RenderMessageHandlerCore(resizedCompositionTarget); EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientRenderHandlerEnd); 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. /// private object AnimatedRenderMessageHandler( object resizedCompositionTarget /* can be null if we are not resizing*/ ) { if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info)) { EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientAnimRenderHandlerBegin, EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, PerfService.GetPerfElementID(this)); } RenderMessageHandlerCore(resizedCompositionTarget); EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientAnimRenderHandlerEnd); return null; } ////// This handles the _inputMarkerOp message. We're using /// _inputMarkerOp to determine if input priority dispatcher ops /// have been processes. /// private 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; } } private int InvokeOnRenderCallbacksCount { get { return _invokeOnRenderCallbacks != null ? _invokeOnRenderCallbacks.Count : 0; } } ////// Calls all _invokeOnRenderCallbacks until no more are added /// private void FireInvokeOnRenderCallbacks() { int callbackLoopCount = 0; int count = InvokeOnRenderCallbacksCount; // 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)); } FrugalObjectListcallbacks = _invokeOnRenderCallbacks; _invokeOnRenderCallbacks = null; for (int i = 0; i < count; i++) { callbacks[i].DoWork(); } count = InvokeOnRenderCallbacksCount; } // Fire all the pending Loaded events before Render happens // but after the layout storm has subsided FireLoadedPendingCallbacks(); count = InvokeOnRenderCallbacksCount; } 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) { var count = _loadedOrUnloadedPendingOperations.Count; if (count == 0) { return; } // Create a copy of the _loadedOrUnloadedPendingOperations var copyOfPendingCallbacks = _loadedOrUnloadedPendingOperations; // Clear up the _loadedOrUnloadedPendingOperations in case the broadcast of Loaded causes // more of the pending operations to get posted. _loadedOrUnloadedPendingOperations = null; // 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. /// private 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); // ETW event tracing bool etwTracingEnabled = false; uint renderID = (uint)Interlocked.Increment(ref _contextRenderID); if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info)) { etwTracingEnabled = true; DUCE.ETWEvent.RaiseEvent( _uceEtwEvent.Handle, renderID, Channel); EventTrace.EventProvider.TraceEvent( EventTrace.Event.WClientMediaRenderBegin, EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, renderID, TicksToCounts(_estimatedNextPresentationTime.Ticks) ); } // --------------------------------------------------------------- // 1) Render each registered ICompositionTarget to finish up the batch. foreach (ICompositionTarget registeredTarget in _registeredICompositionTargets.Keys) { DUCE.ChannelSet channelSet; channelSet.Channel = _channelManager.Channel; channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel; _currentRenderingChannel = channelSet; registeredTarget.Render((registeredTarget == resizedCompositionTarget), channelSet.Channel); _currentRenderingChannel = null; } // ---------------------------------------------------------------- // 2) Update any resources that need to be updated for this render. RaiseResourcesUpdated(); // // 3) 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 // if (Channel != null) { Channel.CloseBatch(); } _needToCommitChannel = true; _commitPendingAfterRender = true; if (!InterlockIsWaiting) { //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(); } } // --------------------------------------------------------------- // 4) Raise RenderComplete event. if (etwTracingEnabled) { EventTrace.EventProvider.TraceEvent( EventTrace.Event.WClientMediaRenderEnd, EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info); // trace the UI Response event EventTrace.EventProvider.TraceEvent( EventTrace.Event.WClientUIResponse, EventTrace.Keyword.KeywordGraphics, EventTrace.Level.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) { 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; } if (CommittingBatch != null) { CommittingBatch(Channel, new EventArgs()); } Channel.Commit(); if (_commitPendingAfterRender) { // // Raise Render Complete event since a Render happened and // the commit for that render happened. // if (_renderCompleteHandlers != null) { _renderCompleteHandlers(this, null); } _commitPendingAfterRender = false; } // // The channel has just been commited. There's nothing more in it. // // The payload data for this event is the render ID of the frame we're committing. EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordGraphics | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientUICommitChannel, _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), true); } } ////// 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) { if (_interlockState == InterlockState.WaitingForResponse) { do { // WaitForNextMessage will Commit() if (CommittingBatch != null) { CommittingBatch(Channel, new EventArgs()); } 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) { CommitChannel(); if (_interlockState == InterlockState.WaitingForResponse) { do { // WaitForNextMessage will Commit(), but CommitChannel() // was just called already and it handles CommittingBatch 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 { // SyncFlush() will Commit() if (CommittingBatch != null) { CommittingBatch(Channel, new EventArgs()); } // // Issue a [....] flush, which will only return after // the last frame is presented // Channel.SyncFlush(); } } } ////// This function is registered with the MediaContext's TimeManager. It is called whenever /// a clock managed by the TimeManager goes active, but only if there hasn't been already an /// active clock. For now we start the animation thread in there. /// private void OnNeedTickSooner(object sender, EventArgs e) { PostRender(); } ////// Checks if the current context can request the specified permissions. /// internal void VerifyWriteAccess() { if (!WriteAccessEnabled) { throw new InvalidOperationException(SR.Get(SRID.MediaContext_APINotAllowed)); } } ////// Returns false if the MediaContext is currently read-only /// internal bool WriteAccessEnabled { get { return _readOnlyAccessCounter <= 0; } } ////// Methods to lock down the Visual tree for write access. /// internal void PushReadOnlyAccess() { _readOnlyAccessCounter++; } internal void PopReadOnlyAccess() { _readOnlyAccessCounter--; } ////// Each MediaContext is associated with a TimeManager. The TimeManager is shared by all ICompositionTargets. /// private TimeManager _timeManager; internal TimeManager TimeManager { get { return _timeManager; } } ////// RenderComplete event is fired when Render method commits the channel. /// This is used for ink transition. Currently this event is internal and will /// be accessed using reflection until proper object model is defined. /// internal event EventHandler RenderComplete { add { // // If the Render happened (i.e. flag is true) and corresponding Commit // has not happened, then set the flag to false, since the next // Commit will not have the changes after Render and before +RenderComplete. // In other words, consider this event sequence:- // 1. Render // 2. Some resource property changes (eg. visual.Clip = null) // 3. +RenderComplete // 4. Commit // Then the 4th Commit will not have 2's changes(as it requires a // new Render pass) and so raising the event is wrong. // // Note: If the event is added every time in the middle of Render // and Commit, then RenderComplete will starve. // if (_commitPendingAfterRender) { _commitPendingAfterRender = false; } _renderCompleteHandlers += value; } remove { _renderCompleteHandlers -= value; } } ////// ResourcesUpdatedHandler - This event handler prototype defines the callback /// for our async update callback in the ResourcesUpdated Event. /// The method which implements this prototype is also often called in situations where /// the resource is known to be "on channel" - in those cases, "true" is passed for the second /// parameter (allowing the implementation to skip the check). /// internal delegate void ResourcesUpdatedHandler(DUCE.Channel channel, bool skipOnChannelCheck); internal event ResourcesUpdatedHandler ResourcesUpdated { add { _resourcesUpdatedHandlers += value; } remove { _resourcesUpdatedHandlers -= value; } } private void RaiseResourcesUpdated() { if (_resourcesUpdatedHandlers != null) { DUCE.ChannelSet channelSet = GetChannels(); _resourcesUpdatedHandlers(channelSet.Channel, false /* do not skip the "on channel" check */); _resourcesUpdatedHandlers = null; } } ////// Create a fresh or fetch one from the pool synchronous channel. /// internal DUCE.Channel AllocateSyncChannel() { return _channelManager.AllocateSyncChannel(); } ////// Returns a [....] channel back to the pool. /// internal void ReleaseSyncChannel(DUCE.Channel channel) { _channelManager.ReleaseSyncChannel(channel); } ////// Returns the asynchronous channel for this media context. /// ////// This property is deprecated and scheduled to be removed as per task #26681. /// Please do not create additional dependencies on it. /// internal DUCE.Channel Channel { get { return _channelManager.Channel; } } ////// Returns the asynchronous out-of-band channel for this media context. /// internal DUCE.Channel OutOfBandChannel { get { return _channelManager.OutOfBandChannel; } } internal bool IsConnected { get { return _isConnected; } } ////// Returns the BoundsDrawingContextWalker for this media context. /// To handle reentrance we want to make sure that /// no one else on the same thread gets the same context. /// internal BoundsDrawingContextWalker AcquireBoundsDrawingContextWalker() { if (_cachedBoundsDrawingContextWalker == null) { return new BoundsDrawingContextWalker(); } BoundsDrawingContextWalker ctx = _cachedBoundsDrawingContextWalker; _cachedBoundsDrawingContextWalker = null; ctx.ClearState(); return ctx; } ////// Set the BoundsDrawingContextWalker for next use /// To handle reentrance we want to make sure that /// no one else on the same thread gets the same context. /// internal void ReleaseBoundsDrawingContextWalker(BoundsDrawingContextWalker ctx) { _cachedBoundsDrawingContextWalker = ctx; } private void PromoteRenderOpToInput(object sender, EventArgs e) { if(_currentRenderOp != null) { _currentRenderOp.Priority = DispatcherPriority.Input; } ((DispatcherTimer)sender).Stop(); } private void PromoteRenderOpToRender(object sender, EventArgs e) { if(_currentRenderOp != null) { _currentRenderOp.Priority = DispatcherPriority.Render; } ((DispatcherTimer)sender).Stop(); } ////// 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) { 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) { CommitChannel(); //schedule the next render so we're back on the np-commit-render pattern ScheduleNextRenderOp(TimeSpan.Zero); } timer.Stop(); } //+---------------------------------------------------------------------- // // Private Methods // //---------------------------------------------------------------------- #region Private Methods ////// Tells the composition engine that we want to receive asynchronous /// notifications on this channel. /// ////// Critical - Contains an unsafe code block. /// TreatAsSafe - Unsafe block just uses the sizeof operator. /// Sending a message to a channel is safe. /// [SecurityCritical, SecurityTreatAsSafe] private void RegisterForNotifications(DUCE.Channel channel) { DUCE.MILCMD_PARTITION_REGISTERFORNOTIFICATIONS registerCmd; registerCmd.Type = MILCMD.MilCmdPartitionRegisterForNotifications; registerCmd.Enable = 1; // Enable notifications. unsafe { channel.SendCommand( (byte*)®isterCmd, sizeof(DUCE.MILCMD_PARTITION_REGISTERFORNOTIFICATIONS) ); } } #endregion Private Methods //+--------------------------------------------------------------------- // // Private Fields // //---------------------------------------------------------------------- #region Private Fields ////// Returns the current channel set for this MediaContext. /// internal DUCE.ChannelSet GetChannels() { DUCE.ChannelSet channelSet; channelSet.Channel = _channelManager.Channel; channelSet.OutOfBandChannel = _channelManager.OutOfBandChannel; return channelSet; } ////// The disposed flag indicates if the object got disposed. /// private bool _isDisposed; ////// Event handler that is called when the context is destroyed. /// private EventHandler _destroyHandler; ////// This event is raised when the MediaContext.Render has /// committed the channel. /// private event EventHandler _renderCompleteHandlers; ////// This event is raised when the MediaContext is ready for all of the /// invalid resources to update their values. /// private event ResourcesUpdatedHandler _resourcesUpdatedHandlers; ////// Use a guid to uniquely identify the current context. Note that /// we can't use static data to generate a unique id since static /// data is not shared across app domains and this id must be /// truly unique. /// private Guid _contextGuid; ////// Message delegate. /// private DispatcherOperation _currentRenderOp; private DispatcherOperation _inputMarkerOp; private DispatcherOperationCallback _renderMessage; private DispatcherOperationCallback _animRenderMessage; private DispatcherOperationCallback _inputMarkerMessage; private DispatcherOperationCallback _renderModeMessage; 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 FrugalObjectList_invokeOnRenderCallbacks; /// /// Set of ICompositionTargets that are currently registered with the MediaSystem; /// private Dictionary_registeredICompositionTargets; /// /// This are the the permissions the Context has to access Visual APIs. /// private int _readOnlyAccessCounter; 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 render tier associated with this MediaContext. This is updated // when channels are created. private int _tier; ////// Rendering event. Registers a delegate to be notified after animation and layout but before rendering /// Its EventArgs parameter can be cast to RenderingEventArgs to get the last presentation time. /// internal event EventHandler Rendering; ////// CommittingBatch event. Registers a delegate to be notified when a batch is /// about to be committed to MIL. /// internal event EventHandler CommittingBatch; // List of pending loaded event dispatcher operations private FrugalObjectList_loadedOrUnloadedPendingOperations; // Time to wait for unthrottled renders private TimeSpan _timeDelay = TimeSpan.FromMilliseconds(10); // 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 DUCE.ChannelSet? _currentRenderingChannel = null; #endregion Private Fields //+--------------------------------------------------------------------- // // Animation Smoothing // //--------------------------------------------------------------------- #region Animation Smoothing /// /// This enum indicates that we are in an interlocked presentation mode. /// We render a frame. send it to be presented and the composition comes /// to tell us that the frame was presented. Otherwise we just keep giving /// frames to the composition thread and assume that they get presented /// as requested. /// private enum InterlockState { ////// Interlock presentation mode is disabled. We send a frame to the /// composition thread and simply schedule our next one when we know /// that we have something to render /// Disabled = 0, ////// Interlock presentation mode has requested a roundtrip message to /// before enabling the mode. This state indicates that we've /// requested the UCE enter the interlocked presentation mode but we /// haven't received the response yet. /// RequestedStart, ////// We are in interlocked presentation mode and are not waiting for /// anything. If we get a render request we will process it /// immediately /// Idle, ////// We are in interlocked presentation mode and have sumitted a /// frame to be presented. We are waiting for the notification from /// the UCE thread. /// WaitingForResponse, ////// We are in interlocked presentation mode but the last frame we /// submitted to be presented wasn't presented. We are waiting until /// the next VSync before submitting another frame for presentation. /// WaitingForNextFrame }; ////// The current state of the interlocked presentation mode /// private InterlockState _interlockState; ////// This indicates that we are waiting for something (either the next /// frame time to occur or for a response from the UCE). /// private bool InterlockIsWaiting { get { return (_interlockState == InterlockState.WaitingForNextFrame || _interlockState == InterlockState.WaitingForResponse); } } ////// We are currently in an interlocked presentation mode and the UCE /// has acknolwedged that it is also in that mode. /// private bool InterlockIsEnabled { get { return ( _interlockState != InterlockState.Disabled && _interlockState != InterlockState.RequestedStart); } } ////// This is used in interlocked presentation mode. This flag indicates /// that we've put something on the channel but that we haven't commited /// it yet. This occurs when we know that the composition thread is already /// processing a batch for a frame. We only want to give the UCE 1 frame /// per VSync so that we try to make it present the frame at the time /// that we have estimated. At this point we have rendered 1 frame in /// advance and will wait until we reach the next frame boundary before /// committing the channel to have the information sent to the /// composition thread on the right frame. /// private bool _needToCommitChannel; ////// Last time the composition presented a frame /// Units: counts /// private long _lastPresentationTime; ////// Last time the UI thread committed a frame /// Units: Ticks /// private long _lastCommitTime; ////// Last time the the input marker was added to the queue /// Units: Ticks /// private long _lastInputMarkerTime; ////// Average time it takes the UCE to present a frame /// Units: Ticks /// private long _averagePresentationInterval; ////// Estimation of the next time we want a frame to appear on screen. We /// will set the TimeManager's time to this to have the animations look /// smooth /// private TimeSpan _estimatedNextPresentationTime; ////// The refresh rate of the monitor that we are displaying to /// private int _displayRefreshRate; ////// The rate at which we try to display content if no special throttling /// mechanism is used /// private int _adjustedRefreshRate; ////// The rate at which we are rendering animations. This can be the /// refresh rate of the monitor that we are presenting on or can be /// overridden with a DesiredFrameRate on the Timeline. This is /// used to estimate the time of the next frame that we want to present. /// private int _animationRenderRate; ////// The results of the last present call. /// private MIL_PRESENTATION_RESULTS _lastPresentationResults = MIL_PRESENTATION_RESULTS.MIL_PRESENTATION_VSYNC_UNSUPPORTED; static private long _perfCounterFreq; private const long MaxTicksWithoutInput = TimeSpan.TicksPerSecond / 2; #endregion Animation Smoothing } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- MessageSecurityProtocolFactory.cs
- UIElement3D.cs
- SafeNativeMethods.cs
- BatchParser.cs
- StringWriter.cs
- Visual3D.cs
- Command.cs
- SecureConversationDriver.cs
- TextTreeUndo.cs
- NullableLongAverageAggregationOperator.cs
- ConnectionPoint.cs
- Transform3DCollection.cs
- documentsequencetextview.cs
- TreeViewAutomationPeer.cs
- BamlRecordReader.cs
- TimeSpanMinutesOrInfiniteConverter.cs
- MembershipAdapter.cs
- ValidatedMobileControlConverter.cs
- SortKey.cs
- InvalidateEvent.cs
- AssemblyInfo.cs
- BitStream.cs
- UriScheme.cs
- webbrowsersite.cs
- EntityCommandDefinition.cs
- MD5CryptoServiceProvider.cs
- ReadWriteObjectLock.cs
- ListBase.cs
- ProgressBar.cs
- Deserializer.cs
- ConfigurationPropertyAttribute.cs
- MessageBox.cs
- MimeTypePropertyAttribute.cs
- ApplicationDirectory.cs
- XmlSchemaValidationException.cs
- EpmSyndicationContentDeSerializer.cs
- ObjectStateManager.cs
- OciEnlistContext.cs
- ObjectDisposedException.cs
- TrayIconDesigner.cs
- CryptoHelper.cs
- DataObject.cs
- ComponentResourceManager.cs
- DiscoveryMessageSequenceGenerator.cs
- RawStylusSystemGestureInputReport.cs
- Converter.cs
- JsonServiceDocumentSerializer.cs
- SafeCryptContextHandle.cs
- SelectedPathEditor.cs
- SafeSystemMetrics.cs
- ServicePointManager.cs
- TextureBrush.cs
- TextEditorLists.cs
- ProcessThreadCollection.cs
- MarshalByRefObject.cs
- MouseGestureValueSerializer.cs
- ConcurrentDictionary.cs
- PathGradientBrush.cs
- DataObjectCopyingEventArgs.cs
- DBPropSet.cs
- OpenFileDialog.cs
- CustomValidator.cs
- AnimationClockResource.cs
- Polygon.cs
- AutomationElementIdentifiers.cs
- BitmapFrameDecode.cs
- SystemResourceKey.cs
- FrameworkContextData.cs
- OrthographicCamera.cs
- InstanceDescriptor.cs
- SettingsPropertyValueCollection.cs
- PropertyMapper.cs
- GestureRecognitionResult.cs
- SettingsBindableAttribute.cs
- OleDbStruct.cs
- httpstaticobjectscollection.cs
- IdentityReference.cs
- InputProcessorProfilesLoader.cs
- DataObjectSettingDataEventArgs.cs
- Merger.cs
- IChannel.cs
- Tracer.cs
- SmtpDigestAuthenticationModule.cs
- BaseTemplateParser.cs
- COM2ColorConverter.cs
- TransformerInfoCollection.cs
- EntityConnectionStringBuilder.cs
- XmlSerializationGeneratedCode.cs
- ProgressBarAutomationPeer.cs
- CodeExporter.cs
- RuleSetReference.cs
- HtmlTableRowCollection.cs
- EventArgs.cs
- GroupBox.cs
- RichTextBox.cs
- SmtpNtlmAuthenticationModule.cs
- DesignerDataView.cs
- SafeLibraryHandle.cs
- StylusPoint.cs
- DataGridCommandEventArgs.cs