Skip to content

Commit 61ec6fc

Browse files
committed
Add AlphaMode and transparency
1 parent bf2e769 commit 61ec6fc

File tree

18 files changed

+698
-143
lines changed

18 files changed

+698
-143
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- Added `AlphaMode` to allow configuring transparency and zero-copy on Web. Set it with `Surface::configure`.
4+
- Added `Surface::supports_alpha_mode` for querying supported alpha modes.
35
- Added `PixelFormat` enum.
46
- Added `Buffer::pixels()` for accessing the buffer's pixel data.
57
- Added `Buffer::pixel_rows()` for iterating over rows of the buffer data.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ drm = { version = "0.14.1", default-features = false, optional = true }
8686
[target.'cfg(target_os = "windows")'.dependencies]
8787
windows-sys = { version = "0.61.2", features = [
8888
"Win32_Graphics_Gdi",
89+
"Win32_UI_ColorSystem",
8990
"Win32_UI_Shell",
9091
"Win32_UI_WindowsAndMessaging",
9192
"Win32_Foundation",

examples/transparency.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//! An example to test transparent rendering.
2+
//!
3+
//! Press `1`, `2`, `3` or `4` to change the alpha mode to `Opaque`, `Ignored`, `Premultiplied` and
4+
//! `Postmultiplied` respectively.
5+
//!
6+
//! This should render 6 rectangular areas. For details on the terminology, see:
7+
//! <https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context>
8+
//!
9+
//! (255, 127, 0, 255):
10+
//! - Opaque/Ignored: Completely-opaque orange.
11+
//! - Postmultiplied: Completely-opaque orange.
12+
//! - Premultiplied: Completely-opaque orange.
13+
//!
14+
//! (255, 255, 0, 127):
15+
//! - Opaque/Ignored: Completely-opaque yellow.
16+
//! - Postmultiplied: Halfway-opaque yellow.
17+
//! - Premultiplied: Additive halfway-opaque yellow.
18+
//!
19+
//! (127, 127, 0, 127):
20+
//! - Opaque/Ignored: Completely-opaque dark yellow.
21+
//! - Postmultiplied: Halfway-opaque dark yellow.
22+
//! - Premultiplied: Halfway-opaque yellow.
23+
//!
24+
//! (255, 127, 0, 127):
25+
//! - Opaque/Ignored: Completely-opaque orange.
26+
//! - Postmultiplied: Halfway-opaque orange.
27+
//! - Premultiplied: Additive halfway-opaque orange.
28+
//!
29+
//! (255, 127, 0, 0):
30+
//! - Opaque/Ignored: Completely-opaque orange.
31+
//! - Postmultiplied: Fully-transparent orange.
32+
//! - Premultiplied: Additive fully-transparent orange.
33+
//!
34+
//! (0, 0, 0, 0):
35+
//! - Opaque/Ignored: Completely-opaque black.
36+
//! - Postmultiplied: Fully-transparent.
37+
//! - Premultiplied: Fully-transparent.
38+
//!
39+
//! For images of the expected output, see <https://github.com/rust-windowing/softbuffer/pull/321>.
40+
use softbuffer::{AlphaMode, Context, Pixel, Surface};
41+
use std::num::NonZeroU32;
42+
use winit::event::{ElementState, KeyEvent, WindowEvent};
43+
use winit::event_loop::{ControlFlow, EventLoop};
44+
use winit::keyboard::{Key, NamedKey};
45+
46+
mod util;
47+
48+
fn main() {
49+
util::setup();
50+
51+
let event_loop = EventLoop::new().unwrap();
52+
53+
let context = Context::new(event_loop.owned_display_handle()).unwrap();
54+
55+
let app = util::WinitAppBuilder::with_init(
56+
|elwt| util::make_window(elwt, |w| w),
57+
move |_elwt, window| Surface::new(&context, window.clone()).unwrap(),
58+
)
59+
.with_event_handler(|window, surface, window_id, event, elwt| {
60+
elwt.set_control_flow(ControlFlow::Wait);
61+
62+
if window_id != window.id() {
63+
return;
64+
}
65+
66+
match event {
67+
WindowEvent::Resized(size) => {
68+
let Some(surface) = surface else {
69+
tracing::error!("Resized fired before Resumed or after Suspended");
70+
return;
71+
};
72+
73+
if let (Some(width), Some(height)) =
74+
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
75+
{
76+
surface.resize(width, height).unwrap();
77+
}
78+
}
79+
WindowEvent::RedrawRequested => {
80+
let Some(surface) = surface else {
81+
tracing::error!("RedrawRequested fired before Resumed or after Suspended");
82+
return;
83+
};
84+
85+
tracing::info!(alpha_mode = ?surface.alpha_mode(), "redraw");
86+
87+
let alpha_mode = surface.alpha_mode();
88+
let mut buffer = surface.buffer_mut().unwrap();
89+
let width = buffer.width().get();
90+
for (x, _, pixel) in buffer.pixels_iter() {
91+
let rectangle_number = (x * 6) / width;
92+
*pixel = match rectangle_number {
93+
0 => Pixel::new_rgba(255, 127, 0, 255),
94+
1 => Pixel::new_rgba(255, 255, 0, 127),
95+
2 => Pixel::new_rgba(127, 127, 0, 127),
96+
3 => Pixel::new_rgba(255, 127, 0, 127),
97+
4 => Pixel::new_rgba(255, 127, 0, 0),
98+
_ => Pixel::new_rgba(0, 0, 0, 0),
99+
};
100+
101+
// Convert `AlphaMode::Opaque` -> `AlphaMode::Ignored`.
102+
if alpha_mode == AlphaMode::Opaque {
103+
pixel.a = 255;
104+
};
105+
}
106+
107+
buffer.present().unwrap();
108+
}
109+
WindowEvent::CloseRequested
110+
| WindowEvent::KeyboardInput {
111+
event:
112+
KeyEvent {
113+
logical_key: Key::Named(NamedKey::Escape),
114+
repeat: false,
115+
..
116+
},
117+
..
118+
} => {
119+
elwt.exit();
120+
}
121+
WindowEvent::KeyboardInput {
122+
event:
123+
KeyEvent {
124+
logical_key,
125+
repeat: false,
126+
state: ElementState::Pressed,
127+
..
128+
},
129+
..
130+
} => {
131+
let Some(surface) = surface else {
132+
tracing::error!("KeyboardInput fired before Resumed or after Suspended");
133+
return;
134+
};
135+
136+
let alpha_mode = match logical_key.to_text() {
137+
Some("1") => AlphaMode::Opaque,
138+
Some("2") => AlphaMode::Ignored,
139+
Some("3") => AlphaMode::Premultiplied,
140+
Some("4") => AlphaMode::Postmultiplied,
141+
_ => return,
142+
};
143+
144+
if !surface.supports_alpha_mode(alpha_mode) {
145+
tracing::warn!(?alpha_mode, "not supported by the backend");
146+
return;
147+
}
148+
149+
tracing::info!(?alpha_mode, "set alpha");
150+
let size = window.inner_size();
151+
let width = NonZeroU32::new(size.width).unwrap();
152+
let height = NonZeroU32::new(size.height).unwrap();
153+
surface.configure(width, height, alpha_mode).unwrap();
154+
assert_eq!(surface.alpha_mode(), alpha_mode);
155+
156+
window.set_transparent(matches!(
157+
alpha_mode,
158+
AlphaMode::Premultiplied | AlphaMode::Postmultiplied
159+
));
160+
161+
window.request_redraw();
162+
}
163+
_ => {}
164+
}
165+
});
166+
167+
util::run_app(event_loop, app);
168+
}

src/backend_dispatch.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Implements `buffer_interface::*` traits for enums dispatching to backends
22
3-
use crate::{backend_interface::*, backends, InitError, Pixel, Rect, SoftBufferError};
3+
use crate::{backend_interface::*, backends, AlphaMode, InitError, Pixel, Rect, SoftBufferError};
44

55
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
66
use std::fmt;
@@ -99,20 +99,30 @@ macro_rules! make_dispatch {
9999
}
100100
}
101101

102-
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
102+
#[inline]
103+
fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool {
104+
match self {
105+
$(
106+
$(#[$attr])*
107+
Self::$name(inner) => inner.supports_alpha_mode(alpha_mode),
108+
)*
109+
}
110+
}
111+
112+
fn configure(&mut self, width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode) -> Result<(), SoftBufferError> {
103113
match self {
104114
$(
105115
$(#[$attr])*
106-
Self::$name(inner) => inner.resize(width, height),
116+
Self::$name(inner) => inner.configure(width, height, alpha_mode),
107117
)*
108118
}
109119
}
110120

111-
fn buffer_mut(&mut self) -> Result<BufferDispatch<'_>, SoftBufferError> {
121+
fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> Result<BufferDispatch<'_>, SoftBufferError> {
112122
match self {
113123
$(
114124
$(#[$attr])*
115-
Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut()?)),
125+
Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut(alpha_mode)?)),
116126
)*
117127
}
118128
}

src/backend_interface.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Interface implemented by backends
22
3-
use crate::{InitError, Pixel, Rect, SoftBufferError};
3+
use crate::{AlphaMode, InitError, Pixel, Rect, SoftBufferError};
44

55
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
66
use std::num::NonZeroU32;
@@ -22,12 +22,23 @@ pub(crate) trait SurfaceInterface<D: HasDisplayHandle + ?Sized, W: HasWindowHand
2222
where
2323
W: Sized,
2424
Self: Sized;
25+
2526
/// Get the inner window handle.
2627
fn window(&self) -> &W;
27-
/// Resize the internal buffer to the given width and height.
28-
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError>;
28+
29+
fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool;
30+
31+
/// Reconfigure the internal buffer(s).
32+
fn configure(
33+
&mut self,
34+
width: NonZeroU32,
35+
height: NonZeroU32,
36+
alpha_mode: AlphaMode,
37+
) -> Result<(), SoftBufferError>;
38+
2939
/// Get a mutable reference to the buffer.
30-
fn buffer_mut(&mut self) -> Result<Self::Buffer<'_>, SoftBufferError>;
40+
fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> Result<Self::Buffer<'_>, SoftBufferError>;
41+
3142
/// Fetch the buffer from the window.
3243
fn fetch(&mut self) -> Result<Vec<Pixel>, SoftBufferError> {
3344
Err(SoftBufferError::Unimplemented)

src/backends/android.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use raw_window_handle::AndroidNdkWindowHandle;
1212
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
1313

1414
use crate::error::InitError;
15-
use crate::{util, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface};
15+
use crate::{util, AlphaMode, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface};
1616

1717
/// The handle to a window for software buffering.
1818
#[derive(Debug)]
@@ -52,31 +52,45 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for Android
5252
&self.window
5353
}
5454

55-
/// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`].
56-
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
55+
#[inline]
56+
fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool {
57+
matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored)
58+
}
59+
60+
/// Also changes the pixel format.
61+
fn configure(
62+
&mut self,
63+
width: NonZeroU32,
64+
height: NonZeroU32,
65+
alpha_mode: AlphaMode,
66+
) -> Result<(), SoftBufferError> {
5767
let (width, height) = (|| {
5868
let width = NonZeroI32::try_from(width).ok()?;
5969
let height = NonZeroI32::try_from(height).ok()?;
6070
Some((width, height))
6171
})()
6272
.ok_or(SoftBufferError::SizeOutOfRange { width, height })?;
6373

74+
// Default is typically R5G6B5 16bpp, switch to 32bpp
75+
let pixel_format = match alpha_mode {
76+
AlphaMode::Opaque | AlphaMode::Ignored => HardwareBufferFormat::R8G8B8X8_UNORM,
77+
AlphaMode::Premultiplied => todo!("HardwareBufferFormat::R8G8B8A8_UNORM"),
78+
_ => unimplemented!(),
79+
};
80+
6481
self.native_window
65-
.set_buffers_geometry(
66-
width.into(),
67-
height.into(),
68-
// Default is typically R5G6B5 16bpp, switch to 32bpp
69-
Some(HardwareBufferFormat::R8G8B8X8_UNORM),
70-
)
82+
.set_buffers_geometry(width.into(), height.into(), Some(pixel_format))
7183
.map_err(|err| {
7284
SoftBufferError::PlatformError(
7385
Some("Failed to set buffer geometry on ANativeWindow".to_owned()),
7486
Some(Box::new(err)),
7587
)
76-
})
88+
})?;
89+
90+
Ok(())
7791
}
7892

79-
fn buffer_mut(&mut self) -> Result<BufferImpl<'_>, SoftBufferError> {
93+
fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> Result<BufferImpl<'_>, SoftBufferError> {
8094
let native_window_buffer = self.native_window.lock(None).map_err(|err| {
8195
SoftBufferError::PlatformError(
8296
Some("Failed to lock ANativeWindow".to_owned()),

0 commit comments

Comments
 (0)