Merge remote-tracking branch 'origin/master' into damage
This commit is contained in:
commit
1e7b9213d2
13 changed files with 265 additions and 23 deletions
|
|
@ -1,2 +1,5 @@
|
|||
[alias]
|
||||
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
runner = "wasm-bindgen-test-runner"
|
||||
|
|
|
|||
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
|
|
@ -3,7 +3,7 @@ name: CI
|
|||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
Check_Formatting:
|
||||
|
|
@ -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,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: >
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
* On MacOS, the contents scale is updated when set_buffer() is called, to adapt when the window is on a new screen.
|
||||
|
||||
# 0.2.1
|
||||
|
||||
* Bump `windows-sys` to 0.48
|
||||
|
||||
# 0.2.0
|
||||
|
||||
* Add support for Redox/Orbital.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,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.
|
||||
|
|
|
|||
21
src/lib.rs
21
src/lib.rs
|
|
@ -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> {
|
||||
|
|
@ -337,6 +346,18 @@ 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.
|
||||
/// - 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<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. Call [`Buffer::age`] to determine this.
|
||||
|
|
|
|||
|
|
@ -150,6 +150,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 {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ impl WaylandImpl {
|
|||
};
|
||||
|
||||
let age = self.buffers.as_mut().unwrap().1.age;
|
||||
|
||||
Ok(BufferImpl {
|
||||
stack: util::BorrowStack::new(self, |buffer| {
|
||||
Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() })
|
||||
|
|
@ -135,6 +134,11 @@ impl WaylandImpl {
|
|||
})
|
||||
}
|
||||
|
||||
/// Fetch the buffer from the window.
|
||||
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
|
||||
Err(SoftBufferError::Unimplemented)
|
||||
}
|
||||
|
||||
fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {
|
||||
let _ = self
|
||||
.display
|
||||
|
|
|
|||
29
src/web.rs
29
src/web.rs
|
|
@ -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 })
|
||||
}
|
||||
|
|
@ -164,11 +164,36 @@ impl WebImpl {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch the buffer from the window.
|
||||
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
|
||||
let (width, height) = self
|
||||
.size
|
||||
.expect("Must set size of surface before calling `fetch()`");
|
||||
|
||||
let image_data = self
|
||||
.ctx
|
||||
.get_image_data(0., 0., width.get().into(), height.get().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).
|
||||
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<Self, SoftBufferError>;
|
||||
}
|
||||
|
||||
|
|
|
|||
28
src/win32.rs
28
src/win32.rs
|
|
@ -228,6 +228,34 @@ impl Win32Impl {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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,
|
||||
);
|
||||
}
|
||||
|
||||
// Flush the operation so that it happens immediately.
|
||||
unsafe {
|
||||
Gdi::GdiFlush();
|
||||
}
|
||||
|
||||
Ok(temp_buffer.pixels().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferImpl<'a>(&'a mut Win32Impl);
|
||||
|
|
|
|||
86
src/x11.rs
86
src/x11.rs
|
|
@ -109,6 +109,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,
|
||||
|
||||
|
|
@ -183,11 +186,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
|
||||
|
|
@ -206,9 +224,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 {
|
||||
|
|
@ -227,6 +259,7 @@ impl X11Impl {
|
|||
window,
|
||||
gc,
|
||||
depth: geometry_reply.depth,
|
||||
visual_id,
|
||||
buffer,
|
||||
buffer_presented: false,
|
||||
size: None,
|
||||
|
|
@ -278,6 +311,43 @@ 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);
|
||||
|
||||
let (width, height) = self
|
||||
.size
|
||||
.expect("Must set size of surface before calling `fetch()`");
|
||||
|
||||
// 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,
|
||||
width.get(),
|
||||
height.get(),
|
||||
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);
|
||||
|
|
|
|||
56
tests/present_and_fetch.rs
Normal file
56
tests/present_and_fetch.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use softbuffer::{Context, Surface};
|
||||
use std::num::NonZeroU32;
|
||||
use winit::event_loop::EventLoopWindowTarget;
|
||||
|
||||
fn all_red(elwt: &EventLoopWindowTarget<()>) {
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title("all_red")
|
||||
.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
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
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();
|
||||
|
||||
// Set all pixels to red.
|
||||
let mut buffer = surface.buffer_mut().unwrap();
|
||||
buffer.fill(0x00FF0000);
|
||||
buffer.present().unwrap();
|
||||
|
||||
// Check that all pixels are red.
|
||||
let screen_contents = match surface.fetch() {
|
||||
Err(softbuffer::SoftBufferError::Unimplemented) => return,
|
||||
cont => cont.unwrap(),
|
||||
};
|
||||
for pixel in screen_contents.iter() {
|
||||
assert_eq!(*pixel, 0x00FF0000);
|
||||
}
|
||||
}
|
||||
|
||||
winit_test::main!(all_red);
|
||||
Loading…
Add table
Add a link
Reference in a new issue