M3: rendered pixels are moved out of the pixmap, not copied (#404)
`render_page` copied every raster out of vello_cpu's pixmap with `data_as_u8_slice().to_vec()`. That is a second buffer the size of the raster (up to 256 MB for a 64 MP page), alive alongside the first for as long as the copy runs. `Pixmap::take` hands over the pixel vector, and `bytemuck` reinterprets its four one-byte channels as bytes in place: the same bytes, in the same buffer. On a 6182×8000 px page, peak RSS goes from about 425 MB to 246 MB and render time from 102 ms to 81 ms. Pixels are identical: every page of #260's test file, at two scales and as a tile, hashes the same before and after. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e130af2ef4
commit
d6c2a694ec
|
|
@ -3745,6 +3745,7 @@ dependencies = [
|
|||
name = "pdfcraft-render"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"getrandom 0.4.3",
|
||||
"hayro",
|
||||
"hayro-syntax",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ lopdf = { workspace = true }
|
|||
kurbo = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
# Moves rendered pixels out of the renderer's pixmap without copying; already in the tree through hayro.
|
||||
bytemuck = { version = "1", features = ["extern_crate_alloc"] }
|
||||
|
||||
# lopdf (bootstrap) draws random bytes for encryption; on the web they come from the browser.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use hayro::hayro_interpret::InterpreterSettings;
|
|||
use hayro::hayro_interpret::font::{FontData, FontQuery};
|
||||
use hayro::hayro_interpret::hayro_cmap::CidFamily;
|
||||
use hayro::hayro_syntax::Pdf;
|
||||
use hayro::vello_cpu::Pixmap;
|
||||
use hayro::vello_cpu::color::palette::css::WHITE;
|
||||
use hayro::{RenderCache, RenderSettings, render};
|
||||
|
||||
|
|
@ -222,7 +223,7 @@ fn render_page<'a>(pdf: &'a Pdf, cache: &RenderCache<'a>, settings: &Interpreter
|
|||
}
|
||||
};
|
||||
let pixmap = render(page, cache, settings, &rs);
|
||||
Ok((pixmap.width() as u32, pixmap.height() as u32, pixmap.data_as_u8_slice().to_vec(), None))
|
||||
Ok((pixmap.width() as u32, pixmap.height() as u32, into_bytes(pixmap), None))
|
||||
}));
|
||||
match result {
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
|
|
@ -231,6 +232,13 @@ fn render_page<'a>(pdf: &'a Pdf, cache: &RenderCache<'a>, settings: &Interpreter
|
|||
}
|
||||
}
|
||||
|
||||
/// The pixmap's premultiplied RGBA bytes, moved out rather than copied (a page raster can be
|
||||
/// hundreds of megabytes). Four one-byte channels always recast to bytes in place; the copy is
|
||||
/// only there because the cast's signature allows failure.
|
||||
fn into_bytes(pixmap: Pixmap) -> Vec<u8> {
|
||||
bytemuck::allocation::try_cast_vec(pixmap.take()).unwrap_or_else(|(_, pixels)| pixels.iter().flat_map(|p| [p.r, p.g, p.b, p.a]).collect())
|
||||
}
|
||||
|
||||
fn finish(req: RenderRequest, start: Stopwatch, r: Result<Output, (String, bool)>) -> RenderedPage {
|
||||
let millis = start.millis();
|
||||
match r {
|
||||
|
|
@ -732,6 +740,18 @@ trailer << /Root 1 0 R >>
|
|||
assert_eq!(px(80, 5), &[255, 255, 255, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rendered_pixels_are_moved_out_of_the_pixmap() {
|
||||
let mut pixmap = Pixmap::new(3, 2);
|
||||
for (i, b) in pixmap.data_as_u8_slice_mut().iter_mut().enumerate() {
|
||||
*b = i as u8;
|
||||
}
|
||||
let (at, expected) = (pixmap.data_as_u8_slice().as_ptr(), pixmap.data_as_u8_slice().to_vec());
|
||||
let bytes = into_bytes(pixmap);
|
||||
assert_eq!(bytes, expected);
|
||||
assert_eq!(bytes.as_ptr(), at, "the pixmap's own buffer, not a copy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_page_and_garbage_input_fail_gracefully() {
|
||||
let mut r = PageRenderer::new(Arc::new(ONE_PAGE.to_vec()), RenderConfig::default());
|
||||
|
|
|
|||
Loading…
Reference in New Issue