Skip to content

Commit ad19d25

Browse files
viallykchibisov
authored andcommitted
api: make surfaceSend
1 parent 26b883b commit ad19d25

File tree

8 files changed

+368
-26
lines changed

8 files changed

+368
-26
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+
- Change `Surface` to be `Send`. This makes it consistent with the context, so now they are both `Send` but not `Sync`.
4+
35
# Version 0.31.2
46

57
- Fixed EGL not setting context version with EGL versions before 1.5 and missing context ext.

glutin/src/api/cgl/surface.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl Display {
8181
config: config.clone(),
8282
ns_view,
8383
ns_window,
84-
_nosendsync: PhantomData,
84+
_nosync: PhantomData,
8585
_ty: PhantomData,
8686
};
8787
Ok(surface)
@@ -94,10 +94,13 @@ pub struct Surface<T: SurfaceTypeTrait> {
9494
config: Config,
9595
pub(crate) ns_view: MainThreadBound<Id<NSView>>,
9696
ns_window: MainThreadBound<Id<NSWindow>>,
97-
_nosendsync: PhantomData<*const std::ffi::c_void>,
97+
_nosync: PhantomData<*const std::ffi::c_void>,
9898
_ty: PhantomData<T>,
9999
}
100100

101+
// Impl only `Send` for Surface.
102+
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}
103+
101104
impl<T: SurfaceTypeTrait> GlSurface<T> for Surface<T> {
102105
type Context = PossiblyCurrentContext;
103106
type SurfaceType = T;

glutin/src/api/egl/surface.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ pub struct Surface<T: SurfaceTypeTrait> {
252252
_ty: PhantomData<T>,
253253
}
254254

255+
// Impl only `Send` for Surface.
256+
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}
257+
255258
impl<T: SurfaceTypeTrait> Surface<T> {
256259
/// Swaps the underlying back buffers when the surface is not single
257260
/// buffered and pass the [`Rect`] information to the system

glutin/src/api/glx/surface.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ pub struct Surface<T: SurfaceTypeTrait> {
159159
_ty: PhantomData<T>,
160160
}
161161

162+
// Impl only `Send` for Surface.
163+
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}
164+
162165
impl<T: SurfaceTypeTrait> Surface<T> {
163166
/// # Safety
164167
///

glutin/src/api/wgl/surface.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ pub struct Surface<T: SurfaceTypeTrait> {
8181
_ty: PhantomData<T>,
8282
}
8383

84+
// Impl only `Send` for Surface.
85+
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}
86+
8487
impl<T: SurfaceTypeTrait> Drop for Surface<T> {
8588
fn drop(&mut self) {
8689
unsafe {

glutin/src/surface.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,11 @@ pub enum SurfaceType {
262262

263263
/// The GL surface that is used for rendering.
264264
///
265-
/// The GL surface is not thread safe, it can neither be [`Send`] nor [`Sync`],
266-
/// so it should be created on the thread it'll be used to render.
265+
/// Similar to the context, the GL surface is [`Send`] but not [`Sync`]. This
266+
/// means it could be sent to a different thread as long as it is not current on
267+
/// another thread.
267268
///
268-
/// ```compile_fail
269+
/// ```no_run
269270
/// fn test_send<T: Send>() {}
270271
/// test_send::<glutin::surface::Surface<glutin::surface::WindowSurface>>();
271272
/// ```
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
use std::error::Error;
2+
use std::num::NonZeroU32;
3+
use std::sync::mpsc::{self, Sender};
4+
use std::sync::{Arc, Mutex};
5+
use std::thread;
6+
7+
use glutin::config::ConfigTemplateBuilder;
8+
use glutin::context::{ContextAttributesBuilder, PossiblyCurrentContext};
9+
use glutin::display::GetGlDisplay;
10+
use glutin::error::{Error as GlutinError, ErrorKind};
11+
use glutin::prelude::{NotCurrentGlContext, PossiblyCurrentGlContext, *};
12+
use glutin::surface::{GlSurface, Surface, WindowSurface};
13+
use glutin_examples::gl::types::GLfloat;
14+
use glutin_examples::{gl_config_picker, Renderer};
15+
use glutin_winit::{self, DisplayBuilder, GlWindow};
16+
use raw_window_handle::HasRawWindowHandle;
17+
use winit::dpi::PhysicalSize;
18+
use winit::event::{ElementState, Event, WindowEvent};
19+
use winit::event_loop::{EventLoopBuilder, EventLoopProxy};
20+
use winit::window::{Window, WindowBuilder};
21+
22+
fn main() -> Result<(), Box<dyn Error>> {
23+
let event_loop = EventLoopBuilder::<PlatformThreadEvent>::with_user_event().build().unwrap();
24+
25+
let (_window, render_context) = create_window_with_render_context(&event_loop)?;
26+
let render_context = Arc::new(Mutex::new(render_context));
27+
28+
// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
29+
// loop from any thread.
30+
let event_loop_proxy = event_loop.create_proxy();
31+
32+
let (_render_threads, render_thread_senders) =
33+
spawn_render_threads(render_context, event_loop_proxy);
34+
35+
let mut app_state = AppState {
36+
render_thread_senders,
37+
render_thread_index: 0,
38+
thread_switch_in_progress: false,
39+
};
40+
app_state.send_event_to_current_render_thread(RenderThreadEvent::MakeCurrent);
41+
42+
event_loop.run(move |event, elwt| match event {
43+
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => elwt.exit(),
44+
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
45+
if size.width != 0 && size.height != 0 {
46+
app_state.send_event_to_current_render_thread(RenderThreadEvent::Resize(
47+
PhysicalSize {
48+
width: NonZeroU32::new(size.width).unwrap(),
49+
height: NonZeroU32::new(size.height).unwrap(),
50+
},
51+
));
52+
}
53+
},
54+
Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => {
55+
app_state.send_event_to_current_render_thread(RenderThreadEvent::Draw);
56+
},
57+
Event::WindowEvent {
58+
event: WindowEvent::MouseInput { state: ElementState::Pressed, .. },
59+
..
60+
} => {
61+
app_state.start_render_thread_switch();
62+
},
63+
Event::UserEvent(event) => match event {
64+
PlatformThreadEvent::ContextNotCurrent => {
65+
app_state.complete_render_thread_switch();
66+
},
67+
},
68+
_ => (),
69+
})?;
70+
71+
Ok(())
72+
}
73+
74+
struct AppState {
75+
render_thread_senders: Vec<Sender<RenderThreadEvent>>,
76+
render_thread_index: usize,
77+
thread_switch_in_progress: bool,
78+
}
79+
80+
impl AppState {
81+
fn send_event_to_current_render_thread(&self, event: RenderThreadEvent) {
82+
if self.thread_switch_in_progress {
83+
return;
84+
}
85+
86+
if let Some(tx) = self.render_thread_senders.get(self.render_thread_index) {
87+
tx.send(event).expect("sending event to current render thread failed");
88+
}
89+
}
90+
91+
fn start_render_thread_switch(&mut self) {
92+
self.send_event_to_current_render_thread(RenderThreadEvent::MakeNotCurrent);
93+
94+
self.thread_switch_in_progress = true;
95+
}
96+
97+
fn complete_render_thread_switch(&mut self) {
98+
self.thread_switch_in_progress = false;
99+
100+
self.render_thread_index += 1;
101+
if self.render_thread_index == self.render_thread_senders.len() {
102+
self.render_thread_index = 0;
103+
}
104+
105+
self.send_event_to_current_render_thread(RenderThreadEvent::MakeCurrent);
106+
self.send_event_to_current_render_thread(RenderThreadEvent::Draw);
107+
}
108+
}
109+
110+
/// A rendering context that can be shared between tasks.
111+
struct RenderContext {
112+
context: Option<PossiblyCurrentContext>,
113+
surface: Surface<WindowSurface>,
114+
renderer: Renderer,
115+
}
116+
117+
unsafe impl Send for RenderContext {}
118+
119+
impl RenderContext {
120+
fn new(
121+
context: PossiblyCurrentContext,
122+
surface: Surface<WindowSurface>,
123+
renderer: Renderer,
124+
) -> Self {
125+
Self { context: Some(context), surface, renderer }
126+
}
127+
128+
fn make_current(&mut self) -> Result<(), impl Error> {
129+
let ctx =
130+
self.context.take().ok_or_else(|| GlutinError::from(ErrorKind::BadContextState))?;
131+
let result = ctx.make_current(&self.surface);
132+
self.context = Some(ctx);
133+
result
134+
}
135+
136+
fn make_not_current(&mut self) -> Result<(), impl Error> {
137+
let ctx =
138+
self.context.take().ok_or_else(|| GlutinError::from(ErrorKind::BadContextState))?;
139+
let not_current_ctx = ctx.make_not_current()?;
140+
self.context = Some(not_current_ctx.treat_as_possibly_current());
141+
Ok::<(), GlutinError>(())
142+
}
143+
144+
fn swap_buffers(&mut self) -> Result<(), impl Error> {
145+
let ctx =
146+
self.context.take().ok_or_else(|| GlutinError::from(ErrorKind::BadContextState))?;
147+
let result = self.surface.swap_buffers(&ctx);
148+
self.context = Some(ctx);
149+
result
150+
}
151+
152+
fn draw_with_clear_color(&self, red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat) {
153+
self.renderer.draw_with_clear_color(red, green, blue, alpha)
154+
}
155+
156+
fn resize(&mut self, size: PhysicalSize<NonZeroU32>) {
157+
let Some(ctx) = self.context.take() else {
158+
return;
159+
};
160+
self.surface.resize(&ctx, size.width, size.height);
161+
self.context = Some(ctx);
162+
163+
self.renderer.resize(size.width.get() as i32, size.height.get() as i32);
164+
}
165+
}
166+
167+
fn create_window_with_render_context(
168+
event_loop: &winit::event_loop::EventLoop<PlatformThreadEvent>,
169+
) -> Result<(Window, RenderContext), Box<dyn Error>> {
170+
let window_builder = WindowBuilder::new().with_transparent(true);
171+
172+
let template = ConfigTemplateBuilder::new().with_alpha_size(8);
173+
174+
let display_builder = DisplayBuilder::new().with_window_builder(Some(window_builder));
175+
176+
let (mut window, gl_config) = display_builder.build(event_loop, template, gl_config_picker)?;
177+
178+
println!("Picked a config with {} samples", gl_config.num_samples());
179+
180+
let raw_window_handle = window.as_ref().map(|window| window.raw_window_handle());
181+
182+
let window = window.take().unwrap();
183+
184+
let gl_display = gl_config.display();
185+
186+
let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle);
187+
188+
let not_current_gl_context = unsafe {
189+
gl_display
190+
.create_context(&gl_config, &context_attributes)
191+
.expect("failed to create context")
192+
};
193+
194+
let attrs = window.build_surface_attributes(<_>::default());
195+
let gl_surface =
196+
unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() };
197+
198+
// Make it current.
199+
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
200+
201+
// The context needs to be current for the Renderer to set up shaders and
202+
// buffers. It also performs function loading, which needs a current context on
203+
// WGL.
204+
let renderer = Renderer::new(&gl_display);
205+
206+
let gl_context = gl_context.make_not_current().unwrap().treat_as_possibly_current();
207+
208+
Ok((window, RenderContext::new(gl_context, gl_surface, renderer)))
209+
}
210+
211+
fn spawn_render_threads(
212+
render_context: Arc<Mutex<RenderContext>>,
213+
event_loop_proxy: EventLoopProxy<PlatformThreadEvent>,
214+
) -> (Vec<RenderThread>, Vec<Sender<RenderThreadEvent>>) {
215+
let mut senders = Vec::new();
216+
let mut render_threads = Vec::new();
217+
218+
for id in 0..3 {
219+
let render_thread = RenderThread::new(id, render_context.clone());
220+
let tx = render_thread.spawn(event_loop_proxy.clone());
221+
222+
render_threads.push(render_thread);
223+
senders.push(tx);
224+
}
225+
226+
(render_threads, senders)
227+
}
228+
229+
#[derive(Debug, Clone, Copy, Default)]
230+
struct Color {
231+
r: GLfloat,
232+
g: GLfloat,
233+
b: GLfloat,
234+
a: GLfloat,
235+
}
236+
237+
impl Color {
238+
fn new(r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat) -> Self {
239+
Self { r, g, b, a }
240+
}
241+
242+
fn new_from_index(color_index: i32) -> Self {
243+
match color_index {
244+
0 => Color::new(1.0, 0.0, 0.0, 0.9),
245+
1 => Color::new(0.0, 1.0, 0.0, 0.9),
246+
2 => Color::new(0.0, 0.0, 1.0, 0.9),
247+
_ => Default::default(),
248+
}
249+
}
250+
}
251+
252+
#[derive(Debug, Clone, Copy)]
253+
enum RenderThreadEvent {
254+
Draw,
255+
MakeCurrent,
256+
MakeNotCurrent,
257+
Resize(PhysicalSize<NonZeroU32>),
258+
}
259+
260+
#[derive(Debug, Clone, Copy)]
261+
enum PlatformThreadEvent {
262+
ContextNotCurrent,
263+
}
264+
265+
struct RenderThread {
266+
id: i32,
267+
color: Color,
268+
render_context: Arc<Mutex<RenderContext>>,
269+
}
270+
271+
impl RenderThread {
272+
fn new(id: i32, render_context: Arc<Mutex<RenderContext>>) -> Self {
273+
let color = Color::new_from_index(id);
274+
Self { id, color, render_context }
275+
}
276+
277+
fn spawn(
278+
&self,
279+
event_loop_proxy: EventLoopProxy<PlatformThreadEvent>,
280+
) -> Sender<RenderThreadEvent> {
281+
let (tx, rx) = mpsc::channel();
282+
283+
let (id, color, render_context) = (self.id, self.color, self.render_context.clone());
284+
285+
thread::spawn(move || {
286+
for event in rx {
287+
let mut render_context_guard = render_context.lock().unwrap();
288+
289+
match event {
290+
RenderThreadEvent::Draw => {
291+
println!("thread {}: drawing", id);
292+
render_context_guard
293+
.draw_with_clear_color(color.r, color.g, color.b, color.a);
294+
render_context_guard.swap_buffers().expect("swap buffers failed");
295+
},
296+
RenderThreadEvent::MakeCurrent => {
297+
println!("thread {}: make current", id);
298+
render_context_guard.make_current().expect("make current failed");
299+
},
300+
RenderThreadEvent::MakeNotCurrent => {
301+
println!("thread {}: make not current", id);
302+
render_context_guard.make_not_current().expect("make not current failed");
303+
event_loop_proxy
304+
.send_event(PlatformThreadEvent::ContextNotCurrent)
305+
.expect("sending context-not-current event failed");
306+
},
307+
RenderThreadEvent::Resize(size) => {
308+
render_context_guard.resize(size);
309+
},
310+
}
311+
}
312+
});
313+
314+
tx
315+
}
316+
}

0 commit comments

Comments
 (0)