feat(web): proper viewport scaling, instant load, and file downloads
Make the Emscripten build behave like the desktop app for loading, window scaling, and exporting. All changes are #ifdef __EMSCRIPTEN__-gated or no-op on desktop, so native behavior is unchanged. Scaling: - Drop FLAG_WINDOW_HIGHDPI on web. The emscripten-GLFW shim forces a fixed pixel canvas style (!important) when HiDPI-aware, overriding the shell's 100vw/100vh CSS so the canvas can't fill the page; raylib's own resize/window callbacks also disagree about dividing by devicePixelRatio, desyncing the framebuffer from the reported screen size. - Sync raylib's window size to window.innerWidth/innerHeight each frame via SetWindowSize (guarded against no-op churn). This keeps screen size, GL viewport, and projection consistent, so the UI fills the viewport and reflows on resize like the desktop window. Loading: - Compute the full-resolution STFT synchronously when a file loads instead of the desktop overview-then-deferred-high-res path, which relied on many main-loop iterations yielding to the browser and appeared to stall partway. - Allow the wasm heap to grow (INITIAL_MEMORY + ALLOW_MEMORY_GROWTH) so longer recordings fit now that everything is computed up front. Exports: - Add Platform_OfferFileToUser(): no-op on desktop (file is already on disk); on web it reads the just-written file from MEMFS and triggers a browser download, then unlinks the temp copy. Wired into PNG and WAV export, which now show just the filename in the status message. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,23 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
// Keep raylib's framebuffer/screen size matched to the browser viewport, so the
|
||||
// (immediate-mode) UI fills the page and reflows on window resize the same way
|
||||
// the desktop OS window does. Going through SetWindowSize keeps the screen size,
|
||||
// GL viewport, and projection consistent; the != guard avoids per-frame churn.
|
||||
static void SyncCanvasToWindow(void)
|
||||
{
|
||||
int w = EM_ASM_INT({ return window.innerWidth; });
|
||||
int h = EM_ASM_INT({ return window.innerHeight; });
|
||||
if (w > 0 && h > 0 && (w != GetScreenWidth() || h != GetScreenHeight())) {
|
||||
SetWindowSize(w, h);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Global State (declared extern in spectrogram_types.h)
|
||||
// ============================================================================
|
||||
@@ -217,7 +234,18 @@ static void DispatchKeymap(void)
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// FLAG_WINDOW_HIGHDPI is buggy on the web backend: the Emscripten resize
|
||||
// callback sets the screen size to window.innerWidth, but the GLFW window-
|
||||
// size callback it triggers divides that by devicePixelRatio when HIGHDPI
|
||||
// is set. On a HiDPI display the framebuffer and the reported screen size
|
||||
// desync and the UI renders into a corner. UI scaling is handled by
|
||||
// GetUIScale() regardless, so the flag is unnecessary here. raylib auto-
|
||||
// resizes the canvas to the window when FLAG_WINDOW_RESIZABLE is set.
|
||||
SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE);
|
||||
#else
|
||||
SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI);
|
||||
#endif
|
||||
InitWindow(1280, 800, "Spectrogram Viewer");
|
||||
SetTargetFPS(60);
|
||||
SetTraceLogLevel(LOG_WARNING); // Suppress INFO texture logs
|
||||
@@ -320,6 +348,11 @@ int main(int argc, char* argv[])
|
||||
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Track the browser viewport (fill + reflow on resize, like desktop).
|
||||
SyncCanvasToWindow();
|
||||
#endif
|
||||
|
||||
// Drag & Drop
|
||||
if (IsFileDropped()) {
|
||||
FilePathList dropped = LoadDroppedFiles();
|
||||
@@ -764,6 +797,27 @@ int main(int argc, char* argv[])
|
||||
|
||||
// Processing (incremental across frames)
|
||||
if (app.loaded && !app.stftComputed) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Web build: there are no worker threads, and the desktop path's
|
||||
// overview-then-deferred-high-res fill depends on many main-loop
|
||||
// iterations yielding to the browser (which made loading appear to
|
||||
// stall partway). Compute the full-resolution STFT in one shot so
|
||||
// the spectrogram is completely ready as soon as the file loads.
|
||||
ComputeSTFTInit(&app.signal, &app.stft, app.fftSize);
|
||||
app.skipFactor = 1; // full resolution, no overview stride
|
||||
ComputeSTFTIncremental(&app.signal, &app.stft, app.fftSize, 0); // computes every segment
|
||||
AutoScaleAmplitude(&app.stft);
|
||||
GenerateSpectrogramTexture(&app.stft, &app.spectrogramImage, &app.spectrogramTexture);
|
||||
app.currentSTFTSegment = app.stft.numSegments;
|
||||
app.bgHighResSeg = app.stft.numSegments;
|
||||
app.loadingProgress = 1.0f;
|
||||
app.stftComputed = true;
|
||||
app.highResFinished = true;
|
||||
app.bgFinished = true;
|
||||
app.isBgProcessing = false;
|
||||
app.loadingPhase = 0;
|
||||
SaveToCache();
|
||||
#else
|
||||
if (app.loadingPhase == 0) {
|
||||
// Initialize STFT once
|
||||
ComputeSTFTInit(&app.signal, &app.stft, app.fftSize);
|
||||
@@ -802,6 +856,7 @@ int main(int argc, char* argv[])
|
||||
// Save the overview result to cache (will be overwritten when full-res completes)
|
||||
SaveToCache();
|
||||
}
|
||||
#endif // __EMSCRIPTEN__
|
||||
}
|
||||
|
||||
// Loading overlay (drawn during STFT computation)
|
||||
|
||||
Reference in New Issue
Block a user