Skip to content

Commit ebf31d1

Browse files
committed
Add AlphaMode and transparency
1 parent b636a35 commit ebf31d1

File tree

18 files changed

+657
-134
lines changed

18 files changed

+657
-134
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: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//! An example to test transparent rendering.
2+
//!
3+
//! Press `o`, `i`, `m` or `t` 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+
use softbuffer::{AlphaMode, Context, Pixel, Surface};
39+
use std::num::NonZeroU32;
40+
use winit::event::{ElementState, KeyEvent, WindowEvent};
41+
use winit::event_loop::{ControlFlow, EventLoop};
42+
use winit::keyboard::{Key, NamedKey};
43+
44+
mod util;
45+
46+
fn main() {
47+
util::setup();
48+
49+
let event_loop = EventLoop::new().unwrap();
50+
51+
let context = Context::new(event_loop.owned_display_handle()).unwrap();
52+
53+
let app = util::WinitAppBuilder::with_init(
54+
|elwt| util::make_window(elwt, |w| w),
55+
move |_elwt, window| Surface::new(&context, window.clone()).unwrap(),
56+
)
57+
.with_event_handler(|window, surface, window_id, event, elwt| {
58+
elwt.set_control_flow(ControlFlow::Wait);
59+
60+
if window_id != window.id() {
61+
return;
62+
}
63+
64+
match event {
65+
WindowEvent::Resized(size) => {
66+
let Some(surface) = surface else {
67+
tracing::error!("Resized fired before Resumed or after Suspended");
68+
return;
69+
};
70+
71+
if let (Some(width), Some(height)) =
72+
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
73+
{
74+
surface.resize(width, height).unwrap();
75+
}
76+
}
77+
WindowEvent::RedrawRequested => {
78+
let Some(surface) = surface else {
79+
tracing::error!("RedrawRequested fired before Resumed or after Suspended");
80+
return;
81+
};
82+
83+
tracing::info!(alpha_mode = ?surface.alpha_mode(), "redraw");
84+
85+
let alpha_mode = surface.alpha_mode();
86+
let mut buffer = surface.buffer_mut().unwrap();
87+
let width = buffer.width().get();
88+
for (x, _, pixel) in buffer.pixels_iter() {
89+
let rectangle_number = (x * 6) / width;
90+
*pixel = match rectangle_number {
91+
0 => Pixel::new_rgba(255, 127, 0, 255),
92+
1 => Pixel::new_rgba(255, 255, 0, 127),
93+
2 => Pixel::new_rgba(127, 127, 0, 127),
94+
3 => Pixel::new_rgba(255, 127, 0, 127),
95+
4 => Pixel::new_rgba(255, 127, 0, 0),
96+
_ => Pixel::new_rgba(0, 0, 0, 0),
97+
};
98+
99+
// Convert `AlphaMode::Opaque` -> `AlphaMode::Ignored`.
100+
if alpha_mode == AlphaMode::Opaque {
101+
pixel.a = 255;
102+
};
103+
}
104+
105+
buffer.present().unwrap();
106+
}
107+
WindowEvent::CloseRequested
108+
| WindowEvent::KeyboardInput {
109+
event:
110+
KeyEvent {
111+
logical_key: Key::Named(NamedKey::Escape),
112+
repeat: false,
113+
..
114+
},
115+
..
116+
} => {
117+
elwt.exit();
118+
}
119+
WindowEvent::KeyboardInput {
120+
event:
121+
KeyEvent {
122+
logical_key,
123+
repeat: false,
124+
state: ElementState::Pressed,
125+
..
126+
},
127+
..
128+
} => {
129+
let Some(surface) = surface else {
130+
tracing::error!("KeyboardInput fired before Resumed or after Suspended");
131+
return;
132+
};
133+
134+
let alpha_mode = match logical_key.to_text() {
135+
Some("o") => AlphaMode::Opaque,
136+
Some("i") => AlphaMode::Ignored,
137+
Some("m") => AlphaMode::Premultiplied,
138+
Some("t") => AlphaMode::Postmultiplied,
139+
_ => return,
140+
};
141+
142+
if !surface.supports_alpha_mode(alpha_mode) {
143+
tracing::warn!(?alpha_mode, "not supported by the backend");
144+
return;
145+
}
146+
147+
tracing::info!(?alpha_mode, "set alpha");
148+
let size = window.inner_size();
149+
let width = NonZeroU32::new(size.width).unwrap();
150+
let height = NonZeroU32::new(size.height).unwrap();
151+
surface.configure(width, height, alpha_mode).unwrap();
152+
assert_eq!(surface.alpha_mode(), alpha_mode);
153+
154+
window.set_transparent(matches!(
155+
alpha_mode,
156+
AlphaMode::Premultiplied | AlphaMode::Postmultiplied
157+
));
158+
159+
window.request_redraw();
160+
}
161+
_ => {}
162+
}
163+
});
164+
165+
util::run_app(event_loop, app);
166+
}

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()),

src/backends/cg.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Softbuffer implementation using CoreGraphics.
2-
use crate::backend_interface::*;
32
use crate::error::InitError;
3+
use crate::{backend_interface::*, AlphaMode};
44
use crate::{util, Pixel, Rect, SoftBufferError};
55
use objc2::rc::Retained;
66
use objc2::runtime::{AnyObject, Bool};
@@ -252,19 +252,42 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for CGImpl<
252252
&self.window_handle
253253
}
254254

255-
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
255+
#[inline]
256+
fn supports_alpha_mode(&self, _alpha_mode: AlphaMode) -> bool {
257+
true
258+
}
259+
260+
fn configure(
261+
&mut self,
262+
width: NonZeroU32,
263+
height: NonZeroU32,
264+
alpha_mode: AlphaMode,
265+
) -> Result<(), SoftBufferError> {
266+
let opaque = matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored);
267+
self.layer.setOpaque(opaque);
268+
// TODO: Set opaque-ness on root layer too? Is that our responsibility, or Winit's?
269+
// self.root_layer.setOpaque(opaque);
270+
256271
self.width = width.get() as usize;
257272
self.height = height.get() as usize;
258273
Ok(())
259274
}
260275

261-
fn buffer_mut(&mut self) -> Result<BufferImpl<'_>, SoftBufferError> {
276+
fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> Result<BufferImpl<'_>, SoftBufferError> {
262277
let buffer_size = util::byte_stride(self.width as u32) as usize * self.height / 4;
263278
Ok(BufferImpl {
264279
buffer: util::PixelBuffer(vec![Pixel::default(); buffer_size]),
265280
width: self.width,
266281
height: self.height,
267282
color_space: &self.color_space,
283+
alpha_info: match (alpha_mode, cfg!(target_endian = "little")) {
284+
(AlphaMode::Opaque | AlphaMode::Ignored, true) => CGImageAlphaInfo::NoneSkipFirst,
285+
(AlphaMode::Opaque | AlphaMode::Ignored, false) => CGImageAlphaInfo::NoneSkipLast,
286+
(AlphaMode::Premultiplied, true) => CGImageAlphaInfo::PremultipliedFirst,
287+
(AlphaMode::Premultiplied, false) => CGImageAlphaInfo::PremultipliedLast,
288+
(AlphaMode::Postmultiplied, true) => CGImageAlphaInfo::First,
289+
(AlphaMode::Postmultiplied, false) => CGImageAlphaInfo::Last,
290+
},
268291
layer: &mut self.layer,
269292
})
270293
}
@@ -276,6 +299,7 @@ pub struct BufferImpl<'a> {
276299
height: usize,
277300
color_space: &'a CGColorSpace,
278301
buffer: util::PixelBuffer,
302+
alpha_info: CGImageAlphaInfo,
279303
layer: &'a mut SendCALayer,
280304
}
281305

@@ -331,9 +355,9 @@ impl BufferInterface for BufferImpl<'_> {
331355
//
332356
// TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released.
333357
let bitmap_info = CGBitmapInfo(
334-
CGImageAlphaInfo::NoneSkipFirst.0
358+
self.alpha_info.0
335359
| CGImageComponentInfo::Integer.0
336-
| CGImageByteOrderInfo::Order32Little.0
360+
| CGImageByteOrderInfo::Order32Host.0
337361
| CGImagePixelFormatInfo::Packed.0,
338362
);
339363

0 commit comments

Comments
 (0)