File: System\Windows\Input\Stylus\Common\DynamicRenderer.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//#define DEBUG_RENDERING_FEEDBACK
 
// Description:
//      DynamicRenderer PlugIn - Provides off (and on) app Dispatcher Inking support.
 
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Ink;
using MS.Internal.Ink;
 
namespace System.Windows.Input.StylusPlugIns
{
    /////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// [TBS]
    /// </summary>
    public class DynamicRenderer : StylusPlugIn
    {
        /////////////////////////////////////////////////////////////////////
 
        private class StrokeInfo
        {
            int _stylusId;
            int _startTime;
            int _lastTime;
            ContainerVisual _strokeCV;  // App thread rendering CV
            ContainerVisual _strokeRTICV; // real time input CV
            bool _seenUp; // Have we seen the stylusUp event yet?
            bool _isReset; // Was reset used to create this StrokeInfo?
            SolidColorBrush _fillBrush; // app thread based brushed
            DrawingAttributes _drawingAttributes;
            StrokeNodeIterator _strokeNodeIterator;
            double _opacity;
            DynamicRendererHostVisual   _strokeHV;  // App thread rendering HostVisual
 
            public StrokeInfo(DrawingAttributes drawingAttributes, int stylusDeviceId, int startTimestamp, DynamicRendererHostVisual hostVisual)
            {
                _stylusId = stylusDeviceId;
                _startTime = startTimestamp;
                _lastTime = _startTime;
                _drawingAttributes = drawingAttributes.Clone(); // stroke copy for duration of stroke.
                _strokeNodeIterator = new StrokeNodeIterator(_drawingAttributes);
                Color color = _drawingAttributes.Color;
                _opacity = _drawingAttributes.IsHighlighter ? 0 : (double)color.A / (double)StrokeRenderer.SolidStrokeAlpha;
                color.A = StrokeRenderer.SolidStrokeAlpha;
                
                // Set the brush to be used with this new stroke too (since frozen can be shared by threads)
                SolidColorBrush brush = new SolidColorBrush(color);
                brush.Freeze();
                _fillBrush = brush;
                _strokeHV = hostVisual;
                hostVisual.AddStrokeInfoRef(this); // Add ourselves as reference.
            }
 
            // Public props to access info
            public int StylusId 
            { 
                get { return _stylusId; }
            }
            public int StartTime 
            { 
                get { return _startTime; }
            }
            public int LastTime 
            { 
                get { return _lastTime; } 
                set { _lastTime = value; } 
            }
            public ContainerVisual StrokeCV 
            { 
                get { return _strokeCV; } 
                set { _strokeCV = value; } 
            }
            public ContainerVisual StrokeRTICV 
            { 
                get { return _strokeRTICV; } 
                set { _strokeRTICV = value; } 
            }
            public bool SeenUp 
            { 
                get { return _seenUp; } 
                set { _seenUp = value; } 
            }
            public bool IsReset
            { 
                get { return _isReset; }
                set { _isReset = value; }
            }
            public StrokeNodeIterator StrokeNodeIterator
            { 
                get { return _strokeNodeIterator; }
                set 
                {
                    ArgumentNullException.ThrowIfNull(value);
                    _strokeNodeIterator = value; 
                }
            }
            public SolidColorBrush FillBrush
            { 
                get { return _fillBrush; } 
                set { _fillBrush = value; } 
            }
            public DrawingAttributes DrawingAttributes
            { 
                get { return _drawingAttributes; }
            }
            public double Opacity
            { 
                get { return _opacity; }
            }
            public DynamicRendererHostVisual StrokeHV
            { 
                get { return _strokeHV; }
            }
 
            // See if timestamp is part of this stroke.  Deals with tickcount wrapping.
            public bool IsTimestampWithin(int timestamp)
            {
                // If we've seen up use the start and end to figure out if timestamp
                // is between start and last.  Note that we need to deal with the 
                // times wrapping back to 0.
                if (SeenUp)
                {
                    if (StartTime < LastTime) // wrapping check
                    {
                        return ((timestamp >= StartTime) && (timestamp <= LastTime));
                    }
                    else // The timestamp wrapped back to zero
                    {
                        return ((timestamp >= StartTime) || (timestamp <= LastTime));
                    }
                }
                else
                {
                    return true; // everything is consider part of an open StrokeInfo.
                }
            }
 
            // See if a new timestamp adding at the end of this stroke.  Deals with tickcount wrapping.
            public bool IsTimestampAfter(int timestamp)
            {
                // If we've seen up then timestamp can't be after, otherwise do the check.
                // Note that we need to deal with the times wrapping (goes negative).
                if (!SeenUp)
                {
                    if (LastTime >= StartTime)
                    {
                        if (timestamp >= LastTime)
                        {
                            return true;
                        }
                        else
                        {
                            return ((LastTime > 0) && (timestamp < 0));  // true if we wrapped
                        }
                    }
                    else // The timestamp may have wrapped, see if greater than last time and less than start time
                    {
                        return timestamp >= LastTime && timestamp <= StartTime;
                    }
                }
                else
                {
                    return false; // Nothing can be after a closed StrokeInfo (see up).
                }
            }
}
 
        private class DynamicRendererHostVisual : HostVisual
        {
            internal bool InUse
            {
                get { return _strokeInfoList.Count > 0; }
            }
            internal bool HasSingleReference
            {
                get { return _strokeInfoList.Count == 1; }
            }
            internal void AddStrokeInfoRef(StrokeInfo si)
            {
                _strokeInfoList.Add(si);
            }
            internal void RemoveStrokeInfoRef(StrokeInfo si)
            {
                _strokeInfoList.Remove(si);
            }
            
            internal VisualTarget VisualTarget
            {
                get 
                { 
                    if (_visualTarget == null)
                    {
                        _visualTarget = new VisualTarget(this);
                        _visualTarget.RootVisual = new ContainerVisual();
                    }
                    return _visualTarget;
                }
            }
            
            VisualTarget       _visualTarget;
            List<StrokeInfo>   _strokeInfoList = new List<StrokeInfo>();
        }
        
        /////////////////////////////////////////////////////////////////////
 
        /// <summary>
        /// [TBS] - On UIContext
        /// </summary>
        public DynamicRenderer() : base()
        {
            _zeroSizedFrozenRect = new RectangleGeometry(new Rect(0,0,0,0));
            _zeroSizedFrozenRect.Freeze();
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Reset will stop the current strokes being dynamically rendered 
        /// and start a new stroke with the packets passed in.  Specified StylusDevice 
        /// must be in down position when calling this method.
        /// Only call from application dispatcher.
        /// </summary>
        /// <param name="stylusDevice"></param>
        /// <param name="stylusPoints"></param>
        public virtual void Reset(StylusDevice stylusDevice, StylusPointCollection stylusPoints)
        {
            // NOTE: stylusDevice == null means the mouse device.
 
            // Nothing to do if root visual not queried or not hookup up to element yet.
            if (_mainContainerVisual == null || _applicationDispatcher == null || !IsActiveForInput)
                return;
            
            // Ensure on UIContext.
            _applicationDispatcher.VerifyAccess();
 
            // Make sure the stylusdevice specified (or mouse if null stylusdevice) is currently in 
            // down state!
            bool inAir = (stylusDevice != null) ? 
                            stylusDevice.InAir : 
                            Mouse.PrimaryDevice.LeftButton == MouseButtonState.Released;
            
            if (inAir)
            {
                throw new ArgumentException(SR.Stylus_MustBeDownToCallReset, "stylusDevice");
            }
 
            // Avoid reentrancy due to lock() call.
            using(_applicationDispatcher.DisableProcessing())
            {
                lock(__siLock)
                {
                    AbortAllStrokes(); // stop any current inking strokes
 
                    // Now create new si and insert it in the list.
                    StrokeInfo si = new StrokeInfo(DrawingAttributes, 
                                                   (stylusDevice != null) ? stylusDevice.Id : 0, 
                                                   Environment.TickCount, GetCurrentHostVisual());
                    _strokeInfoList.Add(si);
                    si.IsReset = true;
 
                    if (stylusPoints != null)
                    {
                        RenderPackets(stylusPoints, si); // do this inside of lock to make sure this renders first.
                    }
                }
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On app Dispatcher
        /// </summary>
        public Visual RootVisual
        {
            get
            {
                // NOTE: We don't create any visuals (real time or non real time) until someone
                //  queries for this property since we can't display anything until this is done and
                // they hook the returned visual up to their visual tree.
                if (_mainContainerVisual == null)
                {
                    CreateInkingVisuals(); // ensure at least the app dispatcher visuals are created.
                }
                return _mainContainerVisual;
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On app Dispatcher
        /// </summary>
        protected override void OnAdded()
        {
            // Grab the dispatcher we're hookup up to.
            _applicationDispatcher = Element.Dispatcher;
            
            // If we are active for input, make sure we create the real time inking thread
            // and visuals if needed.
            if (IsActiveForInput)
            {
                CreateRealTimeVisuals();  // Transitions to inking thread.
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On app dispatcher
        /// </summary>
        protected override void OnRemoved()
        {
            // Make sure we destroy any real time visuals and thread when removed.
            DestroyRealTimeVisuals();
            _applicationDispatcher = null; // removed from tree.
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On UIContext
        /// </summary>
        protected override void OnIsActiveForInputChanged()
        {
            // We only want to keep our real time inking thread references around only
            // when we need them.  If not enabled for input then we don't need them.
            if (IsActiveForInput)
            {
                // Make sure we create the real time inking visuals if we in tree.
                CreateRealTimeVisuals();  // Transitions to inking thread.
            }
            else
            {
                DestroyRealTimeVisuals();
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On pen threads or app thread
        /// </summary>
        protected override void OnStylusEnter(RawStylusInput rawStylusInput, bool confirmed)
        {
            HandleStylusEnterLeave(rawStylusInput, true, confirmed);
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On pen threads or app thread
        /// </summary>
        protected override void OnStylusLeave(RawStylusInput rawStylusInput, bool confirmed)
        {
            HandleStylusEnterLeave(rawStylusInput, false, confirmed);
        }
 
        private void HandleStylusEnterLeave(RawStylusInput rawStylusInput, bool isEnter, bool isConfirmed)
        {
            // See if we need to abort a stroke due to entering or leaving within a stroke.
            if (isConfirmed)
            {
                StrokeInfo si = FindStrokeInfo(rawStylusInput.Timestamp);
 
                if (si != null)
                {
                    if (rawStylusInput.StylusDeviceId == si.StylusId)
                    {
                        if ((isEnter && (rawStylusInput.Timestamp > si.StartTime)) ||
                            (!isEnter && !si.SeenUp))
                        {
                            // abort this stroke.
                            TransitionStrokeVisuals(si, true);
                        }
                    }
                }
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On UIContext
        /// </summary>
        protected override void OnEnabledChanged()
        {
            // If going disabled cancel all real time strokes.  We won't be getting any more
            // events.
            if (!Enabled)
            {
                AbortAllStrokes();
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected override void OnStylusDown(RawStylusInput rawStylusInput)
        {
            // Only allow inking if someone has queried our RootVisual.
            if (_mainContainerVisual != null)
            {
                StrokeInfo si;
                
                lock(__siLock)
                {
                    si = FindStrokeInfo(rawStylusInput.Timestamp);
 
                    // If we find we are already in the middle of stroke then bail out.
                    // Can only ink with one stylus at a time.
                    if (si != null)
                    {
                        return; 
                    }
 
                    si = new StrokeInfo(DrawingAttributes, rawStylusInput.StylusDeviceId, rawStylusInput.Timestamp, GetCurrentHostVisual());
                    _strokeInfoList.Add(si);
                }
                
                rawStylusInput.NotifyWhenProcessed(si);
                RenderPackets(rawStylusInput.GetStylusPoints(), si);
            }
        }
        
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected override void OnStylusMove(RawStylusInput rawStylusInput)
        {
            // Only allow inking if someone has queried our RootVisual.
            if (_mainContainerVisual != null)
            {
                StrokeInfo si = FindStrokeInfo(rawStylusInput.Timestamp);
 
                if (si != null && (si.StylusId == rawStylusInput.StylusDeviceId))
                {
                    // We only render packets that are in the proper order due to
                    // how our incremental rendering uses the last point to continue
                    // the path geometry from.
                    // NOTE: We also update the LastTime value here too
                    if (si.IsTimestampAfter(rawStylusInput.Timestamp))
                    {
                        si.LastTime = rawStylusInput.Timestamp;
                        RenderPackets(rawStylusInput.GetStylusPoints(), si);
                    }
                }
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected override void OnStylusUp(RawStylusInput rawStylusInput)
        {
            // Only allow inking if someone has queried our RootVisual.
            if (_mainContainerVisual != null)
            {
                StrokeInfo si = FindStrokeInfo(rawStylusInput.Timestamp);
 
                if (si != null && 
                    ((si.StylusId == rawStylusInput.StylusDeviceId) ||
                     (rawStylusInput.StylusDeviceId == 0 && 
                      (si.IsReset || 
                      (si.IsTimestampAfter(rawStylusInput.Timestamp) && IsStylusUp(si.StylusId))))))
                {
                    si.SeenUp = true;
                    si.LastTime = rawStylusInput.Timestamp;
                    rawStylusInput.NotifyWhenProcessed(si);
                }
            }
        }
 
        private bool IsStylusUp(int stylusId)
        {
            TabletDeviceCollection tabletDevices = Tablet.TabletDevices;
            for (int i=0; i<tabletDevices.Count; i++)
            {
                TabletDevice tabletDevice = tabletDevices[i];
                for (int j=0; j<tabletDevice.StylusDevices.Count; j++)
                {
                    StylusDevice stylusDevice = tabletDevice.StylusDevices[j];
                    if (stylusId == stylusDevice.Id)
                        return stylusDevice.InAir;
                }
            }
            
            return true; // not found so must be up.
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        private void OnRenderComplete()
        {
            StrokeInfo si = _renderCompleteStrokeInfo;
            Debug.Assert(si!=null);  // should never get here unless we are transitioning a stroke.
            
            if (si != null)
            {
                // See if we are done transitioning this stroke!!
                if (si.StrokeHV.Clip == null)
                {
                    // Getting _applicationDispatcher is safe, because this runs in main UI thread.
                    TransitionComplete(si, _applicationDispatcher);
                    _renderCompleteStrokeInfo = null;
                }
                else
                {
                    // Wait for real time visual to be removed and updated.
                    RemoveDynamicRendererVisualAndNotifyWhenDone(si);
                }
            }
        }
 
        void RemoveDynamicRendererVisualAndNotifyWhenDone(StrokeInfo si)
        {
            if (si != null)
            {
                DynamicRendererThreadManager renderingThread = _renderingThread; // Keep it alive
                if (renderingThread != null)
                {
                    // We are being called by the main UI thread, so marshal over to
                    // the inking thread before cleaning up the stroke visual.
                    renderingThread.ThreadDispatcher.BeginInvoke(DispatcherPriority.Send,
                    (DispatcherOperationCallback)delegate(object unused)
                    {
                        if (si.StrokeRTICV != null)
                        {
                            // Now wait till this is rendered and then notify UI thread.
                            if (_onDRThreadRenderComplete == null)
                            {
                                _onDRThreadRenderComplete = new EventHandler(OnDRThreadRenderComplete);
                            }
 
                            // Add to list to transact.
                            _renderCompleteDRThreadStrokeInfoList.Enqueue(si);
                            
                            // See if we are already waiting for a removed stroke to be rendered.
                            // If we aren't then remove visuals and wait for it to be rendered.
                            // Otherwise we'll do the work when the current stroke has been removed.
                            if (!_waitingForDRThreadRenderComplete)
                            {
                                ((ContainerVisual)si.StrokeHV.VisualTarget.RootVisual).Children.Remove(si.StrokeRTICV);
                                si.StrokeRTICV = null;
 
                                // hook up render complete notification for one time then unhook.
                                MediaContext.From(renderingThread.ThreadDispatcher).RenderComplete += _onDRThreadRenderComplete;
                                _waitingForDRThreadRenderComplete = true;
                            }
                        }
                        else
                        {
                            // Nothing to transition so just say we're done!
                            NotifyAppOfDRThreadRenderComplete(si);
                        }
                        
                        return null;
                    },
                    null);
                }
            }
        }
 
 
        private void NotifyAppOfDRThreadRenderComplete(StrokeInfo si)
        {
            Dispatcher dispatcher = _applicationDispatcher;
            if (dispatcher != null)
            {
                // We are being called by the inking thread, so marshal over to
                // the UI thread before handling the StrokeInfos that are done rendering.
                dispatcher.BeginInvoke(DispatcherPriority.Send,
                (DispatcherOperationCallback)delegate(object unused)
                {
                    // See if this is the one we are doing a full transition for.
                    if (si == _renderCompleteStrokeInfo)
                    {
                        if (si.StrokeHV.Clip != null)
                        {
                            si.StrokeHV.Clip = null;
                            NotifyOnNextRenderComplete();
                        }
                        else
                        {
                            Debug.Assert(_waitingForRenderComplete, "We were expecting to be waiting for a RenderComplete to call our OnRenderComplete, we might never reset and get flashing strokes from here on out");
                            TransitionComplete(si, dispatcher); // We're done
                        }
                    }
                    else
                    {
                        TransitionComplete(si, dispatcher); // We're done
                    }
                    return null;
                },
                null);
            }
        }
 
 
        private void OnDRThreadRenderComplete(object sender, EventArgs e)
        {
            DynamicRendererThreadManager drThread = _renderingThread;
            Dispatcher drDispatcher = null;
                       
            // Remove RenderComplete hook.
            if (drThread != null)
            {
                drDispatcher = drThread.ThreadDispatcher;
                
                if (drDispatcher != null)
                {
                    if (_renderCompleteDRThreadStrokeInfoList.Count > 0)
                    {
                        StrokeInfo si = _renderCompleteDRThreadStrokeInfoList.Dequeue();
                        NotifyAppOfDRThreadRenderComplete(si);
                    }
 
                    // If no other queued up transitions, then remove event listener.
                    if (_renderCompleteDRThreadStrokeInfoList.Count == 0)
                    {
                        // First unhook event handler
                        MediaContext.From(drDispatcher).RenderComplete -= _onDRThreadRenderComplete;
                        _waitingForDRThreadRenderComplete = false;
                    }
                    else
                    {
                        // Process next waiting one.  Note we don't remove till removed processed.
                        StrokeInfo siNext = _renderCompleteDRThreadStrokeInfoList.Peek();
                        if (siNext.StrokeRTICV != null)
                        {
                            // Post this back to our thread to make sure we return from the
                            // this render complete call first before queuing up the next.
                            drDispatcher.BeginInvoke(DispatcherPriority.Send,
                            (DispatcherOperationCallback)delegate(object unused)
                            {
                                ((ContainerVisual)siNext.StrokeHV.VisualTarget.RootVisual).Children.Remove(siNext.StrokeRTICV);
                                siNext.StrokeRTICV = null;
                                return null;
                            },
                            null);
                        }
                    }
                }
            }
        }
 
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected override void OnStylusDownProcessed(object callbackData, bool targetVerified)
        {
            StrokeInfo si = callbackData as StrokeInfo;
 
            if (si == null)
                return;
            
            // See if we need to abort this stroke or reset the HostVisual clipping rect to null.
            if (!targetVerified)
            {
                TransitionStrokeVisuals(si, true);
            }
        }
        
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected override void OnStylusUpProcessed(object callbackData, bool targetVerified)
        {
            StrokeInfo si = callbackData as StrokeInfo;
 
            if (si == null)
                return;
 
            // clean up stroke visuals (and move to transitional VisualTarget as needed)
            TransitionStrokeVisuals(si, !targetVerified);
        }
 
        private void  OnInternalRenderComplete(object sender, EventArgs e)
        {
            // First unhook event handler
            MediaContext.From(_applicationDispatcher).RenderComplete -= _onRenderComplete;
            _waitingForRenderComplete = false;
            
            // Make sure lock() doesn't cause reentrancy.
            using(_applicationDispatcher.DisableProcessing())
            {
                // Now notify event happened.
                OnRenderComplete();
            }
        }
 
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        private void NotifyOnNextRenderComplete()
        {
            // Nothing to do if not hooked up to plugin collection.
            if (_applicationDispatcher == null)
                return;
 
            // Ensure on application Dispatcher.
            _applicationDispatcher.VerifyAccess();
 
            if (_onRenderComplete == null)
            {
                _onRenderComplete = new EventHandler(OnInternalRenderComplete);
            }
 
            if (!_waitingForRenderComplete)
            {
                // hook up render complete notification for one time then unhook.
                MediaContext.From(_applicationDispatcher).RenderComplete += _onRenderComplete;
                _waitingForRenderComplete = true;
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected virtual void OnDraw(  DrawingContext drawingContext, 
                                        StylusPointCollection stylusPoints, 
                                        Geometry geometry, 
                                        Brush fillBrush)
        {
            ArgumentNullException.ThrowIfNull(drawingContext);
            drawingContext.DrawGeometry(fillBrush, null, geometry);
        }
        
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS]
        /// </summary>
        protected virtual void OnDrawingAttributesReplaced()
        {
        }
        
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// Retrieves the Dispatcher for the thread used for rendering dynamic strokes
        /// when receiving data from the stylus input thread(s).
        /// </summary>
        protected Dispatcher GetDispatcher()
        {
            return _renderingThread != null ? _renderingThread.ThreadDispatcher : null;
        }
 
        /////////////////////////////////////////////////////////////////////
        
        void RenderPackets(StylusPointCollection stylusPoints,  StrokeInfo si)
        {
            // If no points or not hooked up to element then do nothing.
            if (stylusPoints.Count == 0 || _applicationDispatcher == null)
                return;
 
            // Get a collection of ink nodes built from the new stylusPoints.
            si.StrokeNodeIterator = si.StrokeNodeIterator.GetIteratorForNextSegment(stylusPoints);
            if (si.StrokeNodeIterator != null)
            {
                // Create a PathGeometry representing the contour of the ink increment
                Geometry strokeGeometry;
                Rect bounds;
                StrokeRenderer.CalcGeometryAndBounds(si.StrokeNodeIterator,
                                                     si.DrawingAttributes,
#if DEBUG_RENDERING_FEEDBACK
                                                     null, //debug dc
                                                     0d,   //debug feedback size
                                                     false,//render debug feedback
#endif
                                                     false, //calc bounds
                                                     out strokeGeometry,
                                                     out bounds);
        
                // If we are called from the app thread we can just stay on it and render to that
                // visual tree.  Otherwise we need to marshal over to our inking thread to do our work.
                if (_applicationDispatcher.CheckAccess())
                {
                    // See if we need to create a new container visual for the stroke.
                    if (si.StrokeCV == null)
                    {
                        // Create new container visual for this stroke and add our incremental rendering visual to it.
                        si.StrokeCV = new ContainerVisual();
 
                        // two incrementally rendered stroke segments blend together
                        // at the rendering point location, thus the alpha value at those locations are higher than the set value.
                        // This is like you draw two strokes using static rendeer and the intersection part becomes darker.
                        // Set the opacity of the RootContainerVisual of the whole incremental stroke as color.A/255.0 and override
                        // the alpha value of the color we send to mil for rendering.
                        if (!si.DrawingAttributes.IsHighlighter)
                        {
                            si.StrokeCV.Opacity = si.Opacity;
                        }
                        _mainRawInkContainerVisual.Children.Add(si.StrokeCV);
                    }
                    
                    // Create new visual and render the geometry into it
                    DrawingVisual visual = new DrawingVisual();
                    DrawingContext drawingContext = visual.RenderOpen();
                    try
                    {
                        OnDraw(drawingContext, stylusPoints, strokeGeometry, si.FillBrush);
                    }
                    finally
                    {
                        drawingContext.Close();
                    }
                    
                    // Now add it to the visual tree (making sure we still have StrokeCV after
                    // onDraw called above).
                    if (si.StrokeCV != null)
                    {
                        si.StrokeCV.Children.Add(visual);
                    }
                }
                else
                {
                    DynamicRendererThreadManager renderingThread = _renderingThread; // keep it alive
                    Dispatcher drDispatcher = renderingThread != null ? renderingThread.ThreadDispatcher : null;
 
                    // Only try to render if we get a ref on the rendering thread.
                    if (drDispatcher != null)
                    {
                        // We are on a pen thread so marshal this call to our inking thread.
                        drDispatcher.BeginInvoke(DispatcherPriority.Send,
                        (DispatcherOperationCallback) delegate(object unused)
                        {
                            SolidColorBrush fillBrush = si.FillBrush;
 
                            // Make sure this stroke is not aborted
                            if (fillBrush != null)
                            {
                                // See if we need to create a new container visual for the stroke.
                                if (si.StrokeRTICV == null)
                                {
                                    // Create new container visual for this stroke and add our incremental rendering visual to it.
                                    si.StrokeRTICV = new ContainerVisual();
 
                                    // two incrementally rendered stroke segments blend together
                                    // at the rendering point location, thus the alpha value at those locations are higher than the set value.
                                    // This is like you draw two strokes using static rendeer and the intersection part becomes darker.
                                    // Set the opacity of the RootContainerVisual of the whole incremental stroke as color.A/255.0 and override
                                    // the alpha value of the color we send to mil for rendering.
                                    if (!si.DrawingAttributes.IsHighlighter)
                                    {
                                        si.StrokeRTICV.Opacity = si.Opacity;
                                    }
                                    ((ContainerVisual)si.StrokeHV.VisualTarget.RootVisual).Children.Add(si.StrokeRTICV);
                                }
                                
                                // Create new visual and render the geometry into it
                                DrawingVisual visual = new DrawingVisual();
                                DrawingContext drawingContext = visual.RenderOpen();
                                try
                                {
                                    OnDraw(drawingContext, stylusPoints, strokeGeometry, fillBrush);
                                }
                                finally
                                {
                                    drawingContext.Close();
                                }
                                // Add it to the visual tree
                                si.StrokeRTICV.Children.Add(visual);
                            }
                
                            return null;
                        },
                        null);
                    }
                }
            }
        }
 
        /////////////////////////////////////////////////////////////////////
 
        void AbortAllStrokes()
        {
            lock(__siLock)
            {
                while (_strokeInfoList.Count > 0)
                {
                    TransitionStrokeVisuals(_strokeInfoList[0], true);
                }
            }
        }
 
 
        // The starting point for doing flicker free rendering when transitioning a real time
        // stroke from the DynamicRenderer thread to the application thread.
        //
        // There's a multi-step process to do this.  We now alternate between the two host visuals
        // to do the transtion work.  Only one HostVisual can be doing a full transition at one time.
        // When ones busy the other one reverts back to just removing the real time visual without
        // doing the full flicker free work.
        //
        // Here's the steps for a full transition using a Single DynamicRendererHostVisual:
        //
        // 1) [UI Thread] Set HostVisual.Clip = zero rect and then wait for render complete of that
        // 2) [UI Thread] On RenderComplete gets hit - Call over to DR thread to remove real time visual
        // 3) [DR Thread] Removed real time stroke visual and wait for rendercomplete of that
        // 4) [DR Thread] On RenderComplete of that call back over to UI thread to let it know that's done
        // 5) [UI Thread] Reset HostVisual.Clip = null and wait for render complete of that
        // 6) [UI Thread] On rendercomplete - we done.  Mark this HostVisual as free.
        //
        // In the case of another stroke coming through before a previous transition has completed
        // then basically instead of starting with step 1 we jump to step 2 and when then on step 5
        // we mark the HostVisual free and we are done.
        //
        void TransitionStrokeVisuals(StrokeInfo si, bool abortStroke)
        {
            // Make sure we don't get any more input for this stroke.
            RemoveStrokeInfo(si);
            
            // remove si visuals and this si
            if (si.StrokeCV != null)
            {
                if (_mainRawInkContainerVisual != null)
                {
                    _mainRawInkContainerVisual.Children.Remove(si.StrokeCV);
                }
                si.StrokeCV = null;
            }
 
            si.FillBrush = null;
 
            // Nothing to do if we've destroyed our host visuals.
            if (_rawInkHostVisual1 == null)
                return;
 
            bool doRenderComplete = false;
            
            // See if we can do full transition (only when none in progress and not abort)
            if (!abortStroke && _renderCompleteStrokeInfo == null)
            {
                // make sure lock does not cause reentrancy on application thread!
                using (_applicationDispatcher.DisableProcessing())
                {
                    lock (__siLock)
                    {
                        // We can transition the host visual only if a single reference is on it.
                        if (si.StrokeHV.HasSingleReference)
                        {
                            Debug.Assert(si.StrokeHV.Clip == null);
                            si.StrokeHV.Clip = _zeroSizedFrozenRect;
                            Debug.Assert(_renderCompleteStrokeInfo == null);
                            _renderCompleteStrokeInfo = si;
                            doRenderComplete = true;
                        }
                    }
                }
            }
 
            if (doRenderComplete)
            {
                NotifyOnNextRenderComplete();
            }
            else
            {
                // Just wait to dynamic rendering thread is updated then we're done.
                RemoveDynamicRendererVisualAndNotifyWhenDone(si);
            }
        }
 
        // Figures out the correct DynamicRenderHostVisual to use.
        private DynamicRendererHostVisual GetCurrentHostVisual()
        {
            // Find which of the two host visuals to use as current.
            if (_currentHostVisual == null)
            {
                _currentHostVisual = _rawInkHostVisual1;
            }
            else
            {
                HostVisual transitioningHostVisual = _renderCompleteStrokeInfo != null ?
                                                        _renderCompleteStrokeInfo.StrokeHV : null;
 
                if (_currentHostVisual.InUse)
                {
                    if (_currentHostVisual == _rawInkHostVisual1)
                    {
                        if (!_rawInkHostVisual2.InUse || _rawInkHostVisual1 == transitioningHostVisual)
                        {
                            _currentHostVisual = _rawInkHostVisual2;
                        }
                    }
                    else
                    {
                        if (!_rawInkHostVisual1.InUse || _rawInkHostVisual2 == transitioningHostVisual)
                        {
                            _currentHostVisual = _rawInkHostVisual1;
                        }
                    }
                }
            }
            return _currentHostVisual;
        }
 
 
        // Removes ref from DynamicRendererHostVisual.
        void TransitionComplete(StrokeInfo si, Dispatcher applicationDispatcher)
        {
            // make sure lock does not cause reentrancy on application thread!
            using(applicationDispatcher.DisableProcessing())
            {
                lock(__siLock)
                {
                    si.StrokeHV.RemoveStrokeInfoRef(si);
                }
            }
        }
 
        void RemoveStrokeInfo(StrokeInfo si)
        {
            lock(__siLock)
            {
                _strokeInfoList.Remove(si);
            }
        }
 
        StrokeInfo FindStrokeInfo(int timestamp)
        {
            lock(__siLock)
            {
                for (int i=0; i < _strokeInfoList.Count; i++)
                {
                    StrokeInfo siCur = _strokeInfoList[i];
                    
                    if (siCur.IsTimestampWithin(timestamp))
                    {
                        return siCur;
                    }
                }
            }
            
            return null;
        }
    
        /////////////////////////////////////////////////////////////////////
 
        /////////////////////////////////////////////////////////////////////
        /// <summary>
        /// [TBS] - On UIContext
        /// </summary>
        public DrawingAttributes DrawingAttributes
        {
            get // called from two UIContexts
            {
                return _drawAttrsSource;
            }
            set // (called in UIContext)
            {
                ArgumentNullException.ThrowIfNull(value);
 
                _drawAttrsSource = value;
 
                OnDrawingAttributesReplaced();
            }
        }
 
        private void CreateInkingVisuals()
        {
            if (_mainContainerVisual == null)
            {
                _mainContainerVisual = new ContainerVisual();
                _mainRawInkContainerVisual = new ContainerVisual();
                _mainContainerVisual.Children.Add(_mainRawInkContainerVisual);
            }
            
            if (IsActiveForInput)
            {
                // Make sure lock() doesn't cause reentrancy.
                using (Element.Dispatcher.DisableProcessing())
                {
                    CreateRealTimeVisuals();
                }
            }
        }
        
        /// <summary>
        /// Create the visual target
        /// This method is called from the application context
        /// </summary>
        private void CreateRealTimeVisuals()
        {
            // Only create if we have a root visual and have not already created them.
            if (_mainContainerVisual != null && _rawInkHostVisual1 == null)
            {
                // Create new VisualTarget and hook up in apps visuals under element.
                _rawInkHostVisual1 = new DynamicRendererHostVisual();
                _rawInkHostVisual2 = new DynamicRendererHostVisual();
                _currentHostVisual = null;  // Pick a new current HostVisual on first stylus input.
                _mainContainerVisual.Children.Add(_rawInkHostVisual1);
                _mainContainerVisual.Children.Add(_rawInkHostVisual2);
                // NOTE: Do the work later if perf is bad hooking up VisualTargets on StylusDown...
                
                // Guarentee that objects are valid when on the DR thread below.
                //DynamicRendererHostVisual[] myArgs = new DynamicRendererHostVisual[2] { _rawInkHostVisual1, _rawInkHostVisual2 };
 
                // Do this last since we can be reentrant on this call and we want to set
                // things up so we are all set except for the real time thread visuals which 
                // we set up on first usage.
                _renderingThread = DynamicRendererThreadManager.GetCurrentThreadInstance();
 
                /*
                // We are being called by the main UI thread, so invoke a call over to
                // the inking thread to create the visual targets.
                // NOTE: Since input rendering uses the same priority we are guanenteed
                //       that this will be processed before any input will try to be rendererd.
                _renderingThread.ThreadDispatcher.BeginInvoke(DispatcherPriority.Send,
                (DispatcherOperationCallback)delegate(object args)
                {
                    DynamicRendererHostVisual[] hostVisuals = (DynamicRendererHostVisual[])args;
                    VisualTarget vt;
                    // Query the VisualTarget properties to initialize them.
                    vt = hostVisuals[0].VisualTarget;
                    vt = hostVisuals[1].VisualTarget;
                    
                    return null;
                },
                myArgs);
                */
            }
        }
 
        /// <summary>
        /// Unhoot the visual target.
        /// This method is called from the application Dispatcher
        /// </summary>
        private void DestroyRealTimeVisuals()
        {
            // Only need to handle if already created visuals.
            if (_mainContainerVisual != null && _rawInkHostVisual1 != null)
            {
                // Make sure we unhook the rendercomplete event.
                if (_waitingForRenderComplete)
                {
                    MediaContext.From(_applicationDispatcher).RenderComplete -= _onRenderComplete;
                    _waitingForRenderComplete = false;
                }
 
                _mainContainerVisual.Children.Remove(_rawInkHostVisual1);
                _mainContainerVisual.Children.Remove(_rawInkHostVisual2);
 
                _renderCompleteStrokeInfo = null;
 
                DynamicRendererThreadManager renderingThread = _renderingThread; // keep ref to keep it alive in this routine
                Dispatcher drDispatcher = renderingThread != null ? renderingThread.ThreadDispatcher : null;
 
                if (drDispatcher != null)
                {
                    drDispatcher.BeginInvoke(DispatcherPriority.Send,
                    (DispatcherOperationCallback)delegate(object unused)
                    {
                        _renderCompleteDRThreadStrokeInfoList.Clear();
                        
                        drDispatcher = renderingThread.ThreadDispatcher;
                        
                        if (drDispatcher != null && _waitingForDRThreadRenderComplete)
                        {
                            MediaContext.From(drDispatcher).RenderComplete -= _onDRThreadRenderComplete;
                        }
                        _waitingForDRThreadRenderComplete = false;
                
                        return null;
                    },
                    null);
                }
 
                // Make sure to free up inking thread ref to ensure thread shuts down properly.
                _renderingThread = null;
 
                _rawInkHostVisual1 = null;
                _rawInkHostVisual2 = null;
                _currentHostVisual = null;  // We create new HostVisuals next time we're enabled.
 
                AbortAllStrokes(); // Doing this here avoids doing a begininvoke to enter the rendering thread (avoid reentrancy).
            }
        }
 
        /////////////////////////////////////////////////////////////////////
        private Dispatcher          _applicationDispatcher;
        private Geometry            _zeroSizedFrozenRect;
        private DrawingAttributes   _drawAttrsSource = new DrawingAttributes();
        List<StrokeInfo>            _strokeInfoList = new List<StrokeInfo>();
 
        // Visuals layout:
        // 
        //  _mainContainerVisual (root of inking tree - RootVisual [on app Dispatcher])
        //     |
        //     +-- _mainRawInkDispatcher (app dispatcher based stylus events renderer here [on app dispatcher])
        //     |
        //     +-- _rawInkHostVisual1 (HostVisual for inking on separate thread [on app dispatcher])
        //     |          |
        //     |          +-- VisualTarget ([on RealTimeInkingDispatcher thread])
        //     |
        //     +-- _rawInkHostVisual2 (HostVisual for inking on separate thread [on app dispatcher])
        //                |
        //                +-- VisualTarget ([on RealTimeInkingDispatcher thread])
        // 
        private ContainerVisual              _mainContainerVisual;
        private ContainerVisual              _mainRawInkContainerVisual;
        private DynamicRendererHostVisual    _rawInkHostVisual1;
        private DynamicRendererHostVisual    _rawInkHostVisual2;
 
        DynamicRendererHostVisual            _currentHostVisual; // Current HV.
 
        // For OnRenderComplete support (for UI Thread)
        EventHandler  _onRenderComplete;
        bool          _waitingForRenderComplete;
        readonly object        __siLock = new object();
        private StrokeInfo  _renderCompleteStrokeInfo;
 
        // On internal real time ink rendering thread.
        private DynamicRendererThreadManager _renderingThread;
        
        // For OnRenderComplete support (for DynamicRenderer Thread)
        EventHandler  _onDRThreadRenderComplete;
        bool          _waitingForDRThreadRenderComplete;
        Queue<StrokeInfo>    _renderCompleteDRThreadStrokeInfoList = new Queue<StrokeInfo>();
}
}