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

docs: add explanation on proper web compression #151

Merged

Conversation

miltoncandelero
Copy link
Contributor

@miltoncandelero miltoncandelero commented Jun 1, 2024

Added a link to my blog post explaining how web compression works, why Unity's default brotli compression needs odd server configurations and how to properly compress a game so that it can work on most (if not all) web servers.

@JohannesDeml
Copy link
Owner

JohannesDeml commented Jun 3, 2024

Hi @miltoncandelero and thanks for the PR.

thanks for the great writeup! Compressing for all scenarios for more flexibility sounds like a good idea, and I didn't know of zstd until now.

A few questions/remarks:

  • You're mentioning in the end, that WebAssembly.instantiateStreaming can be called earlier - Can this be seen somewhere or do you have an idea of how much impact that actually has?
  • Maybe note that if you want to use zstd (as in your suggested command), that you will need to have zstd installed and added to your path
  • Maybe also note somewhere that if you try this on a server where you previously had unity's setup, you will need to remove the directives that you had to add to make the unity specific generation to work.
  • Since browser adoption of brotli is now at 97% I think the only real problem is that it is not supported with http. Compressing only for brotli is fine if your server setup is done with https, you won't have a lot of clients that will have problems with the compression.

Your article also inspired me to check if I can also use this for a godot webgl build, and it works great (I tried to do it the unity way of only generating the brotli and then pointing to the brotli file at all costs, but that didn't work).

@JohannesDeml JohannesDeml merged commit 77b054e into JohannesDeml:master Jun 3, 2024
@miltoncandelero
Copy link
Contributor Author

Hello! Thanks for accepting the PR!

Some answers

  • You're mentioning in the end, that WebAssembly.instantiateStreaming can be called earlier - Can this be seen somewhere or do you have an idea of how much impact that actually has?

If you check the unity generated JavaScript, it uses the instantiate streaming (as opposed to the old instantiate that didn't do streaming). This means that if your wasm file downloads with content type "wasm" it will be parsed as it downloads (streaming) instead of downloading it entirely and then parsing it. This is not a unity thing but a web assembly standard feature.

  • Maybe note that if you want to use zstd (as in your suggested command), that you will need to have zstd installed and added to your path

Interesting, it worked on my work and home computer without any extra steps, I'll check why I have zstd installed 😅

  • Maybe also note somewhere that if you try this on a server where you previously had unity's setup, you will need to remove the directives that you had to add to make the unity specific generation to work.

The unity special settings shouldn't conflict with serving pre-compressed files. You can remove those settings but you don't need to.

  • Since browser adoption of brotli is now at 97% I think the only real problem is that it is not supported with http. Compressing only for brotli is fine if your server setup is done with https, you won't have a lot of clients that will have problems with the compression.

Correct! By now all modern browsers should support brotli assuming we are using HTTPS. If total build size (not "downloadable size" but "total size in the server") is a concern, gzip can be skipped

Your article also inspired me to check if I can also use this for a godot webgl build, and it works great (I tried to do it the unity way of only generating the brotli and then pointing to the brotli file at all costs, but that didn't work).

I didn't think about Godot 😅
I have some friends in the community, I will check with them if they want to add compression to the build pipeline


Thanks again for accepting the pr and if you have any more questions let me know!

@JohannesDeml
Copy link
Owner

JohannesDeml commented Jun 5, 2024

If you check the unity generated JavaScript, it uses the instantiate streaming (as opposed to the old instantiate that didn't do streaming). This means that if your wasm file downloads with content type "wasm" it will be parsed as it downloads (streaming) instead of downloading it entirely and then parsing it. This is not a unity thing but a web assembly standard feature.

I quickly checked the code and found the following:

function instantiateArrayBuffer(receiver) {
    return getBinaryPromise()
        .then(function (binary) {
            return WebAssembly.instantiate(binary, info);
        })
        .then(function (instance) {
            return instance;
        })
        .then(receiver, function (reason) {
            err("failed to asynchronously prepare wasm: " + reason);
            abort(reason);
        });
}
function instantiateAsync() {
    if (!wasmBinary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(wasmBinaryFile) && !isFileURI(wasmBinaryFile) && typeof fetch == "function") {
        return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function (response) {
            var result = WebAssembly.instantiateStreaming(response, info);
            return result.then(receiveInstantiationResult, function (reason) {
                err("wasm streaming compile failed: " + reason);
                err("falling back to ArrayBuffer instantiation");
                return instantiateArrayBuffer(receiveInstantiationResult);
            });
        });
    } else {
        return instantiateArrayBuffer(receiveInstantiationResult);
    }
}

I guess the first one is the not so optimal solution where first the whole wasm needs to be downloaded and can then be instantiated, while the second function does this in a streaming manner? I guess I will need to profile how much that difference is in startup time with some internet throttling to see the effect, but this sounds quite interesting!

The unity special settings shouldn't conflict with serving pre-compressed files. You can remove those settings but you don't need to.

At least for the godot build I tested, the uncompressed version was used when I still had the the directives set in the .htaccess, e.g.

<IfModule mod_mime.c>
  # No compression
  AddType application/wasm .wasm
  AddOutputFilterByType DEFLATE application/wasm

  # Gzip support
  <Files *.js.gz>
    AddType "text/javascript" .gz
    AddEncoding gzip .gz
  </Files>

  <Files *.wasm.gz>
    AddType "application/wasm" .gz
    AddEncoding gzip .gz
  </Files>
...

Looking at the content again, maybe removing only the # No compression part would have been enough.

I didn't think about Godot 😅
I have some friends in the community, I will check with them if they want to add compression to the build pipeline

Nice, this is the result I got: 4.1.2-custom-template-compressed (3.35 MB)

@JohannesDeml
Copy link
Owner

Hm, one thing I don't understand is the difference in file size transferred when using the solution you proposed. With Unity's brotli compression the file sizes match, while with the gzipper compression the file size is quite a bit bigger. Do you have any idea why that is?

Unity with Brotli
firefox_iPw4TDOcwQ

Godot with gzipper brotli compression
firefox_QEGPozkx0D

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

Successfully merging this pull request may close these issues.

2 participants