makepad/platform/tests/texture_lifetime.rs
Admin a4ea2536a4 platform: topic hunks the per-commit assembly could not place, the Sep 2–15 dev PRs (#1208–#1236) as adapted to this tree, and the zero-warning chore
code_editor/Cargo.toml                          |     1 -
 code_editor/src/lib.rs                          |     3 -
 draw/src/cx_2d.rs                               |    83 +
 draw/src/cx_draw.rs                             |    43 +
 draw/src/draw_list_2d.rs                        |    22 +
 draw/src/geometry/geometry_gen.rs               |     7 +-
 draw/src/image_cache.rs                         |    51 +-
 draw/src/lib.rs                                 |     1 -
 draw/src/shader/draw_text.rs                    |    40 +-
 draw/src/shader/mod.rs                          |     1 -
 draw/src/text/fonts.rs                          |     2 +-
 draw/src/text/mod.rs                            |     1 -
 draw/src/text/slug_atlas.rs                     |     2 +-
 draw/src/turtle.rs                              |  3469 ++++-
 draw/src/vector/triangulate.rs                  |   378 +-
 libs/error_log/src/lib.rs                       |    17 +-
 platform/network/src/backend.rs                 |     6 +
 platform/network/src/backend/apple/http.rs      |     2 +-
 platform/network/src/http_server.rs             |    15 +-
 platform/script/derive/src/derive_scriptable.rs |    19 +-
 platform/script/src/lib.rs                      |     4 +
 platform/script/src/shader_calls.rs             |    92 +-
 platform/script/src/shader_hlsl.rs              |     9 +-
 platform/script/src/shader_metal.rs             |    18 +-
 platform/script/src/shader_wgsl.rs              |    12 +-
 platform/script/tests/hook_scope.rs             |     8 +-
 platform/src/action.rs                          |    12 +-
 platform/src/app_main.rs                        |   110 +-
 platform/src/cx.rs                              |   466 +-
 platform/src/cx_api.rs                          |   368 +-
 platform/src/draw_list.rs                       |  2598 +++-
 platform/src/draw_pass.rs                       |    35 +-
 platform/src/draw_vars.rs                       |   103 +-
 platform/src/geometry.rs                        |   588 +-
 platform/src/gpu_texture.rs                     |     2 +-
 platform/src/id_pool.rs                         |    71 +-
 platform/src/lib.rs                             |    26 +-
 platform/src/log.rs                             |   111 +-
 platform/src/os/apple/apple_game_input.rs       |     6 +
 platform/src/os/apple/apple_sys.rs              |     1 +
 platform/src/os/apple/macos/macos_app.rs        |   384 +-
 platform/src/os/apple/metal.rs                  |  3347 +++-
 platform/src/os/cx_native.rs                    |     5 +-
 platform/src/os/cx_shared.rs                    |   202 +-
 platform/src/os/gpusim/event_loop.rs            |    24 +-
 platform/src/os/gpusim/mod.rs                   |     8 +
 platform/src/os/gpusim/raster.rs                |    11 +
 platform/src/os/linux/opengl.rs                 |   856 +-
 platform/src/os/linux/vulkan.rs                 |   752 +-
 platform/src/os/linux/vulkan_linux.rs           |     4 -
 platform/src/os/linux/wayland/linux_wayland.rs  |    15 +-
 platform/src/os/linux/wayland/opengl_wayland.rs |    67 +-
 platform/src/os/linux/wayland/wayland_state.rs  |    37 +-
 platform/src/os/web/web.js                      |  1409 +-
 platform/src/os/web/web.rs                      |   212 +-
 platform/src/os/web/web_gl.rs                   |   284 +-
 platform/src/os/web/web_worker.js               |    76 +-
 platform/src/os/windows/d3d11.rs                |  1323 +-
 platform/src/remote.rs                          |  1297 +-
 platform/src/retained_instances.rs              |     3 -
 platform/src/script/res.rs                      |   245 +-
 platform/src/sploded.rs                         |    59 +-
 platform/src/texture.rs                         |   416 +-
 platform/src/thread.rs                          |  2816 +++-
 platform/src/thread/ui_hang/linux.rs            |     2 +-
 platform/src/thread/ui_hang/macos.rs            |    22 +-
 platform/src/thread/ui_hang/native.rs           |     3 +
 platform/src/thread/ui_hang/windows.rs          |     2 +-
 platform/src/web_socket.rs                      |    45 +-
 platform/src/window.rs                          |   129 +-
 platform/tests/texture_lifetime.rs              |   143 +-
 platform/video/src/stream_debug.rs              |     2 +-
 platform/video/tests/stream_codec.rs            |    85 +-
 widgets/derive_widget/src/derive_widget.rs      |     4 +-
 widgets/src/animator.rs                         |   138 +-
 widgets/src/combo_box.rs                        |    10 +
 widgets/src/desktop_style.rs                    |     4 +-
 widgets/src/drop_down.rs                        |    96 +
 widgets/src/font_policy.rs                      |    25 +
 widgets/src/grid.rs                             |     1 +
 widgets/src/image.rs                            |     3 +
 widgets/src/lib.rs                              |    18 +-
 widgets/src/map/archive.rs                      |   544 +-
 widgets/src/map/geometry.rs                     |    34 +-
 widgets/src/map/label.rs                        |    48 +-
 widgets/src/map/tile.rs                         |  6890 ++++++---
 widgets/src/map/tile_draw.rs                    |     2 -
 widgets/src/map/view.rs                         | 17896 ++++++++++++++--------
 widgets/src/menu_bar.rs                         |    18 +-
 widgets/src/screen_cap.rs                       |   513 +-
 widgets/src/slider.rs                           |     6 +
 widgets/src/splash.rs                           |   280 +-
 widgets/src/tweaker.rs                          |   221 +-
 widgets/src/view.rs                             |   105 +-
 widgets/src/widget.rs                           |     5 +-
 widgets/src/widget_async.rs                     |   269 +-
 widgets/src/window.rs                           |   601 +-
 widgets/src/window_voice_input.rs               |    17 +-
 98 files changed, 38159 insertions(+), 12677 deletions(-)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:40:33 +02:00

214 lines
9.9 KiB
Rust

//! Run with CARGO_TARGET_DIR=target-gpusim MAKEPAD=gpusim RUSTFLAGS='--cfg gpusim' cargo test --release
//! -p makepad-platform --test texture_lifetime. No window, capture or JIT is
//! needed: an empty draw list exercises the real render-pass clear/allocation.
#![cfg(gpusim)]
use makepad_platform::*;
use makepad_platform::os::{ReadbackChannelOrder, ReadbackError, ReadbackOrigin, ReadbackRequest, TEXTURE_READBACK_MAX_BYTES};
#[test]
fn released_render_texture_reallocates_fifty_sizes_without_retaining_storage() {
let mut cx = Cx::new(Box::new(|_, _| {}));
let texture = Texture::new_with_format(
&mut cx,
TextureFormat::RenderBGRAu8 {
size: TextureSize::Fixed {
width: 2048,
height: 2048,
},
initial: true,
},
);
let clone = texture.clone();
let id = texture.texture_id();
let pass = DrawPass::new(&mut cx);
let list = DrawList::new(&mut cx);
pass.add_color_texture(
&mut cx,
&texture,
DrawPassClearColor::InitWith(vec4(0.25, 0.5, 0.75, 1.0)),
);
cx.passes[pass.draw_pass_id()].main_draw_list_id = Some(list.id());
let baseline = cx.texture_pool_bytes();
assert_eq!(texture.allocated_bytes(&cx), None);
assert_eq!(cx.frame_submission_serial(), 0);
assert_eq!(cx.frame_completion_serial(), 0);
let mut last_serial = 0;
for iteration in 0..51 {
let size = if iteration == 0 {
2048
} else {
31 + iteration * 7
};
let TextureFormat::RenderBGRAu8 { size: extent, .. } = texture.get_format(&mut cx) else {
unreachable!()
};
*extent = TextureSize::Fixed {
width: size,
height: size,
};
pass.set_size(&mut cx, dvec2(size as f64, size as f64));
cx.passes[pass.draw_pass_id()].dpi_factor = Some(1.0);
cx.passes[pass.draw_pass_id()].paint_dirty = true;
cx.gpusim_render_all_passes(iteration as f64);
let submitted = cx.frame_submission_serial();
assert!(submitted > last_serial);
assert_eq!(cx.frame_completion_serial(), submitted);
// Gpusim stores RGBA float pixels even for a BGRA8 target.
let bytes = (size * size * 16) as u64;
assert_eq!(texture.allocated_bytes(&cx), Some(bytes));
assert_eq!(cx.texture_pool_bytes(), baseline + bytes);
if iteration < 2 {
let (width, height, pixels) = cx.debug_read_render_texture(&texture).unwrap();
assert_eq!((width, height), (size, size));
assert_eq!(
&pixels[..4],
&[191, 128, 64, 255],
"InitWith must clear a fresh allocation"
);
}
clone.release(&mut cx);
assert_eq!(cx.frame_completion_serial(), submitted);
assert_eq!(texture.texture_id(), id);
assert_eq!(texture.allocated_bytes(&cx), None);
assert_eq!(cx.texture_pool_bytes(), baseline);
assert!(cx.debug_read_render_texture(&texture).is_none());
// Idempotent through every clone; format/handle survive.
texture.release(&mut cx);
assert_eq!(cx.texture_pool_bytes(), baseline);
last_serial = submitted;
}
}
#[test]
fn readback_round_trip_512_and_fifty_bounded_tickets() {
let mut cx = Cx::new(Box::new(|_, _| {}));
let texture = Texture::new_with_format(&mut cx, TextureFormat::RenderBGRAu8 {
size: TextureSize::Fixed { width: 512, height: 512 }, initial: true,
});
let pass = DrawPass::new(&mut cx);
let list = DrawList::new(&mut cx);
pass.add_color_texture(&mut cx, &texture, DrawPassClearColor::ClearWith(vec4(0.25, 0.5, 0.75, 1.0)));
pass.set_size(&mut cx, dvec2(512.0, 512.0));
cx.passes[pass.draw_pass_id()].main_draw_list_id = Some(list.id());
cx.passes[pass.draw_pass_id()].dpi_factor = Some(1.0);
cx.passes[pass.draw_pass_id()].paint_dirty = true;
let first = texture.read_back(&mut cx, ReadbackRequest::default()).unwrap();
assert!(cx.try_take_texture_readbacks().is_empty());
cx.gpusim_render_all_passes(0.0);
let results = cx.try_take_texture_readbacks();
assert_eq!(results.len(), 1);
let result = &results[0];
assert_eq!(result.ticket, first);
assert_eq!(result.producer_serial, cx.frame_submission_serial());
assert_eq!((result.width, result.height, result.stride), (512, 512, 2048));
assert_eq!(result.channel_order, ReadbackChannelOrder::Bgra);
assert_eq!(result.origin, ReadbackOrigin::TopLeft);
let generation = result.allocation_generation;
let data = result.data.as_ref().unwrap();
assert_eq!(data.len(), 512 * 512 * 4);
assert!(data.chunks_exact(4).all(|pixel| pixel == [191, 128, 64, 255]));
let serial = cx.frame_submission_serial();
let mut tickets = std::collections::HashSet::new();
let mut rejected = 0;
for _ in 0..50 {
match texture.read_back(&mut cx, ReadbackRequest::default()) {
Ok(ticket) => { assert!(tickets.insert(ticket)); }
Err(ReadbackError::Backpressure) => rejected += 1,
other => panic!("unexpected admission: {other:?}"),
}
assert!(cx.texture_readback_usage().reserved_bytes <= TEXTURE_READBACK_MAX_BYTES);
}
assert!(rejected > 0, "undrained results must exert backpressure");
assert_eq!(tickets.len() + rejected, 50);
// Releasing through another handle cannot invalidate completed/in-flight
// bytes, and current-content copies do not render or advance the serial.
texture.clone().release(&mut cx);
let results = cx.try_take_texture_readbacks();
assert_eq!(results.len(), tickets.len());
for result in results {
assert!(tickets.remove(&result.ticket));
assert_eq!(result.allocation_generation, generation);
assert_eq!(result.producer_serial, serial);
assert_eq!(result.data.unwrap().as_ref(), data.as_ref());
}
assert!(tickets.is_empty());
assert_eq!(cx.frame_submission_serial(), serial);
assert_eq!(cx.texture_readback_usage().reserved_bytes, 0);
assert!(cx.try_take_texture_readbacks().is_empty());
// Retain rejected work and retry it after draining. Render a distinct
// byte pattern for each producer, then overwrite/release the target before
// taking results: each ticket must still identify its own submitted image.
let mut expected = std::collections::HashMap::new();
let mut completed = 0;
for iteration in 0..50u8 {
let pixel = [iteration, 255 - iteration, iteration * 3, 255];
cx.passes[pass.draw_pass_id()].color_textures[0].clear_color = DrawPassClearColor::ClearWith(vec4(
pixel[2] as f32 / 255.0, pixel[1] as f32 / 255.0, pixel[0] as f32 / 255.0, 1.0,
));
cx.passes[pass.draw_pass_id()].paint_dirty = true;
let ticket = loop {
match texture.read_back(&mut cx, ReadbackRequest::default()) {
Ok(ticket) => break ticket,
Err(ReadbackError::Backpressure) => {
let ready = cx.try_take_texture_readbacks();
assert!(!ready.is_empty());
for result in ready {
let pixel = expected.remove(&result.ticket).unwrap();
assert!(result.data.unwrap().chunks_exact(4).all(|bytes| bytes == pixel));
completed += 1;
}
}
other => panic!("unexpected retry result: {other:?}"),
}
};
assert!(expected.insert(ticket, pixel).is_none());
cx.gpusim_render_all_passes(iteration as f64 + 1.0);
assert!(cx.texture_readback_usage().reserved_bytes <= TEXTURE_READBACK_MAX_BYTES);
}
texture.release(&mut cx);
for result in cx.try_take_texture_readbacks() {
let pixel = expected.remove(&result.ticket).unwrap();
assert!(result.data.unwrap().chunks_exact(4).all(|bytes| bytes == pixel));
completed += 1;
}
assert!(expected.is_empty());
assert_eq!(completed, 50);
assert_eq!(cx.texture_readback_usage().reserved_bytes, 0);
}
#[test]
fn readback_cancellation_and_reallocation_are_terminal() {
let mut cx = Cx::new(Box::new(|_, _| {}));
let texture = Texture::new_with_format(&mut cx, TextureFormat::RenderBGRAu8 {
size: TextureSize::Fixed { width: 512, height: 512 }, initial: true,
});
let pass = DrawPass::new(&mut cx);
let list = DrawList::new(&mut cx);
pass.add_color_texture(&mut cx, &texture, DrawPassClearColor::ClearWith(vec4(0.1, 0.2, 0.3, 0.5)));
pass.set_size(&mut cx, dvec2(512.0, 512.0));
cx.passes[pass.draw_pass_id()].main_draw_list_id = Some(list.id());
cx.passes[pass.draw_pass_id()].dpi_factor = Some(1.0);
cx.passes[pass.draw_pass_id()].paint_dirty = true;
let cancelled = texture.read_back(&mut cx, ReadbackRequest::default()).unwrap();
let invalidated = texture.read_back(&mut cx, ReadbackRequest::default()).unwrap();
assert!(cx.cancel_texture_readback(cancelled));
texture.release(&mut cx);
cx.gpusim_render_all_passes(0.0);
let results = cx.try_take_texture_readbacks();
assert_eq!(results.len(), 2);
assert_eq!(results.iter().find(|r| r.ticket == cancelled).unwrap().data, Err(ReadbackError::Cancelled));
assert_eq!(results.iter().find(|r| r.ticket == invalidated).unwrap().data, Err(ReadbackError::AllocationChanged));
assert!(!cx.cancel_texture_readback(cancelled));
let fresh = texture.read_back(&mut cx, ReadbackRequest::default()).unwrap();
let result = cx.try_take_texture_readbacks().pop().unwrap();
assert_eq!(result.ticket, fresh);
// Raw attachment channels must not be unpremultiplied by readback.
assert_eq!(&result.data.unwrap()[..4], &[77, 51, 26, 128]);
let orphan = texture.read_back(&mut cx, ReadbackRequest { next_render: true }).unwrap();
drop(pass);
let result = cx.try_take_texture_readbacks().pop().unwrap();
assert_eq!(result.ticket, orphan);
assert_eq!(result.data, Err(ReadbackError::Cancelled));
}