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>
214 lines
9.9 KiB
Rust
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));
|
|
}
|