如何缩放Emscripten驱动的SDL3程序所使用的Canvas?
The issue you're seeing happens because Emscripten/SDL resets the Canvas 2D context's state (including transformations like scale()) every time it renders a frame (via SDL_RenderPresent). Any manual changes you make to the context outside of SDL's rendering loop get overwritten immediately in the next frame. Here are two reliable solutions to fix this:
Solution 1: Control Scaling via SDL's Native Render API (Recommended)
Modify your C++ code to track a dynamic scale value, expose it to JavaScript, and let SDL handle the scaling internally. This keeps the rendering in sync with SDL's pipeline:
Step 1: Update the C++ Code
Add a global scale variable and expose a function to modify it from JS:
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ //NOLINT #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> //keep uncommented for release const int static WIDTH = 300; const int static HEIGHT = 300; const int static MAX = 255; static float global_scale = 4.0F; // Dynamic scale variable const char static *MESSAGE = "Hello, World!7"; //NOLINT const float XPOS = 10; const float YPOS = 10; // Expose this function to JavaScript extern "C" { void set_render_scale(float scale) { global_scale = scale; } } SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { //NOLINT SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; if (!SDL_CreateWindowAndRenderer("Hello, World!", WIDTH, HEIGHT, SDL_WINDOW_FULLSCREEN, &window, &renderer)) { SDL_Log("Couldn't create window and renderer: %s", SDL_GetError()); //NOLINT return SDL_APP_FAILURE; } if (!SDL_SetRenderVSync(renderer, 1)) { SDL_Log("Could not enable VSync! SDL error: %s\n", // NOLINT SDL_GetError()); } *appstate = renderer; return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { //NOLINT if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void *appstate) { //NOLINT auto *renderer = static_cast<SDL_Renderer *>(appstate); int width = 0; int height = 0; /* Center the message and scale it up using our dynamic global scale */ SDL_GetRenderOutputSize(renderer, &width, &height); SDL_SetRenderScale(renderer, global_scale, global_scale); // Use dynamic scale here /* Draw the message */ SDL_SetRenderDrawColor(renderer, 0, 0, 0, MAX); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, MAX, MAX, MAX, MAX); SDL_RenderDebugText(renderer, XPOS, YPOS, MESSAGE); SDL_RenderPresent(renderer); return SDL_APP_CONTINUE; } /* This function runs once at shutdown. */ void SDL_AppQuit(void *appstate, SDL_AppResult result) {}//NOLINT misc-use-anonymous-namespace
Step 2: Compile with Exported Function
When compiling with Emscripten, explicitly export the set_render_scale function so JavaScript can access it:
emcc hello.cpp -o hello_wasm.js -s USE_SDL=3 -s EXPORTED_FUNCTIONS=['_set_render_scale']
Step 3: Update the JavaScript Code
Track the current scale and call the exported C++ function when the canvas is clicked. Wait for Emscripten's runtime to initialize first:
console.log("wasm loaded"); const canvas = document.getElementById("canvas"); const canvas2 = document.getElementById("canvas2"); const ctx2 = canvas2.getContext("2d"); let currentScale = 4.0; // Match initial scale from C++ ctx2.fillStyle = "green"; ctx2.fillRect(10, 10, 110, 110); // Wait for Emscripten module to be ready window.Module = window.Module || {}; Module.onRuntimeInitialized = function() { canvas.addEventListener('click', function (event) { currentScale *= 1.1; window.Module._set_render_scale(currentScale); console.log(`canvas clicked, scale updated to ${currentScale.toFixed(2)}`); }); }; canvas2.addEventListener('click', function (event) { ctx2.scale(1.1, 1.1) ctx2.fillStyle = "green"; ctx2.fillRect(10, 10, 110, 110); console.log("canvas2 clicked") }); console.log("checkers_cust.js finished")
Solution 2: Scale via CSS (No C++ Changes)
If you don't want to modify the C++ code, you can scale the canvas visually using CSS transforms or size properties. This affects how the canvas is displayed in the browser, but doesn't change the actual rendering buffer size:
console.log("wasm loaded"); const canvas = document.getElementById("canvas"); const canvas2 = document.getElementById("canvas2"); const ctx2 = canvas2.getContext("2d"); let cssScale = 1; ctx2.fillStyle = "green"; ctx2.fillRect(10, 10, 110, 110); canvas.addEventListener('click', function (event) { cssScale *= 1.1; // Use CSS transform to scale the canvas canvas.style.transform = `scale(${cssScale})`; // Optional: Adjust position to prevent shifting canvas.style.transformOrigin = "top left"; console.log(`canvas clicked, CSS scale updated to ${cssScale.toFixed(2)}`); }); canvas2.addEventListener('click', function (event) { ctx2.scale(1.1, 1.1) ctx2.fillStyle = "green"; ctx2.fillRect(10, 10, 110, 110); console.log("canvas2 clicked") }); console.log("checkers_cust.js finished")
Why Your Original Code Didn't Work
Your canvas2 works because you're manually re-drawing the rectangle after applying scale()—the context's transformation applies to the new drawing. For the Emscripten canvas, SDL resets the 2D context's entire state (transforms, colors, etc.) every frame during SDL_RenderPresent, so your manual scale() gets erased before the next frame is drawn.
内容的提问来源于stack exchange,提问作者user2309803

