Search Unity

WebGL build on OSX / Retina screens issue

Discussion in 'Web' started by Aurigan, Jan 9, 2016.

  1. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Hi - I'm struggling to get a webGL build to look good on a retina screen. According to https://www.khronos.org/webgl/wiki/HandlingHighDPI the canvas style sets the window points while the width/height attributes define the drawing buffer size.

    In practice setting the w/h as 2x the style w/h results in the unity content being drawn in 1/4 of the screen.

    Is there any way to get a Unity WebGL build looking good on a retina screen?
     
  2. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    small update to the above - after spending a few hours screwing around with js I'm still unable to get this to work. The intended behavior is to resize the game canvas so that it maintains aspect ratio but fills the browser window (aspect-fit). The closest I could get was this:

    Code (JavaScript):
    1.   window.addEventListener("resize", resizeCanvas, false);
    2.  
    3.   var pointHeight, pointWidth, pixelHeight, pixelWidth;
    4.   var ratio = .66;
    5.   var resolution;
    6.   var canvas;
    7.  
    8.   function unityLoaded(){
    9.     resizeCanvas();
    10.   }
    11.  
    12.   function resizeCanvas () {
    13.     pointHeight = document.documentElement.clientHeight;
    14.     pointWidth = Math.round(pointHeight * ratio);
    15.  
    16.     var devicePixelRatio = window.devicePixelRatio || 1;
    17.     pixelWidth = Math.round(pointWidth * devicePixelRatio);
    18.     pixelHeight = Math.round(pointHeight * devicePixelRatio);
    19.  
    20.     canvas = document.getElementById("canvas");
    21.  
    22.     // set the display size of the canvas.
    23.     canvas.setAttribute("style","width:"+pointWidth+"px;height:"+pointHeight+"px");
    24.  
    25.     // set the size of the canvas drawing buffer
    26.     canvas.height = pixelHeight;
    27.     canvas.width = pixelWidth;
    28.  
    29.     // tell unity to render at the new drawing buffer size
    30.     resolution = pixelWidth+","+pixelHeight;
    31.     SendMessage("Game Controller","UpdateResolutionForWeb",resolution);
    32.   }
    unityLoaded is getting called from one of the monobehavior Start() methods in my project to do the initial resize. UpdateResolutionForWeb looks like:

    Code (CSharp):
    1.     public void UpdateResolutionForWeb(string clientSize){
    2.         string[] hw = clientSize.Split(',');
    3.         int clientWidth = int.Parse(hw[0]);
    4.         int clientHeight = int.Parse(hw[1]);
    5.         Screen.SetResolution(clientWidth, clientHeight, Screen.fullScreen);
    6.     }
    The net effect, on a mbp retina screen, is that the canvas ends up 2x too large.

    I think this is because something in the Unity build is wiping away the style from the canvas element. As far as I can tell there's some sort of post-resize hook that is removing the style that the js I wrote is setting. Inspecting the canvas element using dev tools in chrome I can see the style get added then something instantaneously removes it.

    So, this appears to be a Unity bug.
     
  3. themartorana

    themartorana

    Joined:
    Mar 5, 2013
    Posts:
    3
    We're dealing with the same issue, where Unity is resetting the canvas size, instead of respecting the canvas size. We haven't found a good solution, but it means our game is decidedly low-res next to all other assets on-screen.

    I would very much like to either a) be able to set global JS variables for width/height that are respected by the game, or b) have Unity read and respect the size of the <canvas> element as set in HTML.
     
  4. putyavka

    putyavka

    Joined:
    Jun 19, 2015
    Posts:
    1
    The bug is still exists in 5.3.1p2. Is any solutions or bug report exists?
     
  5. tomhog

    tomhog

    Joined:
    Dec 22, 2012
    Posts:
    36
    So I've been looking into this today as the client for my current project are all using macs and the app I'm making them looks terrible on their fancy 'retina' displays.

    So the fundamental issue is that Unity isn't allowing you to set the resolution to higher than the screen res, but the screen res isn't factoring the devicePixelRatio as defined by the window.

    I have managed to make it work but messing with the js code that unity generates, tonight I'll try and write a shell script that does it automatically as the edits will get wiped out after each build.

    But basically there is a SystemInfo class, you need to multiple the screen width and height in there, there's also a getcurrentcanvas_width and height function (something along them lines) you have to multiply it there too, and also a fill mouse data function (again something along them lines, I don't have the code in front of me) in there you also need to multiple the mouse coords so all that works with the new canvas res.

    There is mention of HDPI support in future releases of Unity but they only specifically mention support in the Editor.
     
  6. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Hi there, please do post your solution when you're back in front of the code!
     
  7. tomhog

    tomhog

    Joined:
    Dec 22, 2012
    Posts:
    36
    Ok, so I've had a sit down and got it working for release builds. I've also developed an Editor script you can run to fix a release build once it's complete. You just need to set the buildJSFull variable to the full path to your jsgz file.

    I'm not guaranteeing this will work for everyone, my app is full window all the time so pretty straight forward, no other html elements involved. There's also a very very high chance it'll break in future releases as it depends on find and replace, but it'll do for now.

    Code (CSharp):
    1. //
    2. // Written by Thomas Hogarth, Hogbox Studios Ltd, thomas.hogarth@gmail.com
    3. // Developed against WebGL build from Unity 5.3.1
    4. //
    5. using UnityEngine;
    6. using UnityEditor;
    7. using System;
    8. using System.Collections;
    9. using System.IO;
    10. using System.Diagnostics;
    11. using System.Text;
    12.  
    13. public class FixWebglHDPI
    14. {
    15.     [MenuItem("hbx/Webgl Tools/Fix HDPI in Release Build")]
    16.     public static void FixHDPIReleaseBuild()
    17.     {
    18.         string buildJSFull = "/Users/thomashogarth/Sites/amtico/Release/amtico.jsgz";
    19.  
    20.         if(!File.Exists(buildJSFull))
    21.         {
    22.             UnityEngine.Debug.Log("FixHDPIReleaseBuild: Error: Could Not find built js file");
    23.             return;
    24.         }
    25.  
    26.         EditorUtility.DisplayProgressBar("Fixing WebGL HDPI", "Uncompressing...", 0.0f);
    27.  
    28.         string buildJSFolder = Path.GetDirectoryName(buildJSFull);
    29.         string buildJSName = Path.GetFileNameWithoutExtension(buildJSFull);
    30.         string buildJSExt = Path.GetExtension(buildJSFull);
    31.  
    32.         //gunzip only seems to work if the extension is gz
    33.         string gzPath = Path.Combine(buildJSFolder, buildJSName + ".gz");
    34.         File.Move(buildJSFull, gzPath);
    35.  
    36.         // run gunzip, we should end up with the same file name but no extension
    37.         ProcessStartInfo startInfo = new ProcessStartInfo()
    38.         {
    39.             FileName = "gunzip",
    40.             WorkingDirectory = buildJSFolder,
    41.             Arguments = gzPath,
    42.             UseShellExecute = false
    43.         };
    44.         Process proc = Process.Start(startInfo);
    45.         proc.WaitForExit();
    46.  
    47.         EditorUtility.DisplayProgressBar("Fixing WebGL HDPI", "Opening...", 0.25f);
    48.  
    49.         string unzipedPath = Path.Combine(buildJSFolder, buildJSName);
    50.         // load the uncompressed js code (this might trip over on large projects)
    51.         StringBuilder source = new StringBuilder(File.ReadAllText(unzipedPath));
    52.  
    53.         EditorUtility.DisplayProgressBar("Fixing WebGL HDPI", "Fixing source...", 0.5f);
    54.  
    55.         // fix fillMouseEventData
    56.         source.Replace("fillMouseEventData:(function(eventStruct,e,target){HEAPF64[eventStruct>>3]=JSEvents.tick();HEAP32[eventStruct+8>>2]=e.screenX;HEAP32[eventStruct+12>>2]=e.screenY;HEAP32[eventStruct+16>>2]=e.clientX;HEAP32[eventStruct+20>>2]=e.clientY;HEAP32[eventStruct+24>>2]=e.ctrlKey;HEAP32[eventStruct+28>>2]=e.shiftKey;HEAP32[eventStruct+32>>2]=e.altKey;HEAP32[eventStruct+36>>2]=e.metaKey;HEAP16[eventStruct+40>>1]=e.button;HEAP16[eventStruct+42>>1]=e.buttons;HEAP32[eventStruct+44>>2]=e[\"movementX\"]||e[\"mozMovementX\"]||e[\"webkitMovementX\"]||e.screenX-JSEvents.previousScreenX;HEAP32[eventStruct+48>>2]=e[\"movementY\"]||e[\"mozMovementY\"]||e[\"webkitMovementY\"]||e.screenY-JSEvents.previousScreenY;if(Module[\"canvas\"]){var rect=Module[\"canvas\"].getBoundingClientRect();HEAP32[eventStruct+60>>2]=e.clientX-rect.left;HEAP32[eventStruct+64>>2]=e.clientY-rect.top}else{HEAP32[eventStruct+60>>2]=0;HEAP32[eventStruct+64>>2]=0}if(target){var rect=JSEvents.getBoundingClientRectOrZeros(target);HEAP32[eventStruct+52>>2]=e.clientX-rect.left;HEAP32[eventStruct+56>>2]=e.clientY-rect.top}else{HEAP32[eventStruct+52>>2]=0;HEAP32[eventStruct+56>>2]=0}JSEvents.previousScreenX=e.screenX;JSEvents.previousScreenY=e.screenY})",
    57.             "fillMouseEventData:(function(eventStruct,e,target){var devicePixelRatio = window.devicePixelRatio || 1;HEAPF64[eventStruct>>3]=JSEvents.tick();HEAP32[eventStruct+8>>2]=e.screenX*devicePixelRatio;HEAP32[eventStruct+12>>2]=e.screenY*devicePixelRatio;HEAP32[eventStruct+16>>2]=e.clientX*devicePixelRatio;HEAP32[eventStruct+20>>2]=e.clientY*devicePixelRatio;HEAP32[eventStruct+24>>2]=e.ctrlKey;HEAP32[eventStruct+28>>2]=e.shiftKey;HEAP32[eventStruct+32>>2]=e.altKey;HEAP32[eventStruct+36>>2]=e.metaKey;HEAP16[eventStruct+40>>1]=e.button;HEAP16[eventStruct+42>>1]=e.buttons;HEAP32[eventStruct+44>>2]=e[\"movementX\"]||e[\"mozMovementX\"]||e[\"webkitMovementX\"]||(e.screenX*devicePixelRatio)-JSEvents.previousScreenX;HEAP32[eventStruct+48>>2]=e[\"movementY\"]||e[\"mozMovementY\"]||e[\"webkitMovementY\"]||(e.screenY*devicePixelRatio)-JSEvents.previousScreenY;if(Module[\"canvas\"]){var rect=Module[\"canvas\"].getBoundingClientRect();rect.left *= devicePixelRatio;rect.top *= devicePixelRatio;HEAP32[eventStruct+60>>2]=(e.clientX*devicePixelRatio)-rect.left;HEAP32[eventStruct+64>>2]=(e.clientY*devicePixelRatio)-rect.top}else{HEAP32[eventStruct+60>>2]=0;HEAP32[eventStruct+64>>2]=0}if(target){var rect=JSEvents.getBoundingClientRectOrZeros(target);rect.left *= devicePixelRatio;rect.top *= devicePixelRatio;HEAP32[eventStruct+52>>2]=(e.clientX*devicePixelRatio)-rect.left;HEAP32[eventStruct+56>>2]=(e.clientY*devicePixelRatio)-rect.top}else{HEAP32[eventStruct+52>>2]=0;HEAP32[eventStruct+56>>2]=0}JSEvents.previousScreenX=e.screenX*devicePixelRatio;JSEvents.previousScreenY=e.screenY*devicePixelRatio})");
    58.  
    59.         // fix SystemInfo screen width height
    60.         source.Replace("var systemInfo={get:(function(){if(systemInfo.hasOwnProperty(\"hasWebGL\"))return this;var unknown=\"-\";this.width=screen.width?screen.width:0;this.height=screen.height?screen.height:0;",
    61.             "var systemInfo={get:(function(){if(systemInfo.hasOwnProperty(\"hasWebGL\"))return this;var unknown=\"-\";var devicePixelRatio = window.devicePixelRatio || 1;this.width=screen.width?screen.width*devicePixelRatio:0;this.height=screen.height?screen.height*devicePixelRatio:0;");
    62.  
    63.         // fix _JS_SystemInfo_GetCurrentCanvasHeight
    64.         source.Replace("function _JS_SystemInfo_GetCurrentCanvasHeight(){return Module[\"canvas\"].clientHeight}",
    65.             "function _JS_SystemInfo_GetCurrentCanvasHeight(){var devicePixelRatio = window.devicePixelRatio || 1;return Module[\"canvas\"].clientHeight*devicePixelRatio;}");
    66.  
    67.         // fix get _JS_SystemInfo_GetCurrentCanvasWidth
    68.         source.Replace("function _JS_SystemInfo_GetCurrentCanvasWidth(){return Module[\"canvas\"].clientWidth}",
    69.             "function _JS_SystemInfo_GetCurrentCanvasWidth(){var devicePixelRatio = window.devicePixelRatio || 1;return Module[\"canvas\"].clientWidth*devicePixelRatio;}");
    70.  
    71.         EditorUtility.DisplayProgressBar("Fixing WebGL HDPI", "Saving...", 0.75f);
    72.  
    73.         // save the file
    74.         File.WriteAllText(unzipedPath, source.ToString());
    75.  
    76.         EditorUtility.DisplayProgressBar("Fixing WebGL HDPI", "Recompressing...", 1.0f);
    77.  
    78.         // re compress
    79.         startInfo = new ProcessStartInfo()
    80.         {
    81.             FileName = "gzip",
    82.             WorkingDirectory = buildJSFolder,
    83.             Arguments = "-8 " + unzipedPath,
    84.             UseShellExecute = false
    85.         };
    86.         proc = Process.Start(startInfo);
    87.         proc.WaitForExit();
    88.  
    89.         // rename to original
    90.         File.Move(gzPath, buildJSFull);
    91.  
    92.         UnityEngine.Debug.Log("FixHDPIReleaseBuild Complete");
    93.         EditorUtility.ClearProgressBar();
    94.     }
    95. }
     
    Aurigan likes this.
  8. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Ha, well it doesn't quite work for me - appears to rapidly double the width/height of the canvas element over and over. Thanks for posting your work though, feel like maybe it's a step closer to getting a workable solution!
     
  9. tomhog

    tomhog

    Joined:
    Dec 22, 2012
    Posts:
    36
    Bummer

    I just tried using it with the default webgl template and experienced the same, infact it crashed my computer :/ Sorry if that happened to you to. My guess is it's some css resizing thing, I change the canvas size which triggers some css resize, which resizes my canvas again and around we go.

    So I've put together an example project, that has a stripped down version of my template and uploaded two builds to my ftp.

    This is with no fix, it should display the current canvas resolution
    http://www.hogbox.com/webgl/hdpitests/HDPITemplate

    This is with the fix and should display a much higher resolution (if run on a mac)
    http://www.hogbox.com/webgl/hdpitests/HDPITemplate-fixed

    Here's also a link to the project itself, with my template
    http://www.hogbox.com/webgl/hdpitests/HDPI-Webgl.zip
     
  10. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Thanks for confirming the behavior and v. cool to see it working for your example project! Frankly beyond me where to go from here to get the standard template working.

    I've sent a bug report, will have to hope this gets some Unity attention.
     
    Last edited: Feb 24, 2016
  11. Julien-Lynge

    Julien-Lynge

    Joined:
    Nov 5, 2010
    Posts:
    142
    This is still an issue - do you mind sharing your bug report so I can comment on it and lend support that it really is an issue they need to fix?
     
  12. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    Um ... how do I see my previous bug reports? Doesn't look like it ever got added to issue tracker.
     
  13. tomhog

    tomhog

    Joined:
    Dec 22, 2012
    Posts:
    36
    Hey

    I've created a solution to this and uploaded it to the asset store

    lite version is free and works on development builds only http://u3d.as/Cpw
    paid version support release also http://u3d.as/CjZ

    This script will open your built js files, including compressed ones, add the required changes then recompress if required.

    since my post above I have also fixed issues with mouse coords and css styles. With the included templates it's pretty solid now.

    It also includes some WebGLTemplates that mimic the default examples you can use as a test bed.
     
    fastgamedev, kognito1 and Aurigan like this.
  14. kognito1

    kognito1

    Joined:
    Apr 7, 2015
    Posts:
    331
    Hey @Marco-Trivellato, is there some build file we can edit so we don't have to do this postprocessing step everytime (I was thinking like what was done for Audio.js)? I found and edited the corresponding code in the UnityNative.js file ([install directory]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\lib\UnityNativeJs), but seems like that file is unused (when examining the temp files in the staging folder, didn't have my modified code). I couldn't find any other file that contains this code so perhaps it's in a precompiled module somewhere? ¯\_(ツ)_/¯ It'd just be nice to edit a file once and be done with it.
     
  15. kognito1

    kognito1

    Joined:
    Apr 7, 2015
    Posts:
    331
    Nevermind, I found it in [install directory]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\src\library_html5.js! I think I can make this work. :)
     
  16. kognito1

    kognito1

    Joined:
    Apr 7, 2015
    Posts:
    331
    Yes it worked! No more postprocessing!
     
  17. kognito1

    kognito1

    Joined:
    Apr 7, 2015
    Posts:
    331
    Okay when redoing this for 2019.1, I guess misremembered originally which files I had to edit to make this work. Turns out it was few and not one! Sorry for being misleading 6 months ago! :oops: At any rate the files are:

    • [Unity install]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\UnityLoader.js
    • [Unity install]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\UnityLoader.min.js (you must minify your edited UnityLoader.js yourself and rename it to UnityLoader.min.js)
    • [Unity install]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\lib\SystemInfo.js
    • [Unity install]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\src\library_browser.js
    • [Unity install]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\src\library_html5.js
    You basically want to find any place in those files where width/height and input (mouse/touch) is calculated and scale (by some predefined value...for most of you that's window.devicePixelRatio) those values.

    I actually think I've come up with a good way to generalize this enough that anyone could use my files and adjust the "back-buffer resolution" pretty easily. I'm not sure how Unity feels about me distributing edited Unity Editor files though... :p
     
    Deleted User likes this.
  18. Deleted User

    Deleted User

    Guest

    where are your files?
     
  19. kognito1

    kognito1

    Joined:
    Apr 7, 2015
    Posts:
    331
    I don't really feel comfortable rehosting edited Unity Editor files without permission, sorry. However I really don't think it's hard to make the edits yourself. Just look within each file where any type of width/height is referenced and make sure you scale it by window.devicePixelRatio.
     
  20. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,087
    Everything under WebGLSupport\BuildTools\Emscripten is MIT licensed :)

    How did you forced emscripten to recompile? I've deleted temp folder and webgl_cache but it didn't help.
     
    Last edited: Sep 20, 2019
  21. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,087
    Oh nvm. It's fixed in 2019.3 beta.