emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.83k stars 3.31k forks source link

Canvas is the wrong size for the graphics when writing directly to pixels and using a high DPI and using SDL_WINDOW_ALLOW_HIGHDPI #22944

Open inhahe opened 23 hours ago

inhahe commented 23 hours ago

Please include the following in your bug report:

Version of emscripten/emsdk: 3.1.71

Full link command and output with -v appended:

 "/home/inhahe/emsdk/upstream/bin/clang++" -target wasm32-unknown-emscripten -fignore-exceptions -mno-bulk-memory -mno-nontrapping-fptoint -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=/home/inhahe/emsdk/upstream/emscripten/cache/sysroot -DEMSCRIPTEN -isystem /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/SDL2 -Xclang -iwithsysroot/include/compat -v "/mnt/d/visual studio projects/scribbles3/sdl2bug.cpp" -c -o /tmp/emscripten_temp_cpdzmkie/sdl2bug_0.o
clang version 20.0.0git (https:/github.com/llvm/llvm-project d6344c1cd0d099f8d99ee320f33fc9254dbe8288)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/inhahe/emsdk/upstream/bin
 (in-process)
 "/home/inhahe/emsdk/upstream/bin/clang-20" -cc1 -triple wasm32-unknown-emscripten -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name sdl2bug.cpp -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-cpu generic -target-feature -bulk-memory -target-feature -nontrapping-fptoint -fvisibility=hidden -debugger-tuning=gdb -fdebug-compilation-dir=/home/inhahe -v -fcoverage-compilation-dir=/home/inhahe -resource-dir /home/inhahe/emsdk/upstream/lib/clang/20 -isystem /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/SDL2 -D EMSCRIPTEN -isysroot /home/inhahe/emsdk/upstream/emscripten/cache/sysroot -internal-isystem /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1 -internal-isystem /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1 -internal-isystem /home/inhahe/emsdk/upstream/lib/clang/20/include -internal-isystem /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten -internal-isystem /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fignore-exceptions -fexceptions -fcolor-diagnostics -iwithsysroot/include/compat -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o /tmp/emscripten_temp_cpdzmkie/sdl2bug_0.o -x c++ "/mnt/d/visual studio projects/scribbles3/sdl2bug.cpp"
clang -cc1 version 20.0.0git based upon LLVM 20.0.0git default target x86_64-unknown-linux-gnu
ignoring nonexistent directory "/home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "/home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
 /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/SDL2
 /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/compat
 /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1
 /home/inhahe/emsdk/upstream/lib/clang/20/include
 /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/include
End of search list.
 /home/inhahe/emsdk/upstream/bin/clang --version
 /home/inhahe/emsdk/upstream/bin/wasm-ld -o sdl2bug.wasm /tmp/emscripten_temp_cpdzmkie/sdl2bug_0.o -L/home/inhahe/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten /home/inhahe/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libSDL2.a -lGL-getprocaddr -lal -lhtml5 -lstubs-debug -lnoexit -lc-debug -ldlmalloc -lcompiler_rt -lc++-noexcept -lc++abi-debug-noexcept -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr /tmp/tmp0gqt38m2libemscripten_js_symbols.so --strip-debug --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_get_current --export=emscripten_stack_init --export=_emscripten_stack_alloc --export=__get_temp_ret --export=__set_temp_ret --export=__wasm_call_ctors --export=_emscripten_stack_restore --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-if-defined=main --export-if-defined=__main_argc_argv --export-if-defined=fflush --export-table -z stack-size=65536 --max-memory=2147483648 --initial-heap=16777216 --no-entry --stack-first --table-base=1
 /home/inhahe/emsdk/upstream/bin/llvm-objcopy sdl2bug.wasm sdl2bug.wasm --remove-section=.debug* --remove-section=producers
 /home/inhahe/emsdk/upstream/bin/wasm-emscripten-finalize --dyncalls-i64 --pass-arg=legalize-js-interface-exported-helpers sdl2bug.wasm -o sdl2bug.wasm --detect-features
 /home/inhahe/emsdk/node/20.18.0_64bit/bin/node /home/inhahe/emsdk/upstream/emscripten/src/compiler.mjs /tmp/tmpapet3dfw.json
 /home/inhahe/emsdk/node/20.18.0_64bit/bin/node /home/inhahe/emsdk/upstream/emscripten/tools/preprocessor.mjs /tmp/emscripten_temp_cpdzmkie/settings.js shell.html

I've made a minimal example that exhibits this behavior. Ideally, the whole canvas should turn red. But on a high-DPI screen, the canvas is about 1.5x the size of the graphics (the red square). Changing the canvas size is CSS only shrinks the whole thing, with the red square still taking only a portion of the canvas. The parts of the canvas not used show up in black. Honestly, I'm not sure how this stuff works well enough to know whether this is an Emscripten bug or an SDL bug. But... I have a version of the same program for the desktop instead of the web, and it doesn't have this problem.

#include <emscripten.h>
#include <SDL.h>

int width = 1000;
int height = 1000;

struct Context
{
  SDL_Renderer* renderer = nullptr;
  SDL_Window* window = nullptr;
  SDL_Surface* surface = nullptr;
  SDL_PixelFormat* pixel_format_surface = nullptr;
};

void mainloop(void* arg)
{ 
  Context* context = static_cast<Context*>(arg);
  SDL_RenderClear(context->renderer);
  uint8_t* pixels = (uint8_t*)(context->surface->pixels);
  int pitch = context->surface->pitch;
  int color = SDL_MapRGBA(context->pixel_format_surface, 255, 0, 0, 255);
  if (SDL_MUSTLOCK(context->surface)) SDL_LockSurface(context->surface);
  uint8_t* sp_pixels = nullptr;
  for (int y = 0; y < height; y++)
  {
    sp_pixels = pixels + y * pitch;
    for (int x = 0; x < width; x++)
    {
      *(uint32_t*)sp_pixels = color;
      sp_pixels += 4;
    }
  }
  if (SDL_MUSTLOCK(context->surface)) SDL_UnlockSurface(context->surface);
  SDL_UpdateWindowSurface(context->window);
}

int main(int argc, char* argv[])
{
  Context context;
  SDL_Init(SDL_INIT_VIDEO);
  context.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_ALLOW_HIGHDPI);
  context.surface = SDL_GetWindowSurface(context.window);
  context.pixel_format_surface = context.surface->format;
  emscripten_set_main_loop_arg(mainloop, &context, 0, 1);
}

Here's a screenshot of how it looks on my display: image

sbc100 commented 20 hours ago

So this bug only occurs with SDL_WINDOW_ALLOW_HIGHDPI?

I would assume this is an SDL bug. @Daft-Freak WDYT?

sbc100 commented 20 hours ago

Also, is this a regression? (i.e. did it used to work in the past?)

Daft-Freak commented 20 hours ago

I think this is this old issue: https://github.com/emscripten-ports/SDL2/issues/109. Specifically, the combination of framebuffer rendering and HiDPI, which when I looked at it back then was handled in other ports by... basically not doing HiDPI.

inhahe commented 19 hours ago

@sbc100 I don't know of it ever working right in the past, but I haven't been using Emscripten for very long.

@Daft-Freak I really want HighDPI to work, I dislike the automatic upscaling, at least for the project I'm doing now, which is my only Emscripten project. I would like to see the HighDPI flag work across the board (and correctly), not just with this particular rendering method.. :P (I noticed that any other rendering method just ignores AllowHighDPI..)

inhahe commented 16 hours ago

I don't like the two workarounds that are suggested in https://github.com/emscripten-ports/SDL2/issues/109,

"As a workaround, you can use SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "1"); before you call SDL_Init to force going through the renderer.

EDIT: Hmm, since the framebuffer seems to always be non-HiDPI you could just disable HiDPI and use CSS image-rendering on the canvas to control the scaling..."

The first one would just have the effect of ignoring allowhighdpi, if I understand correctly, since in my experience all the other rendering methods ignore allowhighdpi.

And to the second one, I want a one-to-one pixel correspondence between the rendering and the monitor, and I don't know how to find out exactly how much I'd have to scale it to get that ratio, let alone in a 100% reliable way...

inhahe commented 15 hours ago

Sorry to ask in the issue box, but I've been looking and asking everywhere and I can't seem to find answers: how can I write to the pixel data of the canvas without using SDL?

Daft-Freak commented 7 hours ago

Looking at this more closely, it seems that the framebuffer is correctly sized now and switching width/height for context->surface->w/->h in the loop fills the entire canvas.

2024-11-16T11:36:29,819973586+00:00 (That ended up at the correct scale (1.5x) but goes through a 2x scale in the middle because devicePixelRatio is 2 for some reason. That seems like a browser thing outside of SDL's control though...)

inhahe commented 7 hours ago

@Daft-Freak Oh, that helps, but it's still kind of awkward because i wanted a 1000x1000 canvas, not an approximately 1500x1500 canvas. and if i just create my window 66% as large then it will be too small on monitors that don't have a high dpi.. (it'll be 666x666) But this is a great find, I should have realized it.