Skip to content

wasm: broken timing when ResistFingerprinting is enabled #612

@mochou-p

Description

@mochou-p

some browsers implement a security feature called ResistFingerprinting. among other things, it also caps the framerate of animation to 60FPS

context

screenshot from Home / Firefox / Advanced and experimental features / Resist Fingerprinting

Image

but miniquad still runs at VSYNC, so for example 120FPS. there, macroquad will correctly report 60FPS half the time, but completely invalid values the other half of the time (every other frame): macroquad::time::get_frame_time will return 0.0, and macroquad::time::get_fps will return i32::MAX (2147483647). this is problematic for programs that use timing information for anything outside of showing it on the screen or just printing it

explanation

since the frame time is calculated by subtracting the last frame from the current one, when macroquad reads the same old value (since the timer is being updated slower than how fast macroquad reads it), it will subtract the same number from itself: n - n = 0


another problem is how bad the rendering looks on some refresh rates. this mismatch of timing, when the timer really only updates at 60Hz, but it is being polled at VSYNC, for example 144Hz, makes rendering look even worse than if it was TRULY running at 60FPS

explanation

because 60 does not fit an integer amount of times into 144: 144 / 60 = 2.4

Y = reading a new timestamp
N = reading the same old timestamp again
|       |       |       |     # browser timer updates 60 times a second
--------------------------->  # time axis
|  |  |  |  |  |  |  |  |     # macroquad timer polls 144 times a second
Y  N  N  Y  N  N  Y  N  Y
                   # ^ the pattern changed here, from 2x N between Y, to just one N,
                   #   (and also the Ys are almost never perfectly aligned to the timer update, not true 60FPS)

for completeness, this would not be an issue on systems with a 120Hz refresh rate, since it will perfectly run twice as fast: 120 / 60 = 2

|       |       |       |     # browser timer updates 60 times a second
--------------------------->  # time axis
|   |   |   |   |   |   |     # macroquad timer polls 120 times a second
Y   N   Y   N   Y   N   Y     # very consistent and predictable pattern, true 60FPS

the solution im proposing is to run animation() from gl.js at a fixed interval of 60FPS, when ResistFingerprinting is suspected to be enabled. javascript gives us setInterval, which could be used in this case. there is no clear way to get the state of this browser preference, but i think its pretty safe to assume, that on the first time when macroquad::time::get_frame_time returns 0.0, macroquad::Context can turn on a new internal flag, and tell miniquad to run at a fixed interval

on the draw() side, we are not losing anything, only gaining a true 60FPS experience, instead of an emulated inconsistent one. the fake 60FPS also gets worse, depending on how incompatible the native refresh rate is with 60Hz

on the update() side, we lose VSYNC to 60FPS, but it is probably for the better. also, miniquad mentions in its documentation, that the update and draw functions should be called sequentially, and this would fix that. because currently when ResistFingerprinting is enabled, it is VSYNC update and fake 60FPS draw. the VSYNC update is not perfect also, since the delta time will often be 0, this means that using it can lead to issues. for example, if your code moves something by an offset multiplied by frame time, you sometimes multiply the offset by 0, meaning you do not actually move anything


i am not sure if this is the proper final solution, but at the very least, miniquad should print a warning to the console, that it is experiencing timer inaccuracies, suggesting to the user to consider disabling ResistFingerprinting, or atleast make the user aware that the program could experience bugs

and just to reiterate, this change would not affect most users who are using programs made with miniquad/macroquad. only the niche of users who have strict browser security preferences, and use miniquad/macroquad programs built for the web

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions