// Copyright 2016 The Gfx-rs Developers. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Cross platform GFX example application framework. //! //! Supports OpenGL 3.2, OpenGL ES 2.0, WebGL 2, and DirectX 11. Handles windowing via `winit`. //! //! Note: The documentation will be available only for the backends corresponding to the platform you're compiling to. //! //! `gfx_app` exposes the following helpers: //! - `gfx_app::Application` - trait that creates a window and backend for the given compile target and feature set (DirectX on Windows, OpenGL elsewhere. The feature `vulkan` enables Vulkan and `metal` enables Metal on macOS). //! - `gfx_app::ColorFormat`/`gfx_app::DepthFormat` - the pixel formats for the window's color and depth buffers. //! - `gfx_app::DefaultResources` - type that picks the correct `gfx::Resources` for the current backend. //! - `gfx_app::shade::Source` - container for shaders for multiple backends. //! //! ## Sample usage //! //! ``` //! struct App { //! window_targets: gfx_app::WindowTargets, //! } //! //! impl gfx_app::Application for App { //! fn new>( //! _factory: &mut F, //! _backend: gfx_app::shade::Backend, //! window_targets: gfx_app::WindowTargets, //! ) -> Self { //! App { //! window_targets //! } //! } //! //! fn render>(&mut self, encoder: &mut gfx::Encoder) { //! encoder.clear(&self.window_targets.color, [1.0, 0.0, 1.0, 1.0]); //! } //! //! fn on_resize(&mut self, window_targets: gfx_app::WindowTargets) { //! self.window_targets = window_targets; //! } //! //! fn on(&mut self, event: winit::WindowEvent) { //! match event { //! _ => (), //! } //! } //! } //! //! // Then just call `App::launch_simple("Window title");` from `main`. //! ``` //! //! Note: only `new` and `render` are required to be implemented, but implementing `on` (and `on_resize` or `on_resize_ext`) allows an app to handle events. #[allow(unused_imports)] #[macro_use] extern crate log; extern crate env_logger; extern crate winit; extern crate glutin; extern crate gfx; extern crate gfx_device_gl; extern crate gfx_window_glutin; // extern crate gfx_window_glfw; #[cfg(target_os = "windows")] extern crate gfx_device_dx11; #[cfg(target_os = "windows")] extern crate gfx_window_dxgi; #[cfg(feature = "metal")] extern crate gfx_device_metal; #[cfg(feature = "metal")] extern crate gfx_window_metal; #[cfg(feature = "vulkan")] extern crate gfx_device_vulkan; #[cfg(feature = "vulkan")] extern crate gfx_window_vulkan; pub mod shade; /// The canonical color format for the current backend. #[cfg(not(any(feature = "vulkan", feature = "metal")))] pub type ColorFormat = gfx::format::Rgba8; /// The canonical color format for the current backend. #[cfg(feature = "vulkan")] pub type ColorFormat = gfx::format::Bgra8; /// The canonical color format for the current backend. #[cfg(feature = "metal")] pub type ColorFormat = (gfx::format::B8_G8_R8_A8, gfx::format::Srgb); /// The canonical depth format for the current backend. #[cfg(feature = "metal")] pub type DepthFormat = gfx::format::Depth32F; /// The canonical depth format for the current backend. #[cfg(not(feature = "metal"))] pub type DepthFormat = gfx::format::DepthStencil; /// Aggregator of the render and depth targets (plus additional information about them) for a window. pub struct WindowTargets { /// Color target for the window pub color: gfx::handle::RenderTargetView, /// Depth target for the window pub depth: gfx::handle::DepthStencilView, /// For use in projection matrices. Calculated as `width/height`. pub aspect_ratio: f32, } /// Helper to calculate frame statistics. /// /// Prints results on `drop`. struct Harness { start: std::time::Instant, num_frames: f64, } impl Harness { fn new() -> Harness { Harness { start: std::time::Instant::now(), num_frames: 0.0, } } /// Increment internal counters. /// /// Call once per frame. fn bump(&mut self) { self.num_frames += 1.0; } } impl Drop for Harness { fn drop(&mut self) { let time_end = self.start.elapsed(); println!("Avg frame time: {} ms", ((time_end.as_secs() * 1000) as f64 + (time_end.subsec_nanos() / 1_000_000) as f64) / self.num_frames); } } /// Trait to allow `Application` to create a backend-specific resources. pub trait Factory: gfx::Factory { type CommandBuffer: gfx::CommandBuffer; fn create_encoder(&mut self) -> gfx::Encoder; } /// Represents an application container. Consider using `Application`, as it is simpler to use. pub trait ApplicationBase> { fn new(&mut F, shade::Backend, WindowTargets) -> Self where F: Factory; fn render(&mut self, &mut D) where D: gfx::Device; fn get_exit_key() -> Option; fn on(&mut self, winit::WindowEvent); fn on_resize(&mut self, &mut F, WindowTargets) where F: Factory; } impl Factory for gfx_device_gl::Factory { type CommandBuffer = gfx_device_gl::CommandBuffer; fn create_encoder(&mut self) -> gfx::Encoder { self.create_command_buffer().into() } } /// Creates a window and starts the main loop for OpenGL /// /// Can target WebGL if the `target_os` is `emscripten`. Otherwise, tries OpenGL 3.2, then OpenGL ES 2.0. pub fn launch_gl3(window: winit::WindowBuilder) where A: Sized + ApplicationBase { use gfx::traits::Device; env_logger::init(); #[cfg(target_os = "emscripten")] let gl_version = glutin::GlRequest::Specific( glutin::Api::WebGl, (2, 0), ); #[cfg(not(target_os = "emscripten"))] let gl_version = glutin::GlRequest::GlThenGles { opengl_version: (3, 2), // TODO: try more versions opengles_version: (2, 0), }; let context = glutin::ContextBuilder::new() .with_gl(gl_version) .with_vsync(true); let mut events_loop = glutin::EventsLoop::new(); let (window, mut device, mut factory, main_color, main_depth) = gfx_window_glutin::init::(window, context, &events_loop) .expect("Failed to create window"); let mut current_size = window.window() .get_inner_size() .unwrap() .to_physical(window.window().get_hidpi_factor()); let shade_lang = device.get_info().shading_language; let backend = if shade_lang.is_embedded { shade::Backend::GlslEs(shade_lang) } else { shade::Backend::Glsl(shade_lang) }; let mut app = A::new(&mut factory, backend, WindowTargets { color: main_color, depth: main_depth, aspect_ratio: current_size.width as f32 / current_size.height as f32, }); let mut harness = Harness::new(); let mut running = true; while running { events_loop.poll_events(|event| { if let winit::Event::WindowEvent { event, .. } = event { match event { winit::WindowEvent::CloseRequested => running = false, winit::WindowEvent::KeyboardInput { input: winit::KeyboardInput { state: winit::ElementState::Pressed, virtual_keycode: key, .. }, .. } if key == A::get_exit_key() => running = false, winit::WindowEvent::Resized(size) => { let physical = size.to_physical(window.window().get_hidpi_factor()); if physical != current_size { window.resize(physical); current_size = physical; let (new_color, new_depth) = gfx_window_glutin::new_views(&window); app.on_resize(&mut factory, WindowTargets { color: new_color, depth: new_depth, aspect_ratio: size.width as f32 / size.height as f32, }); } } _ => app.on(event), } } }); // draw a frame app.render(&mut device); window.swap_buffers().unwrap(); device.cleanup(); harness.bump(); } } #[cfg(target_os = "windows")] pub type D3D11CommandBuffer = gfx_device_dx11::CommandBuffer; #[cfg(target_os = "windows")] pub type D3D11CommandBufferFake = gfx_device_dx11::CommandBuffer; #[cfg(target_os = "windows")] impl Factory for gfx_device_dx11::Factory { type CommandBuffer = D3D11CommandBuffer; fn create_encoder(&mut self) -> gfx::Encoder { self.create_command_buffer_native().into() } } /// Creates a window and starts the main loop for DirectX 11 #[cfg(target_os = "windows")] pub fn launch_d3d11(wb: winit::WindowBuilder) where A: Sized + ApplicationBase { use gfx::traits::{Device, Factory}; env_logger::init(); let mut events_loop = winit::EventsLoop::new(); let (mut window, device, mut factory, main_color) = gfx_window_dxgi::init::(wb, &events_loop).unwrap(); let main_depth = factory.create_depth_stencil_view_only(window.size.0, window.size.1) .unwrap(); let backend = shade::Backend::Hlsl(device.get_shader_model()); let mut app = A::new(&mut factory, backend, WindowTargets { color: main_color, depth: main_depth, aspect_ratio: window.size.0 as f32 / window.size.1 as f32, }); let mut device = gfx_device_dx11::Deferred::from(device); let mut harness = Harness::new(); let mut running = true; while running { let mut new_size = None; events_loop.poll_events(|event| { if let winit::Event::WindowEvent { event, .. } = event { match event { winit::WindowEvent::CloseRequested => running = false, winit::WindowEvent::KeyboardInput { input: winit::KeyboardInput { state: winit::ElementState::Pressed, virtual_keycode: key, .. }, .. } if key == A::get_exit_key() => running = false, winit::WindowEvent::Resized(size) => { let physical = size.to_physical(window.inner.get_hidpi_factor()); let (width, height): (u32, u32) = physical.into(); let size = (width as gfx::texture::Size, height as gfx::texture::Size); if size != window.size { // working around the borrow checker: window is already borrowed here new_size = Some(size); } }, _ => app.on(event), } } }); if let Some((width, height)) = new_size { use gfx_window_dxgi::update_views; match update_views(&mut window, &mut factory, &mut device, width, height) { Ok(new_color) => { let new_depth = factory.create_depth_stencil_view_only(width, height).unwrap(); app.on_resize(&mut factory, WindowTargets { color: new_color, depth: new_depth, aspect_ratio: width as f32 / height as f32, }); }, Err(e) => error!("Resize failed: {}", e), } continue; } app.render(&mut device); window.swap_buffers(1); device.cleanup(); harness.bump(); } } #[cfg(feature = "metal")] impl Factory for gfx_device_metal::Factory { type CommandBuffer = gfx_device_metal::CommandBuffer; fn create_encoder(&mut self) -> gfx::Encoder { self.create_command_buffer().into() } } /// Creates a window and starts the main loop for Metal #[cfg(feature = "metal")] pub fn launch_metal(wb: winit::WindowBuilder) where A: Sized + ApplicationBase { use gfx::traits::{Device, Factory}; use gfx::texture::Size; env_logger::init(); let mut events_loop = winit::EventsLoop::new(); let (window, mut device, mut factory, main_color) = gfx_window_metal::init::(wb, &events_loop) .unwrap(); let (width, height): (u32, u32) = window.get_inner_size().unwrap().into(); let main_depth = factory.create_depth_stencil_view_only(width as Size, height as Size).unwrap(); let backend = shade::Backend::Msl(device.get_shader_model()); let mut app = A::new(&mut factory, backend, WindowTargets { color: main_color, depth: main_depth, aspect_ratio: width as f32 / height as f32 }); let mut harness = Harness::new(); let mut running = true; while running { events_loop.poll_events(|event| { if let winit::Event::WindowEvent { event, .. } = event { match event { winit::WindowEvent::CloseRequested => running = false, winit::WindowEvent::KeyboardInput { input: winit::KeyboardInput { state: winit::ElementState::Pressed, virtual_keycode: key, .. }, .. } if key == A::get_exit_key() => running = false, winit::WindowEvent::Resized(_size) => { warn!("TODO: resize on Metal"); }, _ => app.on(event), } } }); app.render(&mut device); window.swap_buffers().unwrap(); device.cleanup(); harness.bump(); } } #[cfg(feature = "vulkan")] impl Factory for gfx_device_vulkan::Factory { type CommandBuffer = gfx_device_vulkan::CommandBuffer; fn create_encoder(&mut self) -> gfx::Encoder { self.create_command_buffer().into() } } /// Creates a window and starts the main loop for Vulkan #[cfg(feature = "vulkan")] pub fn launch_vulkan(wb: winit::WindowBuilder) where A: Sized + ApplicationBase { use gfx::traits::{Device, Factory}; use gfx::texture::Size; env_logger::init(); let mut events_loop = winit::EventsLoop::new(); let (mut win, mut factory) = gfx_window_vulkan::init::(wb, &events_loop); let (width, height) = win.get_size(); let main_depth = factory.create_depth_stencil::(width as Size, height as Size).unwrap(); let backend = shade::Backend::Vulkan; let mut app = A::new(&mut factory, backend, WindowTargets { color: win.get_any_target(), depth: main_depth.2, aspect_ratio: width as f32 / height as f32, //TODO }); let mut harness = Harness::new(); let mut running = true; while running { events_loop.poll_events(|event| { if let winit::Event::WindowEvent { event, .. } = event { match event { winit::WindowEvent::CloseRequested => running = false, winit::WindowEvent::KeyboardInput { input: winit::KeyboardInput { state: winit::ElementState::Pressed, virtual_keycode: key, .. }, .. } if key == A::get_exit_key() => running = false, winit::WindowEvent::Resized(_size) => { warn!("TODO: resize on Vulkan"); }, _ => app.on(event), } } }); let mut frame = win.start_frame(); app.render(frame.get_queue()); frame.get_queue().cleanup(); harness.bump(); } } /// The implementation of `gfx::Resources` for the current backend. #[cfg(all(not(target_os = "windows"), not(feature = "vulkan"), not(feature = "metal")))] pub type DefaultResources = gfx_device_gl::Resources; /// The implementation of `gfx::Resources` for the current backend. #[cfg(all(target_os = "windows", not(feature = "vulkan")))] pub type DefaultResources = gfx_device_dx11::Resources; /// The implementation of `gfx::Resources` for the current backend. #[cfg(feature = "metal")] pub type DefaultResources = gfx_device_metal::Resources; /// The implementation of `gfx::Resources` for the current backend. #[cfg(feature = "vulkan")] pub type DefaultResources = gfx_device_vulkan::Resources; /// Represents a cross-platform application container. pub trait Application: Sized { /// Called once to initialize the app. fn new>(&mut F, shade::Backend, WindowTargets) -> Self; /// Render the application. Called once per frame. fn render>(&mut self, &mut gfx::Encoder); /// The application will exit when this key is pressed. /// /// Note: Implement this and return `None` to disable hotkey exit. fn get_exit_key() -> Option { Some(winit::VirtualKeyCode::Escape) } /// See `on_resize_ext`. fn on_resize(&mut self, WindowTargets) {} /// User-specified handler for `winit::WindowEvent::Resized`. /// /// Note: implement this method if you have resources that need to be recreated when the window size changes (e.g. G-buffers), otherwise just implement `on_resize`. fn on_resize_ext>(&mut self, _factory: &mut F, targets: WindowTargets) { self.on_resize(targets); } /// User-specified handler for `winit::WindowEvent`s. /// /// Note: will not be called for the following events: /// - `winit::WindowEvent::CloseRequested` /// - `winit::WindowEvent::Resized` /// - `winit::WindowEvent::KeyboardInput` when the `winit::VirtualKeyCode` is equal to `get_exit_key()` fn on(&mut self, _event: winit::WindowEvent) {} /// Launch the app with the default `winit::WindowBuilder` parameters and run the main loop. fn launch_simple(name: &str) where Self: Application { let wb = winit::WindowBuilder::new().with_title(name); >::launch_default(wb) } /// Launch the app with a specified `winit::WindowBuilder` and run the main loop. fn launch_default(wb: winit::WindowBuilder) where Self: Application { #[cfg(all(not(target_os = "windows"), not(feature = "vulkan"), not(feature = "metal")))] launch_gl3::>(wb); #[cfg(all(target_os = "windows", not(feature = "vulkan")))] launch_d3d11::>(wb); #[cfg(feature = "metal")] launch_metal::>(wb); #[cfg(feature = "vulkan")] launch_vulkan::>(wb); } } /// Wraps a `gfx::Encoder` and `Application` to simplify usage of `ApplicationBase`. pub struct Wrap { encoder: gfx::Encoder, app: A, } impl ApplicationBase for Wrap where R: gfx::Resources, C: gfx::CommandBuffer, A: Application { fn new(factory: &mut F, backend: shade::Backend, window_targets: WindowTargets) -> Self where F: Factory { Wrap { encoder: factory.create_encoder(), app: A::new(factory, backend, window_targets), } } fn render(&mut self, device: &mut D) where D: gfx::Device { self.app.render(&mut self.encoder); self.encoder.flush(device); } fn get_exit_key() -> Option { A::get_exit_key() } fn on(&mut self, event: winit::WindowEvent) { self.app.on(event) } fn on_resize(&mut self, factory: &mut F, window_targets: WindowTargets) where F: Factory { self.app.on_resize_ext(factory, window_targets); } }