diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a5cccb40..54df635e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/glutin/src/api/cgl/surface.rs b/glutin/src/api/cgl/surface.rs index 31d1b6d2d5..7dcebb63f1 100644 --- a/glutin/src/api/cgl/surface.rs +++ b/glutin/src/api/cgl/surface.rs @@ -81,7 +81,7 @@ impl Display { config: config.clone(), ns_view, ns_window, - _nosendsync: PhantomData, + _nosync: PhantomData, _ty: PhantomData, }; Ok(surface) @@ -94,10 +94,13 @@ pub struct Surface { config: Config, pub(crate) ns_view: MainThreadBound>, ns_window: MainThreadBound>, - _nosendsync: PhantomData<*const std::ffi::c_void>, + _nosync: PhantomData<*const std::ffi::c_void>, _ty: PhantomData, } +// Impl only `Send` for Surface. +unsafe impl Send for Surface {} + impl GlSurface for Surface { type Context = PossiblyCurrentContext; type SurfaceType = T; diff --git a/glutin/src/api/egl/surface.rs b/glutin/src/api/egl/surface.rs index 102c74077e..7a13d69c4c 100644 --- a/glutin/src/api/egl/surface.rs +++ b/glutin/src/api/egl/surface.rs @@ -252,6 +252,9 @@ pub struct Surface { _ty: PhantomData, } +// Impl only `Send` for Surface. +unsafe impl Send for Surface {} + impl Surface { /// Swaps the underlying back buffers when the surface is not single /// buffered and pass the [`Rect`] information to the system diff --git a/glutin/src/api/glx/surface.rs b/glutin/src/api/glx/surface.rs index aeaa42386d..0026f34168 100644 --- a/glutin/src/api/glx/surface.rs +++ b/glutin/src/api/glx/surface.rs @@ -159,6 +159,9 @@ pub struct Surface { _ty: PhantomData, } +// Impl only `Send` for Surface. +unsafe impl Send for Surface {} + impl Surface { /// # Safety /// diff --git a/glutin/src/api/wgl/surface.rs b/glutin/src/api/wgl/surface.rs index fb3c204666..77cdbc16c6 100644 --- a/glutin/src/api/wgl/surface.rs +++ b/glutin/src/api/wgl/surface.rs @@ -81,6 +81,9 @@ pub struct Surface { _ty: PhantomData, } +// Impl only `Send` for Surface. +unsafe impl Send for Surface {} + impl Drop for Surface { fn drop(&mut self) { unsafe { diff --git a/glutin/src/surface.rs b/glutin/src/surface.rs index cdde086a04..e7f6aa2b93 100644 --- a/glutin/src/surface.rs +++ b/glutin/src/surface.rs @@ -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() {} /// test_send::>(); /// ``` diff --git a/glutin_examples/examples/switch_render_thread.rs b/glutin_examples/examples/switch_render_thread.rs new file mode 100644 index 0000000000..672f04f369 --- /dev/null +++ b/glutin_examples/examples/switch_render_thread.rs @@ -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> { + let event_loop = EventLoopBuilder::::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>, + 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, + surface: Surface, + renderer: Renderer, +} + +unsafe impl Send for RenderContext {} + +impl RenderContext { + fn new( + context: PossiblyCurrentContext, + surface: Surface, + 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) { + 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, +) -> Result<(Window, RenderContext), Box> { + 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>, + event_loop_proxy: EventLoopProxy, +) -> (Vec, Vec>) { + 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), +} + +#[derive(Debug, Clone, Copy)] +enum PlatformThreadEvent { + ContextNotCurrent, +} + +struct RenderThread { + id: i32, + color: Color, + render_context: Arc>, +} + +impl RenderThread { + fn new(id: i32, render_context: Arc>) -> Self { + let color = Color::new_from_index(id); + Self { id, color, render_context } + } + + fn spawn( + &self, + event_loop_proxy: EventLoopProxy, + ) -> Sender { + 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 + } +} diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index 059df51b48..665de5601e 100644 --- a/glutin_examples/src/lib.rs +++ b/glutin_examples/src/lib.rs @@ -3,11 +3,12 @@ use std::ffi::{CStr, CString}; use std::num::NonZeroU32; use std::ops::Deref; +use gl::types::GLfloat; use raw_window_handle::HasRawWindowHandle; use winit::event::{Event, WindowEvent}; use winit::window::WindowBuilder; -use glutin::config::ConfigTemplateBuilder; +use glutin::config::{Config, ConfigTemplateBuilder}; use glutin::context::{ContextApi, ContextAttributesBuilder, Version}; use glutin::display::GetGlDisplay; use glutin::prelude::*; @@ -44,22 +45,7 @@ pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box accum.num_samples() { - config - } else { - accum - } - }) - .unwrap() - })?; + let (mut window, gl_config) = display_builder.build(&event_loop, template, gl_config_picker)?; println!("Picked a config with {} samples", gl_config.num_samples()); @@ -69,9 +55,7 @@ pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box) -> Result<(), Box + '_>) -> Config { + configs + .reduce(|accum, config| { + let transparency_check = config.supports_transparency().unwrap_or(false) + & !accum.supports_transparency().unwrap_or(false); + + if transparency_check || config.num_samples() > accum.num_samples() { + config + } else { + accum + } + }) + .unwrap() +} + pub struct Renderer { program: gl::types::GLuint, vao: gl::types::GLuint, @@ -265,13 +266,23 @@ impl Renderer { } pub fn draw(&self) { + self.draw_with_clear_color(0.1, 0.1, 0.1, 0.9) + } + + pub fn draw_with_clear_color( + &self, + red: GLfloat, + green: GLfloat, + blue: GLfloat, + alpha: GLfloat, + ) { unsafe { self.gl.UseProgram(self.program); self.gl.BindVertexArray(self.vao); self.gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo); - self.gl.ClearColor(0.1, 0.1, 0.1, 0.9); + self.gl.ClearColor(red, green, blue, alpha); self.gl.Clear(gl::COLOR_BUFFER_BIT); self.gl.DrawArrays(gl::TRIANGLES, 0, 3); }