Skip to content

Commit

Permalink
api: make surfaceSend
Browse files Browse the repository at this point in the history
  • Loading branch information
vially authored Feb 12, 2024
1 parent 719671f commit 7c93de9
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Change `Surface` to be `Send`. This makes it consistent with the context, so now they are both `Send` but not `Sync`.

# Version 0.31.2

- Fixed EGL not setting context version with EGL versions before 1.5 and missing context ext.
Expand Down
7 changes: 5 additions & 2 deletions glutin/src/api/cgl/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl Display {
config: config.clone(),
ns_view,
ns_window,
_nosendsync: PhantomData,
_nosync: PhantomData,
_ty: PhantomData,
};
Ok(surface)
Expand All @@ -94,10 +94,13 @@ pub struct Surface<T: SurfaceTypeTrait> {
config: Config,
pub(crate) ns_view: MainThreadBound<Id<NSView>>,
ns_window: MainThreadBound<Id<NSWindow>>,
_nosendsync: PhantomData<*const std::ffi::c_void>,
_nosync: PhantomData<*const std::ffi::c_void>,
_ty: PhantomData<T>,
}

// Impl only `Send` for Surface.
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}

impl<T: SurfaceTypeTrait> GlSurface<T> for Surface<T> {
type Context = PossiblyCurrentContext;
type SurfaceType = T;
Expand Down
3 changes: 3 additions & 0 deletions glutin/src/api/egl/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ pub struct Surface<T: SurfaceTypeTrait> {
_ty: PhantomData<T>,
}

// Impl only `Send` for Surface.
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}

impl<T: SurfaceTypeTrait> Surface<T> {
/// Swaps the underlying back buffers when the surface is not single
/// buffered and pass the [`Rect`] information to the system
Expand Down
3 changes: 3 additions & 0 deletions glutin/src/api/glx/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ pub struct Surface<T: SurfaceTypeTrait> {
_ty: PhantomData<T>,
}

// Impl only `Send` for Surface.
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}

impl<T: SurfaceTypeTrait> Surface<T> {
/// # Safety
///
Expand Down
3 changes: 3 additions & 0 deletions glutin/src/api/wgl/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub struct Surface<T: SurfaceTypeTrait> {
_ty: PhantomData<T>,
}

// Impl only `Send` for Surface.
unsafe impl<T: SurfaceTypeTrait> Send for Surface<T> {}

impl<T: SurfaceTypeTrait> Drop for Surface<T> {
fn drop(&mut self) {
unsafe {
Expand Down
7 changes: 4 additions & 3 deletions glutin/src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,11 @@ pub enum SurfaceType {

/// The GL surface that is used for rendering.
///
/// The GL surface is not thread safe, it can neither be [`Send`] nor [`Sync`],
/// so it should be created on the thread it'll be used to render.
/// Similar to the context, the GL surface is [`Send`] but not [`Sync`]. This
/// means it could be sent to a different thread as long as it is not current on
/// another thread.
///
/// ```compile_fail
/// ```no_run
/// fn test_send<T: Send>() {}
/// test_send::<glutin::surface::Surface<glutin::surface::WindowSurface>>();
/// ```
Expand Down
316 changes: 316 additions & 0 deletions glutin_examples/examples/switch_render_thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
use std::error::Error;
use std::num::NonZeroU32;
use std::sync::mpsc::{self, Sender};
use std::sync::{Arc, Mutex};
use std::thread;

use glutin::config::ConfigTemplateBuilder;
use glutin::context::{ContextAttributesBuilder, PossiblyCurrentContext};
use glutin::display::GetGlDisplay;
use glutin::error::{Error as GlutinError, ErrorKind};
use glutin::prelude::{NotCurrentGlContext, PossiblyCurrentGlContext, *};
use glutin::surface::{GlSurface, Surface, WindowSurface};
use glutin_examples::gl::types::GLfloat;
use glutin_examples::{gl_config_picker, Renderer};
use glutin_winit::{self, DisplayBuilder, GlWindow};
use raw_window_handle::HasRawWindowHandle;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, Event, WindowEvent};
use winit::event_loop::{EventLoopBuilder, EventLoopProxy};
use winit::window::{Window, WindowBuilder};

fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoopBuilder::<PlatformThreadEvent>::with_user_event().build().unwrap();

let (_window, render_context) = create_window_with_render_context(&event_loop)?;
let render_context = Arc::new(Mutex::new(render_context));

// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
// loop from any thread.
let event_loop_proxy = event_loop.create_proxy();

let (_render_threads, render_thread_senders) =
spawn_render_threads(render_context, event_loop_proxy);

let mut app_state = AppState {
render_thread_senders,
render_thread_index: 0,
thread_switch_in_progress: false,
};
app_state.send_event_to_current_render_thread(RenderThreadEvent::MakeCurrent);

event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => elwt.exit(),
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
if size.width != 0 && size.height != 0 {
app_state.send_event_to_current_render_thread(RenderThreadEvent::Resize(
PhysicalSize {
width: NonZeroU32::new(size.width).unwrap(),
height: NonZeroU32::new(size.height).unwrap(),
},
));
}
},
Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => {
app_state.send_event_to_current_render_thread(RenderThreadEvent::Draw);
},
Event::WindowEvent {
event: WindowEvent::MouseInput { state: ElementState::Pressed, .. },
..
} => {
app_state.start_render_thread_switch();
},
Event::UserEvent(event) => match event {
PlatformThreadEvent::ContextNotCurrent => {
app_state.complete_render_thread_switch();
},
},
_ => (),
})?;

Ok(())
}

struct AppState {
render_thread_senders: Vec<Sender<RenderThreadEvent>>,
render_thread_index: usize,
thread_switch_in_progress: bool,
}

impl AppState {
fn send_event_to_current_render_thread(&self, event: RenderThreadEvent) {
if self.thread_switch_in_progress {
return;
}

if let Some(tx) = self.render_thread_senders.get(self.render_thread_index) {
tx.send(event).expect("sending event to current render thread failed");
}
}

fn start_render_thread_switch(&mut self) {
self.send_event_to_current_render_thread(RenderThreadEvent::MakeNotCurrent);

self.thread_switch_in_progress = true;
}

fn complete_render_thread_switch(&mut self) {
self.thread_switch_in_progress = false;

self.render_thread_index += 1;
if self.render_thread_index == self.render_thread_senders.len() {
self.render_thread_index = 0;
}

self.send_event_to_current_render_thread(RenderThreadEvent::MakeCurrent);
self.send_event_to_current_render_thread(RenderThreadEvent::Draw);
}
}

/// A rendering context that can be shared between tasks.
struct RenderContext {
context: Option<PossiblyCurrentContext>,
surface: Surface<WindowSurface>,
renderer: Renderer,
}

unsafe impl Send for RenderContext {}

impl RenderContext {
fn new(
context: PossiblyCurrentContext,
surface: Surface<WindowSurface>,
renderer: Renderer,
) -> Self {
Self { context: Some(context), surface, renderer }
}

fn make_current(&mut self) -> Result<(), impl Error> {
let ctx =
self.context.take().ok_or_else(|| GlutinError::from(ErrorKind::BadContextState))?;
let result = ctx.make_current(&self.surface);
self.context = Some(ctx);
result
}

fn make_not_current(&mut self) -> Result<(), impl Error> {
let ctx =
self.context.take().ok_or_else(|| GlutinError::from(ErrorKind::BadContextState))?;
let not_current_ctx = ctx.make_not_current()?;
self.context = Some(not_current_ctx.treat_as_possibly_current());
Ok::<(), GlutinError>(())
}

fn swap_buffers(&mut self) -> Result<(), impl Error> {
let ctx =
self.context.take().ok_or_else(|| GlutinError::from(ErrorKind::BadContextState))?;
let result = self.surface.swap_buffers(&ctx);
self.context = Some(ctx);
result
}

fn draw_with_clear_color(&self, red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat) {
self.renderer.draw_with_clear_color(red, green, blue, alpha)
}

fn resize(&mut self, size: PhysicalSize<NonZeroU32>) {
let Some(ctx) = self.context.take() else {
return;
};
self.surface.resize(&ctx, size.width, size.height);
self.context = Some(ctx);

self.renderer.resize(size.width.get() as i32, size.height.get() as i32);
}
}

fn create_window_with_render_context(
event_loop: &winit::event_loop::EventLoop<PlatformThreadEvent>,
) -> Result<(Window, RenderContext), Box<dyn Error>> {
let window_builder = WindowBuilder::new().with_transparent(true);

let template = ConfigTemplateBuilder::new().with_alpha_size(8);

let display_builder = DisplayBuilder::new().with_window_builder(Some(window_builder));

let (mut window, gl_config) = display_builder.build(event_loop, template, gl_config_picker)?;

println!("Picked a config with {} samples", gl_config.num_samples());

let raw_window_handle = window.as_ref().map(|window| window.raw_window_handle());

let window = window.take().unwrap();

let gl_display = gl_config.display();

let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle);

let not_current_gl_context = unsafe {
gl_display
.create_context(&gl_config, &context_attributes)
.expect("failed to create context")
};

let attrs = window.build_surface_attributes(<_>::default());
let gl_surface =
unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() };

// Make it current.
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();

// The context needs to be current for the Renderer to set up shaders and
// buffers. It also performs function loading, which needs a current context on
// WGL.
let renderer = Renderer::new(&gl_display);

let gl_context = gl_context.make_not_current().unwrap().treat_as_possibly_current();

Ok((window, RenderContext::new(gl_context, gl_surface, renderer)))
}

fn spawn_render_threads(
render_context: Arc<Mutex<RenderContext>>,
event_loop_proxy: EventLoopProxy<PlatformThreadEvent>,
) -> (Vec<RenderThread>, Vec<Sender<RenderThreadEvent>>) {
let mut senders = Vec::new();
let mut render_threads = Vec::new();

for id in 0..3 {
let render_thread = RenderThread::new(id, render_context.clone());
let tx = render_thread.spawn(event_loop_proxy.clone());

render_threads.push(render_thread);
senders.push(tx);
}

(render_threads, senders)
}

#[derive(Debug, Clone, Copy, Default)]
struct Color {
r: GLfloat,
g: GLfloat,
b: GLfloat,
a: GLfloat,
}

impl Color {
fn new(r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat) -> Self {
Self { r, g, b, a }
}

fn new_from_index(color_index: i32) -> Self {
match color_index {
0 => Color::new(1.0, 0.0, 0.0, 0.9),
1 => Color::new(0.0, 1.0, 0.0, 0.9),
2 => Color::new(0.0, 0.0, 1.0, 0.9),
_ => Default::default(),
}
}
}

#[derive(Debug, Clone, Copy)]
enum RenderThreadEvent {
Draw,
MakeCurrent,
MakeNotCurrent,
Resize(PhysicalSize<NonZeroU32>),
}

#[derive(Debug, Clone, Copy)]
enum PlatformThreadEvent {
ContextNotCurrent,
}

struct RenderThread {
id: i32,
color: Color,
render_context: Arc<Mutex<RenderContext>>,
}

impl RenderThread {
fn new(id: i32, render_context: Arc<Mutex<RenderContext>>) -> Self {
let color = Color::new_from_index(id);
Self { id, color, render_context }
}

fn spawn(
&self,
event_loop_proxy: EventLoopProxy<PlatformThreadEvent>,
) -> Sender<RenderThreadEvent> {
let (tx, rx) = mpsc::channel();

let (id, color, render_context) = (self.id, self.color, self.render_context.clone());

thread::spawn(move || {
for event in rx {
let mut render_context_guard = render_context.lock().unwrap();

match event {
RenderThreadEvent::Draw => {
println!("thread {}: drawing", id);
render_context_guard
.draw_with_clear_color(color.r, color.g, color.b, color.a);
render_context_guard.swap_buffers().expect("swap buffers failed");
},
RenderThreadEvent::MakeCurrent => {
println!("thread {}: make current", id);
render_context_guard.make_current().expect("make current failed");
},
RenderThreadEvent::MakeNotCurrent => {
println!("thread {}: make not current", id);
render_context_guard.make_not_current().expect("make not current failed");
event_loop_proxy
.send_event(PlatformThreadEvent::ContextNotCurrent)
.expect("sending context-not-current event failed");
},
RenderThreadEvent::Resize(size) => {
render_context_guard.resize(size);
},
}
}
});

tx
}
}
Loading

0 comments on commit 7c93de9

Please sign in to comment.