You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何缩放Emscripten驱动的SDL3程序所使用的Canvas?

How to Scale the Canvas Used by Emscripten/SDL3

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:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 06:42:32