Owned pixel buffer for no-copy presentation

This is based on the API that will be used for no-copy presentation. But
wraps it in `set_buffer`.

This also fixes the Wayland buffer code to set `self.width` and
`self.height` on resize, and set the length of the shared memory file
when the buffer is created.

Co-authored-by: jtnunley <jtnunley01@gmail.com>
This commit is contained in:
Ian Douglas Scott 2023-04-06 00:30:59 -07:00 committed by GitHub
parent e5d546ff9e
commit a09e4cf679
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1176 additions and 438 deletions

View file

@ -17,6 +17,17 @@ jobs:
- name: Check Formatting
run: cargo +stable fmt --all -- --check
miri-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: nightly
components: miri
- name: Run tests with miri
run: cargo +nightly miri test
tests:
name: Tests
strategy:

View file

@ -14,7 +14,7 @@ rust-version = "1.64.0"
[features]
default = ["x11", "wayland", "wayland-dlopen"]
wayland = ["wayland-backend", "wayland-client", "nix", "fastrand"]
wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"]
wayland-dlopen = ["wayland-sys/dlopen"]
x11 = ["bytemuck", "nix", "x11rb", "x11-dl"]
@ -25,6 +25,7 @@ thiserror = "1.0.30"
[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies]
bytemuck = { version = "1.12.3", optional = true }
memmap2 = { version = "0.5.8", optional = true }
nix = { version = "0.26.1", optional = true }
wayland-backend = { version = "0.1.0", features = ["client_system"], optional = true }
wayland-client = { version = "0.30.0", optional = true }
@ -40,6 +41,7 @@ version = "0.45.0"
features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"]
[target.'cfg(target_os = "macos")'.dependencies]
bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] }
cocoa = "0.24.0"
core-graphics = "0.22.3"
foreign-types = "0.3.0"

View file

@ -58,6 +58,7 @@ To run an example with the web backend: `cargo run-wasm --example winit`
Example
==
```rust,no_run
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
@ -77,21 +78,25 @@ fn main() {
let size = window.inner_size();
(size.width, size.height)
};
let buffer = (0..((width * height) as usize))
.map(|index| {
let y = index / (width as usize);
let x = index % (width as usize);
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
let color = blue | (green << 8) | (red << 16);
let mut buffer = surface.buffer_mut().unwrap();
for index in 0..(width * height) {
let y = index / width;
let x = index % width;
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
color as u32
})
.collect::<Vec<_>>();
buffer[index as usize] = blue | (green << 8) | (red << 16);
}
surface.set_buffer(&buffer, width as u16, height as u16);
buffer.present().unwrap();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,

View file

@ -2,6 +2,7 @@ use instant::Instant;
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*;
use std::f64::consts::PI;
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
@ -47,8 +48,17 @@ fn main() {
frames = pre_render_frames(width as usize, height as usize);
};
let buffer = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)];
surface.set_buffer(buffer.as_slice(), width as u16, height as u16);
let frame = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)];
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
buffer.copy_from_slice(frame);
buffer.present().unwrap();
}
Event::MainEventsCleared => {
window.request_redraw();

View file

@ -1,4 +1,5 @@
use image::GenericImageView;
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
@ -6,16 +7,6 @@ use winit::window::WindowBuilder;
fn main() {
//see fruit.jpg.license for the license of fruit.jpg
let fruit = image::load_from_memory(include_bytes!("fruit.jpg")).unwrap();
let buffer = fruit
.pixels()
.map(|(_x, _y, pixel)| {
let red = pixel.0[0] as u32;
let green = pixel.0[1] as u32;
let blue = pixel.0[2] as u32;
blue | (green << 8) | (red << 16)
})
.collect::<Vec<_>>();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@ -45,7 +36,25 @@ fn main() {
match event {
Event::RedrawRequested(window_id) if window_id == window.id() => {
surface.set_buffer(&buffer, fruit.width() as u16, fruit.height() as u16);
surface
.resize(
NonZeroU32::new(fruit.width()).unwrap(),
NonZeroU32::new(fruit.height()).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
let width = fruit.width() as usize;
for (x, y, pixel) in fruit.pixels() {
let red = pixel.0[0] as u32;
let green = pixel.0[1] as u32;
let blue = pixel.0[2] as u32;
let color = blue | (green << 8) | (red << 16);
buffer[y as usize * width + x as usize] = color;
}
buffer.present().unwrap();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,

View file

@ -3,6 +3,7 @@
#[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))]
mod example {
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
use std::num::NonZeroU32;
use x11rb::{
connection::Connection,
protocol::{
@ -12,6 +13,8 @@ mod example {
xcb_ffi::XCBConnection,
};
const RED: u32 = 255 << 16;
pub(crate) fn run() {
// Create a new XCB connection
let (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server");
@ -96,13 +99,15 @@ mod example {
match event {
Event::Expose(_) => {
// Draw a width x height red rectangle.
let red = 255 << 16;
let source = std::iter::repeat(red)
.take((width as usize * height as usize) as _)
.collect::<Vec<_>>();
// Draw the buffer.
surface.set_buffer(&source, width, height);
surface
.resize(
NonZeroU32::new(width.into()).unwrap(),
NonZeroU32::new(height.into()).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
buffer.fill(RED);
buffer.present().unwrap();
}
Event::ConfigureNotify(configure_notify) => {
width = configure_notify.width;

View file

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
@ -43,7 +44,6 @@ fn main() {
let context = unsafe { softbuffer::Context::new(&window) }.unwrap();
let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap();
let mut buffer = Vec::new();
let mut flag = false;
event_loop.run(move |event, _, control_flow| {
@ -54,19 +54,21 @@ fn main() {
// Grab the window's client area dimensions
let (width, height) = {
let size = window.inner_size();
(size.width as usize, size.height as usize)
(size.width, size.height)
};
// Resize the off-screen buffer if the window size has changed
if buffer.len() != width * height {
buffer.resize(width * height, 0);
}
// Resize surface if needed
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
// Draw something in the offscreen buffer
redraw(&mut buffer, width, height, flag);
// Blit the offscreen buffer to the window's client area
surface.set_buffer(&buffer, width as u16, height as u16);
// Draw something in the window
let mut buffer = surface.buffer_mut().unwrap();
redraw(&mut buffer, width as usize, height as usize, flag);
buffer.present().unwrap();
}
Event::WindowEvent {

View file

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
@ -32,21 +33,26 @@ fn main() {
let size = window.inner_size();
(size.width, size.height)
};
let buffer = (0..((width * height) as usize))
.map(|index| {
let y = index / (width as usize);
let x = index % (width as usize);
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
let color = blue | (green << 8) | (red << 16);
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
color as u32
})
.collect::<Vec<_>>();
let mut buffer = surface.buffer_mut().unwrap();
for index in 0..(width * height) {
let y = index / width;
let x = index % width;
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
surface.set_buffer(&buffer, width as u16, height as u16);
buffer[index as usize] = blue | (green << 8) | (red << 16);
}
buffer.present().unwrap();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,

View file

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
@ -31,21 +32,25 @@ fn main() {
match event {
Event::RedrawRequested(window_id) if window_id == window.id() => {
let buffer = (0..(BUFFER_WIDTH * BUFFER_HEIGHT))
.map(|index| {
let y = index / BUFFER_WIDTH;
let x = index % BUFFER_WIDTH;
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
surface
.resize(
NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(),
NonZeroU32::new(BUFFER_HEIGHT as u32).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
for y in 0..BUFFER_HEIGHT {
for x in 0..BUFFER_WIDTH {
let red = x as u32 % 255;
let green = y as u32 % 255;
let blue = (x as u32 * y as u32) % 255;
let color = blue | (green << 8) | (red << 16);
color as u32
})
.collect::<Vec<_>>();
surface.set_buffer(&buffer, BUFFER_WIDTH as u16, BUFFER_HEIGHT as u16);
buffer[y * BUFFER_WIDTH + x] = color;
}
}
buffer.present().unwrap();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,

View file

@ -12,11 +12,23 @@ use cocoa::base::{id, nil};
use cocoa::quartzcore::{transaction, CALayer, ContentsGravity};
use foreign_types::ForeignType;
use std::num::NonZeroU32;
use std::sync::Arc;
struct Buffer(Vec<u32>);
impl AsRef<[u8]> for Buffer {
fn as_ref(&self) -> &[u8] {
bytemuck::cast_slice(&self.0)
}
}
pub struct CGImpl {
layer: CALayer,
window: id,
color_space: CGColorSpace,
width: u32,
height: u32,
}
impl CGImpl {
@ -35,22 +47,55 @@ impl CGImpl {
view.addSubview_(subview); // retains subview (+1) = 2
let _: () = msg_send![subview, release]; // releases subview (-1) = 1
}
Ok(Self { layer, window })
let color_space = CGColorSpace::create_device_rgb();
Ok(Self {
layer,
window,
color_space,
width: 0,
height: 0,
})
}
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
let color_space = CGColorSpace::create_device_rgb();
let data =
unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4) }
.to_vec();
let data_provider = CGDataProvider::from_buffer(Arc::new(data));
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
self.width = width.get();
self.height = height.get();
Ok(())
}
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl {
buffer: vec![0; self.width as usize * self.height as usize],
imp: self,
})
}
}
pub struct BufferImpl<'a> {
imp: &'a mut CGImpl,
buffer: Vec<u32>,
}
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
&self.buffer
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
&mut self.buffer
}
pub fn present(self) -> Result<(), SoftBufferError> {
let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer)));
let image = CGImage::new(
width as usize,
height as usize,
self.imp.width as usize,
self.imp.height as usize,
8,
32,
(width * 4) as usize,
&color_space,
(self.imp.width * 4) as usize,
&self.imp.color_space,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
&data_provider,
false,
@ -64,12 +109,15 @@ impl CGImpl {
transaction::set_disable_actions(true);
unsafe {
self.layer
.set_contents_scale(self.window.backingScaleFactor());
self.layer.set_contents(image.as_ptr() as id);
self.imp
.layer
.set_contents_scale(self.imp.window.backingScaleFactor());
self.imp.layer.set_contents(image.as_ptr() as id);
};
transaction::commit();
Ok(())
}
}

View file

@ -1,8 +1,10 @@
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use std::error::Error;
use std::num::NonZeroU32;
use thiserror::Error;
#[derive(Error, Debug)]
#[allow(missing_docs)] // TODO
#[non_exhaustive]
pub enum SoftBufferError {
#[error(
@ -27,6 +29,12 @@ pub enum SoftBufferError {
#[error("The provided display handle is null.")]
IncompleteDisplayHandle,
#[error("Surface size {width}x{height} out of range for backend.")]
SizeOutOfRange {
width: NonZeroU32,
height: NonZeroU32,
},
#[error("Platform error")]
PlatformError(Option<String>, Option<Box<dyn Error>>),
}

View file

@ -1,5 +1,6 @@
#![doc = include_str!("../README.md")]
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]
#[cfg(target_os = "macos")]
#[macro_use]
@ -20,9 +21,13 @@ mod win32;
mod x11;
mod error;
mod util;
use std::marker::PhantomData;
use std::num::NonZeroU32;
use std::ops;
#[cfg(any(wayland_platform, x11_platform))]
use std::sync::Arc;
use std::rc::Rc;
pub use error::SoftBufferError;
@ -35,6 +40,7 @@ use raw_window_handle::{
pub struct Context {
/// The inner static dispatch object.
context_impl: ContextDispatch,
_marker: PhantomData<*mut ()>,
}
/// A macro for creating the enum used to statically dispatch to the platform-specific implementation.
@ -42,7 +48,7 @@ macro_rules! make_dispatch {
(
$(
$(#[$attr:meta])*
$name: ident ($context_inner: ty, $surface_inner : ty),
$name: ident ($context_inner: ty, $surface_inner: ty, $buffer_inner: ty),
)*
) => {
enum ContextDispatch {
@ -71,11 +77,58 @@ macro_rules! make_dispatch {
}
impl SurfaceDispatch {
unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
match self {
$(
$(#[$attr])*
Self::$name(inner) => unsafe { inner.set_buffer(buffer, width, height) },
Self::$name(inner) => inner.resize(width, height),
)*
}
}
pub fn buffer_mut(&mut self) -> Result<BufferDispatch, SoftBufferError> {
match self {
$(
$(#[$attr])*
Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut()?)),
)*
}
}
}
enum BufferDispatch<'a> {
$(
$(#[$attr])*
$name($buffer_inner),
)*
}
impl<'a> BufferDispatch<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.pixels(),
)*
}
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.pixels_mut(),
)*
}
}
pub fn present(self) -> Result<(), SoftBufferError> {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.present(),
)*
}
}
@ -83,19 +136,21 @@ macro_rules! make_dispatch {
};
}
// XXX empty enum with generic bound is invalid?
make_dispatch! {
#[cfg(x11_platform)]
X11(Arc<x11::X11DisplayImpl>, x11::X11Impl),
X11(Rc<x11::X11DisplayImpl>, x11::X11Impl, x11::BufferImpl<'a>),
#[cfg(wayland_platform)]
Wayland(std::sync::Arc<wayland::WaylandDisplayImpl>, wayland::WaylandImpl),
Wayland(Rc<wayland::WaylandDisplayImpl>, wayland::WaylandImpl, wayland::BufferImpl<'a>),
#[cfg(target_os = "windows")]
Win32((), win32::Win32Impl),
Win32((), win32::Win32Impl, win32::BufferImpl<'a>),
#[cfg(target_os = "macos")]
CG((), cg::CGImpl),
CG((), cg::CGImpl, cg::BufferImpl<'a>),
#[cfg(target_arch = "wasm32")]
Web(web::WebDisplayImpl, web::WebImpl),
Web(web::WebDisplayImpl, web::WebImpl, web::BufferImpl<'a>),
#[cfg(target_os = "redox")]
Orbital((), orbital::OrbitalImpl),
Orbital((), orbital::OrbitalImpl, orbital::BufferImpl<'a>),
}
impl Context {
@ -117,17 +172,15 @@ impl Context {
let imple: ContextDispatch = match raw_display_handle {
#[cfg(x11_platform)]
RawDisplayHandle::Xlib(xlib_handle) => unsafe {
ContextDispatch::X11(Arc::new(x11::X11DisplayImpl::from_xlib(xlib_handle)?))
ContextDispatch::X11(Rc::new(x11::X11DisplayImpl::from_xlib(xlib_handle)?))
},
#[cfg(x11_platform)]
RawDisplayHandle::Xcb(xcb_handle) => unsafe {
ContextDispatch::X11(Arc::new(x11::X11DisplayImpl::from_xcb(xcb_handle)?))
ContextDispatch::X11(Rc::new(x11::X11DisplayImpl::from_xcb(xcb_handle)?))
},
#[cfg(wayland_platform)]
RawDisplayHandle::Wayland(wayland_handle) => unsafe {
ContextDispatch::Wayland(Arc::new(wayland::WaylandDisplayImpl::new(
wayland_handle,
)?))
ContextDispatch::Wayland(Rc::new(wayland::WaylandDisplayImpl::new(wayland_handle)?))
},
#[cfg(target_os = "windows")]
RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()),
@ -149,17 +202,20 @@ impl Context {
Ok(Self {
context_impl: imple,
_marker: PhantomData,
})
}
}
/// A surface for drawing to a window with software buffers.
pub struct Surface {
/// This is boxed so that `Surface` is the same size on every platform.
surface_impl: Box<SurfaceDispatch>,
_marker: PhantomData<*mut ()>,
}
impl Surface {
/// Creates a new instance of this struct, using the provided window and display.
/// Creates a new surface for the context for the provided window.
///
/// # Safety
///
@ -172,7 +228,7 @@ impl Surface {
unsafe { Self::from_raw(context, window.raw_window_handle()) }
}
/// Creates a new instance of this struct, using the provided raw window and display handles
/// Creates a new surface for the context for the provided raw window handle.
///
/// # Safety
///
@ -232,38 +288,77 @@ impl Surface {
Ok(Self {
surface_impl: Box::new(imple),
_marker: PhantomData,
})
}
/// Shows the given buffer with the given width and height on the window corresponding to this
/// graphics context. Panics if buffer.len() ≠ width*height. If the size of the buffer does
/// not match the size of the window, the buffer is drawn in the upper-left corner of the window.
/// It is recommended in most production use cases to have the buffer fill the entire window.
/// Use your windowing library to find the size of the window.
/// Set the size of the buffer that will be returned by [`Surface::buffer_mut`].
///
/// The format of the buffer is as follows. There is one u32 in the buffer for each pixel in
/// the area to draw. The first entry is the upper-left most pixel. The second is one to the right
/// etc. (Row-major top to bottom left to right one u32 per pixel). Within each u32 the highest
/// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the
/// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for
/// one way to build this format using bitwise operations.
///
/// --------
///
/// Pixel format (u32):
///
/// 00000000RRRRRRRRGGGGGGGGBBBBBBBB
///
/// 0: Bit is 0
/// R: Red channel
/// G: Green channel
/// B: Blue channel
/// If the size of the buffer does not match the size of the window, the buffer is drawn
/// in the upper-left corner of the window. It is recommended in most production use cases
/// to have the buffer fill the entire window. Use your windowing library to find the size
/// of the window.
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
self.surface_impl.resize(width, height)
}
/// 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.
pub fn buffer_mut(&mut self) -> Result<Buffer, SoftBufferError> {
Ok(Buffer {
buffer_impl: self.surface_impl.buffer_mut()?,
_marker: PhantomData,
})
}
}
/// A buffer that can be written to by the CPU and presented to the window.
///
/// This derefs to a `[u32]`, which depending on the backend may be a mapping into shared memory
/// accessible to the display server, so presentation doesn't require any (client-side) copying.
///
/// This trusts the display server not to mutate the buffer, which could otherwise be unsound.
///
/// # Data representation
///
/// The format of the buffer is as follows. There is one `u32` in the buffer for each pixel in
/// the area to draw. The first entry is the upper-left most pixel. The second is one to the right
/// etc. (Row-major top to bottom left to right one `u32` per pixel). Within each `u32` the highest
/// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the
/// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for
/// one way to build this format using bitwise operations.
///
/// --------
///
/// Pixel format (`u32`):
///
/// 00000000RRRRRRRRGGGGGGGGBBBBBBBB
///
/// 0: Bit is 0
/// R: Red channel
/// G: Green channel
/// B: Blue channel
///
/// # Platform dependent behavior
/// No-copy presentation is currently supported on:
/// - Wayland
/// - X, when XShm is available
/// - Win32
/// - Orbital, when buffer size matches window size
/// Currently [`Buffer::present`] must block copying image data on:
/// - Web
/// - macOS
pub struct Buffer<'a> {
buffer_impl: BufferDispatch<'a>,
_marker: PhantomData<*mut ()>,
}
impl<'a> Buffer<'a> {
/// Presents buffer to the window.
///
/// # Platform dependent behavior
///
/// This section of the documentation details how some platforms may behave when [`set_buffer`](Surface::set_buffer)
/// is called.
///
/// ## Wayland
///
/// On Wayland, calling this function may send requests to the underlying `wl_surface`. The
@ -272,15 +367,24 @@ impl Surface {
///
/// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the
/// Wayland compositor before calling this function.
#[inline]
pub fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
if (width as usize) * (height as usize) != buffer.len() {
panic!("The size of the passed buffer is not the correct size. Its length must be exactly width*height.");
}
pub fn present(self) -> Result<(), SoftBufferError> {
self.buffer_impl.present()
}
}
unsafe {
self.surface_impl.set_buffer(buffer, width, height);
}
impl<'a> ops::Deref for Buffer<'a> {
type Target = [u32];
#[inline]
fn deref(&self) -> &[u32] {
self.buffer_impl.pixels()
}
}
impl<'a> ops::DerefMut for Buffer<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut [u32] {
self.buffer_impl.pixels_mut()
}
}

View file

@ -1,11 +1,12 @@
use raw_window_handle::OrbitalWindowHandle;
use std::{cmp, slice, str};
use std::{cmp, num::NonZeroU32, slice, str};
use crate::SoftBufferError;
struct OrbitalMap {
address: usize,
size: usize,
size_unaligned: usize,
}
impl OrbitalMap {
@ -27,7 +28,19 @@ impl OrbitalMap {
)?
};
Ok(Self { address, size })
Ok(Self {
address,
size,
size_unaligned,
})
}
unsafe fn data(&self) -> &[u32] {
unsafe { slice::from_raw_parts(self.address as *const u32, self.size_unaligned / 4) }
}
unsafe fn data_mut(&self) -> &mut [u32] {
unsafe { slice::from_raw_parts_mut(self.address as *mut u32, self.size_unaligned / 4) }
}
}
@ -42,50 +55,79 @@ impl Drop for OrbitalMap {
pub struct OrbitalImpl {
handle: OrbitalWindowHandle,
width: u32,
height: u32,
}
impl OrbitalImpl {
pub fn new(handle: OrbitalWindowHandle) -> Result<Self, SoftBufferError> {
Ok(Self { handle })
Ok(Self {
handle,
width: 0,
height: 0,
})
}
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width_u16: u16, height_u16: u16) {
let window_fd = self.handle.window as usize;
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
self.width = width.get();
self.height = height.get();
Ok(())
}
// Read the current width and size
fn window_fd(&self) -> usize {
self.handle.window as usize
}
// Read the current width and size
fn window_size(&self) -> (usize, usize) {
let mut window_width = 0;
let mut window_height = 0;
{
let mut buf: [u8; 4096] = [0; 4096];
let count = syscall::fpath(window_fd, &mut buf).unwrap();
let path = str::from_utf8(&buf[..count]).unwrap();
// orbital:/x/y/w/h/t
let mut parts = path.split('/').skip(3);
if let Some(w) = parts.next() {
window_width = w.parse::<usize>().unwrap_or(0);
}
if let Some(h) = parts.next() {
window_height = h.parse::<usize>().unwrap_or(0);
}
let mut buf: [u8; 4096] = [0; 4096];
let count = syscall::fpath(self.window_fd(), &mut buf).unwrap();
let path = str::from_utf8(&buf[..count]).unwrap();
// orbital:/x/y/w/h/t
let mut parts = path.split('/').skip(3);
if let Some(w) = parts.next() {
window_width = w.parse::<usize>().unwrap_or(0);
}
if let Some(h) = parts.next() {
window_height = h.parse::<usize>().unwrap_or(0);
}
(window_width, window_height)
}
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
let (window_width, window_height) = self.window_size();
let pixels = if self.width as usize == window_width && self.height as usize == window_height
{
Pixels::Mapping(
unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) }
.expect("failed to map orbital window"),
)
} else {
Pixels::Buffer(vec![0; self.width as usize * self.height as usize])
};
Ok(BufferImpl { imp: self, pixels })
}
fn set_buffer(&self, buffer: &[u32], width_u32: u32, height_u32: u32) {
// Read the current width and size
let (window_width, window_height) = self.window_size();
{
// Map window buffer
let window_map =
unsafe { OrbitalMap::new(window_fd, window_width * window_height * 4) }
unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) }
.expect("failed to map orbital window");
// Window buffer is u32 color data in 0xAABBGGRR format
let window_data = unsafe {
slice::from_raw_parts_mut(
window_map.address as *mut u32,
window_width * window_height,
)
};
let window_data = unsafe { window_map.data_mut() };
// Copy each line, cropping to fit
let width = width_u16 as usize;
let height = height_u16 as usize;
let width = width_u32 as usize;
let height = height_u32 as usize;
let min_width = cmp::min(width, window_width);
let min_height = cmp::min(height, window_height);
for y in 0..min_height {
@ -99,6 +141,49 @@ impl OrbitalImpl {
}
// Tell orbital to show the latest window data
syscall::fsync(window_fd).expect("failed to sync orbital window");
syscall::fsync(self.window_fd()).expect("failed to sync orbital window");
}
}
enum Pixels {
Mapping(OrbitalMap),
Buffer(Vec<u32>),
}
pub struct BufferImpl<'a> {
imp: &'a mut OrbitalImpl,
pixels: Pixels,
}
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
match &self.pixels {
Pixels::Mapping(mapping) => unsafe { mapping.data() },
Pixels::Buffer(buffer) => buffer,
}
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
match &mut self.pixels {
Pixels::Mapping(mapping) => unsafe { mapping.data_mut() },
Pixels::Buffer(buffer) => buffer,
}
}
pub fn present(self) -> Result<(), SoftBufferError> {
match self.pixels {
Pixels::Mapping(mapping) => {
drop(mapping);
syscall::fsync(self.imp.window_fd()).expect("failed to sync orbital window");
}
Pixels::Buffer(buffer) => {
self.imp
.set_buffer(&buffer, self.imp.width, self.imp.height);
}
}
Ok(())
}
}

63
src/util.rs Normal file
View file

@ -0,0 +1,63 @@
// Not needed on all platforms
#![allow(dead_code)]
use crate::SoftBufferError;
/// Takes a mutable reference to a container and a function deriving a
/// reference into it, and stores both, making it possible to get back the
/// reference to the container once the other reference is no longer needed.
///
/// This should be consistent with stacked borrow rules, and miri seems to
/// accept it at least in simple cases.
pub struct BorrowStack<'a, T: 'static + ?Sized, U: 'static + ?Sized> {
container: *mut T,
member: *mut U,
_phantom: std::marker::PhantomData<&'a mut T>,
}
impl<'a, T: 'static + ?Sized, U: 'static + ?Sized> BorrowStack<'a, T, U> {
pub fn new<F>(container: &'a mut T, f: F) -> Result<Self, SoftBufferError>
where
F: for<'b> FnOnce(&'b mut T) -> Result<&'b mut U, SoftBufferError>,
{
let container = container as *mut T;
let member = f(unsafe { &mut *container })? as *mut U;
Ok(Self {
container,
member,
_phantom: std::marker::PhantomData,
})
}
pub fn member(&self) -> &U {
unsafe { &*self.member }
}
pub fn member_mut(&mut self) -> &mut U {
unsafe { &mut *self.member }
}
pub fn into_container(self) -> &'a mut T {
// SAFETY: Since we consume self and no longer reference member, this
// mutable reference is unique.
unsafe { &mut *self.container }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_borrowstack_slice_int() {
fn f(mut stack: BorrowStack<[u32], u32>) {
assert_eq!(*stack.member(), 3);
*stack.member_mut() = 42;
assert_eq!(stack.into_container(), &[1, 2, 42, 4, 5]);
}
let mut v = vec![1, 2, 3, 4, 5];
f(BorrowStack::new(v.as_mut(), |v: &mut [u32]| Ok(&mut v[2])).unwrap());
assert_eq!(&v, &[1, 2, 42, 4, 5]);
}
}

View file

@ -1,7 +1,9 @@
use memmap2::MmapMut;
use std::{
ffi::CStr,
fs::File,
os::unix::prelude::{AsRawFd, FileExt, FromRawFd},
os::unix::prelude::{AsRawFd, FromRawFd},
slice,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
@ -69,9 +71,19 @@ fn create_memfile() -> File {
panic!("Failed to generate non-existant shm name")
}
// Round size to use for pool for given dimentions, rounding up to power of 2
fn get_pool_size(width: i32, height: i32) -> i32 {
((width * height * 4) as u32).next_power_of_two() as i32
}
unsafe fn map_file(file: &File) -> MmapMut {
unsafe { MmapMut::map_mut(file.as_raw_fd()).expect("Failed to map shared memory") }
}
pub(super) struct WaylandBuffer {
qh: QueueHandle<State>,
tempfile: File,
map: MmapMut,
pool: wl_shm_pool::WlShmPool,
pool_size: i32,
buffer: wl_buffer::WlBuffer,
@ -82,8 +94,15 @@ pub(super) struct WaylandBuffer {
impl WaylandBuffer {
pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle<State>) -> Self {
// Calculate size to use for shm pool
let pool_size = get_pool_size(width, height);
// Create an `mmap` shared memory
let tempfile = create_memfile();
let pool_size = width * height * 4;
let _ = tempfile.set_len(pool_size as u64);
let map = unsafe { map_file(&tempfile) };
// Create wayland shm pool and buffer
let pool = shm.create_pool(tempfile.as_raw_fd(), pool_size, qh, ());
let released = Arc::new(AtomicBool::new(true));
let buffer = pool.create_buffer(
@ -95,8 +114,10 @@ impl WaylandBuffer {
qh,
released.clone(),
);
Self {
qh: qh.clone(),
map,
tempfile,
pool,
pool_size,
@ -119,6 +140,7 @@ impl WaylandBuffer {
let _ = self.tempfile.set_len(size as u64);
self.pool.resize(size);
self.pool_size = size;
self.map = unsafe { map_file(&self.tempfile) };
}
// Create buffer with correct size
@ -136,14 +158,6 @@ impl WaylandBuffer {
}
}
pub fn write(&self, buffer: &[u32]) {
let buffer =
unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4) };
self.tempfile
.write_all_at(buffer, 0)
.expect("Failed to write buffer to temporary file.");
}
pub fn attach(&self, surface: &wl_surface::WlSurface) {
self.released.store(false, Ordering::SeqCst);
surface.attach(Some(&self.buffer), 0, 0);
@ -152,6 +166,14 @@ impl WaylandBuffer {
pub fn released(&self) -> bool {
self.released.load(Ordering::SeqCst)
}
fn len(&self) -> usize {
self.width as usize * self.height as usize
}
pub unsafe fn mapped_mut(&mut self) -> &mut [u32] {
unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr() as *mut u32, self.len()) }
}
}
impl Drop for WaylandBuffer {

View file

@ -1,6 +1,10 @@
use crate::{error::unwrap, SoftBufferError};
use crate::{error::unwrap, util, SoftBufferError};
use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle};
use std::sync::{Arc, Mutex};
use std::{
cell::RefCell,
num::{NonZeroI32, NonZeroU32},
rc::Rc,
};
use wayland_client::{
backend::{Backend, ObjectId},
globals::{registry_queue_init, GlobalListContents},
@ -15,7 +19,7 @@ struct State;
pub struct WaylandDisplayImpl {
conn: Connection,
event_queue: Mutex<EventQueue<State>>,
event_queue: RefCell<EventQueue<State>>,
qh: QueueHandle<State>,
shm: wl_shm::WlShm,
}
@ -36,7 +40,7 @@ impl WaylandDisplayImpl {
)?;
Ok(Self {
conn,
event_queue: Mutex::new(event_queue),
event_queue: RefCell::new(event_queue),
qh,
shm,
})
@ -44,15 +48,16 @@ impl WaylandDisplayImpl {
}
pub struct WaylandImpl {
display: Arc<WaylandDisplayImpl>,
display: Rc<WaylandDisplayImpl>,
surface: wl_surface::WlSurface,
buffers: Option<(WaylandBuffer, WaylandBuffer)>,
size: Option<(NonZeroI32, NonZeroI32)>,
}
impl WaylandImpl {
pub unsafe fn new(
window_handle: WaylandWindowHandle,
display: Arc<WaylandDisplayImpl>,
display: Rc<WaylandDisplayImpl>,
) -> Result<Self, SoftBufferError> {
// SAFETY: Ensured by user
let surface_id = unwrap(
@ -72,60 +77,119 @@ impl WaylandImpl {
display,
surface,
buffers: Default::default(),
size: None,
})
}
fn buffer(&mut self, width: i32, height: i32) -> &WaylandBuffer {
self.buffers = Some(if let Some((front, mut back)) = self.buffers.take() {
// Swap buffers; block if back buffer not released yet
if !back.released() {
let mut event_queue = self.display.event_queue.lock().unwrap();
while !back.released() {
event_queue.blocking_dispatch(&mut State).unwrap();
}
}
back.resize(width, height);
(back, front)
} else {
// Allocate front and back buffer
(
WaylandBuffer::new(&self.display.shm, width, height, &self.display.qh),
WaylandBuffer::new(&self.display.shm, width, height, &self.display.qh),
)
});
&self.buffers.as_ref().unwrap().0
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
self.size = Some(
(|| {
let width = NonZeroI32::try_from(width).ok()?;
let height = NonZeroI32::try_from(height).ok()?;
Some((width, height))
})()
.ok_or(SoftBufferError::SizeOutOfRange { width, height })?,
);
Ok(())
}
pub(super) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
let _ = self
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
let (width, height) = self
.size
.expect("Must set size of surface before calling `buffer_mut()`");
if let Some((_front, back)) = &mut self.buffers {
// Block if back buffer not released yet
if !back.released() {
let mut event_queue = self.display.event_queue.borrow_mut();
while !back.released() {
event_queue.blocking_dispatch(&mut State).map_err(|err| {
SoftBufferError::PlatformError(
Some("Wayland dispatch failure".to_string()),
Some(Box::new(err)),
)
})?;
}
}
// Resize, if buffer isn't large enough
back.resize(width.get(), height.get());
} else {
// Allocate front and back buffer
self.buffers = Some((
WaylandBuffer::new(
&self.display.shm,
width.get(),
height.get(),
&self.display.qh,
),
WaylandBuffer::new(
&self.display.shm,
width.get(),
height.get(),
&self.display.qh,
),
));
};
Ok(BufferImpl(util::BorrowStack::new(self, |buffer| {
Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() })
})?))
}
}
pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>);
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
self.0.member()
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.0.member_mut()
}
pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0.into_container();
let (width, height) = imp
.size
.expect("Must set size of surface before calling `present()`");
let _ = imp
.display
.event_queue
.lock()
.unwrap()
.borrow_mut()
.dispatch_pending(&mut State);
let surface = self.surface.clone();
let wayland_buffer = self.buffer(width.into(), height.into());
wayland_buffer.write(buffer);
wayland_buffer.attach(&surface);
if let Some((front, back)) = &mut imp.buffers {
// Swap front and back buffer
std::mem::swap(front, back);
// FIXME: Proper damaging mechanism.
//
// In order to propagate changes on compositors which track damage, for now damage the entire surface.
if self.surface.version() < 4 {
// FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while
// wl_surface::damage_buffer is in buffer coordinates.
front.attach(&imp.surface);
// FIXME: Proper damaging mechanism.
//
// i32::MAX is a valid damage box (most compositors interpret the damage box as "the entire surface")
self.surface.damage(0, 0, i32::MAX, i32::MAX);
} else {
// Introduced in version 4, it is an error to use this request in version 3 or lower.
self.surface
.damage_buffer(0, 0, width as i32, height as i32);
}
self.surface.commit();
// In order to propagate changes on compositors which track damage, for now damage the entire surface.
if imp.surface.version() < 4 {
// FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while
// wl_surface::damage_buffer is in buffer coordinates.
//
// i32::MAX is a valid damage box (most compositors interpret the damage box as "the entire surface")
imp.surface.damage(0, 0, i32::MAX, i32::MAX);
} else {
// Introduced in version 4, it is an error to use this request in version 3 or lower.
imp.surface.damage_buffer(0, 0, width.get(), height.get());
}
let _ = self.display.event_queue.lock().unwrap().flush();
imp.surface.commit();
}
let _ = imp.display.event_queue.borrow_mut().flush();
Ok(())
}
}

View file

@ -1,3 +1,7 @@
//! Implementation of software buffering for web targets.
#![allow(clippy::uninlined_format_args)]
use raw_window_handle::WebWindowHandle;
use wasm_bindgen::Clamped;
use wasm_bindgen::JsCast;
@ -6,6 +10,8 @@ use web_sys::HtmlCanvasElement;
use web_sys::ImageData;
use crate::SoftBufferError;
use std::convert::TryInto;
use std::num::NonZeroU32;
/// Display implementation for the web platform.
///
@ -36,8 +42,17 @@ impl WebDisplayImpl {
}
pub struct WebImpl {
/// The handle to the canvas that we're drawing to.
canvas: HtmlCanvasElement,
/// The 2D rendering context for the canvas.
ctx: CanvasRenderingContext2d,
/// The buffer that we're drawing to.
buffer: Vec<u32>,
/// The current width of the canvas.
width: u32,
}
impl WebImpl {
@ -73,24 +88,82 @@ impl WebImpl {
.dyn_into()
.expect("`getContext(\"2d\") didn't return a `CanvasRenderingContext2d`");
Ok(Self { canvas, ctx })
Ok(Self {
canvas,
ctx,
buffer: Vec::new(),
width: 0,
})
}
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
self.canvas.set_width(width.into());
self.canvas.set_height(height.into());
/// Resize the canvas to the given dimensions.
pub(crate) fn resize(
&mut self,
width: NonZeroU32,
height: NonZeroU32,
) -> Result<(), SoftBufferError> {
let width = width.get();
let height = height.get();
let bitmap: Vec<_> = buffer
self.buffer.resize(total_len(width, height), 0);
self.canvas.set_width(width);
self.canvas.set_height(height);
self.width = width;
Ok(())
}
/// Get a pointer to the mutable buffer.
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl { imp: self })
}
}
pub struct BufferImpl<'a> {
imp: &'a mut WebImpl,
}
impl<'a> BufferImpl<'a> {
pub fn pixels(&self) -> &[u32] {
&self.imp.buffer
}
pub fn pixels_mut(&mut self) -> &mut [u32] {
&mut self.imp.buffer
}
/// Push the buffer to the canvas.
pub fn present(self) -> Result<(), SoftBufferError> {
// Create a bitmap from the buffer.
let bitmap: Vec<_> = self
.imp
.buffer
.iter()
.copied()
.flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255])
.collect();
// This should only throw an error if the buffer we pass's size is incorrect, which is checked in the outer `set_buffer` call.
// This should only throw an error if the buffer we pass's size is incorrect.
let image_data =
ImageData::new_with_u8_clamped_array(Clamped(&bitmap), width.into()).unwrap();
ImageData::new_with_u8_clamped_array(Clamped(&bitmap), self.imp.width).unwrap();
// This can only throw an error if `data` is detached, which is impossible.
self.ctx.put_image_data(&image_data, 0.0, 0.0).unwrap();
self.imp.ctx.put_image_data(&image_data, 0.0, 0.0).unwrap();
Ok(())
}
}
#[inline(always)]
fn total_len(width: u32, height: u32) -> usize {
// Convert width and height to `usize`, then multiply.
width
.try_into()
.ok()
.and_then(|w: usize| height.try_into().ok().and_then(|h| w.checked_mul(h)))
.unwrap_or_else(|| {
panic!(
"Overflow when calculating total length of buffer: {}x{}",
width, height
);
})
}

View file

@ -2,33 +2,134 @@
//!
//! This module converts the input buffer into a bitmap and then stretches it to the window.
use crate::SoftBufferError;
use crate::{util, SoftBufferError};
use raw_window_handle::Win32WindowHandle;
use std::io;
use std::mem;
use std::os::raw::c_int;
use std::num::{NonZeroI32, NonZeroU32};
use std::ptr::{self, NonNull};
use std::slice;
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::Graphics::Gdi::{
GetDC, StretchDIBits, ValidateRect, BITMAPINFOHEADER, BI_BITFIELDS, DIB_RGB_COLORS, HDC,
RGBQUAD, SRCCOPY,
use windows_sys::Win32::Graphics::Gdi;
const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD {
rgbBlue: 0,
rgbGreen: 0,
rgbRed: 0,
rgbReserved: 0,
};
struct Buffer {
dc: Gdi::HDC,
bitmap: Gdi::HBITMAP,
pixels: NonNull<u32>,
width: NonZeroI32,
height: NonZeroI32,
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
Gdi::DeleteDC(self.dc);
Gdi::DeleteObject(self.bitmap);
}
}
}
impl Buffer {
fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self {
let dc = unsafe { Gdi::CreateCompatibleDC(window_dc) };
assert!(dc != 0);
// Create a new bitmap info struct.
let bitmap_info = BitmapInfo {
bmi_header: Gdi::BITMAPINFOHEADER {
biSize: mem::size_of::<Gdi::BITMAPINFOHEADER>() as u32,
biWidth: width.get(),
biHeight: -height.get(),
biPlanes: 1,
biBitCount: 32,
biCompression: Gdi::BI_BITFIELDS,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmi_colors: [
Gdi::RGBQUAD {
rgbRed: 0xff,
..ZERO_QUAD
},
Gdi::RGBQUAD {
rgbGreen: 0xff,
..ZERO_QUAD
},
Gdi::RGBQUAD {
rgbBlue: 0xff,
..ZERO_QUAD
},
],
};
// XXX alignment?
// XXX better to use CreateFileMapping, and pass hSection?
// XXX test return value?
let mut pixels: *mut u32 = ptr::null_mut();
let bitmap = unsafe {
Gdi::CreateDIBSection(
dc,
&bitmap_info as *const BitmapInfo as *const _,
Gdi::DIB_RGB_COLORS,
&mut pixels as *mut *mut u32 as _,
0,
0,
)
};
assert!(bitmap != 0);
let pixels = NonNull::new(pixels).unwrap();
unsafe {
Gdi::SelectObject(dc, bitmap);
}
Self {
dc,
bitmap,
width,
height,
pixels,
}
}
fn pixels_mut(&mut self) -> &mut [u32] {
unsafe {
slice::from_raw_parts_mut(
self.pixels.as_ptr(),
i32::from(self.width) as usize * i32::from(self.height) as usize,
)
}
}
}
/// The handle to a window for software buffering.
pub struct Win32Impl {
/// The window handle.
window: HWND,
/// The device context for the window.
dc: HDC,
dc: Gdi::HDC,
buffer: Option<Buffer>,
}
/// The Win32-compatible bitmap information.
#[repr(C)]
struct BitmapInfo {
pub bmi_header: BITMAPINFOHEADER,
pub bmi_colors: [RGBQUAD; 3],
bmi_header: Gdi::BITMAPINFOHEADER,
bmi_colors: [Gdi::RGBQUAD; 3],
}
impl Win32Impl {
@ -46,7 +147,7 @@ impl Win32Impl {
// Get the handle to the device context.
// SAFETY: We have confirmed that the window handle is valid.
let hwnd = handle.hwnd as HWND;
let dc = unsafe { GetDC(hwnd) };
let dc = unsafe { Gdi::GetDC(hwnd) };
// GetDC returns null if there is a platform error.
if dc == 0 {
@ -56,72 +157,76 @@ impl Win32Impl {
));
}
Ok(Self { dc, window: hwnd })
Ok(Self {
dc,
window: hwnd,
buffer: None,
})
}
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
// Create a new bitmap info struct.
let bmi_header = BITMAPINFOHEADER {
biSize: mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: width as i32,
biHeight: -(height as i32),
biPlanes: 1,
biBitCount: 32,
biCompression: BI_BITFIELDS,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
};
let zero_quad = RGBQUAD {
rgbBlue: 0,
rgbGreen: 0,
rgbRed: 0,
rgbReserved: 0,
};
let bmi_colors = [
RGBQUAD {
rgbRed: 0xff,
..zero_quad
},
RGBQUAD {
rgbGreen: 0xff,
..zero_quad
},
RGBQUAD {
rgbBlue: 0xff,
..zero_quad
},
];
let bitmap_info = BitmapInfo {
bmi_header,
bmi_colors,
};
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
let (width, height) = (|| {
let width = NonZeroI32::new(i32::try_from(width.get()).ok()?)?;
let height = NonZeroI32::new(i32::try_from(height.get()).ok()?)?;
Some((width, height))
})()
.ok_or(SoftBufferError::SizeOutOfRange { width, height })?;
// Stretch the bitmap onto the window.
// SAFETY:
// - The bitmap information is valid.
// - The buffer is a valid pointer to image data.
unsafe {
StretchDIBits(
self.dc,
0,
0,
width as c_int,
height as c_int,
0,
0,
width as c_int,
height as c_int,
buffer.as_ptr().cast(),
&bitmap_info as *const BitmapInfo as *const _,
DIB_RGB_COLORS,
SRCCOPY,
)
};
if let Some(buffer) = self.buffer.as_ref() {
if buffer.width == width && buffer.height == height {
return Ok(());
}
}
// Validate the window.
unsafe { ValidateRect(self.window, std::ptr::null_mut()) };
self.buffer = Some(Buffer::new(self.dc, width, height));
Ok(())
}
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl(util::BorrowStack::new(self, |surface| {
Ok(surface
.buffer
.as_mut()
.expect("Must set size of surface before calling `buffer_mut()`")
.pixels_mut())
})?))
}
}
pub struct BufferImpl<'a>(util::BorrowStack<'a, Win32Impl, [u32]>);
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
self.0.member()
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.0.member_mut()
}
pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0.into_container();
let buffer = imp.buffer.as_ref().unwrap();
unsafe {
Gdi::BitBlt(
imp.dc,
0,
0,
buffer.width.get(),
buffer.height.get(),
buffer.dc,
0,
0,
Gdi::SRCCOPY,
);
// Validate the window.
Gdi::ValidateRect(imp.window, ptr::null_mut());
}
Ok(())
}
}

View file

@ -5,11 +5,11 @@
#![allow(clippy::uninlined_format_args)]
use crate::SoftBufferError;
use crate::{util, SoftBufferError};
use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID};
use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle};
use std::ptr::{null_mut, NonNull};
use std::{fmt, io, sync::Arc};
use std::{fmt, io, mem, num::NonZeroU32, rc::Rc};
use x11_dl::xlib::Display;
use x11_dl::xlib_xcb::Xlib_xcb;
@ -80,6 +80,9 @@ impl X11DisplayImpl {
};
let is_shm_available = is_shm_available(&connection);
if !is_shm_available {
log::warn!("SHM extension is not available. Performance may be poor.");
}
Ok(Self {
connection,
@ -91,7 +94,7 @@ impl X11DisplayImpl {
/// The handle to an X11 drawing context.
pub struct X11Impl {
/// X display this window belongs to.
display: Arc<X11DisplayImpl>,
display: Rc<X11DisplayImpl>,
/// The window to draw to.
window: xproto::Window,
@ -102,11 +105,26 @@ pub struct X11Impl {
/// The depth (bits per pixel) of the drawing context.
depth: u8,
/// Information about SHM, if it is available.
shm: Option<ShmInfo>,
/// The buffer we draw to.
buffer: Buffer,
/// The current buffer width.
width: u16,
/// The current buffer height.
height: u16,
}
struct ShmInfo {
/// The buffer that is being drawn to.
enum Buffer {
/// A buffer implemented using shared memory to prevent unnecessary copying.
Shm(ShmBuffer),
/// A normal buffer that we send over the wire.
Wire(Vec<u32>),
}
struct ShmBuffer {
/// The shared memory segment, paired with its ID.
seg: Option<(ShmSegment, shm::Seg)>,
@ -133,7 +151,7 @@ impl X11Impl {
/// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid.
pub unsafe fn from_xlib(
window_handle: XlibWindowHandle,
display: Arc<X11DisplayImpl>,
display: Rc<X11DisplayImpl>,
) -> Result<Self, SoftBufferError> {
let mut xcb_window_handle = XcbWindowHandle::empty();
xcb_window_handle.window = window_handle.window as _;
@ -150,8 +168,10 @@ impl X11Impl {
/// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid.
pub(crate) unsafe fn from_xcb(
window_handle: XcbWindowHandle,
display: Arc<X11DisplayImpl>,
display: Rc<X11DisplayImpl>,
) -> Result<Self, SoftBufferError> {
log::trace!("new: window_handle={:X}", window_handle.window,);
// Check that the handle is valid.
if window_handle.window == 0 {
return Err(SoftBufferError::IncompleteWindowHandle);
@ -187,18 +207,15 @@ impl X11Impl {
.swbuf_err("Failed to get geometry reply")?;
// See if SHM is available.
let shm_info = {
let present = display.is_shm_available;
if present {
// SHM is available.
Some(ShmInfo {
seg: None,
done_processing: None,
})
} else {
None
}
let buffer = if display.is_shm_available {
// SHM is available.
Buffer::Shm(ShmBuffer {
seg: None,
done_processing: None,
})
} else {
// SHM is not available.
Buffer::Wire(Vec::new())
};
Ok(Self {
@ -206,119 +223,184 @@ impl X11Impl {
window,
gc,
depth: geometry_reply.depth,
shm: shm_info,
buffer,
width: 0,
height: 0,
})
}
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
// Draw the image to the buffer.
let result = unsafe { self.set_buffer_shm(buffer, width, height) }.and_then(|had_shm| {
if had_shm {
Ok(())
} else {
log::debug!("Falling back to non-SHM method");
self.set_buffer_fallback(buffer, width, height)
}
});
if let Err(e) = result {
log::error!("Failed to draw image to window: {}", e);
}
}
/// Put the given buffer into the window using the SHM method.
///
/// Returns `false` if SHM is not available.
///
/// # Safety
///
/// The buffer's length must be `width * height`.
unsafe fn set_buffer_shm(
/// Resize the internal buffer to the given width and height.
pub(crate) fn resize(
&mut self,
buffer: &[u32],
width: u16,
height: u16,
) -> Result<bool, PushBufferError> {
let shm_info = match self.shm {
Some(ref mut info) => info,
None => return Ok(false),
};
width: NonZeroU32,
height: NonZeroU32,
) -> Result<(), SoftBufferError> {
log::trace!(
"resize: window={:X}, size={}x{}",
self.window,
width,
height
);
// If the X server is still processing the last image, wait for it to finish.
shm_info.finish_wait(&self.display.connection)?;
// Width and height should fit in u16.
let width: u16 = width
.get()
.try_into()
.or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
let height: u16 = height
.get()
.try_into()
.or(Err(SoftBufferError::SizeOutOfRange {
width: NonZeroU32::new(width.into()).unwrap(),
height,
}))?;
// Get the SHM segment to use.
let necessary_size = (width as usize) * (height as usize) * 4;
let (segment, segment_id) = shm_info.segment(&self.display.connection, necessary_size)?;
if width != self.width || height != self.height {
self.buffer
.resize(&self.display.connection, width, height)
.swbuf_err("Failed to resize X11 buffer")?;
// Copy the buffer into the segment.
// SAFETY: The buffer is properly sized and we've ensured that the X server isn't reading from it.
unsafe {
segment.copy(buffer);
// We successfully resized the buffer.
self.width = width;
self.height = height;
}
// Put the image into the window.
self.display
.connection
.shm_put_image(
self.window,
self.gc,
width,
height,
0,
0,
width,
height,
0,
0,
self.depth,
xproto::ImageFormat::Z_PIXMAP.into(),
false,
segment_id,
0,
)?
.ignore_error();
// Send a short request to act as a notification for when the X server is done processing the image.
shm_info.begin_wait(&self.display.connection)?;
Ok(true)
}
/// Put the given buffer into the window using the fallback wire transfer method.
fn set_buffer_fallback(
&mut self,
buffer: &[u32],
width: u16,
height: u16,
) -> Result<(), PushBufferError> {
self.display
.connection
.put_image(
xproto::ImageFormat::Z_PIXMAP,
self.window,
self.gc,
width,
height,
0,
0,
0,
self.depth,
bytemuck::cast_slice(buffer),
)?
.ignore_error();
Ok(())
}
/// Get a mutable reference to the buffer.
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
log::trace!("buffer_mut: window={:X}", self.window);
Ok(BufferImpl(util::BorrowStack::new(self, |surface| {
let buffer = surface
.buffer
.buffer_mut(&surface.display.connection)
.swbuf_err("Failed to get mutable X11 buffer")?;
// Crop it down to the window size.
Ok(&mut buffer[..total_len(surface.width, surface.height) / 4])
})?))
}
}
impl ShmInfo {
pub struct BufferImpl<'a>(util::BorrowStack<'a, X11Impl, [u32]>);
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
self.0.member()
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.0.member_mut()
}
/// Push the buffer to the window.
pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0.into_container();
log::trace!("present: window={:X}", imp.window);
let result = match imp.buffer {
Buffer::Wire(ref wire) => {
// This is a suboptimal strategy, raise a stink in the debug logs.
log::debug!("Falling back to non-SHM method for window drawing.");
imp.display
.connection
.put_image(
xproto::ImageFormat::Z_PIXMAP,
imp.window,
imp.gc,
imp.width,
imp.height,
0,
0,
0,
imp.depth,
bytemuck::cast_slice(wire),
)
.map(|c| c.ignore_error())
.push_err()
}
Buffer::Shm(ref mut shm) => {
// If the X server is still processing the last image, wait for it to finish.
shm.finish_wait(&imp.display.connection)
.and_then(|()| {
// Put the image into the window.
if let Some((_, segment_id)) = shm.seg {
imp.display
.connection
.shm_put_image(
imp.window,
imp.gc,
imp.width,
imp.height,
0,
0,
imp.width,
imp.height,
0,
0,
imp.depth,
xproto::ImageFormat::Z_PIXMAP.into(),
false,
segment_id,
0,
)
.push_err()
.map(|c| c.ignore_error())
} else {
Ok(())
}
})
.and_then(|()| {
// Send a short request to act as a notification for when the X server is done processing the image.
shm.begin_wait(&imp.display.connection)
})
}
};
result.swbuf_err("Failed to draw image to window")
}
}
impl Buffer {
/// Resize the buffer to the given size.
fn resize(
&mut self,
conn: &impl Connection,
width: u16,
height: u16,
) -> Result<(), PushBufferError> {
match self {
Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),
Buffer::Wire(wire) => {
wire.resize(total_len(width, height), 0);
Ok(())
}
}
}
/// Get a mutable reference to the buffer.
fn buffer_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> {
match self {
Buffer::Shm(ref mut shm) => shm.as_mut(conn),
Buffer::Wire(wire) => Ok(wire),
}
}
}
impl ShmBuffer {
/// Allocate a new `ShmSegment` of the given size.
fn segment(
fn alloc_segment(
&mut self,
conn: &impl Connection,
size: usize,
) -> Result<(&mut ShmSegment, shm::Seg), PushBufferError> {
) -> Result<(), PushBufferError> {
// Round the size up to the next power of two to prevent frequent reallocations.
let size = size.next_power_of_two();
@ -334,12 +416,25 @@ impl ShmInfo {
self.associate(conn, new_seg)?;
}
// Get the segment and ID.
Ok(self
.seg
.as_mut()
.map(|(ref mut seg, id)| (seg, *id))
.unwrap())
Ok(())
}
/// Get the SHM buffer as a mutable reference.
fn as_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> {
// Make sure that, if we're waiting for the X server to finish processing the last image,
// that we finish the wait.
self.finish_wait(conn)?;
match self.seg.as_mut() {
Some((seg, _)) => {
// SAFETY: No other code should be able to access the segment.
Ok(bytemuck::cast_slice_mut(unsafe { seg.as_mut() }))
}
None => {
// Nothing has been allocated yet.
Ok(&mut [])
}
}
}
/// Associate an SHM segment with the server.
@ -354,6 +449,9 @@ impl ShmInfo {
// Take out the old one and detach it.
if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {
// Wait for the old segment to finish processing.
self.finish_wait(conn)?;
conn.shm_detach(old_id)?.ignore_error();
// Drop the old segment.
@ -415,23 +513,13 @@ impl ShmSegment {
}
}
/// Copy data into this shared memory segment.
/// Get this shared memory segment as a mutable reference.
///
/// # Safety
///
/// This function assumes that the size of `self`'s buffer is larger than or equal to `data.len()`.
/// In addition, no other processes should be reading from or writing to this memory.
unsafe fn copy<T: bytemuck::NoUninit>(&mut self, data: &[T]) {
debug_assert!(data.len() * std::mem::size_of::<T>() <= self.size,);
let incoming_data = bytemuck::cast_slice::<_, u8>(data);
unsafe {
std::ptr::copy_nonoverlapping(
incoming_data.as_ptr(),
self.ptr.as_ptr() as *mut u8,
incoming_data.len(),
)
}
/// One must ensure that no other processes are reading from or writing to this memory.
unsafe fn as_mut(&mut self) -> &mut [i8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
}
/// Get the size of this shared memory segment.
@ -460,7 +548,7 @@ impl Drop for ShmSegment {
impl Drop for X11Impl {
fn drop(&mut self) {
// If we used SHM, make sure it's detached from the server.
if let Some(mut shm) = self.shm.take() {
if let Buffer::Shm(mut shm) = mem::replace(&mut self.buffer, Buffer::Wire(Vec::new())) {
// If we were in the middle of processing a buffer, wait for it to finish.
shm.finish_wait(&self.display.connection).ok();
@ -563,11 +651,11 @@ impl From<io::Error> for PushBufferError {
}
/// Convenient wrapper to cast errors into SoftBufferError.
trait ResultExt<T, E> {
trait SwResultExt<T, E> {
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError>;
}
impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> {
impl<T, E: std::error::Error + 'static> SwResultExt<T, E> for Result<T, E> {
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError> {
self.map_err(|e| {
SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e))))
@ -575,6 +663,17 @@ impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> {
}
}
/// Convenient wrapper to cast errors into PushBufferError.
trait PushResultExt<T, E> {
fn push_err(self) -> Result<T, PushBufferError>;
}
impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {
fn push_err(self) -> Result<T, PushBufferError> {
self.map_err(Into::into)
}
}
/// A wrapper around a library error.
///
/// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast
@ -594,3 +693,15 @@ impl<E: fmt::Display> fmt::Display for LibraryError<E> {
}
impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {}
/// Get the length that a slice needs to be to hold a buffer of the given dimensions.
#[inline(always)]
fn total_len(width: u16, height: u16) -> usize {
let width: usize = width.into();
let height: usize = height.into();
width
.checked_mul(height)
.and_then(|len| len.checked_mul(4))
.unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))
}