From 2ea54c19caa19a135c2af71726d7e718435d1004 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Fri, 12 May 2023 19:24:34 -0700 Subject: [PATCH 1/9] Add API for fetch() --- src/cg.rs | 5 +++++ src/lib.rs | 14 ++++++++++++++ src/orbital.rs | 5 +++++ src/wayland/mod.rs | 5 +++++ src/web.rs | 5 +++++ src/win32.rs | 5 +++++ src/x11.rs | 5 +++++ 7 files changed, 44 insertions(+) diff --git a/src/cg.rs b/src/cg.rs index fce628d6..06e73bd3 100644 --- a/src/cg.rs +++ b/src/cg.rs @@ -119,6 +119,11 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + todo!() + } } impl Drop for CGImpl { diff --git a/src/lib.rs b/src/lib.rs index b7064d3c..0de7e712 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,15 @@ macro_rules! make_dispatch { )* } } + + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.fetch(), + )* + } + } } }; } @@ -374,6 +383,11 @@ impl<'a> Buffer<'a> { pub fn present(self) -> Result<(), SoftBufferError> { self.buffer_impl.present() } + + /// Copies the window contents into this buffer. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + self.buffer_impl.fetch() + } } impl<'a> ops::Deref for Buffer<'a> { diff --git a/src/orbital.rs b/src/orbital.rs index f999b6a9..7b53b3bf 100644 --- a/src/orbital.rs +++ b/src/orbital.rs @@ -143,6 +143,11 @@ impl OrbitalImpl { // Tell orbital to show the latest window data syscall::fsync(self.window_fd()).expect("failed to sync orbital window"); } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + todo!() + } } enum Pixels { diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 2550e249..86f0de5e 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -184,6 +184,11 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + todo!() + } } impl Dispatch for State { diff --git a/src/web.rs b/src/web.rs index 920b4437..7d6e2f96 100644 --- a/src/web.rs +++ b/src/web.rs @@ -175,6 +175,11 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + todo!() + } } #[inline(always)] diff --git a/src/win32.rs b/src/win32.rs index c040a218..32827d96 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -239,4 +239,9 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + todo!() + } } diff --git a/src/x11.rs b/src/x11.rs index 8d47476c..2b5e133a 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -361,6 +361,11 @@ impl<'a> BufferImpl<'a> { result.swbuf_err("Failed to draw image to window") } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + todo!() + } } impl Buffer { From 35c42766b130aa50ad8ab07476e1b1eb6d3f9404 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Fri, 12 May 2023 19:47:00 -0700 Subject: [PATCH 2/9] Add an impl for X11 Fix X11 bugs - Vec not being filled all the way - Tests need to be run with Xvfb - Add note about visibility --- .github/workflows/ci.yml | 13 +++- src/lib.rs | 32 ++++---- src/x11.rs | 153 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 174 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8fed1d5..6e672c97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,9 +105,20 @@ jobs: !contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'freebsd') && - !contains(matrix.platform.target, 'netbsd') + !contains(matrix.platform.target, 'netbsd') && + !contains(matrix.platform.target, 'linux') run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES + - name: Run tests using Xvfb + shell: bash + if: > + !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && + !contains(matrix.platform.target, 'redox') && + !contains(matrix.platform.target, 'freebsd') && + !contains(matrix.platform.target, 'netbsd') && + contains(matrix.platform.target, 'linux') + run: xvfb-run cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES + - name: Lint with clippy shell: bash if: > diff --git a/src/lib.rs b/src/lib.rs index 0de7e712..c4105362 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,15 @@ macro_rules! make_dispatch { )* } } + + pub fn fetch(&mut self) -> Result, SoftBufferError> { + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.fetch(), + )* + } + } } enum BufferDispatch<'a> { @@ -136,15 +145,6 @@ macro_rules! make_dispatch { )* } } - - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - match self { - $( - $(#[$attr])* - Self::$name(inner) => inner.fetch(), - )* - } - } } }; } @@ -315,6 +315,15 @@ impl Surface { self.surface_impl.resize(width, height) } + /// Copies the window contents into a buffer. + /// + /// ## Platform Dependent Behavior + /// + /// - On X11, the window must be visible. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + self.surface_impl.fetch() + } + /// Return a [`Buffer`] that the next frame should be rendered into. The size must /// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or /// may contain a previous frame. @@ -383,11 +392,6 @@ impl<'a> Buffer<'a> { pub fn present(self) -> Result<(), SoftBufferError> { self.buffer_impl.present() } - - /// Copies the window contents into this buffer. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - self.buffer_impl.fetch() - } } impl<'a> ops::Deref for Buffer<'a> { diff --git a/src/x11.rs b/src/x11.rs index 2b5e133a..ad92058c 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -104,6 +104,9 @@ pub struct X11Impl { /// The depth (bits per pixel) of the drawing context. depth: u8, + /// The visual ID of the drawing context. + visual_id: u32, + /// The buffer we draw to. buffer: Buffer, @@ -178,11 +181,26 @@ impl X11Impl { let window = window_handle.window; - // Run in parallel: start getting the window depth. - let geometry_token = display - .connection - .get_geometry(window) - .swbuf_err("Failed to send geometry request")?; + // Run in parallel: start getting the window depth and (if necessary) visual. + let display2 = display.clone(); + let tokens = { + let geometry_token = display2 + .connection + .get_geometry(window) + .swbuf_err("Failed to send geometry request")?; + let window_attrs_token = if window_handle.visual_id == 0 { + Some( + display2 + .connection + .get_window_attributes(window) + .swbuf_err("Failed to send window attributes request")?, + ) + } else { + None + }; + + (geometry_token, window_attrs_token) + }; // Create a new graphics context to draw to. let gc = display @@ -201,9 +219,23 @@ impl X11Impl { .swbuf_err("Failed to create GC")?; // Finish getting the depth of the window. - let geometry_reply = geometry_token - .reply() - .swbuf_err("Failed to get geometry reply")?; + let (geometry_reply, visual_id) = { + let (geometry_token, window_attrs_token) = tokens; + let geometry_reply = geometry_token + .reply() + .swbuf_err("Failed to get geometry reply")?; + let visual_id = match window_attrs_token { + None => window_handle.visual_id, + Some(window_attrs) => { + window_attrs + .reply() + .swbuf_err("Failed to get window attributes reply")? + .visual + } + }; + + (geometry_reply, visual_id) + }; // See if SHM is available. let buffer = if display.is_shm_available { @@ -222,6 +254,7 @@ impl X11Impl { window, gc, depth: geometry_reply.depth, + visual_id, buffer, width: 0, height: 0, @@ -277,6 +310,39 @@ impl X11Impl { // We can now safely call `buffer_mut` on the buffer. Ok(BufferImpl(self)) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + log::trace!("fetch: window={:X}", self.window); + + // TODO: Is it worth it to do SHM here? Probably not. + let reply = self + .display + .connection + .get_image( + xproto::ImageFormat::Z_PIXMAP, + self.window, + 0, + 0, + self.width, + self.height, + u32::MAX, + ) + .swbuf_err("Failed to send image fetching request")? + .reply() + .swbuf_err("Failed to fetch image from window")?; + + if reply.depth == self.depth && reply.visual == self.visual_id { + let mut out = vec![0u32; reply.data.len() / 4]; + bytemuck::cast_slice_mut::(&mut out).copy_from_slice(&reply.data); + Ok(out) + } else { + Err(SoftBufferError::PlatformError( + Some("Mismatch between reply and window data".into()), + None, + )) + } + } } pub struct BufferImpl<'a>(&'a mut X11Impl); @@ -364,7 +430,76 @@ impl<'a> BufferImpl<'a> { /// Fetch the buffer from the window. pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - todo!() + let imp = &mut self.0; + + log::trace!("fetch: window={:X}", imp.window); + + match imp.buffer { + Buffer::Wire(ref mut wire) => { + log::debug!("Falling back to non-SHM method for window fetching."); + + let reply = imp + .display + .connection + .get_image( + xproto::ImageFormat::Z_PIXMAP, + imp.window, + 0, + 0, + imp.width, + imp.height, + u32::MAX, + ) + .swbuf_err("Failed to send image fetching request")? + .reply() + .swbuf_err("Failed to fetch image from window")?; + + if reply.depth == imp.depth && reply.visual == imp.visual_id { + wire.copy_from_slice(bytemuck::cast_slice(&reply.data)); + Ok(()) + } else { + Err(SoftBufferError::PlatformError( + Some("Mismatch between reply and window data".into()), + None, + )) + } + } + + Buffer::Shm(ref mut shm) => { + if let Some((_, segment_id)) = shm.seg { + // SAFETY: We have already called finish_wait + let reply = imp + .display + .connection + .shm_get_image( + imp.window, + 0, + 0, + imp.width, + imp.height, + u32::MAX, + xproto::ImageFormat::Z_PIXMAP.into(), + segment_id, + 0, + ) + .swbuf_err("Failed to send image fetching request")? + .reply() + .swbuf_err("Failed to fetch image from window")?; + + // Make sure it all matches. + if reply.depth == imp.depth && reply.visual == imp.visual_id { + Ok(()) + } else { + Err(SoftBufferError::PlatformError( + Some("Mismatch between reply and window data".into()), + None, + )) + } + } else { + Ok(()) + } + } + } } } From d721a36ca1482f25eb15134a7cbea0ecc672be12 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Fri, 12 May 2023 20:13:11 -0700 Subject: [PATCH 3/9] Add a test harness for fetch/present Use winit-test as a test harness Redo testing harness for WASM Make sure the wayland runner doesn't use xvfb --- .github/workflows/ci.yml | 5 ++++- Cargo.toml | 6 ++++++ src/win32.rs | 20 ++++++++++++++++++- src/x11.rs | 2 +- tests/present_and_fetch.rs | 39 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 tests/present_and_fetch.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e672c97..397cf147 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,7 @@ jobs: !contains(matrix.platform.target, 'linux') run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES + # TODO: We should also be using Wayland for testing here. - name: Run tests using Xvfb shell: bash if: > @@ -116,7 +117,9 @@ jobs: !contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'freebsd') && !contains(matrix.platform.target, 'netbsd') && - contains(matrix.platform.target, 'linux') + contains(matrix.platform.target, 'linux') && + !contains(matrix.platform.options, '--no-default-features') && + !contains(matrix.platform.features, 'wayland') run: xvfb-run cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Lint with clippy diff --git a/Cargo.toml b/Cargo.toml index 7ad9739c..94af9bde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ cfg_aliases = "0.1.1" criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } instant = "0.1.12" winit = "0.28.1" +winit-test = "0.1.0" [dev-dependencies.image] version = "0.24.6" @@ -86,6 +87,11 @@ members = [ "run-wasm", ] +[[test]] +name = "present_and_fetch" +path = "tests/present_and_fetch.rs" +harness = false + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/win32.rs b/src/win32.rs index 32827d96..79d377eb 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -242,6 +242,24 @@ impl<'a> BufferImpl<'a> { /// Fetch the buffer from the window. pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - todo!() + let imp = &mut self.0; + let buffer = imp.buffer.as_ref().unwrap(); + + // Just go the other way. + unsafe { + Gdi::BitBlt( + buffer.dc, + 0, + 0, + buffer.width.get(), + buffer.height.get(), + imp.dc, + 0, + 0, + Gdi::SRCCOPY, + ); + } + + Ok(()) } } diff --git a/src/x11.rs b/src/x11.rs index ad92058c..6c70381a 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -684,7 +684,7 @@ impl ShmSegment { unsafe { // Create the shared memory segment. - let id = shmget(IPC_PRIVATE, size, 0o600); + let id = shmget(IPC_PRIVATE, size, 0o1777); if id == -1 { return Err(io::Error::last_os_error()); } diff --git a/tests/present_and_fetch.rs b/tests/present_and_fetch.rs new file mode 100644 index 00000000..86940fb6 --- /dev/null +++ b/tests/present_and_fetch.rs @@ -0,0 +1,39 @@ +use softbuffer::{Context, Surface}; +use std::num::NonZeroU32; + +use std::panic::{catch_unwind, AssertUnwindSafe}; + +use winit::event::Event; +use winit::event_loop::{EventLoop, EventLoopWindowTarget}; + +fn all_red(elwt: &EventLoopWindowTarget<()>) { + let window = winit::window::WindowBuilder::new() + .with_title("all_red") + .with_visible(false) + .build(elwt) + .unwrap(); + + let context = unsafe { Context::new(elwt) }.unwrap(); + let mut surface = unsafe { Surface::new(&context, &window) }.unwrap(); + let size = window.inner_size(); + + // Set the size of the surface to the size of the window. + surface.resize( + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ).unwrap(); + + // Set all pixels to red. + let mut buffer = surface.buffer_mut().unwrap(); + buffer.fill(0xFF0000FF); + buffer.present().unwrap(); + + // Check that all pixels are red. + let mut buffer = surface.buffer_mut().unwrap(); + buffer.fetch().unwrap(); + for pixel in buffer.iter() { + assert_eq!(*pixel, 0xFF0000FF); + } +} + +winit_test::main!(all_red); From a4498755ff1bac2d80451a5dfa62231754ce972e Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Sat, 13 May 2023 12:34:10 +0200 Subject: [PATCH 4/9] Add Web implementation Adjust Web code style --- src/web.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/web.rs b/src/web.rs index 7d6e2f96..ca62e7b7 100644 --- a/src/web.rs +++ b/src/web.rs @@ -24,9 +24,9 @@ pub struct WebDisplayImpl { impl WebDisplayImpl { pub(super) fn new() -> Result { let document = web_sys::window() - .swbuf_err("`window` is not present in this runtime")? + .swbuf_err("`Window` is not present in this runtime")? .document() - .swbuf_err("`document` is not present in this runtime")?; + .swbuf_err("`Document` is not present in this runtime")?; Ok(Self { document }) } @@ -44,6 +44,9 @@ pub struct WebImpl { /// The current width of the canvas. width: u32, + + /// The current height of the canvas. + height: u32, } impl WebImpl { @@ -76,6 +79,7 @@ impl WebImpl { ctx, buffer: Vec::new(), width: 0, + height: 0, }) } @@ -92,6 +96,7 @@ impl WebImpl { self.canvas.set_width(width); self.canvas.set_height(height); self.width = width; + self.height = height; Ok(()) } @@ -171,14 +176,28 @@ impl<'a> BufferImpl<'a> { let image_data = result.unwrap(); // This can only throw an error if `data` is detached, which is impossible. - self.imp.ctx.put_image_data(&image_data, 0.0, 0.0).unwrap(); + self.imp.ctx.put_image_data(&image_data, 0., 0.).unwrap(); Ok(()) } /// Fetch the buffer from the window. pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - todo!() + let image_data = self + .imp + .ctx + .get_image_data(0., 0., self.imp.width.into(), self.imp.height.into()) + .ok() + // TODO: Can also error if width or height are 0. + .swbuf_err("`Canvas` contains pixels from a different origin")?; + + self.imp + .buffer + .iter_mut() + .zip(image_data.data().0.chunks_exact(4)) + .for_each(|(old, new)| *old = u32::from_be_bytes([0, new[0], new[1], new[2]])); + + Ok(()) } } From d5ca69064f45483c2faf6024d364dc6bbcd1d9ae Mon Sep 17 00:00:00 2001 From: jtnunley Date: Sat, 13 May 2023 21:14:27 -0700 Subject: [PATCH 5/9] Move fetch() to the surface Adjust `fetch()` test for Web Test fixes --- .cargo/config.toml | 3 ++ .github/workflows/ci.yml | 15 +++----- Cargo.toml | 3 ++ src/cg.rs | 10 ++--- src/orbital.rs | 2 +- src/wayland/mod.rs | 10 ++--- src/web.rs | 36 +++++++++--------- src/win32.rs | 46 +++++++++++------------ src/x11.rs | 76 +------------------------------------- tests/present_and_fetch.rs | 43 +++++++++++++-------- 10 files changed, 91 insertions(+), 153 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index a9f12423..19bc2ee1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ [alias] run-wasm = ["run", "--release", "--package", "run-wasm", "--"] + +[target.wasm32-unknown-unknown] +runner = "wasm-bindgen-test-runner" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 397cf147..f8616969 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,12 +47,10 @@ jobs: - { target: x86_64-unknown-freebsd, os: ubuntu-latest, } - { target: x86_64-unknown-netbsd, os: ubuntu-latest, } - { target: x86_64-apple-darwin, os: macos-latest, } - # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web - # doesn't currently work on Linux. - - { target: wasm32-unknown-unknown, os: windows-latest, } + - { target: wasm32-unknown-unknown, os: ubuntu-latest, } include: - rust_version: nightly - platform: { target: wasm32-unknown-unknown, os: windows-latest, options: "-Zbuild-std=panic_abort,std", rustflags: "-Ctarget-feature=+atomics,+bulk-memory" } + platform: { target: wasm32-unknown-unknown, os: ubuntu-latest, options: "-Zbuild-std=panic_abort,std", rustflags: "-Ctarget-feature=+atomics,+bulk-memory" } env: RUST_BACKTRACE: 1 @@ -67,12 +65,10 @@ jobs: steps: - uses: actions/checkout@v3 - # Used to cache cargo-web - - name: Cache cargo folder - uses: actions/cache@v3 + - uses: taiki-e/install-action@v2 + if: matrix.platform.target == 'wasm32-unknown-unknown' with: - path: ~/.cargo - key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }} + tool: wasm-bindgen-cli - uses: hecrj/setup-rust-action@v1 with: @@ -102,7 +98,6 @@ jobs: shell: bash if: > !((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) && - !contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'freebsd') && !contains(matrix.platform.target, 'netbsd') && diff --git a/Cargo.toml b/Cargo.toml index 94af9bde..69a8f336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,9 @@ features = ["jpeg"] image = "0.24.6" rayon = "1.5.1" +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3" + [workspace] members = [ "run-wasm", diff --git a/src/cg.rs b/src/cg.rs index 06e73bd3..a3b18c6c 100644 --- a/src/cg.rs +++ b/src/cg.rs @@ -69,6 +69,11 @@ impl CGImpl { imp: self, }) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + todo!() + } } pub struct BufferImpl<'a> { @@ -119,11 +124,6 @@ impl<'a> BufferImpl<'a> { Ok(()) } - - /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - todo!() - } } impl Drop for CGImpl { diff --git a/src/orbital.rs b/src/orbital.rs index 7b53b3bf..fd5e34a4 100644 --- a/src/orbital.rs +++ b/src/orbital.rs @@ -145,7 +145,7 @@ impl OrbitalImpl { } /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { + pub fn fetch(&mut self) -> Result, SoftBufferError> { todo!() } } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 86f0de5e..d27d4f83 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -129,6 +129,11 @@ impl WaylandImpl { Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() }) })?)) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + todo!() + } } pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>); @@ -184,11 +189,6 @@ impl<'a> BufferImpl<'a> { Ok(()) } - - /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - todo!() - } } impl Dispatch for State { diff --git a/src/web.rs b/src/web.rs index ca62e7b7..d6c84174 100644 --- a/src/web.rs +++ b/src/web.rs @@ -104,6 +104,23 @@ impl WebImpl { pub(crate) fn buffer_mut(&mut self) -> Result { Ok(BufferImpl { imp: self }) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + let image_data = self + .ctx + .get_image_data(0., 0., self.width.into(), self.height.into()) + .ok() + // TODO: Can also error if width or height are 0. + .swbuf_err("`Canvas` contains pixels from a different origin")?; + + Ok(image_data + .data() + .0 + .chunks_exact(4) + .map(|chunk| u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]])) + .collect()) + } } /// Extension methods for the Wasm target on [`Surface`](crate::Surface). @@ -180,25 +197,6 @@ impl<'a> BufferImpl<'a> { Ok(()) } - - /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - let image_data = self - .imp - .ctx - .get_image_data(0., 0., self.imp.width.into(), self.imp.height.into()) - .ok() - // TODO: Can also error if width or height are 0. - .swbuf_err("`Canvas` contains pixels from a different origin")?; - - self.imp - .buffer - .iter_mut() - .zip(image_data.data().0.chunks_exact(4)) - .for_each(|(old, new)| *old = u32::from_be_bytes([0, new[0], new[1], new[2]])); - - Ok(()) - } } #[inline(always)] diff --git a/src/win32.rs b/src/win32.rs index 79d377eb..2cb90fcc 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -202,6 +202,29 @@ impl Win32Impl { Ok(BufferImpl(self)) } + + /// Fetch the buffer from the window. + pub fn fetch(&mut self) -> Result, SoftBufferError> { + let buffer = self.buffer.as_ref().unwrap(); + let temp_buffer = Buffer::new(self.dc, buffer.width, buffer.height); + + // Just go the other way. + unsafe { + Gdi::BitBlt( + temp_buffer.dc, + 0, + 0, + temp_buffer.width.get(), + temp_buffer.height.get(), + self.dc, + 0, + 0, + Gdi::SRCCOPY, + ); + } + + Ok(temp_buffer.pixels().to_vec()) + } } pub struct BufferImpl<'a>(&'a mut Win32Impl); @@ -239,27 +262,4 @@ impl<'a> BufferImpl<'a> { Ok(()) } - - /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - let imp = &mut self.0; - let buffer = imp.buffer.as_ref().unwrap(); - - // Just go the other way. - unsafe { - Gdi::BitBlt( - buffer.dc, - 0, - 0, - buffer.width.get(), - buffer.height.get(), - imp.dc, - 0, - 0, - Gdi::SRCCOPY, - ); - } - - Ok(()) - } } diff --git a/src/x11.rs b/src/x11.rs index 6c70381a..5c6544ec 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -427,80 +427,6 @@ impl<'a> BufferImpl<'a> { result.swbuf_err("Failed to draw image to window") } - - /// Fetch the buffer from the window. - pub fn fetch(&mut self) -> Result<(), SoftBufferError> { - let imp = &mut self.0; - - log::trace!("fetch: window={:X}", imp.window); - - match imp.buffer { - Buffer::Wire(ref mut wire) => { - log::debug!("Falling back to non-SHM method for window fetching."); - - let reply = imp - .display - .connection - .get_image( - xproto::ImageFormat::Z_PIXMAP, - imp.window, - 0, - 0, - imp.width, - imp.height, - u32::MAX, - ) - .swbuf_err("Failed to send image fetching request")? - .reply() - .swbuf_err("Failed to fetch image from window")?; - - if reply.depth == imp.depth && reply.visual == imp.visual_id { - wire.copy_from_slice(bytemuck::cast_slice(&reply.data)); - Ok(()) - } else { - Err(SoftBufferError::PlatformError( - Some("Mismatch between reply and window data".into()), - None, - )) - } - } - - Buffer::Shm(ref mut shm) => { - if let Some((_, segment_id)) = shm.seg { - // SAFETY: We have already called finish_wait - let reply = imp - .display - .connection - .shm_get_image( - imp.window, - 0, - 0, - imp.width, - imp.height, - u32::MAX, - xproto::ImageFormat::Z_PIXMAP.into(), - segment_id, - 0, - ) - .swbuf_err("Failed to send image fetching request")? - .reply() - .swbuf_err("Failed to fetch image from window")?; - - // Make sure it all matches. - if reply.depth == imp.depth && reply.visual == imp.visual_id { - Ok(()) - } else { - Err(SoftBufferError::PlatformError( - Some("Mismatch between reply and window data".into()), - None, - )) - } - } else { - Ok(()) - } - } - } - } } impl Buffer { @@ -684,7 +610,7 @@ impl ShmSegment { unsafe { // Create the shared memory segment. - let id = shmget(IPC_PRIVATE, size, 0o1777); + let id = shmget(IPC_PRIVATE, size, 0o600); if id == -1 { return Err(io::Error::last_os_error()); } diff --git a/tests/present_and_fetch.rs b/tests/present_and_fetch.rs index 86940fb6..1d149a2a 100644 --- a/tests/present_and_fetch.rs +++ b/tests/present_and_fetch.rs @@ -1,38 +1,51 @@ use softbuffer::{Context, Surface}; use std::num::NonZeroU32; - -use std::panic::{catch_unwind, AssertUnwindSafe}; - -use winit::event::Event; -use winit::event_loop::{EventLoop, EventLoopWindowTarget}; +use winit::event_loop::EventLoopWindowTarget; fn all_red(elwt: &EventLoopWindowTarget<()>) { let window = winit::window::WindowBuilder::new() .with_title("all_red") - .with_visible(false) .build(elwt) .unwrap(); + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + + web_sys::window() + .unwrap() + .document() + .unwrap() + .body() + .unwrap() + .append_child(&window.canvas()) + .unwrap(); + } + + // winit does not wait for the window to be mapped... sigh + std::thread::sleep(std::time::Duration::from_millis(1)); + let context = unsafe { Context::new(elwt) }.unwrap(); let mut surface = unsafe { Surface::new(&context, &window) }.unwrap(); let size = window.inner_size(); // Set the size of the surface to the size of the window. - surface.resize( - NonZeroU32::new(size.width).unwrap(), - NonZeroU32::new(size.height).unwrap(), - ).unwrap(); + surface + .resize( + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ) + .unwrap(); // Set all pixels to red. let mut buffer = surface.buffer_mut().unwrap(); - buffer.fill(0xFF0000FF); + buffer.fill(0xFF000000); buffer.present().unwrap(); // Check that all pixels are red. - let mut buffer = surface.buffer_mut().unwrap(); - buffer.fetch().unwrap(); - for pixel in buffer.iter() { - assert_eq!(*pixel, 0xFF0000FF); + let screen_contents = surface.fetch().unwrap(); + for pixel in screen_contents.iter() { + assert_eq!(*pixel, 0xFF000000); } } From c92a99c712237d5c0ca714e76b92710e646d7463 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Fri, 19 May 2023 11:11:13 -0700 Subject: [PATCH 6/9] Return Unimplemented error on other platforms Add line to docs about unsupported platforms Test fixes --- src/cg.rs | 2 +- src/error.rs | 3 +++ src/lib.rs | 1 + src/orbital.rs | 2 +- src/wayland/mod.rs | 2 +- tests/present_and_fetch.rs | 6 +++++- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cg.rs b/src/cg.rs index a3b18c6c..c25eaedc 100644 --- a/src/cg.rs +++ b/src/cg.rs @@ -72,7 +72,7 @@ impl CGImpl { /// Fetch the buffer from the window. pub fn fetch(&mut self) -> Result, SoftBufferError> { - todo!() + Err(SoftBufferError::Unimplemented) } } diff --git a/src/error.rs b/src/error.rs index 1fd8ba21..26e1aa7c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,9 @@ pub enum SoftBufferError { #[error("Platform error")] PlatformError(Option, Option>), + + #[error("This function is unimplemented on this platform")] + Unimplemented, } /// Convenient wrapper to cast errors into SoftBufferError. diff --git a/src/lib.rs b/src/lib.rs index c4105362..98084704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -320,6 +320,7 @@ impl Surface { /// ## Platform Dependent Behavior /// /// - On X11, the window must be visible. + /// - On macOS, Redox and Wayland, this function is unimplemented. pub fn fetch(&mut self) -> Result, SoftBufferError> { self.surface_impl.fetch() } diff --git a/src/orbital.rs b/src/orbital.rs index fd5e34a4..d69becad 100644 --- a/src/orbital.rs +++ b/src/orbital.rs @@ -146,7 +146,7 @@ impl OrbitalImpl { /// Fetch the buffer from the window. pub fn fetch(&mut self) -> Result, SoftBufferError> { - todo!() + Err(SoftBufferError::Unimplemented) } } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index d27d4f83..a2606a77 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -132,7 +132,7 @@ impl WaylandImpl { /// Fetch the buffer from the window. pub fn fetch(&mut self) -> Result, SoftBufferError> { - todo!() + Err(SoftBufferError::Unimplemented) } } diff --git a/tests/present_and_fetch.rs b/tests/present_and_fetch.rs index 1d149a2a..56a86498 100644 --- a/tests/present_and_fetch.rs +++ b/tests/present_and_fetch.rs @@ -23,6 +23,7 @@ fn all_red(elwt: &EventLoopWindowTarget<()>) { } // winit does not wait for the window to be mapped... sigh + #[cfg(not(target_arch = "wasm32"))] std::thread::sleep(std::time::Duration::from_millis(1)); let context = unsafe { Context::new(elwt) }.unwrap(); @@ -43,7 +44,10 @@ fn all_red(elwt: &EventLoopWindowTarget<()>) { buffer.present().unwrap(); // Check that all pixels are red. - let screen_contents = surface.fetch().unwrap(); + let screen_contents = match surface.fetch() { + Err(softbuffer::SoftBufferError::Unimplemented) => return, + cont => cont.unwrap(), + }; for pixel in screen_contents.iter() { assert_eq!(*pixel, 0xFF000000); } From e4ea2d5ffc2bd32a45ead3a8583dd65ff139e703 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Sat, 20 May 2023 08:01:52 -0700 Subject: [PATCH 7/9] Try blue --- tests/present_and_fetch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/present_and_fetch.rs b/tests/present_and_fetch.rs index 56a86498..2bf7dede 100644 --- a/tests/present_and_fetch.rs +++ b/tests/present_and_fetch.rs @@ -40,7 +40,7 @@ fn all_red(elwt: &EventLoopWindowTarget<()>) { // Set all pixels to red. let mut buffer = surface.buffer_mut().unwrap(); - buffer.fill(0xFF000000); + buffer.fill(0x00FF0000); buffer.present().unwrap(); // Check that all pixels are red. @@ -49,7 +49,7 @@ fn all_red(elwt: &EventLoopWindowTarget<()>) { cont => cont.unwrap(), }; for pixel in screen_contents.iter() { - assert_eq!(*pixel, 0xFF000000); + assert_eq!(*pixel, 0x00FF0000); } } From 0eb4eaf0829a450c783c829bea503c01d449e442 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 29 May 2023 22:44:22 +0200 Subject: [PATCH 8/9] Document possible errors for Web (#113) --- src/lib.rs | 2 ++ src/web.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 98084704..e1365c52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,6 +321,8 @@ impl Surface { /// /// - On X11, the window must be visible. /// - On macOS, Redox and Wayland, this function is unimplemented. + /// - On Web, this will fail if the content was supplied by + /// a different origin depending on the sites CORS rules. pub fn fetch(&mut self) -> Result, SoftBufferError> { self.surface_impl.fetch() } diff --git a/src/web.rs b/src/web.rs index d6c84174..95bcfa72 100644 --- a/src/web.rs +++ b/src/web.rs @@ -126,6 +126,10 @@ impl WebImpl { /// Extension methods for the Wasm target on [`Surface`](crate::Surface). pub trait SurfaceExtWeb: Sized { /// Creates a new instance of this struct, using the provided [`HtmlCanvasElement`]. + /// + /// # Errors + /// - If the canvas was already controlled by an `OffscreenCanvas`. + /// - If a another context then "2d" was already created for this canvas. fn from_canvas(canvas: HtmlCanvasElement) -> Result; } From 65b5d489dbfb377d5baacfe38f2f54b0c1ec8c50 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Thu, 1 Jun 2023 19:46:53 -0700 Subject: [PATCH 9/9] Try adding GdiFlush to Win32 --- src/win32.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/win32.rs b/src/win32.rs index 2cb90fcc..5858070d 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -223,6 +223,11 @@ impl Win32Impl { ); } + // Flush the operation so that it happens immediately. + unsafe { + Gdi::GdiFlush(); + } + Ok(temp_buffer.pixels().to_vec()) } }