Alejandro Alonso 5dab689a6e 🐛 Pack tile atlas and clamp HiDPI surfaces under GPU limits
HQ tiles are 512px and the atlas stays at 4096² (64 full-size
slots). Browser zoom plus a forced ?dpr= can need more visible
tiles than that, and a framebuffer larger than the GPU allows.

Pack interest tiles into smaller atlas cells, blit at 512 then
scale, and inset Linear samples so seams do not bleed. Clamp the
canvas backing store and DPR together, wrap Skia at the real
drawingBuffer size, and wait one frame after DPR changes so CSS
client size and overlays stay aligned.
2026-08-21 11:17:16 +02:00

153 lines
4.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::math::{Matrix, Point, Rect, Size};
use std::ops::Mul;
#[derive(Debug, Copy, Clone)]
pub(crate) struct Viewbox {
pub pan: Point,
pub size: Size,
pub zoom: f32,
pub dpr: f32,
pub area: Rect,
}
impl Default for Viewbox {
fn default() -> Self {
Self {
pan: Point::new(0.0, 0.0),
size: Size::new(0.0, 0.0),
zoom: 1.0,
dpr: 1.0,
area: Rect::new_empty(),
}
}
}
#[allow(dead_code)]
impl Viewbox {
pub fn new(width: f32, height: f32) -> Self {
let size = Size::new(width, height);
let area = Rect::from_size(size);
Self {
size,
area,
..Self::default()
}
}
pub fn dpr_width(&self) -> f32 {
self.size.width * self.dpr
}
pub fn dpr_height(&self) -> f32 {
self.size.height * self.dpr
}
pub fn width(&self) -> f32 {
self.size.width
}
pub fn height(&self) -> f32 {
self.size.height
}
pub fn set_all(&mut self, zoom: f32, pan_x: f32, pan_y: f32) {
self.pan.set(pan_x, pan_y);
self.zoom = zoom;
self.area.set_xywh(
-self.pan.x,
-self.pan.y,
self.size.width / self.zoom,
self.size.height / self.zoom,
);
}
pub fn set_wh(&mut self, width: f32, height: f32) {
self.size.set(width, height);
self.area
.set_wh(self.size.width / self.zoom, self.size.height / self.zoom);
}
pub fn set_dpr(&mut self, dpr: f32) {
self.dpr = dpr;
}
pub fn get_scale(&self) -> f32 {
self.zoom * self.dpr
}
pub fn get_offset(&self) -> Point {
self.area.tl().mul(self.get_scale())
}
pub fn pan(&self) -> Point {
self.pan
}
pub fn zoom(&self) -> f32 {
self.zoom
}
pub fn get_matrix(&self) -> Matrix {
let mut matrix = Matrix::new_identity();
matrix.post_translate(self.pan());
matrix.post_scale((self.zoom, self.zoom), None);
matrix
}
}
/// Scale `dpr` down so `floor(css * dpr)` fits in `max_dim` on both axes.
/// Used when a large viewport combined with a high DPR would exceed the GPU
/// (or our surface cap) on either axis.
pub fn clamp_dpr_for_surface(css_w: f32, css_h: f32, dpr: f32, max_dim: i32) -> f32 {
let css_w = css_w.max(1.0);
let css_h = css_h.max(1.0);
let dpr = dpr.max(0.0);
let max_dim = max_dim.max(1) as f32;
let raw_w = (css_w * dpr).floor().max(1.0);
let raw_h = (css_h * dpr).floor().max(1.0);
let scale = (max_dim / raw_w).min(max_dim / raw_h).min(1.0);
dpr * scale
}
#[cfg(test)]
mod tests {
use super::clamp_dpr_for_surface;
#[test]
fn clamp_dpr_keeps_hidpi_viewport_under_cap() {
let dpr = clamp_dpr_for_surface(2560.0, 1440.0, 2.0, 8192);
assert!((dpr - 2.0).abs() < 1e-5);
assert!((2560.0 * dpr).floor() <= 8192.0);
}
#[test]
fn clamp_dpr_caps_very_large_viewport_at_dpr2() {
// 10240×5760 CSS at DPR 2 → 20480 px unclamped on the long edge.
let dpr = clamp_dpr_for_surface(10240.0, 5760.0, 2.0, 8192);
assert!((10240.0 * dpr).floor() <= 8192.0);
assert!((5760.0 * dpr).floor() <= 8192.0);
assert!(dpr < 2.0);
}
#[test]
fn clamp_dpr_caps_large_viewport_at_dpr2() {
let dpr = clamp_dpr_for_surface(5120.0, 2880.0, 2.0, 8192);
assert!((5120.0 * dpr).floor() <= 8192.0);
assert!(dpr < 2.0);
assert!(dpr > 1.0);
}
#[test]
fn clamp_dpr_physical_size_is_floor_of_css_times_dpr() {
let css_w = 5120.0;
let css_h = 2880.0;
let dpr = clamp_dpr_for_surface(css_w, css_h, 2.0, 8192);
let phys_w = (css_w * dpr).floor();
let phys_h = (css_h * dpr).floor();
assert!(phys_w <= 8192.0);
assert!(phys_h <= 8192.0);
assert!((css_w * dpr - phys_w).abs() < 1.0);
assert!((css_h * dpr - phys_h).abs() < 1.0);
}
}