Page 1 of 1

OSR OnPaint() provides partially rendered buffer

PostPosted: Mon Mar 07, 2022 4:32 am
by cfx
Hi everyone,

Note: this post is a copy of this issue, but I couldn't post here due to an account creation issue.

I’ve derived the cef_simple example to implement offscreen rendering in a restapi.
The webpage I’m trying to capture renders asynchronously, through some JS code. It renders one or several canvas, with a few html elements overlaid on top.
The JS code can notify me when all the canvas/html elements have been drawn/updated, which allows me to trigger a JS callback, which in turn raises a flag to tell my RenderHandler that on the next OnPaint() it needs to copy the provided buffer, and finally calls Invalidate(PET_VIEW) in order to trigger a repaint.

OnPaint() is indeed triggered, the buffer and the one dirty region have the same expected size (usually 1024x1024).

The content of the buffer might however be incorrect: the canvas and/or the html elements might not have been rendered, or the buffer can be partially filled with random noise.
It happens approx. 15% of the time, it looks like its linked to my server’s load.

I’ve tried wrapping my JS callback in requestAnimationFrame, but that does not seems to have any effect (which might actually be expected, since requestAnimationFrame is there to ensure some code is executed before the next repaint, while I’m trying to notify CEF that everything has been painted).

The only solution I found is to delay my JS callback with setTimeout(), but even with a 250ms delay I can still get some incorrect results (and obviously I would like to avoid such delay).

Is there something I need to do to ensure the buffer is completely repainted?


CEF versions : 97.1.9, 98.1.0, 98.1.19, 98.2.0, 98.2.1, 99.2.7 (will try some older versions).
Running in a Docker container, on Linux, with no GPU available.

Re: OSE OnPaint() provides partially rendered buffer

PostPosted: Mon Mar 07, 2022 12:34 pm
by magreenblatt
Just to make sure I understand. Are you saying that calling Invalidate(PET_VIEW) results in OnPaint() with the expected size (e.g. 1024x1024) but the pixels inside that specified region sometimes contain random noise?

Does this also occur for calls to OnPaint() that are not triggered via Invalidate()?

Re: OSR OnPaint() provides partially rendered buffer

PostPosted: Wed Mar 30, 2022 11:52 am
by cfx
Yes, sometimes it contains noise, but I believe it might just be a side effect of another problem.

In my standard workflow, I have a few (512x512) or (1024x1024) canvas stacked on top of each others, e.g.:
+------------+ y=0
| canvas 0 |
+------------+ y=1024
| canvas 1 |
+------------+ y=2048
| canvas 2 |
+------------+ y=3072
Each canvas is rendered asynchronously, but my code is able to notify me when the code that update each canvas has been executed.
When I've received the notifications for every canvas, I trigger an Invalidate(PET_VIEW), which effectively triggers OnPaint().

In my experiments, every OnPaint() that I receive cover the expected region, but if I look at the dirty rectangle, the results varies from one run to the other.

I might get a rectangle that covers the whole requested region, and everything has been correctly painted. This is the perfect behavior, I've got everything I need, I can close my browser.

Or I might get a rectangle in the middle of the requested region (e.g. from y=1024 to y=2048).
Or a rectangle that covers the requested region, but some parts have not been painted, e.g.:
+------------------------------------+
| canvas 0 correctly rendered
+------------------------------------+
| background page color, or
| some of html overlay that
| I have on top of my canvas,
| or random noise
+------------------------------------+
| canvas 2 correctly rendered
+------------------------------------+
If I let CEF continue, I will get other OnPaint() that will fill the blanks.
I can accumulate the dirty rectangle and effectively compose the requested region, but I don't really know when all the OnPaint() will have occurred.

So, at the moment, my theory is that every update to my canvas will post some kind of "Paint" event the event loop.
But for some reason, Invalidate(PET_VIEW) does not guarantee that all the events will be processed before the next call to OnPaint().
That would explain why OnPaint() might give me a partially rendered buffer, and why subsequent OnPaint() will finalize everything.

The random noise part could just be chromium not clearing the overall buffer before painting in it.

I'm about to try to implement a custom event loop that will hopefully allow me to trigger Invalidate(PET_VIEW) only when I detect that there's no more event to process (aka all the "Paint" events have been processed).

Re: OSR OnPaint() provides partially rendered buffer

PostPosted: Wed Apr 06, 2022 10:26 am
by cfx
Well, a custom event loop is probably a dead-end, I can be notified when there's work to do with OnScheduleMessagePumpWork(), I can trigger the processing of some events with CefDoMessageLoopWork(), but I'm still unable to know when there's no more event to process.

Re: OSR OnPaint() provides partially rendered buffer

PostPosted: Thu Aug 11, 2022 7:32 pm
by AndrewVW
Did you have any luck with this issue?

I'm in the same boat, with a similar use case:

--- Use case:
I use CEF as a "rendering engine" - I load a page and some data to do animations. I communicate with JS to seek to frames, and once the JS code has performed all of the actions, it calls a custom CEF-defined JS function to notify that it has completed the frame. After the notification, I use C++ to invalidate and take the first OnPaint to create a bitmap for a video frame. This is done with software rendering on cloud servers.

Similar to you, I've had to put in setTimeout() calls in my JS before calling the completed() function. If I don't, many frames have artifacts or missing elements. With a 250ms setTimeout, it renders most videos correctly, but I've had to go as high as 1000ms for complex animations and content.
---

Like you said, I think this is tied to load, and I think it's an underlying "issue" in Chromium itself. This page seems to confirm it:
https://chromium.googlesource.com/chrom ... c_works.md -- Check the scheduling section, it mentions slow rasters. It also mentions that in the single-threaded mode, it doesn't use a pending tree for full commits. (Note: I don't understand all of this, so I'm sure some I'm looking at the wrong parts often haha)

My theory is similar to yours: Chromium can't complete the required work in a single draw, so it completes over multiple draws.

I've been digging around in cc and components/viz to try to understand the call stack and find out if there's a variable somewhere that can be exposed that shows additional work is needed. It's quite a bit over my head, but it would be so beneficial to know that all compositor work is completed. Using setTimeout makes my renders slow and wasteful, and it still doesn't guarantee perfect output!

Re: OSR OnPaint() provides partially rendered buffer

PostPosted: Thu Nov 17, 2022 8:32 am
by cfx
Hello Andrew,

Sorry for the late answer (for some reason I don't receive notifications).

Unfortunately no, I haven't progressed on this subject, and haven't really touched it since.
But I'm still hoping to revive that next year.