Blog Post

Render Performance Improvements in PSPDFKit for Web

Illustration: Render Performance Improvements in PSPDFKit for Web

Here at PSPDFKit, we’re always reviewing our tech to find areas in which we can improve. Recently, we’ve been working to improve PSPDFKit for Web Standalone, our PDF Web SDK that runs completely in the browser.

PSPDFKit for Web Standalone is written in TypeScript, but all the PDF operations are executed in C++. We do this by utilizing a technology called WebAssembly, which allows us to compile our C++ code into a binary format web engines can execute. Being able to run C++ means all our PSPDFKit products can be driven from the same source, further solidifying the stability of the product.

In this particular blog post, I’m going to take you through the journey of analyzing render performance, and I’ll explain our eventual decision to switch from PNG to WebP encoding when rendering for the SDK user interface (UI). I’ll share the numbers that drove the decisions, along with the theories of what these numbers are telling us.

But first, here’s a sneak peek at the improvement. On the left side is a PNG image, and on the right side is a WebP image. The dimensions of the example PDF page are large, which results in it taking some time to render, but overall, this example highlights the speed improvements gained with the encoding switch.

The Render Process

First, it’s important to understand what exactly happens when we request a page to be rendered.

It begins with a request from PSPDFKit for Web to render a PDF page. This request is sent to the C++ PDF engine using bindings that allow TypeScript to talk to WebAssembly. The request includes information like the page index, the desired dimensions of the resulting rendered image, and the format.

Here’s an example of a request type (not real typing, but it gives you an idea of the data):

declare type RenderRequest {
    pageIndex: number,
    size: {
        width: number,
        height: number
    },
    offset: {
        x: number,
        y: number
    },
    format: 'png' | 'webp' | 'bitmap'
}

The PDF engine will reply with a buffer holding the requested rendered image in the format specified, and PSPDFKit for Web is then free to do what it would like with the buffer.

But what happens in the black box that is the PDF engine?

image

First, PDF objects for the page are parsed. Then, objects are rendered to bitmap. Finally, the bitmap is encoded, if required. This flow is simplified, but it’ll suffice for this blog post.

Each of the steps have aspects that can make the render process faster or slower. For example, a page may have hundreds of paths to parse, which would slow down the document parsing. Or, the requested size of the bitmap may be very large, in turn slowing down the render engine.

But what we’re going to look at here is the encoding used to reduce the data passed between WebAssembly and PSPDFKit for Web code.

Why Encode the Rendered Bitmap?

It’s a valid question: If we already have a bitmap, and if we can render that directly to canvas, why encode the data at all?

To explain why we’d want to encode the render, I’ll introduce the first benchmark example.

With each example, we set performance markers to measure the time between when a render request is sent and when the rendered image is seen on the screen. Technically, we use a high-quality tiling system, which render blocks of the page iteratively, so we actually wait for the last tile to be rendered, which is when the page is shown at its highest quality.

image

Bitmap (ms) PNG (ms) WebP (ms)
1665 2061 787
755 2020 788
716 2198 2480
714 2226 777
761 2209 783
762 2261 749
1527 2186 776
747 2003 757
707 2033 780
716 2103 763

The test above was performed directly on the PSPDFKit for Web UI to represent real-world numbers, and I used a MacBook Pro 2019 (more on that later). Each format was tested 10 times to average out any variability and anomalies.

The first thing that’s obvious is that PNG is slow! This hasn’t always been the case though. We used to transfer the data from WebAssembly in a different way, which meant transferring large data buffers was slow, and thus transferring large bitmaps was undesirable.

But now that we’ve optimized the data transfer, why not just use bitmaps? Based on the last results, that would be a logical conclusion.

Well sadly, our SDK doesn’t only run on high-powered computer devices. Sometimes, we’re faced with a low-end Android device (yes, I still use one) and the results are much different.

image

Here are the results using an underpowered Android device. I used a large PDF page to emphasize the speed disparities.

Bitmap (ms) PNG (ms) WebP (ms)
12323 8154 6996
15060 8006 6339
13934 8482 6669

Now we see the drastic downside to not encoding, likely because we’re passing large buffers around on low-powered devices.

Page Rendering Comparison Results

There are various reasons why WebP is likely a better encoding to use in our rendering process, but let me first further prove that it’s more performant.

image

Bitmap (ms) PNG (ms) WebP (ms)
1608 1880 1565
1699 2082 1544
1614 1877 1541
1517 1871 1526
1551 1871 1531
1717 1903 1576
1597 1868 1529
1637 1903 1572
1481 2118 1577
1551 1905 1603

Again, I’m performing tests on my MacBook Pro 2019.

In the first example, we see a pretty standard PDF with a block of text. This is representative of many PDF use cases, which often consist of either simple PDFs or documents with only a few small images.

We can see that not encoding (bitmap) and WebP perform similarly. And again, PNG is lagging behind.

image

Bitmap (ms) PNG (ms) WebP (ms)
1560 1941 956
1476 1838 1117
1545 1887 639
1494 1793 624
1531 1867 619
1522 2248 629
1527 2501 636
1482 2824 585
1526 2675 608
1569 2618 686

Next, we have a page with very little text, one image, and a few annotations. Note how the rendering of annotations isn’t taken into account here.

We see a dramatic win for WebP in this instance. And I’ll give reasons for why that is later.

image

Bitmap (ms) PNG (ms) WebP (ms)
4875 7099 3817
3841 10710 3880
3112 7021 4206
3108 7089 5035
3090 7378 3982
3100 12403 3990
3173 7357 5174
3047 8772 4035
3099 8479 3863
4534 10259 4006

Lastly, it’s back to our very large PDF page with a single image.

Here, we see bitmaps have slightly better performance, but WebP is still roughly comparable.

Why Is WebP the Answer?

So why does adding an extra operation — encoding a bitmap to WebP — either take the same amount of time to show on the screen, or, as in the example of the underpowered device, even end up faster?

Less Data to Pass

The first theory is that transferring less data across the WebAssembly boundary is faster. We’ve seen this in the past when we’ve optimized boundary crossing, and it’s something we still need to consider when designing code that crosses this boundary.

Passing buffers from one place to the next isn’t free, especially when optimal solutions like C++ move semantics aren’t an option.

Bitmaps can be orders of magnitude larger than encoded images, so if we can perform a fast operation such as encoding, we may see the benefit in reducing the data transferred.

WebP Decoding in the Browser

In 2010, the WebP format was announced, and it was created with the web in mind. Because it supports lossless and lossy encoding, it was touted as an alternative to PNG and JPEG for the web. Even though it’s been 12 years since then, only 5 percent of the web uses WebP. However, that number is growing, and many of the popular sites on the web have now converted to use WebP.

With all this development and focus on WebP for the web, it makes sense that browsers are optimal at decoding and rendering from WebP images. We already have figures to show that decoding WebPs is faster than decoding PNGs.

So in theory, utilizing WebP images is optimal in comparison to other encodings, and possibly even bitmaps, but it’s difficult to produce figures to prove that.

WebP Encoding in C++

In the past, we hadn’t been able to make the WebP project stable under WebAssembly, which is why we hadn’t utilized encoding it. But with the release of version 1.2.1 of the WebP project, our luck changed.

More specifically, we implemented WebP encoding and pushed it through our automated tests and QA procedure, and it passed with flying colors!

With comments from QA like “I love this stuff; it’s cool to see improvements like this,” I’m confident customers will feel the same way.

When it comes to numbers, there were speed increases of up to 50 percent when encoding with WebP vs. PNG, which had a direct contribution to the performance we were seeing.

Conclusion

I hope I’ve managed to convince you that WebP is obviously the way forward for rendering in PSPDFKit for Web. The large speed improvements over PNG, and the stability of performance even on low-end devices, make the change an obvious choice.

It’s worth mentioning that most modern browsers do have support for WebP, but in some instances, we’d have to revert to PNG. However, this is a seamless process, and nothing needs to be changed by the user. To be more specific, IE11 doesn’t support WebP, and Safari has presented some issues with WebP in the past. In the latter case, we added runtime checks to ensure WebP support is fully functional.

Making changes to the encoding within the rendering process is only one of the first improvements we expect to see in PSPDFKit for Web. We’ve identified other areas that can be refined — from document open speed, to large document parsing performance — and we hope to be introducing those improvements soon.

Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
TUTORIALS  |  Web • PDF.js • JavaScript • How To • PDF Viewer

How to Print PDFs Using PDF.js

PRODUCT  |  Web

Handling Browser Support of Media Files in PSPDFKit for Web

PRODUCTS  |  Web • Releases • Components

PSPDFKit for Web 2022.5 Adds Content Editor and Measurement Tools