Use tile-based PDF rendering

Bug #1329087 reported by Martin Spacek
14
This bug affects 2 people
Affects Status Importance Assigned to Milestone
qpdfview
Fix Released
Wishlist
Adam Reichold

Bug Description

I'm guessing that qpdfview does not use tile-based PDF rendering. At high zoom levels, panning and zooming seem quite slow (unless the cache size is set very high, and you happen to hit a cached view), as if the entire page is being unnecessarily rendered. This is a long-standing bug in evince, and it's not clear to me if poppler supports tile-based rendering yet:

https://bugzilla.gnome.org/show_bug.cgi?id=303365

Related branches

Changed in qpdfview:
status: New → Triaged
importance: Undecided → Wishlist
Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello Martin,

thanks for creating a bug report about this and yes, qpdfview does render on a per-page level which makes high scale factors unnecessarily memory-intensive. The API of Poppler itself does expose functions to render arbitrary sub-rectangles of the page's bounding box, so the problem is one of development time and complexity. (You could even render each tile in a separate thread using the Qt frontend.) All related functionality in qpdfview like caching, obsolete pixmaps assumes a per-page granulartiy, so even if the tiled rendeing itself would be rather straight forward, its interaction with other features is not.
In any case, it is mainly a question of someone finding the time to factor out rendering from PageItem and RenderTask into an intermediate TileItem class. (As it stands, I will probably not find the necessary spare time to do this.)

Best regards, Adam.

Changed in qpdfview:
assignee: nobody → Adam Reichold (adamreichold)
status: Triaged → In Progress
Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello again,

I gave this some though and actually started to implement the outlined solution using an intermediate TileItem class to handle rendering rectangular parts of the whole page with a predefined size. Currently this target tile size is hard-coded to 1024 pixel, but I guess making it a hidden setting, i.e. accessible only through the configuration file, is sensibleas well.

Of course, this work is far from complete: I basically copied PageItem and stripped it down to rendering, then used several of those new items as children of the page to render parts of it. Hence, the code needs to be cleaned-up all over the place to remove the redundancies between PageItem and TileItem. It also needs to be simplified w.r.t. passing of the rendering parameters from DocumentView down to RenderTask. And of course, it needs to be tested a lot since this will probably introduce a gazillion new corner cases into the rendering logic.

There is also a more insidious issue: If prefetching is enabled, a lot of render tasks might be in-flight and in need of cancellation when one goes from a large to a small zoom factor (i.e. from many to few tiles per page). Even when implementing forced cancellation to override the prefetching, this can block the interface for several seconds. I am not sure how to solve this. Maybe the amount of prefetching needs to be adjusted w.r.t. the amount of tiling or the cancellation concept has to be rethought. (But we really can't delete a TileItem before its RenderTask is gone, since it holds a reference to the model page.)

Please test and report back here. Best regards, Adam.

@Benjamin: This branch seems like a good candidate for some tests using Foobar.pdf, maybe you can drop me a copy again... ;-)

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello again,

the branch starts to come together: the settings are there, most of the redundancies are gone and the deletion problem is solved by making obsolete tile items try to delete themselves whenever the event loop is idle but only doing so if their render task is already finished. (I also added overlap to the tiles to prevent rendering artifacts due to pixel rounding errors.)

There do remain two serious problem which IMHO prevent this from being universally useful: The obsolete pixmaps functionality is still missing and I am not sure how this can be implemented at all. Also updating the tiles will trigger updates to the neighboring tiles as well, hence if not all visible tiles are cached, the will continuously trigger repaints of each other which never stop. (I have not yet found a way to tell QGraphicsView to repaint one and only one item and depending on how its compositing works, this might not be possible at all. Maybe using Z ordering in between the tiles could help here.)

Best regards, Adam.

Revision history for this message
Martin Spacek (mspacek) wrote :

Wow, that was quick! Nice! OK, I've tried it out on my LaTeX thesis, which is full of big figures, both raster and vector graphics. Note that I have prefetch turned on and set to 1, cache is 1024 MB, obsolete pixmaps are kept, and I tend to use continuous 2-page mode. Some impressions:

1. When zooming in or out with prefetch on, overall rendering time is as slow, or even slower than in trunk. Turning prefetch off fixes this. Actually, with prefetch off, the tile-based branch feels faster than trunk. I already like it better!

2. Would it be possible to display the tiles that rendered the fastest (mostly just text) first, so that they aren't held up by the slower tiles (mostly figures)? A kind of race to the finish line? Right now, I get the impression that doesn't happen. Tiles generally seem to paint left to right, and mostly top-down too, even when the top left is dominated by a figure.

3. Is the tile size in screen pixels? Would it not make more sense to have it in page units, say a few centimeters on a side, something like 1/6 or 1/8 of a (US letter) page? Could tiles be rectangular? It might make more sense to adjust the aspect ratio of the tiles for each page to match the aspect ratio of the page.

4. Actually, the tiles already seem to be rectangular with the right aspect ratio, maybe 1024 pix high, and the proportionally smaller amount (8.5/11) wide. However, when I zoom in, the tile size seems to increase up to a point, and then drops again once less than a certain number of tiles are on screen. Hm, so perhaps they are more page-based than screen-based after all?

5. I've set pixel overlap to 0, and I haven't seen any rendering artifacts.

6. What are the prospects for multithreaded tile rendering and making use of all the cores?

7. Making obsolete pixmaps work with tile-based rendering would definitely be great. Right now, if the tile isn't cached on a zoom, it's blank :(

I'll continue using the tile-rendering branch and report any other problems.

Revision history for this message
Martin Spacek (mspacek) wrote :

Also, are tiles cached when you switch from one zoom level to another? With cache now set to 2048 MB and prefetch off, tiles definitely don't seem to be cached when I alternate back and forth between two adjacent zoom levels. It would be nice if they were. In comparison, tiles do seem to be cached within a given zoom level, i.e. subsequent panning over the same part of the document is fast after the first render at a given zoom level.

Revision history for this message
Adam Reichold (adamreichold) wrote : Re: [Bug 1329087] Re: Use tile-based PDF rendering
Download full text (5.5 KiB)

Hello,

Am 13.06.2014 19:47, schrieb Martin Spacek:
> Wow, that was quick! Nice! OK, I've tried it out on my LaTeX thesis,
> which is full of big figures, both raster and vector graphics. Note that
> I have prefetch turned on and set to 1, cache is 1024 MB, obsolete
> pixmaps are kept, and I tend to use continuous 2-page mode. Some
> impressions:

Note that keeping obsolete pixmaps does nothing in the tiled rendering
branch.

> 1. When zooming in or out with prefetch on, overall rendering time is as
> slow, or even slower than in trunk. Turning prefetch off fixes this.
> Actually, with prefetch off, the tile-based branch feels faster than
> trunk. I already like it better!

The interaction of prefetching with tiling is definitely suboptimal: The
problem is that a high scale factors, a single page will consist of a
lot of tiles and hence prefetching two pages will result in a lot of
render tasks that can flood the thread pool. So prefetching should be
adjusted w.r.t. to tiling, but that would break encapsulation, as the
page is currently the only one who knowns that it is tiled. So
prefetching is an open point as is keeping obsolete pixmaps. (As I said
earlier, tiling itself is rather straight-forward, it is the interaction
with other functions that is problematic.)

> 2. Would it be possible to display the tiles that rendered the fastest
> (mostly just text) first, so that they aren't held up by the slower
> tiles (mostly figures)? A kind of race to the finish line? Right now, I
> get the impression that doesn't happen. Tiles generally seem to paint
> left to right, and mostly top-down too, even when the top left is
> dominated by a figure.

The tiles are not displayed in any particular order, the render task
that finishes first is displayed first. So the apparent order in your
case just indicates that there is no real difference in rendering time
and the tiles finish in the same order as they are started. (Rendering
only 1/8 of a page does not take 1/8 of the time to render the whole
page, rendering tiles does not scale linearly at least for Poppler.)

> 3. Is the tile size in screen pixels? Would it not make more sense to
> have it in page units, say a few centimeters on a side, something like
> 1/6 or 1/8 of a (US letter) page? Could tiles be rectangular? It might
> make more sense to adjust the aspect ratio of the tiles for each page to
> match the aspect ratio of the page.

The tile size is screen pixels since this determines the amount of
memory and processing time they will consume, i.e. I currently aim at 4
MB tiles. And since memory consumption and processing power are the main
problem the tiles need to solve, this is more useful than fixing their
physical dimensions. This also implies that the same page (of a given
physical size) will need a very different number of tiles depending on
the scale factor as it should be. (For example, it makes no sense to
tile up the thumbnails since they will yield small images for the whole
page anyway.)

Adjusting their aspect ratio should not be a problem... (But it will
also not really change anything performance-wise.)

> 4. Actually, the tiles already seem to be rectangular with the right
> aspe...

Read more...

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello,

Am 13.06.2014 19:58, schrieb Martin Spacek:
> Also, are tiles cached when you switch from one zoom level to another?
> With cache now set to 2048 MB and prefetch off, tiles definitely don't
> seem to be cached when I alternate back and forth between two adjacent
> zoom levels. It would be nice if they were. In comparison, tiles do seem
> to be cached within a given zoom level, i.e. subsequent panning over the
> same part of the document is fast after the first render at a given zoom
> level.
>

As with pages, different scale factors are not cached. For simplicity
the cache is one-way associative with the page and now tile items, so
each item has one slot in the cache which is invalidated if the item
needs to be rendered at a different scale factor. Let's say this is a
different possibility for improvement...

Regards, Adam.

Revision history for this message
Adam Reichold (adamreichold) wrote :

> The tiles are definitely square, e.g. 1024px times 1024px. But a the
> right and bottom edges, they are as large as they have to be and hence
> may be rectangular as well. E.g. a page that will displayed within a
> rectangle of 1500px times 750px will get two columns (1024px and 476px)
> and a single row (750px). Of course, one could try to tile up the pages
> more evenly...

I failed to explain my own algorithm as we actually tile evenly but using the tile size traget only to determine column and row count. So in the example the column widths are (750px and 750px) and the row height is still 750px...

Revision history for this message
Adam Reichold (adamreichold) wrote :

Ok, so the tiles are now (branch revision 1579) adjust using the page aspect ratio with the configured tile size as an upper bound...

Revision history for this message
Martin Spacek (mspacek) wrote :

> Ok, so the tiles are now (branch revision 1579) adjust using the page aspect ratio with the configured tile size as an upper bound...

Great! Looks to me like each page is now more likely to be composed of an integer number of tiles. Either 1x1, or 2x2, or 3x3, etc., depending on zoom level. That should make it render a little faster overall now, no?

I didn't know poppler already gave multithreading for free! I'm running Xubuntu 12.10, which uses an older poppler 0.20.4. I'll try booting into Xubuntu 14.04 and see if I get more core use.

Revision history for this message
Martin Spacek (mspacek) wrote :

> As with pages, different scale factors are not cached.

OK, shall I open up a separate bug for this?

Revision history for this message
Martin Spacek (mspacek) wrote :

"The tiles are not displayed in any particular order, the render task
that finishes first is displayed first. So the apparent order in your
case just indicates that there is no real difference in rendering time
and the tiles finish in the same order as they are started. (Rendering
only 1/8 of a page does not take 1/8 of the time to render the whole
page, rendering tiles does not scale linearly at least for Poppler.)"

I wonder if I'm always seeing column-major tile painting (despite some tiles definitely being much slower to render than others) because I'm using an older single-threaded version of Poppler...

"The tile size is screen pixels since this determines the amount of
memory and processing time they will consume, i.e. I currently aim at 4
MB tiles. "

OK, so is it safe to say that it's the redering destination size in screen pixels that mostly determines how long it takes to render a tile (and how much RAM it takes), and not the fraction of the page being rendered by that tile?

Revision history for this message
Martin Spacek (mspacek) wrote :

"since the tiling changes with the scale factor it can't
be the tiles that continue to display the old pixmaps. The page item
would need to collect all tiles at a given scale factor, compose them
into a page image and transform them to yield the obsolete pixmap
approximation of the soon to be displayed page. Not sure how practical
this is or whether I'll find another solution to this."

But, since the tiling (say 2x2 per page) seems to remain the same over a reasonable range of zoom levels, couldn't you reuse those old pixmaps during those zoom changes, and only throw them out when the tiling changes (say to 1x1 or 3x3)?

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 13.06.2014 22:43, schrieb Martin Spacek:
>> As with pages, different scale factors are not cached.
>
> OK, shall I open up a separate bug for this?
>

You may of course open as many bug reports as you like, but without more
contributors, it is unlikely that such feature requests will get any
real attention soon. I'd say having a look at the code and trying to
create a patch is more likely to yield tangible results...

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 13.06.2014 22:49, schrieb Martin Spacek:
> "The tiles are not displayed in any particular order, the render task
> that finishes first is displayed first. So the apparent order in your
> case just indicates that there is no real difference in rendering time
> and the tiles finish in the same order as they are started. (Rendering
> only 1/8 of a page does not take 1/8 of the time to render the whole
> page, rendering tiles does not scale linearly at least for Poppler.)"
>
> I wonder if I'm always seeing column-major tile painting (despite some
> tiles definitely being much slower to render than others) because I'm
> using an older single-threaded version of Poppler...

Yes, the internal locks which are necessary to ensure that only one
thread calls into the older Poppler API would effectively serialize the
rendering.

> "The tile size is screen pixels since this determines the amount of
> memory and processing time they will consume, i.e. I currently aim at 4
> MB tiles. "
>
> OK, so is it safe to say that it's the redering destination size in
> screen pixels that mostly determines how long it takes to render a tile
> (and how much RAM it takes), and not the fraction of the page being
> rendered by that tile?
>

They definitely determine the amount of memory. With processing time it
is difficult to say: Everything that is rasterization in some sense will
have its run time determined by the amount of pixels to fill. Other
steps that have more to do with parsing and/or transforming the page
structure into graphical primivites can have widely different
dependencies on the tile size including none at all.
So limiting the pixel dimensions should the best approximation in most
of the cases and is the only thing that will allow rendering at large
scale factors without trashing memory.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 13.06.2014 22:58, schrieb Martin Spacek:
> "since the tiling changes with the scale factor it can't
> be the tiles that continue to display the old pixmaps. The page item
> would need to collect all tiles at a given scale factor, compose them
> into a page image and transform them to yield the obsolete pixmap
> approximation of the soon to be displayed page. Not sure how practical
> this is or whether I'll find another solution to this."
>
> But, since the tiling (say 2x2 per page) seems to remain the same over a
> reasonable range of zoom levels, couldn't you reuse those old pixmaps
> during those zoom changes, and only throw them out when the tiling
> changes (say to 1x1 or 3x3)?
>

We could probably do that. Adding a setting to always force a 1x1 tiling
and making sure that the feature always works in that case and simply
degrades whenever the tiling changes seems like a good idea. I'll have a
look into it...

Btw., what did using Ubuntu 14.04's Poppler 0.24.5 yield in terms of
multithreading?

Revision history for this message
Martin Spacek (mspacek) wrote :

"We could probably do that. Adding a setting to always force a 1x1 tiling
and making sure that the feature always works in that case and simply
degrades whenever the tiling changes seems like a good idea. I'll have a
look into it..."

Hmm, not sure I understand. If you're currently at a zoom level where 3x3 tiling is used, and you zoom in or out a bit to another level that also still uses 3x3, why would you fall back to (low-res?) 1x1 pixmaps instead of the previously rendered 3x3?

"Btw., what did using Ubuntu 14.04's Poppler 0.24.5 yield in terms of
multithreading?"

Actually, I thought I'd try building the latest Poppler from git (pretty much 0.26.1), and surprisingly it worked! Or at least it seemed to, despite my old Xubuntu 12.10 install. I changed the path to the new poppler in Makefile.pdf-plugin (to /usr/local/lib), and rebuilt qpdfview. Compiler output confirmed qpdfview was using the new poppler in the new path, but strangely it doesn't seem to multithread much. When rapidly zooming in and out over and over, I get at most 24% CPU usage for the process, which is only about 2 cores (I have 8 hyperthreaded cores). It might be slightly faster than before, but it's only barely noticeable.

I realized today that much of the speed difference compared to evince (which is using the old poppler on my system) on the same pdf could be due to evince's use of cairo. The politics of the qt cairo renderer in poppler are very unfortunate.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Ok, branch revision 1583 adds a simplified version of keeping obsolete pixmaps that is compatible with tiling but works only for effective scale factors changes only but not for e.g. rotations. I am not sure whether this is a problem?

So we have a pros for the branch: It has tiling and via the setting this can be reduced to the primitive always tile 1x1 case. And as cons: Prefetching together with tiling is problematic. Keeping obsolete pixmaps will loose some functionality even if tiling is turned off. Not sure whether this situation would allow merging to trunk...

Revision history for this message
Martin Spacek (mspacek) wrote :

Great! That seems to work! I would say if the user rotates the pdf, all bets are off. Having obsolete pixmaps in that case is kind of a luxury, unless of course it's easy to implement. Rotation isn't a very common thing to do, is it?

"Prefetching together with tiling is problematic."

Yes. Could the prefetch distance N be changed to be units of tiles away instead of units of pages away? Something like "for each tile touching the edge of the current viewport, prefetch up to N tiles in the direction(s) outward from the current viewport". In that case, you might want to increase N to get the same level of prefetching as when tiling is disabled and N is set to 1 page (i.e., one 1x1 tile).

With tiling turned off (the default?) and prefetch turned on, everything seems to work the way it used to, which is great. But yes, when tiling is turned on, prefetch slows things down. At least for now, that's the user's fault for enabling them both.

"Keeping obsolete pixmaps will loose some functionality even if tiling is turned off"

I'm afraid I don't understand what you mean by this. You mean the loss of obsolete pixmaps during a rotation? Not a big deal IMO.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 14.06.2014 10:24, schrieb Martin Spacek:
> "We could probably do that. Adding a setting to always force a 1x1 tiling
> and making sure that the feature always works in that case and simply
> degrades whenever the tiling changes seems like a good idea. I'll have a
> look into it..."
>
> Hmm, not sure I understand. If you're currently at a zoom level where
> 3x3 tiling is used, and you zoom in or out a bit to another level that
> also still uses 3x3, why would you fall back to (low-res?) 1x1 pixmaps
> instead of the previously rendered 3x3?

No, what I mean with degrading is that if the tiling stays the same
(e.g. always 1x1 or the zoom change is small), then you will get
obsolete pixmaps but if the tiling (and hence the relative positions of
the tiles on the page) changes, the pixmaps are dropped you immediately
get the progress indicator. (That the relative positions w.r.t. the
(unscaled) page of the tiles have to stay the same is also the reason
for it currently not working with rotations. Maybe I can lift all tile
coordinates to the scene level and use the same transformations as for
the non-tiled branch, but I am not sure how to do this yet.)

> "Btw., what did using Ubuntu 14.04's Poppler 0.24.5 yield in terms of
> multithreading?"
>
> Actually, I thought I'd try building the latest Poppler from git (pretty
> much 0.26.1), and surprisingly it worked! Or at least it seemed to,
> despite my old Xubuntu 12.10 install. I changed the path to the new
> poppler in Makefile.pdf-plugin (to /usr/local/lib), and rebuilt
> qpdfview. Compiler output confirmed qpdfview was using the new poppler
> in the new path, but strangely it doesn't seem to multithread much. When
> rapidly zooming in and out over and over, I get at most 24% CPU usage
> for the process, which is only about 2 cores (I have 8 hyperthreaded
> cores). It might be slightly faster than before, but it's only barely
> noticeable.

It may not have worked as you expected: The available version of Poppler
is determined using pkg-config, so you either need to change your
pkg-config search path or you need to manually add the defines
"HAS_POPPLER_XY", c.f. lines 27 to 32 of "pdf-plugin.pro".

(The intended way of changing the used Poppler library manually is to
set "CONFIG+=without_pkgconfig" and add "PDF_PLUGIN_DEFINES",
"PDF_PLUGIN_INCLUDEPATH" and "PDF_PLUGIN_LIBS" to "qpdfview.pri".)

You also need to check that the linked libraries are really resolved to
"/usr/local/lib" before running the binary, e.g. try running "ldd" on
the binary and adjust "LD_LIBRARY_PATH" accordingly.

> I realized today that much of the speed difference compared to evince
> (which is using the old poppler on my system) on the same pdf could be
> due to evince's use of cairo. The politics of the qt cairo renderer in
> poppler are very unfortunate.
>

The Cairo backend is often faster than the Splash backend used
internally by Poppler but it also lacks some features, most notable
multithreaded rendering. A complete Arthur backend would probably be the
most useful thing to have, but people lack the time to implement it.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 14.06.2014 11:05, schrieb Martin Spacek:
> "Prefetching together with tiling is problematic."
>
> Yes. Could the prefetch distance N be changed to be units of tiles
> away instead of units of pages away? Something like "for each tile
> touching the edge of the current viewport, prefetch up to N tiles
> in the direction(s) outward from the current viewport". In that
> case, you might want to increase N to get the same level of
> prefetching as when tiling is disabled and N is set to 1 page
> (i.e., one 1x1 tile).

All of this is possible, but it would make things rather ugly insofar
as the document view who is responsible for prefetching pages would
need to known about tiling which would result in a much messier
architecture. Especially since the pages themselves do not (and do not
need to) known whether they are visible or not as they scene items and
not views as the document view.)

> With tiling turned off (the default?) and prefetch turned on,
> everything seems to work the way it used to, which is great. But
> yes, when tiling is turned on, prefetch slows things down. At least
> for now, that's the user's fault for enabling them both.

A solution might be to give the prefetching threads a lower priority
but I am not sure how effective this is when the thread pool's queue
is full of tasks.

> "Keeping obsolete pixmaps will loose some functionality even if
> tiling is turned off"
>
> I'm afraid I don't understand what you mean by this. You mean the
> loss of obsolete pixmaps during a rotation? Not a big deal IMO.
>

Yes, I mean that obsolete pixmaps won't work for rotations anymore.
Which probably isn't that bad, but it is definitely a regression.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Ok, if there are no big objections and we find no more large problems or regression, I'd say we let the branch mature for a day or two and then I'll merge this into trunk so it gains some wider testing.

Changed in qpdfview:
milestone: none → 0.4.11
Revision history for this message
Martin Spacek (mspacek) wrote :

Sounds good.

"The Cairo backend is often faster than the Splash backend used
internally by Poppler but it also lacks some features, most notable
multithreaded rendering."

Ah, that complicates things. Oh well.

Speaking of multithreaded poppler, you were right. It seems the version of poppler I compiled from git wasn't being linked to properly. I think all I had to do to fix it was run "sudo ldconfig". Now, "pkg-config --modversion poppler" gives 0.26.1. After cleaning out the qpdfview folder and remaking and compiling, ldd ./qpdfview (still) doesn't list poppler in its output, but the binary runs fine. It's now using up to 50% CPU, and is noticeably even faster. Not sure what I'd have to do to get it to use 100%. Perhaps poppler is checking for real cores (4) instead of virtual ones (8).

Revision history for this message
Benjamin Eltzner (b-eltzner) wrote :

Hi, just as a brief remark: I also do not care much for the use of obsolete pixmaps when rotating, since I rarely use that feature and, if so, only once on a document.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 14.06.2014 13:14, schrieb Martin Spacek:
> Sounds good.
>
> "The Cairo backend is often faster than the Splash backend used
> internally by Poppler but it also lacks some features, most
> notable multithreaded rendering."
>
> Ah, that complicates things. Oh well.
>
> Speaking of multithreaded poppler, you were right. It seems the
> version of poppler I compiled from git wasn't being linked to
> properly. I think all I had to do to fix it was run "sudo
> ldconfig". Now, "pkg-config --modversion poppler" gives 0.26.1.
> After cleaning out the qpdfview folder and remaking and compiling,
> ldd ./qpdfview (still) doesn't list poppler in its output, but the
> binary runs fine. It's now using up to 50% CPU, and is noticeably
> even faster. Not sure what I'd have to do to get it to use 100%.
> Perhaps poppler is checking for real cores (4) instead of virtual
> ones (8).
>

Ah, it's "libqpdfview_pdf.so" that links to Poppler directly.
Depending on the document and on how many pages/tiles are visbile (if
prefetching is off, at least without tiling, prefetching and
multithreading work really well together), you might not get a higher
utilization because of lock contention within Poppler or the load just
not being large enough.

Revision history for this message
Martin Spacek (mspacek) wrote :

> Ah, it's "libqpdfview_pdf.so" that links to Poppler directly.

Yup, ldd on that reveals the linked poppler library.

After setting the display mode to 3 pages wide and scrolling around like mad, I managed to get the process' CPU usage up to 72%, so I think all is well.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Merged into trunk, so that it hopefully see some more widespread testing via the dailydebs.

Changed in qpdfview:
status: In Progress → Fix Committed
Revision history for this message
Martin Spacek (mspacek) wrote :

Great! Thanks for all your work on this.

Revision history for this message
Martin Spacek (mspacek) wrote :

I just noticed that with tiling overlap set to 0 (not recommended, I know), tile size set to 1024, tiling enabled, obsolete pixmaps enabled, prefetch disabled, and cache set to 0 or 8MB, I finally started seeing single pixel wide rendering artifacts, as shimmering vertical or horizontal lines at around the tile borders. It's worse at 0MB cache, with the artifact constantly present. At 8MB it's more transient. When the artifact is present, the process maxes out one core. The artifact seems to go away with cache sizes 16MB or more. This holds for several different PDFs.

If I set overlap to 16, things actually get worse. At 0MB cache, I now get two rapidly shimmering vertical and/or horizontal lines (probably the overlapping tile borders), plus flashing portions of the tile placeholders. CPU usage is now maxed out at 2 cores. Again, if I set cache to 8MB, it's not quite so bad. The higher I set the overlap, the further apart the shimmering lines.

Is this to be expected?

By the way, after some very informal timing, I've found that a tile size of 2048 pix is a bit more efficient than 1024, but that probably depends on screen resolution and window size. This is basically full screen on a 1920x1080 screen.

Revision history for this message
Adam Reichold (adamreichold) wrote :
Download full text (3.9 KiB)

Hello Martin,

Am 16.06.2014 09:45, schrieb Martin Spacek:
> I just noticed that with tiling overlap set to 0 (not recommended, I
> know), tile size set to 1024, tiling enabled, obsolete pixmaps enabled,
> prefetch disabled, and cache set to 0 or 8MB, I finally started seeing
> single pixel wide rendering artifacts, as shimmering vertical or
> horizontal lines at around the tile borders. It's worse at 0MB cache,
> with the artifact constantly present. At 8MB it's more transient. When
> the artifact is present, the process maxes out one core. The artifact
> seems to go away with cache sizes 16MB or more. This holds for several
> different PDFs.
>
> If I set overlap to 16, things actually get worse. At 0MB cache, I now
> get two rapidly shimmering vertical and/or horizontal lines (probably
> the overlapping tile borders), plus flashing portions of the tile
> placeholders. CPU usage is now maxed out at 2 cores. Again, if I set
> cache to 8MB, it's not quite so bad. The higher I set the overlap, the
> further apart the shimmering lines.
>
> Is this to be expected?

Let's say expected but not intended. :-/

The reason for this that Qt's QGraphicsView will not invalidate (and
hence repaint) items but rectangular areas of the view. This means that
if tiles overlap for even just one pixel (which they must to not have
any artefacts), an update of one tile will trigger an update of the
neighbouring tiles which will again trigger an update of tiles that was
updated originally and so on and so on.
This is without caching since this means that to render a tile and to
actually put the result one the screen needs at least one paint
operation that will trigger the rendering and then an update and a
repaint to actually draw the result. With caching, a tile that was
rendered once will be immediately drawn in the subsequent repaints as
long as it stays in the cache.

In summary, tiling without a certain amount of caching is currently
pretty useless (one of the reasons it is disabled by default for the
time being). The only real solution I currently see is to use a
different graphics architecture than QGraphicsView but that would
essentially imply a rewrite... (I started experimenting with Qt5's
Quick2 and its scene graph this weekend, but this would also mean
leaving a lot of platforms behind that do not yet have a Qt5 port and I
also stumbled into some problems that seem to prevent a straight forward
implementation.)

> By the way, after some very informal timing, I've found that a tile size
> of 2048 pix is a bit more efficient than 1024, but that probably depends
> on screen resolution and window size. This is basically full screen on a
> 1920x1080 screen.
>

Well, before anything else, I'd say that without some sort of systematic
benchmarking this will be hard to decide and even then the optimal value
will probably very much depend on the actual machine used.

But thinking heuristically, a larger tile size will probably be more
efficient since it will mean less time spent handling tiles instead of
rendering content and also that less content will be processed
redundantly for the different tiles. But then, no tiling will be most
efficient from this perspe...

Read more...

Revision history for this message
Martin Spacek (mspacek) wrote :

"...an update of one tile will trigger an update of the
neighbouring tiles which will again trigger an update of tiles that was
updated originally and so on and so on."

Yes, makes sense. Thanks for the explanation. There's no reason to have a tiny cache size, I was just wondering. So this artifact is different from the one you mentioned before when overlap=0?

"Long story short, I don't think it helps much to meddle with this value
without any benchmarks to support it."

Oh yes, I agree. I wasn't suggesting changing the default size, just thinking out loud. Ideally, if you're not panning, you'd want a single tile that exactly matches the size and position of the viewport, right? That way, you aren't rendering any unnecessary pixels. But of course, we do want to be able to pan quickly, and to do that, we need a cache, and the cache needs to be made up of non-overlapping tiles, and in normal use the viewport will never be set at exactly the position of a single tile, so there's some kind of tradeoff between tile size and number of tiles to render per average viewport position. From messing around on my system, it seems the optimal tile size is something close to the the maximum viewport dimension. But yeah, without systematic benchmarking, it's hard to say.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Am 16.06.2014 22:13, schrieb Martin Spacek:
> "...an update of one tile will trigger an update of the
> neighbouring tiles which will again trigger an update of tiles that was
> updated originally and so on and so on."
>
> Yes, makes sense. Thanks for the explanation. There's no reason to have
> a tiny cache size, I was just wondering. So this artifact is different
> from the one you mentioned before when overlap=0?

Yes, there are two distinct problems: Continuous updating with small
cache sizes because of overlap and persistent artefacts because of
missing overlap. I have observed those as "striked out" text, i.e.
missing rows or columns a single pixel wide.

But I can admittedly not reproduce them with the current version of code
which might be a side-effect of separating the tile's bounding rectangle
(floating point coordinates) and the rectangle used of tiled rendering
(integer coordinates). I'll keep tileOverlap=0 and maybe we can change
the default value before the next release...

> "Long story short, I don't think it helps much to meddle with this value
> without any benchmarks to support it."
>
> Oh yes, I agree. I wasn't suggesting changing the default size, just
> thinking out loud. Ideally, if you're not panning, you'd want a single
> tile that exactly matches the size and position of the viewport, right?
> That way, you aren't rendering any unnecessary pixels. But of course, we
> do want to be able to pan quickly, and to do that, we need a cache, and
> the cache needs to be made up of non-overlapping tiles, and in normal
> use the viewport will never be set at exactly the position of a single
> tile, so there's some kind of tradeoff between tile size and number of
> tiles to render per average viewport position. From messing around on my
> system, it seems the optimal tile size is something close to the the
> maximum viewport dimension. But yeah, without systematic benchmarking,
> it's hard to say.
>

Revision history for this message
Benjamin Eltzner (b-eltzner) wrote :

Hi, sorry for only commenting so late. I have some performance problems with the tiled rendering:

1. I have a dual core machine but tiled rendering consistently uses only about one core. (I have a very heavy document to test it.)

2. Tiled rendering takes very much longer (a factor of up to roughly 5) to render a single page than the usual page-wise rendering.

3. When turning off tile-based rendering, apparently all tiles for the current page will still be rendered before the program switches back to page-wise rendering. This can lead to an appreciable delay.

I don't know if any of these points can be reasonably addressed, I just wanted to share my observations.

Revision history for this message
Martin Spacek (mspacek) wrote :

1. Are you using a fairly recent version of poppler? (see above)

2. Hm. Have you tried the latest from trunk? This branch is already outdated.

3. I've found that every time I change something in the preferences, or just reload a PDF, there can be a delay before the page(s) are fully displayed. But I think this was the case before the tile-based rendering code was added.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello Benjamin,

thanks for sharing your observations. Performance regressions in a change that is supposed to improve performance are definitely bad. :-( I assume that you use your own Poppler packages which are therefore very recent and your latest dailydebs from built from trunk?

A change of the preferences will definitely refresh the document, i.e. completely destroy all page objects and reload the document from disk, hence rendering will start from scratch as if you just opened the document. Does this explain the delay you noticed?

Concerning points 1 and 2, do you have performance regressions in normal usage when tiling is turned off? (This would be a blocker to releasing this code IMHO.) Have you checked that CPU utilization is higher if tiling is turned off and that you are at a scale factor where several tiles will actually be used? (And in principle, rendering a page as four tiles can take longer than rendering it in one go if parallelism in hindered by locks inside Poppler, but hopefully not by a factor of five.)

Best regards, Adam.

P.S.: Could you share that heavy document with me via Jabber? ;-)

Revision history for this message
Adam Reichold (adamreichold) wrote :

Ok, using the document Benjamin sent me, I can confirm that it is noticeably slow when using tiling but I don't see any differences in CPU utilization. I suppose this document is so image heavy and Poppler's decoding routines are not very parallel in this case, that rendering the whole page takes as long as rendering just a quarter of it which seems sensible since those scanned images always cover the whole page and will probably be decoded completely for each quarter of it.

Revision history for this message
Benjamin Eltzner (b-eltzner) wrote :

As I use Debian unstable, I am currently using Poppler 0.24.5. I have no change in performance without tiling, so this is not a regression. Only in tiled mode do the problems occur. I have used the dailydeb that was current at the time of my reporting. Without tiling both of my cores are running with 100% load, while on tiled rendering they both noticably remain in the range of 50% load.

Your explanation seems very reasonable to me.

As a side remark: When using tiled rendering, the "progress symbol" from the KDE icon set covers part of already rendered regions to the left of an unredered tile. :-)

Revision history for this message
Adam Reichold (adamreichold) wrote :

(That must be a huge icon then... :-))

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello again,

after using "tileOverlap=0" on two different systems with Qt4 and Qt5 for a few days without seeing those static artefacts and reviewing the code again (as I separated the bounding rectangle and the tile which should eliminate rounding issues), I would feel comfortable with removing the tile overlapping. (After thinking about this again, this is also the reason why the icon overlaps with the already rendered tiles, because of - well - the tile overlap. ;-))

Has anyone else seen any static rendering artefacts with tile overlap disabled? If not, I would remove this in trunk.

Best regards, Adam.

Revision history for this message
Martin Spacek (mspacek) wrote :

Sounds good to me. I've had tileOverlap=0 the whole time, and I've never seen the artifact you described.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello again,

Just before turning off the computer, I looked up something in a book which I have lying around as a DjVu file and guess what, I noticed some sluggishness while zooming. I tried a few other files and compared it with version 0.4.10, and there was a definite difference with or without tiling enable. I then tried out the presentation view which became completely unresponsive upon a scale change. After some profiling, it turns out that QGraphicsView really has a problem with the page items having tile items as children and overlapping all the time. (A lot of cycles spent finding parent items and checking visbility). So much for turning off the computer... :-(

At this point with trunk revision 1600, I think I have solved the problem by completely removing the tile from the graphics item hierarchy and reducing them to helper objects for rendering and caching. Of course, now the page items have to check for themsevles which part of them is exposed and draw only the appropiate tiles to the view.

Summarizing this, we definitely need more testing with a wider variety of documents (and maybe some automated benchmarks using Qt's test framework). Also the rewritten tiling also needs to be retested if this was the first commit. :-( Please try it out and try to compare the interface responsiveness with version 0.4.10 if possible. Thanks!

Best regards, Adam.

Revision history for this message
Martin Spacek (mspacek) wrote :

Wow, lots of changes! OK, I'm running it now. Can't say I've noticed any change in zoom speed on my (not so heavy) PDFs. Only thing I've noticed is several of these messages in the command line:

Object::connect: No such signal qpdfview::PageItem::linkClicked(int,qreal,qreal)

I'm not sure what triggered them, and I can't seem to reproduce them at the moment.

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello,

Am 23.06.2014 07:37, schrieb Martin Spacek:
> Wow, lots of changes! OK, I'm running it now. Can't say I've noticed any
> change in zoom speed on my (not so heavy) PDFs. Only thing I've noticed
> is several of these messages in the command line:

My impression is that is it worse with non-uniform page size, but I
really don't understand the exact reason, since I haven't profiled the
internals of QGraphicsView.

> Object::connect: No such signal
> qpdfview::PageItem::linkClicked(int,qreal,qreal)
>
> I'm not sure what triggered them, and I can't seem to reproduce them at
> the moment.

A bug in the presentation view. :-( Since the "linkClicked" signal of
PageItem changed its signature, the corresponding connection within the
presentation view class doesn't fit anymore and there would be no links
within the presentation view. (Released at least in version 0.4.10 with
fix being in trunk revision 1603.)

Best regards, Adam.

Revision history for this message
Martin Spacek (mspacek) wrote :

OK, thanks. That reminds me, why is there all that separate code for the presentation view? How is presentation view any different from fullscreen single page non-continuous view?

Revision history for this message
Adam Reichold (adamreichold) wrote :

Hello,

Am 23.06.2014 15:41, schrieb Martin Spacek:
> OK, thanks. That reminds me, why is there all that separate code for the
> presentation view? How is presentation view any different from
> fullscreen single page non-continuous view?
>

the presentation view used to be much much simpler without even using
the PageItem class but then features crept in and when the time for
caching and prefetching came, I just replaced its internals with a
stripped down version of the document view.

So today, there is no good reason other than that nobody has yet
replaced the thing by a simple "m_presentationMode" flag as is used in
PageItem. And do I think we could save quite some code here, but it
depends on how messy DocumentView will get when all relevant
functionality is included in it using conditions depending on
"m_presentationMode".

Best regards, Adam.

Changed in qpdfview:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Duplicates of this bug

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.