// 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); } }
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); // We're done } } else { TransitionComplete(si); // We're done } return null; }, 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(); // NTRAID#WINOS-1133229-2005/04/27-XIAOTU: 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(); // NTRAID#WINOS-1133229-2005/04/27-XIAOTU: 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); } } } }
///////////////////////////////////////////////////////////////////// /// <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); } }
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); } } }
internal void RemoveStrokeInfoRef(StrokeInfo si) { _strokeInfoList.Remove(si); }
///////////////////////////////////////////////////////////////////// /// <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.Get(SRID.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. } } } }
internal void AddStrokeInfoRef(StrokeInfo si) { _strokeInfoList.Add(si); }
void RemoveStrokeInfo(StrokeInfo si) { lock(__siLock) { _strokeInfoList.Remove(si); } }
// Removes ref from DynamicRendererHostVisual. void TransitionComplete(StrokeInfo si) { // make sure lock does not cause reentrancy on application thread! using(_applicationDispatcher.DisableProcessing()) { lock(__siLock) { si.StrokeHV.RemoveStrokeInfoRef(si); } } }
/// <summary> /// Check whether a certain percentage of the stroke is within the lasso /// </summary> /// <param name="lassoPoints"></param> /// <param name="percentageWithinLasso"></param> /// <returns></returns> public bool HitTest(IEnumerable<Point> lassoPoints, int percentageWithinLasso) { if (lassoPoints == null) { throw new System.ArgumentNullException("lassoPoints"); } if ((percentageWithinLasso < 0) || (percentageWithinLasso > 100)) { throw new System.ArgumentOutOfRangeException("percentageWithinLasso"); } if (percentageWithinLasso == 0) { return true; } StrokeInfo strokeInfo = null; try { strokeInfo = new StrokeInfo(this); StylusPointCollection stylusPoints = strokeInfo.StylusPoints; double target = strokeInfo.TotalWeight * percentageWithinLasso / 100.0f - PercentageTolerance; Lasso lasso = new SingleLoopLasso(); lasso.AddPoints(lassoPoints); for (int i = 0; i < stylusPoints.Count; i++) { if (true == lasso.Contains((Point)stylusPoints[i])) { target -= strokeInfo.GetPointWeight(i); if (DoubleUtil.LessThan(target, 0f)) { return true; } } } return false; } finally { if (strokeInfo != null) { //detach from event handlers, or else we leak. strokeInfo.Detach(); } } }
/// <summary> /// Check whether a certain percentage of the stroke is within the Rect passed in. /// </summary> /// <param name="bounds"></param> /// <param name="percentageWithinBounds"></param> /// <returns></returns> public bool HitTest(Rect bounds, int percentageWithinBounds) { if ((percentageWithinBounds < 0) || (percentageWithinBounds > 100)) { throw new System.ArgumentOutOfRangeException("percentageWithinBounds"); } if (percentageWithinBounds == 0) { return true; } StrokeInfo strokeInfo = null; try { strokeInfo = new StrokeInfo(this); StylusPointCollection stylusPoints = strokeInfo.StylusPoints; double target = strokeInfo.TotalWeight * percentageWithinBounds / 100.0f - PercentageTolerance; for (int i = 0; i < stylusPoints.Count; i++) { if (true == bounds.Contains((Point)stylusPoints[i])) { target -= strokeInfo.GetPointWeight(i); if (DoubleUtil.LessThanOrClose(target, 0d)) { return true; } } } return false; } finally { if (strokeInfo != null) { //detach from event handlers, or else we leak. strokeInfo.Detach(); } } }