// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0 // This file is a part of `piet-glow`. // // `piet-glow` is free software: you can redistribute it and/or modify it under the terms of // either: // // * GNU Lesser General Public License as published by the Free Software Foundation, either // version 3 of the License, or (at your option) any later version. // * Mozilla Public License as published by the Mozilla Foundation, version 2. // // `piet-glow` is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License or the Mozilla Public License for more details. // // You should have received a copy of the GNU Lesser General Public License and the Mozilla // Public License along with `piet-glow`. If not, see . // Easy module for setting up a context for the examples. // Uses glutin on desktop platforms and WebGL on the web. #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] pub(crate) fn init() { tracing_subscriber::fmt::init(); } #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] pub(crate) fn init() { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); } #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] pub(crate) mod glutin_impl { use piet_glow::RenderContext; use glutin::config::{Config, ConfigTemplateBuilder}; use glutin::context::{ ContextApi, ContextAttributesBuilder, NotCurrentContext, PossiblyCurrentContext, Version, }; use glutin::display::{Display, GetGlDisplay}; use glutin::prelude::*; use glutin::surface::{Surface, SwapInterval, WindowSurface}; use glutin_winit::{DisplayBuilder, GlWindow}; use raw_window_handle::HasRawWindowHandle; use std::error::Error; use std::mem; use std::num::NonZeroU32; use std::time::{Duration, Instant}; use winit::event::{Event, WindowEvent}; use winit::event_loop::{EventLoop, EventLoopWindowTarget}; use winit::window::{Window, WindowBuilder}; pub(crate) struct GlutinSetup { display: Display, config: Config, context: ContextType, window: Option, } enum ContextType { NotCurrent(NotCurrentContext), Current { context: PossiblyCurrentContext, window: Window, surface: Surface, }, Hole, } impl Default for ContextType { fn default() -> Self { Self::Hole } } fn make_window_builder() -> WindowBuilder { WindowBuilder::new() .with_title("piet-glow example") .with_transparent(true) } impl GlutinSetup { pub(crate) fn new( event_loop: &EventLoopWindowTarget, ) -> Result> { // Start building a window. let window = if cfg!(windows) { Some(make_window_builder()) } else { None }; // Use the window builder to start building a display. let display = DisplayBuilder::new().with_window_builder(window); // Look for a config that supports transparency and has a good sample count. let (window, gl_config) = display.build( event_loop, ConfigTemplateBuilder::new().with_alpha_size(8), |configs| { 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() }, )?; println!("Config: {:?}", &gl_config); println!("Color Buffer Type: {:?}", gl_config.color_buffer_type()); println!("Float Pixels: {:?}", gl_config.float_pixels()); println!("Alpha Size: {:?}", gl_config.alpha_size()); println!("Depth Size: {:?}", gl_config.depth_size()); println!("Stencil Size: {:?}", gl_config.stencil_size()); println!("Samples: {:?}", gl_config.num_samples()); println!("SRGB Capable: {:?}", gl_config.srgb_capable()); println!( "Supports Transparency: {:?}", gl_config.supports_transparency() ); println!( "Hardware Accelerated: {:?}", gl_config.hardware_accelerated() ); println!( "Config Surface Types: {:?}", gl_config.config_surface_types() ); println!("Api: {:?}", gl_config.api()); // Try to build a several different contexts. let window_handle = window.as_ref().map(|w| w.raw_window_handle()); let contexts = [ ContextAttributesBuilder::new().build(window_handle), ContextAttributesBuilder::new() .with_context_api(ContextApi::Gles(None)) .build(window_handle), ContextAttributesBuilder::new() .with_context_api(ContextApi::Gles(Some(Version::new(2, 0)))) .build(window_handle), ]; let display = gl_config.display(); let gl_handler = (|| { // Try to build a context for each config. for context in &contexts { if let Ok(gl_context) = unsafe { display.create_context(&gl_config, context) } { return Ok(gl_context); } } // If we couldn't build a context, return an error. Err(Box::::from( "Could not create a context", )) })()?; Ok(Self { display, config: gl_config, context: ContextType::NotCurrent(gl_handler), window, }) } pub(crate) fn make_current( &mut self, window_target: &EventLoopWindowTarget, ) -> impl FnOnce() -> glow::Context { let window = self.window.take().unwrap_or_else(|| { let window_builder = make_window_builder(); glutin_winit::finalize_window(window_target, window_builder, &self.config).unwrap() }); let attrs = window.build_surface_attributes(<_>::default()); let gl_surface = unsafe { self.display .create_window_surface(&self.config, &attrs) .unwrap() }; // Make it current. let gl_context = match mem::take(&mut self.context) { ContextType::NotCurrent(context) => context.make_current(&gl_surface).unwrap(), _ => panic!("Invalid state!"), }; // Try setting vsync. if let Err(res) = gl_surface .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) { eprintln!("Error setting vsync: {res:?}"); } self.context = ContextType::Current { context: gl_context, window, surface: gl_surface, }; // Set up the Glow context. let display = self.display.clone(); move || { let glow_context = unsafe { glow::Context::from_loader_function_cstr(|s| { display.get_proc_address(s) as *const _ }) }; #[cfg(not(target_vendor = "apple"))] unsafe { use glow::HasContext; glow_context.enable(glow::DEBUG_OUTPUT); glow_context.debug_message_callback(debug_message_callback); } glow_context } } #[allow(unused)] fn run( mut self, evl: EventLoop, mut f: impl FnMut(&mut RenderContext<'_, glow::Context>, u32, u32) + 'static, ) -> Result<(), Box> { let mut current_size = None; let mut next_render = Instant::now() + Duration::from_millis(16); let mut renderer = None; evl.run(move |event, window_target, control_flow| { control_flow.set_wait_until(next_render); match event { Event::Resumed => { #[cfg(target_os = "android")] println!("Android window available"); let generator = self.make_current(window_target); renderer.get_or_insert_with(move || { let context = generator(); // SAFETY: We are current. unsafe { piet_glow::GlContext::new(context).unwrap() } }); } Event::Suspended => { // This event is only raised on Android, where the backing NativeWindow for a GL // Surface can appear and disappear at any moment. println!("Android window removed"); // Destroy the GL Surface and un-current the GL Context before ndk-glue releases // the window back to the system. let gl_context = match mem::take(&mut self.context) { ContextType::Current { context, .. } => context, _ => panic!("Invalid state!"), }; self.context = ContextType::NotCurrent(gl_context.make_not_current().unwrap()); } Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(size) => { if size.width != 0 && size.height != 0 { // Some platforms like EGL require resizing GL surface to update the size // Notable platforms here are Wayland and macOS, other don't require it // and the function is no-op, but it's wise to resize it for portability // reasons. if let ContextType::Current { context, surface, .. } = &self.context { surface.resize( context, NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap(), ); current_size = Some(size); } } } WindowEvent::CloseRequested => { control_flow.set_exit(); } _ => (), }, Event::RedrawEventsCleared => { if let ContextType::Current { context: gl_context, window, surface: gl_surface, } = &self.context { let renderer = renderer.as_mut().unwrap(); // Run the renderer // SAFETY: Context is current let size = current_size.unwrap_or_else(|| window.inner_size()); let mut context = unsafe { renderer.render_context(size.width, size.height) }; f(&mut context, size.width, size.height); window.request_redraw(); gl_surface.swap_buffers(gl_context).unwrap(); next_render += Duration::from_millis(17); } } _ => (), } }) } } #[allow(unused)] pub(crate) fn with_renderer( f: impl FnMut(&mut RenderContext<'_, glow::Context>, u32, u32) + 'static, ) -> Result<(), Box> { let event_loop = EventLoop::new(); GlutinSetup::new(&event_loop)?.run(event_loop, f) } #[cfg(not(target_vendor = "apple"))] fn debug_message_callback(source: u32, ty: u32, id: u32, severity: u32, message: &str) { let source = match source { glow::DEBUG_SOURCE_API => "API", glow::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System", glow::DEBUG_SOURCE_SHADER_COMPILER => "Shader Compiler", glow::DEBUG_SOURCE_THIRD_PARTY => "Third Party", glow::DEBUG_SOURCE_APPLICATION => "Application", glow::DEBUG_SOURCE_OTHER => "Other", _ => "Unknown", }; let ty = match ty { glow::DEBUG_TYPE_ERROR => "Error", glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior", glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior", glow::DEBUG_TYPE_PORTABILITY => "Portability", glow::DEBUG_TYPE_PERFORMANCE => "Performance", glow::DEBUG_TYPE_MARKER => "Marker", glow::DEBUG_TYPE_OTHER => "Other", _ => "Unknown", }; match severity { glow::DEBUG_SEVERITY_HIGH => { tracing::error!("{ty}-{id} ({source}): {message}"); } glow::DEBUG_SEVERITY_MEDIUM => { tracing::warn!("{ty}-{id} ({source}): {message}"); } glow::DEBUG_SEVERITY_LOW => { tracing::info!("{ty}-{id} ({source}): {message}"); } glow::DEBUG_SEVERITY_NOTIFICATION => { tracing::debug!("{ty}-{id} ({source}): {message}"); } _ => (), }; } } #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[allow(unused)] pub(crate) use glutin_impl::with_renderer; #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] use piet_glow::RenderContext; #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] pub(crate) fn with_renderer( mut f: impl FnMut(&mut piet_glow::RenderContext<'_, glow::Context>, u32, u32) + 'static, ) -> Result<(), Box> { use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; use std::cell::RefCell; use std::rc::Rc; let canvas = web_sys::window() .unwrap() .document() .unwrap() .create_element("canvas") .unwrap() .dyn_into::() .unwrap(); let webgl2_context = canvas .get_context("webgl2") .unwrap() .unwrap() .dyn_into::() .unwrap(); let gl = glow::Context::from_webgl2_context(webgl2_context); // Add the canvas to the DOM. web_sys::window() .unwrap() .document() .unwrap() .body() .unwrap() .append_child(&canvas) .unwrap(); let mut renderer = unsafe { piet_glow::GlContext::new(gl).unwrap() }; let timeout_cb = Rc::new(RefCell::new(None)); let animate_cb: Rc>>> = Rc::new(RefCell::new(None)); // Run the callback every 1/60th of a second. let request_anim_frame = { let animate_cb = animate_cb.clone(); move || { web_sys::window() .unwrap() .request_animation_frame( animate_cb .borrow() .as_ref() .unwrap() .as_ref() .unchecked_ref(), ) .unwrap() } }; *timeout_cb.borrow_mut() = Some(Closure::wrap(Box::new(move || { request_anim_frame(); }) as Box)); let timeout_ms = { let spf = 1.0 / 60.0; (spf * 1000.0) as i32 }; let draw_frame = { let timeout_cb = timeout_cb.clone(); move || { let size = (canvas.width(), canvas.height()); let mut context = unsafe { renderer.render_context(size.0 * 3, size.1 * 3) }; f(&mut context, size.0, size.1); web_sys::window() .unwrap() .set_timeout_with_callback_and_timeout_and_arguments_0( timeout_cb .borrow() .as_ref() .unwrap() .as_ref() .unchecked_ref(), timeout_ms, ) .unwrap(); } }; let draw_frame = Rc::new(RefCell::new(draw_frame)); *animate_cb.borrow_mut() = Some(Closure::wrap(Box::new({ let draw_frame = draw_frame.clone(); move || { (*draw_frame.borrow_mut())(); } }) as Box)); // Start the animation. (draw_frame.borrow_mut())(); // Throw an exception to stop the program. wasm_bindgen::throw_str("Program exited.") }