mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
🐛 Export bitmaps over the GPU surface limit through the exporter (#12455)
This commit is contained in:
parent
ef68304cb8
commit
a1606c42ff
@ -18,6 +18,7 @@
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.websocket :as ws]
|
||||
@ -300,11 +301,18 @@
|
||||
[state]
|
||||
(features/active-feature? state "render-wasm/v1"))
|
||||
|
||||
(defn- use-wasm-export?
|
||||
"Whether to take the client-side WASM export path for `export`."
|
||||
[state export]
|
||||
(and (wasm-export-enabled? state)
|
||||
(contains? wasm-export-types (:type export))))
|
||||
(defn- export-target
|
||||
[state {:keys [type object-id scale]}]
|
||||
(cond
|
||||
(not (and (wasm-export-enabled? state)
|
||||
(contains? wasm-export-types type)))
|
||||
:exporter
|
||||
|
||||
(contains? #{:pdf :svg} type)
|
||||
:client
|
||||
|
||||
:else
|
||||
(or (wasm.api/export-target object-id (or scale 1) type) :exporter)))
|
||||
|
||||
(defn- request-simple-export-wasm
|
||||
[export]
|
||||
@ -318,18 +326,25 @@
|
||||
|
||||
(defn request-simple-export
|
||||
[{:keys [export]}]
|
||||
(let [export (normalize-export export)]
|
||||
(let [export (normalize-export export)
|
||||
target (volatile! nil)]
|
||||
(ptk/reify ::request-simple-export
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(vreset! target (export-target state export))
|
||||
(cond-> state
|
||||
(not (use-wasm-export? state export))
|
||||
(= :exporter @target)
|
||||
(update :export assoc :in-progress true :id uuid/zero)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(if (use-wasm-export? state export)
|
||||
(case @target
|
||||
:client
|
||||
(rx/of (request-simple-export-wasm export))
|
||||
|
||||
:too-large
|
||||
(rx/of (ntf/error (tr "errors.export-too-large")))
|
||||
|
||||
(let [profile-id (:profile-id state)
|
||||
params (normalize-export-shapes-params {:exports [export]
|
||||
:profile-id profile-id
|
||||
|
||||
@ -3009,6 +3009,21 @@
|
||||
(mem/free)
|
||||
{:x x :y y :width w :height h})))))
|
||||
|
||||
(defn export-target
|
||||
[shape-id scale format]
|
||||
(when (initialized?)
|
||||
(let [buffer (uuid/get-u32 shape-id)]
|
||||
(case (h/call wasm/internal-module "_export_target"
|
||||
(aget buffer 0)
|
||||
(aget buffer 1)
|
||||
(aget buffer 2)
|
||||
(aget buffer 3)
|
||||
scale
|
||||
(sr/translate-raster-format format))
|
||||
0 :client
|
||||
2 :too-large
|
||||
:exporter))))
|
||||
|
||||
(defn render-shape-pdf
|
||||
[shape-id]
|
||||
(when (initialized?)
|
||||
|
||||
@ -1586,6 +1586,10 @@ msgstr "The email «%s» has been reported as spam or permanently bounce."
|
||||
msgid "errors.export-failed"
|
||||
msgstr "The export failed. Try again in a moment."
|
||||
|
||||
#: src/app/main/data/exports/assets.cljs:346
|
||||
msgid "errors.export-too-large"
|
||||
msgstr "Too large to export, try a smaller scale"
|
||||
|
||||
#: src/app/main/errors.cljs:974
|
||||
msgid "errors.feature-mismatch"
|
||||
msgstr ""
|
||||
|
||||
@ -1596,6 +1596,10 @@ msgstr "El email «%s» tiene reportes de spam o de rebote permanente."
|
||||
msgid "errors.export-failed"
|
||||
msgstr "La exportación ha fallado. Inténtalo de nuevo en un momento."
|
||||
|
||||
#: src/app/main/data/exports/assets.cljs:346
|
||||
msgid "errors.export-too-large"
|
||||
msgstr "El archivo es demasiado grande, prueba con otra escala"
|
||||
|
||||
#: src/app/main/errors.cljs:974
|
||||
msgid "errors.feature-mismatch"
|
||||
msgstr ""
|
||||
|
||||
@ -806,6 +806,31 @@ pub extern "C" fn render_shape_pixels(
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn export_target(a: u32, b: u32, c: u32, d: u32, scale: f32, format: u32) -> u32 {
|
||||
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||
let Ok(format) = RasterFormat::from_u32(format) else {
|
||||
return 1;
|
||||
};
|
||||
|
||||
with_state!(state, {
|
||||
let Some(shape) = state.shapes.get(&id) else {
|
||||
return 1;
|
||||
};
|
||||
let extrect = get_render_state().get_cached_extrect(shape, &state.shapes, scale);
|
||||
let width = (scale * extrect.width()).ceil() as i32;
|
||||
let height = (scale * extrect.height()).ceil() as i32;
|
||||
let max_size = get_gpu_state().max_surface_size();
|
||||
if width <= max_size && height <= max_size {
|
||||
0
|
||||
} else if format.fits_raster(width, height) {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn render_stats() {
|
||||
get_render_state().print_stats();
|
||||
|
||||
@ -8,6 +8,16 @@ use macros::ToJs;
|
||||
use super::vector;
|
||||
use super::RenderResources;
|
||||
|
||||
/// Half the default 2 GiB emscripten heap (a 16384² RGBA surface), leaving
|
||||
/// room for the encoded copies and the scene. Estimated, not measured.
|
||||
const MAX_RASTER_BYTES: i64 = 1 << 30;
|
||||
/// PNG stores each side in 31 bits; the memory budget limits it first.
|
||||
const MAX_PNG_SIDE: i32 = i32::MAX;
|
||||
/// libjpeg-turbo's `JPEG_MAX_DIMENSION`, below the 16-bit JPEG header limit.
|
||||
const MAX_JPEG_SIDE: i32 = 65500;
|
||||
/// libwebp's `WEBP_MAX_DIMENSION`: WebP stores each side in 14 bits.
|
||||
const MAX_WEBP_SIDE: i32 = 16383;
|
||||
|
||||
/// Encoded output format for [`render_to_raster`] and `render_shape_pixels`.
|
||||
/// `ToJs` publishes the discriminants to `api/shared.js`, so the CLJS side
|
||||
/// reads them from here instead of hardcoding the codes.
|
||||
@ -65,6 +75,17 @@ impl RasterFormat {
|
||||
pub fn clear_color(self) -> skia::Color {
|
||||
self.opaque_background().unwrap_or(skia::Color::TRANSPARENT)
|
||||
}
|
||||
|
||||
pub fn fits_raster(self, width: i32, height: i32) -> bool {
|
||||
let max_side = match self {
|
||||
Self::Png => MAX_PNG_SIDE,
|
||||
Self::Jpeg => MAX_JPEG_SIDE,
|
||||
Self::Webp => MAX_WEBP_SIDE,
|
||||
};
|
||||
width <= max_side
|
||||
&& height <= max_side
|
||||
&& width as i64 * height as i64 * 4 <= MAX_RASTER_BYTES
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a shape tree to encoded image bytes on a CPU raster surface (no
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user