wasm: improve HiDPI support
This commit is contained in:
@@ -26,6 +26,13 @@ EM_JS(float, js_devicePixelRatio, (), {
|
||||
return window.devicePixelRatio || 1.0;
|
||||
});
|
||||
|
||||
// SDL_CreateWindow sets inline width/height on the canvas which overrides
|
||||
// the stylesheet's 100vw/100vh. Clear them once so CSS stays in control.
|
||||
EM_JS(void, js_clearCanvasInlineSize, (), {
|
||||
var c = document.getElementById('canvas');
|
||||
if (c) { c.style.width = ''; c.style.height = ''; }
|
||||
});
|
||||
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
@@ -38,6 +45,30 @@ namespace baudmine {
|
||||
|
||||
Application::Application() = default;
|
||||
|
||||
void Application::syncCanvasSize() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
double cssW, cssH;
|
||||
emscripten_get_element_css_size("#canvas", &cssW, &cssH);
|
||||
float dpr = js_devicePixelRatio();
|
||||
int targetW = static_cast<int>(cssW * dpr + 0.5);
|
||||
int targetH = static_cast<int>(cssH * dpr + 0.5);
|
||||
int curW, curH;
|
||||
emscripten_get_canvas_element_size("#canvas", &curW, &curH);
|
||||
if (curW != targetW || curH != targetH) {
|
||||
// Set backing store + viewport to physical pixels.
|
||||
// CSS display size is handled by the stylesheet (100vw × 100vh).
|
||||
emscripten_set_canvas_element_size("#canvas", targetW, targetH);
|
||||
glViewport(0, 0, targetW, targetH);
|
||||
}
|
||||
// Re-apply UI scale if devicePixelRatio changed (orientation, zoom, etc.)
|
||||
if (std::abs(dpr - lastDpr_) > 0.01f) {
|
||||
lastDpr_ = dpr;
|
||||
float scale = (uiScale_ > 0.0f) ? uiScale_ : dpr;
|
||||
applyUIScale(scale);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::applyUIScale(float scale) {
|
||||
scale = std::clamp(scale, 0.5f, 4.0f);
|
||||
if (std::abs(scale - appliedScale_) < 0.01f) return;
|
||||
@@ -53,17 +84,35 @@ void Application::applyUIScale(float scale) {
|
||||
return s;
|
||||
}();
|
||||
|
||||
// Determine framebuffer scale (e.g. 2x–3x on HiDPI phones).
|
||||
float fbScale = 1.0f;
|
||||
int winW, winH, drawW, drawH;
|
||||
SDL_GetWindowSize(window_, &winW, &winH);
|
||||
SDL_GL_GetDrawableSize(window_, &drawW, &drawH);
|
||||
if (winW > 0) fbScale = static_cast<float>(drawW) / winW;
|
||||
|
||||
logicalScale_ = scale / fbScale;
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Rasterize the font at full physical resolution so the atlas has
|
||||
// crisp glyphs, then tell ImGui to scale them back to logical size.
|
||||
// Without this the 13px atlas gets bilinear-stretched to 13*dpr px.
|
||||
io.Fonts->Clear();
|
||||
ImFontConfig fc;
|
||||
fc.SizePixels = 13.0f * scale;
|
||||
fc.SizePixels = std::max(8.0f, 13.0f * scale); // physical pixels
|
||||
io.Fonts->AddFontDefault(&fc);
|
||||
io.Fonts->Build();
|
||||
ImGui_ImplOpenGL3_DestroyFontsTexture();
|
||||
io.FontGlobalScale = 1.0f / fbScale; // display at logical size
|
||||
|
||||
// Restore base style, then scale from 1x.
|
||||
ImGui::GetStyle() = baseStyle;
|
||||
ImGui::GetStyle().ScaleAllSizes(scale);
|
||||
ImGui::GetStyle().ScaleAllSizes(logicalScale_);
|
||||
}
|
||||
|
||||
void Application::requestUIScale(float scale) {
|
||||
pendingScale_ = scale;
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
@@ -115,6 +164,13 @@ bool Application::init(int argc, char** argv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// SDL_CreateWindow sets inline width/height on the canvas element,
|
||||
// overriding the stylesheet's 100vw/100vh. Clear them so CSS stays
|
||||
// in control of display size while we manage the backing store.
|
||||
js_clearCanvasInlineSize();
|
||||
#endif
|
||||
|
||||
glContext_ = SDL_GL_CreateContext(window_);
|
||||
SDL_GL_MakeCurrent(window_, glContext_);
|
||||
SDL_GL_SetSwapInterval(1); // vsync
|
||||
@@ -144,11 +200,15 @@ bool Application::init(int argc, char** argv) {
|
||||
// Load saved config (overwrites defaults for FFT size, overlap, window, etc.)
|
||||
loadConfig();
|
||||
|
||||
// Sync canvas to physical pixels before first frame (WASM)
|
||||
syncCanvasSize();
|
||||
|
||||
// Apply DPI-aware UI scaling
|
||||
{
|
||||
float dpiScale = 1.0f;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
dpiScale = js_devicePixelRatio();
|
||||
lastDpr_ = dpiScale;
|
||||
#else
|
||||
float ddpi = 0;
|
||||
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
|
||||
@@ -186,6 +246,14 @@ bool Application::init(int argc, char** argv) {
|
||||
}
|
||||
|
||||
void Application::mainLoopStep() {
|
||||
syncCanvasSize();
|
||||
|
||||
// Apply deferred UI scale (must happen outside the ImGui frame).
|
||||
if (pendingScale_ > 0.0f) {
|
||||
applyUIScale(pendingScale_);
|
||||
pendingScale_ = 0.0f;
|
||||
}
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
@@ -429,7 +497,7 @@ void Application::render() {
|
||||
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
|
||||
dpiScale = ddpi / 96.0f;
|
||||
#endif
|
||||
applyUIScale(dpiScale);
|
||||
requestUIScale(dpiScale);
|
||||
saveConfig();
|
||||
}
|
||||
for (int s : kScales) {
|
||||
@@ -437,7 +505,7 @@ void Application::render() {
|
||||
std::snprintf(label, sizeof(label), "%d%%", s);
|
||||
if (ImGui::MenuItem(label, nullptr, uiScale_ > 0.0f && std::abs(curPct - s) <= 2)) {
|
||||
uiScale_ = s / 100.0f;
|
||||
applyUIScale(uiScale_);
|
||||
requestUIScale(uiScale_);
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
@@ -485,7 +553,7 @@ void Application::render() {
|
||||
// Layout
|
||||
float totalW = ImGui::GetContentRegionAvail().x;
|
||||
float contentH = ImGui::GetContentRegionAvail().y;
|
||||
float controlW = showSidebar_ ? 270.0f * appliedScale_ : 0.0f;
|
||||
float controlW = showSidebar_ ? 270.0f * logicalScale_ : 0.0f;
|
||||
float contentW = totalW - (showSidebar_ ? controlW + 8 : 0);
|
||||
|
||||
// Control panel (sidebar)
|
||||
|
||||
@@ -121,9 +121,14 @@ private:
|
||||
FreqScale freqScale_ = FreqScale::Linear;
|
||||
bool paused_ = false;
|
||||
bool vsync_ = true;
|
||||
float uiScale_ = 0.0f; // 0 = auto (use DPI), >0 = manual override
|
||||
float appliedScale_ = 0.0f; // currently applied scale (0 = not yet applied)
|
||||
float uiScale_ = 0.0f; // 0 = auto (use DPI), >0 = manual override
|
||||
float appliedScale_ = 0.0f; // currently applied user-facing scale
|
||||
float pendingScale_ = 0.0f; // deferred scale (applied before next frame)
|
||||
float logicalScale_ = 1.0f; // scale after compensating for framebuffer DPI
|
||||
float lastDpr_ = 0.0f; // last devicePixelRatio (to detect changes)
|
||||
void applyUIScale(float scale);
|
||||
void requestUIScale(float scale); // safe to call mid-frame
|
||||
void syncCanvasSize();
|
||||
// (waterfallW_ removed — texture width tracks bin count automatically)
|
||||
// (waterfallH_ removed — fixed history depth of 1024 rows)
|
||||
|
||||
|
||||
@@ -48,17 +48,9 @@
|
||||
setStatus: function(text) {
|
||||
document.getElementById('status').textContent = text;
|
||||
},
|
||||
// Auto-resize canvas at full device resolution.
|
||||
preRun: [function() {
|
||||
function resize() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = Math.round(window.innerWidth * dpr);
|
||||
canvas.height = Math.round(window.innerHeight * dpr);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
}]
|
||||
// Canvas backing-store sizing is handled in C++ via syncCanvasSize()
|
||||
// each frame, so no JS resize handler is needed.
|
||||
preRun: []
|
||||
};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
|
||||
Reference in New Issue
Block a user