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:
parent
e5d546ff9e
commit
a09e4cf679
19 changed files with 1176 additions and 438 deletions
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
29
README.md
29
README.md
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
76
src/cg.rs
76
src/cg.rs
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>>),
|
||||
}
|
||||
|
|
|
|||
204
src/lib.rs
204
src/lib.rs
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
141
src/orbital.rs
141
src/orbital.rs
|
|
@ -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
63
src/util.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
89
src/web.rs
89
src/web.rs
|
|
@ -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
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
|
|||
249
src/win32.rs
249
src/win32.rs
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
389
src/x11.rs
389
src/x11.rs
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue