clipRegion fails sometimes when region is complex

253 views
Skip to first unread message

mattias...@gmail.com

unread,
Jan 6, 2022, 9:02:19 AM1/6/22
to skia-discuss
Hi!

I have an app with lots of static text so I've decided to use event driven drawing since text seems to be a bottleneck in my case. It kind of works like this:
  1. Cache the previous frame as an SkImage
  2. Keep track of dirty bounds with an SkRegion
  3. Before drawing next frame I draw the cached image to the fresh canvas
  4. Clip canvas (clipRegion(dirtyRegion)) 
  5. Draw
  6. ...repeat...
On MAC I had to modify these settings otherwise I got parts outside of my clipped region that got unexpectedly modified
opts.fAvoidStencilBuffers = true;
opts.fUseDrawInsteadOfClear = GrContextOptions::Enable::kYes;

On Windows I still get parts outside of my clipped region getting modified.

Does anyone know the relationship region clipping and those settings?

My Specs:
MacOS Big Sur
MacBook Pro (13-Inch, M1, 2020)
Metal Family: Supported, Metal GPUFamily Apple 7

I would be super grateful for any help I can get!
// Mattias

mattias...@gmail.com

unread,
Feb 4, 2022, 8:15:14 AM2/4/22
to skia-discuss

Hi! 

Is there anyone working on Skia that knows if there is any bugs related to SkPaths not respecting the clip boundaries? The problem seems to only appear on a Retina display. For filled paths I seem to have found a workaround using clipPath + fillColor, but stroked paths still are buggy. Can anyone please help?

// Mattias

Michael Ludwig

unread,
Feb 7, 2022, 9:32:51 AM2/7/22
to skia-discuss
clipRegion is somewhat unique (so much so that we are actually hoping it can be deprecated and removed), in that it ignores the current transform on the canvas when you call clipRegion; all other clip operations are in the coordinate space of the current matrix. If you're already taking this into account, or aren't modifying the canvas matrix before you draw the previous frame's image and modifying the clip, then that wouldn't be the cause of your issues. However, if you were expecting the region to be somewhere else, then that could explain for the "leaking".

On the GPU backend, clipRegion actually is implemented as either a clipRect or a clipPath, depending on the complexity of the region. Are you using SkRegion::Op to construct a detailed dirty boundary? Or just tracking the net bounding box?  Assuming you create a detailed dirty boundary, that will go to the clip path route. Internally, path clipping can use the stencil buffer as one of its implementation strategies, so changing fAvoidStencilBuffers will certainly affect that. I cannot think of a good reason for UseDrawInsteadOfClear unless there is a bug where we're using a clear to fill a solid rectangle when we shouldn't due to clipping, and forcing to use a draw lets it pick up the clip properly.

Can you either reproduce these bugs using fiddle.skia.org, or atleast post a minimal example of code that reproduces on your machine even if it doesn't reproduce on fiddle? There's a lot of logic under the hood that can affect these things, so it's best to know exactly what you're doing.  Similarly, for your second set of questions, are these paths not respecting any clip boundary? or just clips that come from clipRegion?  Is your code doing anything differently when setting up the GrContext based on what display you are on? Are you using OpenGL or Metal?

mattias...@gmail.com

unread,
Feb 9, 2022, 10:26:08 AM2/9/22
to skia-discuss
I'm really grateful for your answer.

To answer your questions:
I am taking matrix transformation into account, but to exclude the SkRegion I switched to a SkRect and just did clipRect instead which caused the same problem for me.
I haven't been able to produce an example app but will continue to try. 
I am running an OpenGL backend.
I have however been able to find a way for the clipping to behave, iGrContext->asDirectContext()->resetContext(GrGLBackendState::kView_GrGLBackendState) seems to do the trick for me, if called before every time I fetch the surface.
Looking at the source code it looks like some glScissor states being reset when resetContext() is called, but I'm not that fluid in reading Skia source code so I'm not drawing any conclusions.
I'm quite sure we don't modify the glState at all,  these are the only calls we do after GrContext is created.
glViewport() when size changes
makeCurrentContext & clearCurrentContext before we start drawing and after flush
flushBuffer after flushAndSubmit() on SkSurface.

The app does not do anything fancy, works something like this:
surfaceOne = MakeFromBackendRenderTarget()
surfaceTwo = MakeRenderTarget().
We clip surfaceTwo.clipRect(dirtyRect)
....
Walks through the view hierarchy and draws all views intersecting dirtyRect (canvas gets clipped to view bounds before drawing)
....
Then surfaceTwo.draw(surfaceOne.getCanvas(), 0, 0);
surfaceOne.flushAndSubmit();

I do monitor save count so it isn't any mismatches of save()/restore().

mattias...@gmail.com

unread,
Feb 10, 2022, 10:41:47 AM2/10/22
to skia-discuss
Thank you for the help I've gotten. 

I digged deeper into the Skia code base and looked at how the GL_SCISSOR_TEST state was being enabled/disabled & glScissor calls. It was working fine but I stumbled on the "force_update_scissor_state_when_binding_fbo0" setting which had this very interesting comment "The driver forgets the correct scissor state when using FBO 0". Looking at the code path it seems to toggle the GL_SCISSOR_TEST back and forth I guess to make sure state is set and included a glScissor if there was an active clipRect. Turning this setting on solved all my problems. So I guess I was running into a driver bug. 

Reply all
Reply to author
Forward
0 new messages