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

fix: respect device screen DPI #49

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Conversation

x1unix
Copy link

@x1unix x1unix commented Feb 26, 2024

This small PR fixes the blurness issue on HiDPI screens by upscaling a canvas to match a display DPI.

This affects mobile devices and laptops with a resolution above 1080p.

See: https://web.dev/articles/canvas-hidipi

Closes: #50

Demo

Before

demo

Blurred text issue example on a HiDPI screen

After

fixed

Canvas respects screen DPI

@x1unix x1unix marked this pull request as ready for review February 26, 2024 18:43
@x1unix x1unix marked this pull request as draft February 26, 2024 19:08
@x1unix x1unix marked this pull request as ready for review February 26, 2024 19:26
@x1unix x1unix changed the title fix: setup DPI for canvas element fix: respect device screen DPI Feb 26, 2024
@RobLoach
Copy link

image-rendering could be related too: #31

@x1unix
Copy link
Author

x1unix commented Feb 27, 2024

@RobLoach #31 seems more like a hack rather than a solution.

Basically, here is a situation identical to what occurs in legacy apps in Windows with HiDPI displays. Browser renders canvas at its original size and then upscales it to a DPI value and this produces a blurry image.

The problem becomes much worse with fractional scaling.

image

This PR basically adds HiDPI support as per common guidelines.

@RobLoach
Copy link

Just tried it out locally, and it certainly looks nicer. Great work. I much prefer this solution over the other.

@RobLoach RobLoach mentioned this pull request Feb 27, 2024
@adaxiik
Copy link
Contributor

adaxiik commented Feb 27, 2024

I definitely agree that it looks better than #31 :) in some cases but devicePixelRatio changes with the browser zoom level.. If you zoom out bellow 100% it can result in broken image

image

Also.. if you started on zoomed out screen and then zoom in, its even more blurred

image

So I'm not sure if rendering at a higher resolution is the right solution

@x1unix
Copy link
Author

x1unix commented Feb 28, 2024

@adaxiik regarding zooming issue - although this looks like an edge case, probably this can be solved by tracking DPI changes. Right now, size and scaling are applied only once but this can be addressed in the next PR.

Thanks for pointing out devicePixelRatio changes. I'll add fix for that.

Copy link

@AntonPieper AntonPieper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think always using hidpi is not a good idea, as that requires significant processing power on some devices for some examples (i.e. phone with the tsoding_snake example). Therefore I would suggest adding a toggle to the index.html. Also I think fixing the zoom issue could be fixed in this PR as well.

@x1unix
Copy link
Author

x1unix commented Feb 28, 2024

@AntonPieper

I think always using hidpi is not a good idea, as that requires significant processing power on some devices for some examples. (i.e. phone with the tsoding_snake example).

What phone do you mean as a minimal requirement? Galaxy S10 which is quite an old phone model should run that demo easily, especially considering the simplicity of that demo.

Usually games have different configurations depending on a target but IMHO this is out of scope of the PR.

Therefore I would suggest adding a toggle to the index.html

I would suggest adopting a responsive design in that case to improve support of mobile devices as well.

Also I think fixing the zoom issue could be fixed in this PR as well.

I'll do that.

@AntonPieper
Copy link

AntonPieper commented Feb 28, 2024

I am using a Pixel 7 Pro and get about 6 FPS in the tsoding_snake demo (DPI is 3.5)

Also, I tried to help by using the "propose change" feature, but I guess I am bad at GitHub, so here is a patch if that helps you:

Patch
diff --git a/index.html b/index.html
index 960a2cd..223f18c 100644
--- a/index.html
+++ b/index.html
@@ -64,6 +64,8 @@
         <select id="raylib-example-select" onchange="startRaylib(this.value)">
         <!-- This is populated by js -->
         </select>
+    <label for="raylib-hidpi">HiDPI:</label>
+        <input type="checkbox" id="raylib-hidpi" onchange="setHiDpi(this.checked)">
     <canvas id="game"></canvas>
     <script>
         const wasmPaths = {
@@ -88,7 +90,13 @@
         const { protocol } = window.location;
         const isHosted = protocol !== "file:";
         let raylibJs = undefined;
-
+        let hidpi = false;
+        function setHiDpi(newHidpi) {
+            hidpi = newHidpi;
+            if (raylibJs !== undefined) {
+                raylibJs.hidpi = hidpi;
+            }
+        }
         function startRaylib(selectedWasm){
             var queryParams = new URLSearchParams(window.location.search);
             queryParams.set("example", selectedWasm);
@@ -100,6 +108,7 @@
                     raylibJs.stop();
                 }
                 raylibJs = new RaylibJs();
+                raylibJs.hidpi = hidpi;
                 raylibJs.start({
                     wasmPath: `wasm/${selectedWasm}.wasm`,
                     canvasId: "game",
diff --git a/raylib.js b/raylib.js
index 49f31a0..5a51335 100644
--- a/raylib.js
+++ b/raylib.js
@@ -30,11 +30,13 @@ class RaylibJs {
     //
     // It would be nice to have a better approach...
     #FONT_SCALE_MAGIC = 0.65;
+    #dpi = 1;
+    #hidpi = false;
+    #removeDpiListener = () => {};
 
     #reset() {
         this.height = 0;
         this.width = 0;
-        this.dpi = window.devicePixelRatio || 1;
         this.previous = undefined;
         this.wasm = undefined;
         this.ctx = undefined;
@@ -48,6 +50,33 @@ class RaylibJs {
         this.images = [];
         this.quit = false;
     }
+    
+    set dpi(dpi) {
+        this.#dpi = dpi;
+        this.#resize();
+    }
+    
+    get dpi() {
+        return this.#dpi;
+    }
+    
+    set hidpi(hidpi) {
+        this.#hidpi = hidpi;
+        this.#resize();
+    }
+
+    get hidpi() {
+        return this.#hidpi;
+    }
+    
+    #resize() {
+        if (this.ctx) {
+            const scale = this.hidpi ? this.dpi : 1;
+            this.ctx.canvas.height = this.height * scale;
+            this.ctx.canvas.width = this.width * scale;
+            this.ctx.setTransform(scale, 0, 0, scale, 0, 0);
+        }
+  }
 
     constructor() {
         this.#reset();
@@ -55,6 +84,7 @@ class RaylibJs {
 
     stop() {
         this.quit = true;
+        this.#removeDpiListener();
     }
 
     async start({ wasmPath, canvasId }) {
@@ -65,6 +95,9 @@ class RaylibJs {
 
         const canvas = document.getElementById(canvasId);
         this.ctx = canvas.getContext("2d");
+        this.#removeDpiListener = onPixelRatio((dpi) => {
+            this.dpi = dpi;
+        });
         if (this.ctx === null) {
             throw new Error("Could not create 2d canvas context");
         }
@@ -112,18 +145,15 @@ class RaylibJs {
     InitWindow(width, height, title_ptr) {
         // Adjust viewport size according to screen DPI for HiDPI screens.
         // see: https://web.dev/articles/canvas-hidipi
-        const { canvas } = this.ctx;
-        canvas.height = height * this.dpi;
-        canvas.width = width * this.dpi;
-        canvas.style.height = `${height}px`;
-        canvas.style.width = `${width}px`;
-        this.ctx.scale(this.dpi, this.dpi);
+        this.ctx.canvas.style.height = `${height}px`;
+        this.ctx.canvas.style.width = `${width}px`;
 
         // Store original size for GetScreenWidth and GetScreenHeight calls.
         // Necessary as some platforms allow fractional scaling (e.g 1.3)
         // and dividing canvas size by DPR can cause rounding issues.
         this.height = height;
         this.width = width;
+        this.#resize();
 
         const buffer = this.wasm.instance.exports.memory.buffer;
         document.title = cstr_by_ptr(buffer, title_ptr);
@@ -525,3 +555,19 @@ function getColorFromMemory(buffer, color_ptr) {
     const [r, g, b, a] = new Uint8Array(buffer, color_ptr, 4);
     return color_hex_unpacked(r, g, b, a);
 }
+
+function onPixelRatio(fn) {
+    let remove = () => {};
+    function update() {
+        remove();
+        const mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
+        const media = matchMedia(mqString);
+        media.addEventListener("change", update);
+        remove = () => {
+            media.removeEventListener("change", update);
+        }
+        fn(window.devicePixelRatio);
+    }
+    update();
+    return () => remove();
+}

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.

Blurry text on HiDPI screens
4 participants