Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a function for retrieving the window contents #104

Merged
merged 9 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[alias]
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]

[target.wasm32-unknown-unknown]
runner = "wasm-bindgen-test-runner"
31 changes: 20 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -102,12 +98,25 @@ 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')
!contains(matrix.platform.target, 'netbsd') &&
!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: >
!((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') &&
!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
shell: bash
if: >
Expand Down
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -81,11 +82,19 @@ 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",
]

[[test]]
name = "present_and_fetch"
path = "tests/present_and_fetch.rs"
harness = false

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
Expand Down
5 changes: 5 additions & 0 deletions src/cg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ impl CGImpl {
imp: self,
})
}

/// Fetch the buffer from the window.
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
Err(SoftBufferError::Unimplemented)
}
}

pub struct BufferImpl<'a> {
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub enum SoftBufferError {

#[error("Platform error")]
PlatformError(Option<String>, Option<Box<dyn Error>>),

#[error("This function is unimplemented on this platform")]
Unimplemented,
}

/// Convenient wrapper to cast errors into SoftBufferError.
Expand Down
19 changes: 19 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ macro_rules! make_dispatch {
)*
}
}

pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.fetch(),
)*
}
}
}

enum BufferDispatch<'a> {
Expand Down Expand Up @@ -306,6 +315,16 @@ impl Surface {
self.surface_impl.resize(width, height)
}

/// Copies the window contents into a buffer.
///
/// ## Platform Dependent Behavior
ids1024 marked this conversation as resolved.
Show resolved Hide resolved
///
/// - On X11, the window must be visible.
/// - On macOS, Redox and Wayland, this function is unimplemented.
pub fn fetch(&mut self) -> Result<Vec<u32>, 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.
Expand Down
5 changes: 5 additions & 0 deletions src/orbital.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u32>, SoftBufferError> {
Err(SoftBufferError::Unimplemented)
}
}

enum Pixels {
Expand Down
5 changes: 5 additions & 0 deletions src/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u32>, SoftBufferError> {
Err(SoftBufferError::Unimplemented)
}
}

pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>);
Expand Down
28 changes: 25 additions & 3 deletions src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ pub struct WebDisplayImpl {
impl WebDisplayImpl {
pub(super) fn new() -> Result<Self, SoftBufferError> {
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 })
}
Expand All @@ -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 {
Expand Down Expand Up @@ -76,6 +79,7 @@ impl WebImpl {
ctx,
buffer: Vec::new(),
width: 0,
height: 0,
})
}

Expand All @@ -92,13 +96,31 @@ impl WebImpl {
self.canvas.set_width(width);
self.canvas.set_height(height);
self.width = width;
self.height = height;
Ok(())
}

/// Get a pointer to the mutable buffer.
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl { imp: self })
}

/// Fetch the buffer from the window.
pub fn fetch(&mut self) -> Result<Vec<u32>, 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).
Expand Down Expand Up @@ -171,7 +193,7 @@ 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(())
}
Expand Down
23 changes: 23 additions & 0 deletions src/win32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,29 @@ impl Win32Impl {

Ok(BufferImpl(self))
}

/// Fetch the buffer from the window.
pub fn fetch(&mut self) -> Result<Vec<u32>, 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);
Expand Down
82 changes: 74 additions & 8 deletions src/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -222,6 +254,7 @@ impl X11Impl {
window,
gc,
depth: geometry_reply.depth,
visual_id,
buffer,
width: 0,
height: 0,
Expand Down Expand Up @@ -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<Vec<u32>, 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::<u32, u8>(&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);
Expand Down
Loading