|
| 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