/*! Test supports module. */ #![allow(dead_code)] use glium::Display; use glium::backend::Facade; use glium::index::PrimitiveType; use glutin::config::{Config, ConfigTemplateBuilder}; use glutin::context::ContextAttributesBuilder; use glutin::display::GetGlDisplay; use glutin::prelude::*; use glutin::surface::{SurfaceAttributesBuilder, WindowSurface}; use glutin_winit::DisplayBuilder; use raw_window_handle::{HasWindowHandle, WindowHandle, RawWindowHandle}; use glium::winit::event::Event; use glium::winit::event_loop::{EventLoop, EventLoopProxy}; use glium::winit::window::Window; use std::env; use std::num::NonZeroU32; use std::sync::{mpsc::Receiver, Mutex, Once, RwLock}; use std::thread; // The code below here down to `build_display` is a workaround due to a lack of a test initialization hook // This sort of design is recommended against for applications // There is a Wayland version of this extension trait but the X11 version also works on Wayland #[cfg(unix)] use glium::winit::platform::x11::EventLoopBuilderExtX11; #[cfg(windows)] use glium::winit::platform::windows::EventLoopBuilderExtWindows; // Thread communication static EVENT_LOOP_PROXY: RwLock>> = RwLock::new(None); static WINDOW_RECEIVER: Mutex>> = Mutex::new(None); // Initialization static INIT_EVENT_LOOP: Once = Once::new(); static SEND_PROXY: Once = Once::new(); #[derive(Debug)] enum HandleOrWindow { SendHandle(WindowHandle<'static>), RefWindow(&'static Window), } impl From<&'static Window> for HandleOrWindow { fn from(window: &'static Window) -> Self { let window_handle = window.window_handle().unwrap(); match window_handle.as_raw() { RawWindowHandle::Xlib(_) | RawWindowHandle::Xcb(_) | RawWindowHandle::Drm(_) | RawWindowHandle::Win32(_) | RawWindowHandle::Web(_) => HandleOrWindow::SendHandle(window_handle), RawWindowHandle::UiKit(_) | RawWindowHandle::AppKit(_) | RawWindowHandle::Orbital(_) | RawWindowHandle::OhosNdk(_) | RawWindowHandle::Wayland(_) | RawWindowHandle::Gbm(_) | RawWindowHandle::WinRt(_) | RawWindowHandle::WebCanvas(_) | RawWindowHandle::WebOffscreenCanvas(_) | RawWindowHandle::AndroidNdk(_) | RawWindowHandle::Haiku(_) => HandleOrWindow::RefWindow(window), // Intentionally unsupported platforms _ => panic!("Unsupported"), } } } impl From for RawWindowHandle { fn from(handle: HandleOrWindow) -> Self { let handle = match handle { HandleOrWindow::SendHandle(handle) => handle, HandleOrWindow::RefWindow(window) => window.window_handle().unwrap(), }; handle.as_raw() } } // SAFETY // requires `From` implementation to be kept in sync with `raw_window_handle` and `winit` crates unsafe impl Send for HandleOrWindow {} unsafe fn initialize_event_loop() { INIT_EVENT_LOOP.call_once(|| { // One-time-use channel to get the event loop proxy let (ots, otr) = std::sync::mpsc::sync_channel(0); // Transfers window and config for creating display let (sender, receiver) = std::sync::mpsc::channel(); let builder = thread::Builder::new().name("event_loop".into()); builder .spawn(|| { let event_loop_res = if cfg!(unix) || cfg!(windows) { EventLoop::builder().with_any_thread(true).build() } else { EventLoop::builder().build() }; let event_loop = event_loop_res.expect("event loop building"); let proxy = event_loop.create_proxy(); #[allow(deprecated)] event_loop.run(move |event, window_target| { match event { Event::UserEvent(_) => { let window_attributes = Window::default_attributes().with_visible(false); let config_template_builder = ConfigTemplateBuilder::new(); let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes)); let (window, gl_config) = display_builder .build(window_target, config_template_builder, |mut configs| { // Just use the first configuration since we don't have any special preferences here configs.next().unwrap() }) .unwrap(); // Leak the window object to obtain a static reference let boxed_window = Box::new(window.unwrap()); let window = Box::leak(boxed_window); sender.send(((&*window).into(), gl_config)).unwrap(); } _ => { // Send event loop proxy ASAP SEND_PROXY.call_once(|| { ots.send(proxy.clone()).unwrap(); }); } } }) .unwrap(); }) .unwrap(); // `recv` will block until any non-user event is encountered let event_loop_proxy = otr.recv().unwrap(); // Write to the thread communication variables while still in `call_once`'s closure *EVENT_LOOP_PROXY.write().unwrap() = Some(event_loop_proxy); *WINDOW_RECEIVER.lock().unwrap() = Some(receiver); }); } /// Builds a display for tests. pub fn build_display() -> Display { // SAFETY // This is the first function to run when any test thread calls build_display. // `Once` spawns a new thread to create the event loop and sets up the communication channels. // The static mut variables are only ever read with synchronization after initialization. unsafe { initialize_event_loop(); } // Tell event loop to create a window and config for creating a display EVENT_LOOP_PROXY .read().unwrap() .as_ref().unwrap() .send_event(()).unwrap(); // Receive said window and config one thread at a time let (handle_or_window, gl_config) = WINDOW_RECEIVER .lock().unwrap() .as_ref().unwrap() .recv().unwrap(); // Then the configuration which decides which OpenGL version we'll end up using, here we just use the default which is currently 3.3 core // When this fails we'll try and create an ES context, this is mainly used on mobile devices or various ARM SBC's // If you depend on features available in modern OpenGL Versions you need to request a specific, modern, version. Otherwise things will very likely fail. let version = parse_version(); let raw_window_handle = handle_or_window.into(); let context_attributes = ContextAttributesBuilder::new() .with_context_api(version) .build(Some(raw_window_handle)); let not_current_gl_context = unsafe { gl_config.display().create_context(&gl_config, &context_attributes).unwrap() }; let attrs = SurfaceAttributesBuilder::::new().build( raw_window_handle, NonZeroU32::new(800).unwrap(), NonZeroU32::new(600).unwrap(), ); // Now we can create our surface, use it to make our context current and finally create our display let surface = unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() }; let current_context = not_current_gl_context.make_current(&surface).unwrap(); Display::from_context_surface(current_context, surface).unwrap() } /// Rebuilds an existing display. /// /// In real applications this is used for things such as switching to fullscreen. Some things are /// invalidated during a rebuild, and this has to be handled by glium. pub fn rebuild_display(_display: &glium::Display) { todo!(); /* let version = parse_version(); let event_loop = glium::winit::event_loop::EventLoop::new(); let wb = glium::winit::window::WindowBuilder::new().with_visible(false); let cb = glutin::ContextBuilder::new() .with_gl_debug_flag(true) .with_gl(version); display.rebuild(wb, cb, &event_loop).unwrap(); */ } fn parse_version() -> glutin::context::ContextApi { match env::var("GLIUM_GL_VERSION") { Ok(version) => { // expects "OpenGL 3.3" for example let mut iter = version.rsplitn(2, ' '); let version = iter.next().unwrap(); let ty = iter.next().unwrap(); let mut iter = version.split('.'); let major = iter.next().unwrap().parse().unwrap(); let minor = iter.next().unwrap().parse().unwrap(); if ty == "OpenGL" { glutin::context::ContextApi::OpenGl(Some(glutin::context::Version::new(major, minor))) } else if ty == "OpenGL ES" { glutin::context::ContextApi::Gles(Some(glutin::context::Version::new(major, minor))) } else if ty == "WebGL" { glutin::context::ContextApi::Gles(Some(glutin::context::Version::new(major, minor))) } else { panic!(); } }, Err(_) => glutin::context::ContextApi::OpenGl(None), } } /// Builds a 2x2 unicolor texture. pub fn build_unicolor_texture2d(facade: &F, red: f32, green: f32, blue: f32) -> glium::Texture2d where F: Facade { let color = ((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8); glium::texture::Texture2d::new(facade, vec![ vec![color, color], vec![color, color], ]).unwrap() } /// Builds a 2x2 depth texture. pub fn build_constant_depth_texture(facade: &F, depth: f32) -> glium::texture::DepthTexture2d where F: Facade + ?Sized { glium::texture::DepthTexture2d::new(facade, vec![ vec![depth, depth], vec![depth, depth], ]).unwrap() } /// Builds a vertex buffer, index buffer, and program, to draw red `(1.0, 0.0, 0.0, 1.0)` to the whole screen. pub fn build_fullscreen_red_pipeline(facade: &F) -> (glium::vertex::VertexBufferAny, glium::index::IndexBufferAny, glium::Program) where F: Facade { #[derive(Copy, Clone)] struct Vertex { position: [f32; 2], } implement_vertex!(Vertex, position); ( glium::VertexBuffer::new(facade, &[ Vertex { position: [-1.0, 1.0] }, Vertex { position: [1.0, 1.0] }, Vertex { position: [-1.0, -1.0] }, Vertex { position: [1.0, -1.0] }, ]).unwrap().into(), glium::IndexBuffer::new(facade, PrimitiveType::TriangleStrip, &[0u8, 1, 2, 3]).unwrap().into(), program!(facade, 110 => { vertex: " #version 110 attribute vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); } ", fragment: " #version 110 void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ", }, 100 => { vertex: " #version 100 attribute lowp vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); } ", fragment: " #version 100 void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ", }, ).unwrap() ) } /// Builds a vertex buffer and an index buffer corresponding to a rectangle. /// /// The vertex buffer has the "position" attribute of type "vec2". pub fn build_rectangle_vb_ib(facade: &F) -> (glium::vertex::VertexBufferAny, glium::index::IndexBufferAny) where F: Facade { #[derive(Copy, Clone)] struct Vertex { position: [f32; 2], } implement_vertex!(Vertex, position); ( glium::VertexBuffer::new(facade, &[ Vertex { position: [-1.0, 1.0] }, Vertex { position: [1.0, 1.0] }, Vertex { position: [-1.0, -1.0] }, Vertex { position: [1.0, -1.0] }, ]).unwrap().into(), glium::IndexBuffer::new(facade, PrimitiveType::TriangleStrip, &[0u8, 1, 2, 3]).unwrap().into(), ) } /// Builds a texture suitable for rendering. pub fn build_renderable_texture(facade: &F) -> glium::Texture2d where F: Facade { glium::Texture2d::empty(facade, 1024, 1024).unwrap() }