Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ink drying can sometimes result in a flicker when the wet ink isn't rendering but the dry ink hasn't rendered yet #181

Closed
Trasteby opened this issue Oct 22, 2015 · 11 comments
Labels

Comments

@Trasteby
Copy link

When working with Win2D to do a custom dry ink rendering, it is suggested here: http://microsoft.github.io/Win2D/html/M_Microsoft_Graphics_Canvas_CanvasDrawingSession_DrawInk.htm that the EndDry method of an InkSynchronizer can be called immidiately after CanvasDrawingSession.DrawInk. This does not work very well however, as the wet ink is removed before Win2D has time to actually draw the ink on screen. This means that the ink disappears for a split-second before it is drawn on the CanvasControl. Alternatively, you can delay the EndDry call by some time (like 200 ms) and instead get a slighly thicker line during a split second as both the wet and dry ink are rendered at the same time for a brief period of time.

I mean something like this

private async void DrawInk(InkSynchronizer inkSynchronizer) {
    using (CanvasDrawingSession ds = canvasRenderTarget.CreateDrawingSession())
    {
        var strokes = inkSynchronizer.BeginDry();
        ds.DrawInk(strokes);
    }

    CanvasControl.Invalidate(); // CanvasControl draws canvasRenderTarget

    // await drawing to screen here

    inkSynchronizer.EndDry();
}

This could be implemented either as a way to await the CanvasControl.Invalidate (or similar) method, or an event which fires every time a CanvasControl has finished drawing. The latter would be significanly messier in this case, but still a lot better than nothing.

The documentation of CanvasControl.Invalidates states that a Draw event will be fired "shortly afterward". I have attempted to call EndDry at the end of a method which is attached to the Draw event, but even this is too early, as the content hasn't appeared on screen yet at that time.

If there is a way to achieve this, please enlighten me. Otherwise, please consider implementing something like this.

@damyanp
Copy link
Member

damyanp commented Oct 22, 2015

Does the InkExample in ExampleGallery work for you? That shows the recommended pattern for drying ink.

@Trasteby Trasteby reopened this Dec 9, 2015
@Trasteby
Copy link
Author

Trasteby commented Dec 9, 2015

After a lot of testing, I have found that the bug (wet stroke disappearing slightly before the dry stroke appears, causing a visual glitch of the stroke blinking) does appear in the InkExample in the ExampleGallery. For some reason, it happens extremely rarely there, compared to apps I've written using DirectInk/Win2D, even though the way we dry the ink is very similar to the one in the Example Gallery.

To reproduce the issue:

  1. Install the ExampleGallery on a relatively weak computer (I use a i3 Surface Pro 3, but it is likely to be easier to reproduce on something weaker).
  2. Open the Ink Example
  3. Set Stroke Width and Stroke Height to values >50 (not necessary, but makes the bug appear more often). Set a rotation as well.
  4. Make large, rotating strokes across the screen. Change color between strokes. Every once in a while, the stroke will blink when you release the pen/mouse.

I want to mention that in other apps than the ExampleGallery, using a very similar implementation of ink, this becomes much, much more common on the same hardware (happening roughly every other stroke). I'm trying to investigate why there's such a big difference. But being able to reproduce the bug in the example gallery shows that it is a "legit issue" (bug?).

@damyanp damyanp changed the title A way to know when a CanvasControl has finished drawing Ink drying can sometimes result in a flicker when the wet ink isn't rendering but the dry ink hasn't rendered yet Dec 9, 2015
@damyanp
Copy link
Member

damyanp commented Dec 9, 2015

I managed to repro this by having a large amount of dry ink on screen example gallery, and then draw a large amount of ink before releasing the mouse button.

@shawnhar
Copy link
Member

I also can repro very occasionally. I tried adding a large (1 sec) time delay in between calling inkSynchronizer.EndDry and returning from canvasControl_Draw, but oddly that does not make the flicker happen any more often.

@shawnhar shawnhar added the bug label Dec 11, 2015
@shawnhar
Copy link
Member

Hi,

I am wondering whether you are able to repro this issue using the Complex Ink sample (https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/ComplexInk) which uses DirectInk without Win2D?

This does basically the same things as the Win2D ink implementation but in a slightly different order, and I am wondering whether that is the cause of the problem. I haven't been able to repro the flicker using ComplexInk sample, but, my ability to repro it using Win2D Example Gallery is also not very good (I just tried for ~5 minutes and couldn't make it flicker once, although I did see the flicker before) so I'm not very confident in that result!

Hoping that someone who is able to repro more reliably can narrow down whether the problem also occurs in ComplexInk.

Thanks,

@shawnhar
Copy link
Member

Hi,

DirectInk team is still working to understand the root cause of what is going wrong here, but in the meantime we made a change to the Example Gallery code that gets rid of the flicker at least as far as I can tell. It wasn't easy for me to repro before, so I can't be 100% sure, but I spent quite a while trying and cannot repro any more after this code change.

One issue to be aware of is that with this new approach, wet and dry ink will often be displayed at the same time for just one frame, one over the top of the other. I don't see any visual problems from this, though.

We will include this updated sample code in the next release of Win2D (due out on Jan 4th) but to get you unblocked, the idea is to move the BeginDry call earlier and the EndDry call later.

You need a couple of extra fields:

        IReadOnlyList<InkStroke> pendingDry;
        int deferredDryDelay;

Move the BeginDry call into your InkPresenter_StrokesCollected event handler:

            Debug.Assert(pendingDry == null);
            pendingDry = inkSynchronizer.BeginDry();
            canvasControl.Invalidate();

Inside your Draw event handler, instead of directly calling EndDry, register for that to happen in a later CompositionTarget.Rendering event, eg:

            if (pendingDry != null && deferredDryDelay == 0)
            {
                DrawStrokeCollectionToInkSurface(pendingDry);

                // Register to call EndDry on the next-but-one draw,
                // by which time our dry ink will be visible.
                deferredDryDelay = 1;
                CompositionTarget.Rendering += DeferredEndDry;
            }

This event handler looks like so:

        private void DeferredEndDry(object sender, object e)
        {
            Debug.Assert(pendingDry != null);

            if (deferredDryDelay > 0)
            {
                deferredDryDelay--;
            }
            else
            {
                CompositionTarget.Rendering -= DeferredEndDry;

                pendingDry = null;

                inkSynchronizer.EndDry();
            }
        }

Hope this helps. I'm keen to hear whether this approach solves the problem for you as well as fixing my repro case!

@shawnhar
Copy link
Member

shawnhar commented Jan 4, 2016

Win2D 1.12.0 (released today) includes this fix to the ink demo in Example Gallery. Closing issue because I believe this fixes the flicker problem. Please let us know if you are still able to repro with the latest sample!

@shawnhar shawnhar closed this as completed Jan 4, 2016
@Trasteby
Copy link
Author

I will test this as soon as I can. Just looking at the sample code, I have a question: What happens if the StrokesCollected events get fired twice in a row, before the Draw event is fired? If I understand your code correctly, then the first stroke will get overwritten by the line "pendingDry = inkSynchronizer.BeginDry();" (if BeginDry even works before an EndDry is called). As far as I can tell, the code does nothing to guarantee that a Draw will happen before the next StrokesCollected even will fire.

Am I missing something? I'm thinking about changing pendingDry from an IReadOnlyList to a normal List, and append the strokes from BeginDry to it, rather than replacing it. If there are any reasons the sample code is written the way it is, please let me know!

@shawnhar
Copy link
Member

StrokesCollected event will never fire in between a BeginDry and its matching EndDry call.

@Trasteby
Copy link
Author

Okay, thanks for the information (and quick reply!).

I have now tested the solution, as you've written it. While the result looks great on a pixel-dense device like a Surface Pro, running it on a lower-res and bigger screen causes very visible flicker during the single frame when both the wet and dry ink are simultaneously drawn. To the naked eye, this solution looks more or less identical to my original solution (which was to just put an "await Task.Delay(100)" in the Draw even handler, before EndDry).

I have tried both running the ExampleGallery app (latest version from today, 1.15.0) and in my own app and the behaviour is identical.

I would suggest reopening this issue, because the solution here only seems to solve the problem on pixel-dense displays.

@shawnhar
Copy link
Member

With the new algorithm it is normal that you will often see the wet and dry ink at the same time for one frame.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants