diff options
Diffstat (limited to 'gfx/wr/direct-composition/src')
-rw-r--r-- | gfx/wr/direct-composition/src/com.rs | 112 | ||||
-rw-r--r-- | gfx/wr/direct-composition/src/egl.rs | 174 | ||||
-rw-r--r-- | gfx/wr/direct-composition/src/lib.rs | 179 | ||||
-rw-r--r-- | gfx/wr/direct-composition/src/main.rs | 11 | ||||
-rw-r--r-- | gfx/wr/direct-composition/src/main_windows.rs | 212 |
5 files changed, 688 insertions, 0 deletions
diff --git a/gfx/wr/direct-composition/src/com.rs b/gfx/wr/direct-composition/src/com.rs new file mode 100644 index 0000000000..8fb384695c --- /dev/null +++ b/gfx/wr/direct-composition/src/com.rs @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::ops; +use std::ptr; +use winapi::Interface; +use winapi::ctypes::c_void; +use winapi::shared::guiddef::GUID; +use winapi::shared::winerror::HRESULT; +use winapi::shared::winerror::SUCCEEDED; +use winapi::um::unknwnbase::IUnknown; + +pub fn as_ptr<T>(x: &T) -> *mut T { + x as *const T as _ +} + +pub trait CheckHResult { + fn check_hresult(self); +} + +impl CheckHResult for HRESULT { + fn check_hresult(self) { + if !SUCCEEDED(self) { + panic_com(self) + } + } +} + +fn panic_com(hresult: HRESULT) -> ! { + panic!("COM error 0x{:08X}", hresult as u32) +} + +/// Forked from <https://github.com/retep998/wio-rs/blob/44093f7db8/src/com.rs> +#[derive(PartialEq, Debug)] +pub struct ComPtr<T>(*mut T) where T: Interface; + +impl<T> ComPtr<T> where T: Interface { + /// Creates a `ComPtr` to wrap a raw pointer. + /// It takes ownership over the pointer which means it does __not__ call `AddRef`. + /// `T` __must__ be a COM interface that inherits from `IUnknown`. + pub unsafe fn from_raw(ptr: *mut T) -> ComPtr<T> { + assert!(!ptr.is_null()); + ComPtr(ptr) + } + + /// For use with APIs that take an interface UUID and + /// "return" a new COM object through a `*mut *mut c_void` out-parameter. + pub unsafe fn new_with_uuid<F>(f: F) -> Self + where F: FnOnce(&GUID, *mut *mut c_void) -> HRESULT + { + Self::new_with(|ptr| f(&T::uuidof(), ptr as _)) + } + + /// For use with APIs that "return" a new COM object through a `*mut *mut T` out-parameter. + pub unsafe fn new_with<F>(f: F) -> Self + where F: FnOnce(*mut *mut T) -> HRESULT + { + let mut ptr = ptr::null_mut(); + let hresult = f(&mut ptr); + if SUCCEEDED(hresult) { + ComPtr::from_raw(ptr) + } else { + if !ptr.is_null() { + let ptr = ptr as *mut IUnknown; + (*ptr).Release(); + } + panic_com(hresult) + } + } + + pub fn as_raw(&self) -> *mut T { + self.0 + } + + fn as_unknown(&self) -> &IUnknown { + unsafe { + &*(self.0 as *mut IUnknown) + } + } + + /// Performs QueryInterface fun. + pub fn cast<U>(&self) -> ComPtr<U> where U: Interface { + unsafe { + ComPtr::<U>::new_with_uuid(|uuid, ptr| self.as_unknown().QueryInterface(uuid, ptr)) + } + } +} + +impl<T> ops::Deref for ComPtr<T> where T: Interface { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.0 } + } +} + +impl<T> Clone for ComPtr<T> where T: Interface { + fn clone(&self) -> Self { + unsafe { + self.as_unknown().AddRef(); + ComPtr(self.0) + } + } +} + +impl<T> Drop for ComPtr<T> where T: Interface { + fn drop(&mut self) { + unsafe { + self.as_unknown().Release(); + } + } +} diff --git a/gfx/wr/direct-composition/src/egl.rs b/gfx/wr/direct-composition/src/egl.rs new file mode 100644 index 0000000000..8bd5afb72a --- /dev/null +++ b/gfx/wr/direct-composition/src/egl.rs @@ -0,0 +1,174 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use mozangle::egl::ffi::*; +use std::os::raw::c_void; +use std::ptr; +use std::rc::Rc; +use winapi::um::d3d11::ID3D11Device; +use winapi::um::d3d11::ID3D11Texture2D; + +pub use mozangle::egl::get_proc_address; + +pub struct SharedEglThings { + device: EGLDeviceEXT, + display: types::EGLDisplay, + config: types::EGLConfig, + context: types::EGLContext, +} + +fn cast_attributes(slice: &[types::EGLenum]) -> &EGLint { + unsafe { + &*(slice.as_ptr() as *const EGLint) + } +} + +macro_rules! attributes { + ($( $key: expr => $value: expr, )*) => { + cast_attributes(&[ + $( $key, $value, )* + NONE, + ]) + } +} + +impl SharedEglThings { + pub unsafe fn new(d3d_device: *mut ID3D11Device) -> Rc<Self> { + let device = eglCreateDeviceANGLE( + D3D11_DEVICE_ANGLE, + d3d_device as *mut c_void, + ptr::null(), + ).check(); + let display = GetPlatformDisplayEXT( + PLATFORM_DEVICE_EXT, + device, + attributes! [ + EXPERIMENTAL_PRESENT_PATH_ANGLE => EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE, + ], + ).check(); + Initialize(display, ptr::null_mut(), ptr::null_mut()).check(); + + // Adapted from + // https://searchfox.org/mozilla-central/rev/056a4057/gfx/gl/GLContextProviderEGL.cpp#635 + let mut configs = [ptr::null(); 64]; + let mut num_configs = 0; + ChooseConfig( + display, + attributes! [ + SURFACE_TYPE => WINDOW_BIT, + RENDERABLE_TYPE => OPENGL_ES2_BIT, + RED_SIZE => 8, + GREEN_SIZE => 8, + BLUE_SIZE => 8, + ALPHA_SIZE => 8, + ], + configs.as_mut_ptr(), + configs.len() as i32, + &mut num_configs, + ).check(); + let config = pick_config(&configs[..num_configs as usize]); + + let context = CreateContext( + display, config, NO_CONTEXT, + attributes![ + CONTEXT_CLIENT_VERSION => 3, + ] + ).check(); + MakeCurrent(display, NO_SURFACE, NO_SURFACE, context).check(); + + Rc::new(SharedEglThings { device, display, config, context }) + } +} + +fn pick_config(configs: &[types::EGLConfig]) -> types::EGLConfig { + // FIXME: better criteria to make this choice? + // Firefox uses GetConfigAttrib to find a config that has the requested r/g/b/a sizes + // https://searchfox.org/mozilla-central/rev/056a4057/gfx/gl/GLContextProviderEGL.cpp#662-685 + + configs[0] +} + +impl Drop for SharedEglThings { + fn drop(&mut self) { + unsafe { + // FIXME does EGLDisplay or EGLConfig need clean up? How? + DestroyContext(self.display, self.context).check(); + eglReleaseDeviceANGLE(self.device).check(); + } + } +} + +pub struct PerVisualEglThings { + shared: Rc<SharedEglThings>, + surface: types::EGLSurface, +} + +impl PerVisualEglThings { + pub unsafe fn new(shared: Rc<SharedEglThings>, buffer: *const ID3D11Texture2D, + width: u32, height: u32) + -> Self { + let surface = CreatePbufferFromClientBuffer( + shared.display, + D3D_TEXTURE_ANGLE, + buffer as types::EGLClientBuffer, + shared.config, + attributes! [ + WIDTH => width, + HEIGHT => height, + FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE => TRUE, + ], + ).check(); + + PerVisualEglThings { shared, surface } + } + + pub fn make_current(&self) { + unsafe { + MakeCurrent(self.shared.display, self.surface, self.surface, self.shared.context).check(); + } + } +} + +impl Drop for PerVisualEglThings { + fn drop(&mut self) { + unsafe { + DestroySurface(self.shared.display, self.surface).check(); + } + } +} + +fn check_error() { + unsafe { + let error = GetError() as types::EGLenum; + assert_eq!(error, SUCCESS, "0x{:x} != 0x{:x}", error, SUCCESS); + } +} + +trait Check { + fn check(self) -> Self; +} + +impl Check for *const c_void { + fn check(self) -> Self { + check_error(); + assert!(!self.is_null()); + self + } +} + +impl Check for *mut c_void { + fn check(self) -> Self { + check_error(); + assert!(!self.is_null()); + self + } +} + +impl Check for types::EGLBoolean { + fn check(self) -> Self { + check_error(); + assert_eq!(self, TRUE); + self + } +} diff --git a/gfx/wr/direct-composition/src/lib.rs b/gfx/wr/direct-composition/src/lib.rs new file mode 100644 index 0000000000..fadb8f2b72 --- /dev/null +++ b/gfx/wr/direct-composition/src/lib.rs @@ -0,0 +1,179 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![cfg(windows)] + +extern crate gleam; +extern crate mozangle; +extern crate winapi; + +use com::{ComPtr, CheckHResult, as_ptr}; +use std::ptr; +use std::rc::Rc; +use winapi::shared::dxgi1_2::DXGI_SWAP_CHAIN_DESC1; +use winapi::shared::dxgi1_2::IDXGIFactory2; +use winapi::shared::minwindef::{TRUE, FALSE}; +use winapi::shared::windef::HWND; +use winapi::um::d3d11::ID3D11Device; +use winapi::um::dcomp::IDCompositionDevice; +use winapi::um::dcomp::IDCompositionTarget; +use winapi::um::dcomp::IDCompositionVisual; + +mod com; +mod egl; + +pub struct DirectComposition { + d3d_device: ComPtr<ID3D11Device>, + dxgi_factory: ComPtr<IDXGIFactory2>, + + egl: Rc<egl::SharedEglThings>, + pub gleam: Rc<dyn gleam::gl::Gl>, + + composition_device: ComPtr<IDCompositionDevice>, + root_visual: ComPtr<IDCompositionVisual>, + + #[allow(unused)] // Needs to be kept alive + composition_target: ComPtr<IDCompositionTarget>, +} + +impl DirectComposition { + /// Initialize DirectComposition in the given window + /// + /// # Safety + /// + /// `hwnd` must be a valid handle to a window. + pub unsafe fn new(hwnd: HWND) -> Self { + let d3d_device = ComPtr::new_with(|ptr_ptr| winapi::um::d3d11::D3D11CreateDevice( + ptr::null_mut(), + winapi::um::d3dcommon::D3D_DRIVER_TYPE_HARDWARE, + ptr::null_mut(), + winapi::um::d3d11::D3D11_CREATE_DEVICE_BGRA_SUPPORT | + if cfg!(debug_assertions) { + winapi::um::d3d11::D3D11_CREATE_DEVICE_DEBUG + } else { + 0 + }, + ptr::null_mut(), + 0, + winapi::um::d3d11::D3D11_SDK_VERSION, + ptr_ptr, + &mut 0, + ptr::null_mut(), + )); + + let egl = egl::SharedEglThings::new(d3d_device.as_raw()); + let gleam = gleam::gl::GlesFns::load_with(egl::get_proc_address); + + let dxgi_device = d3d_device.cast::<winapi::shared::dxgi::IDXGIDevice>(); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/hh404556(v=vs.85).aspx#code-snippet-1 + // “Because you can create a Direct3D device without creating a swap chain, + // you might need to retrieve the factory that is used to create the device + // in order to create a swap chain.” + let adapter = ComPtr::new_with(|ptr_ptr| dxgi_device.GetAdapter(ptr_ptr)); + let dxgi_factory = ComPtr::<IDXGIFactory2>::new_with_uuid(|uuid, ptr_ptr| { + adapter.GetParent(uuid, ptr_ptr) + }); + + // Create the DirectComposition device object. + let composition_device = ComPtr::<IDCompositionDevice>::new_with_uuid(|uuid, ptr_ptr| { + winapi::um::dcomp::DCompositionCreateDevice(&*dxgi_device, uuid, ptr_ptr) + }); + + // Create the composition target object based on the + // specified application window. + let composition_target = ComPtr::new_with(|ptr_ptr| { + composition_device.CreateTargetForHwnd(hwnd, TRUE, ptr_ptr) + }); + + let root_visual = ComPtr::new_with(|ptr_ptr| composition_device.CreateVisual(ptr_ptr)); + composition_target.SetRoot(&*root_visual).check_hresult(); + + DirectComposition { + d3d_device, dxgi_factory, + egl, gleam, + composition_device, composition_target, root_visual, + } + } + + /// Execute changes to the DirectComposition scene. + pub fn commit(&self) { + unsafe { + self.composition_device.Commit().check_hresult() + } + } + + pub fn create_angle_visual(&self, width: u32, height: u32) -> AngleVisual { + unsafe { + let desc = DXGI_SWAP_CHAIN_DESC1 { + Width: width, + Height: height, + Format: winapi::shared::dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM, + Stereo: FALSE, + SampleDesc: winapi::shared::dxgitype::DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + BufferUsage: winapi::shared::dxgitype::DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: 2, + Scaling: winapi::shared::dxgi1_2::DXGI_SCALING_STRETCH, + SwapEffect: winapi::shared::dxgi::DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + AlphaMode: winapi::shared::dxgi1_2::DXGI_ALPHA_MODE_PREMULTIPLIED, + Flags: 0, + }; + let swap_chain = ComPtr::<winapi::shared::dxgi1_2::IDXGISwapChain1>::new_with(|ptr_ptr| { + self.dxgi_factory.CreateSwapChainForComposition( + as_ptr(&self.d3d_device), + &desc, + ptr::null_mut(), + ptr_ptr, + ) + }); + let back_buffer = ComPtr::<winapi::um::d3d11::ID3D11Texture2D>::new_with_uuid(|uuid, ptr_ptr| { + swap_chain.GetBuffer(0, uuid, ptr_ptr) + }); + let egl = egl::PerVisualEglThings::new(self.egl.clone(), &*back_buffer, width, height); + let gleam = self.gleam.clone(); + + let visual = ComPtr::new_with(|ptr_ptr| self.composition_device.CreateVisual(ptr_ptr)); + visual.SetContent(&*****swap_chain).check_hresult(); + self.root_visual.AddVisual(&*visual, FALSE, ptr::null_mut()).check_hresult(); + + AngleVisual { visual, swap_chain, egl, gleam } + } + } +} + +/// A DirectComposition "visual" configured for rendering with Direct3D. +pub struct AngleVisual { + visual: ComPtr<IDCompositionVisual>, + swap_chain: ComPtr<winapi::shared::dxgi1_2::IDXGISwapChain1>, + egl: egl::PerVisualEglThings, + pub gleam: Rc<dyn gleam::gl::Gl>, +} + +impl AngleVisual { + pub fn set_offset_x(&self, offset_x: f32) { + unsafe { + self.visual.SetOffsetX_1(offset_x).check_hresult() + } + } + + pub fn set_offset_y(&self, offset_y: f32) { + unsafe { + self.visual.SetOffsetY_1(offset_y).check_hresult() + } + } + + pub fn make_current(&self) { + self.egl.make_current() + } + + pub fn present(&self) { + self.gleam.finish(); + unsafe { + self.swap_chain.Present(0, 0).check_hresult() + } + } +} diff --git a/gfx/wr/direct-composition/src/main.rs b/gfx/wr/direct-composition/src/main.rs new file mode 100644 index 0000000000..e1999f5f8f --- /dev/null +++ b/gfx/wr/direct-composition/src/main.rs @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(not(windows))] +fn main() { + println!("This demo only runs on Windows."); +} + +#[cfg(windows)] +include!("main_windows.rs"); diff --git a/gfx/wr/direct-composition/src/main_windows.rs b/gfx/wr/direct-composition/src/main_windows.rs new file mode 100644 index 0000000000..bf06480f2a --- /dev/null +++ b/gfx/wr/direct-composition/src/main_windows.rs @@ -0,0 +1,212 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate direct_composition; +extern crate euclid; +extern crate gleam; +extern crate webrender; +extern crate winit; + +use euclid::size2; +use direct_composition::DirectComposition; +use std::sync::mpsc; +use webrender::api; +use webrender::render_api::*; +use winit::os::windows::{WindowExt, WindowBuilderExt}; +use winit::dpi::LogicalSize; + +fn main() { + let mut events_loop = winit::EventsLoop::new(); + + let (tx, rx) = mpsc::channel(); + let notifier = Box::new(Notifier { events_proxy: events_loop.create_proxy(), tx }); + + let window = winit::WindowBuilder::new() + .with_title("WebRender + ANGLE + DirectComposition") + .with_dimensions(LogicalSize::new(1024., 768.)) + .with_decorations(true) + .with_transparency(true) + .with_no_redirection_bitmap(true) + .build(&events_loop) + .unwrap(); + + let composition = direct_composition_from_window(&window); + let factor = window.get_hidpi_factor() as f32; + + let mut clicks: usize = 0; + let mut offset_y = 100.; + let mut rects = [ + Rectangle::new(&composition, ¬ifier, factor, size2(300, 200), 0., 0.2, 0.4, 1.), + Rectangle::new(&composition, ¬ifier, factor, size2(400, 300), 0., 0.5, 0., 0.5), + ]; + rects[0].render(factor, &rx); + rects[1].render(factor, &rx); + + rects[0].visual.set_offset_x(100.); + rects[0].visual.set_offset_y(50.); + + rects[1].visual.set_offset_x(200.); + rects[1].visual.set_offset_y(offset_y); + + composition.commit(); + + events_loop.run_forever(|event| { + if let winit::Event::WindowEvent { event, .. } = event { + match event { + winit::WindowEvent::CloseRequested => { + return winit::ControlFlow::Break + } + winit::WindowEvent::MouseWheel { delta, .. } => { + let dy = match delta { + winit::MouseScrollDelta::LineDelta(_, dy) => dy, + winit::MouseScrollDelta::PixelDelta(pos) => pos.y as f32, + }; + offset_y = (offset_y - 10. * dy).max(0.).min(468.); + + rects[1].visual.set_offset_y(offset_y); + composition.commit(); + } + winit::WindowEvent::MouseInput { + button: winit::MouseButton::Left, + state: winit::ElementState::Pressed, + .. + } => { + clicks += 1; + let rect = &mut rects[clicks % 2]; + rect.color.g += 0.1; + rect.color.g %= 1.; + rect.render(factor, &rx) + } + _ => {} + } + } + winit::ControlFlow::Continue + }); +} + +fn direct_composition_from_window(window: &winit::Window) -> DirectComposition { + unsafe { + DirectComposition::new(window.get_hwnd() as _) + } +} + +struct Rectangle { + visual: direct_composition::AngleVisual, + renderer: Option<webrender::Renderer>, + api: RenderApi, + document_id: api::DocumentId, + size: api::units::DeviceIntSize, + color: api::ColorF, +} + +impl Rectangle { + fn new(composition: &DirectComposition, notifier: &Box<Notifier>, + device_pixel_ratio: f32, size: api::units::DeviceIntSize, r: f32, g: f32, b: f32, a: f32) + -> Self { + let visual = composition.create_angle_visual(size.width as u32, size.height as u32); + visual.make_current(); + + let (renderer, sender) = webrender::Renderer::new( + composition.gleam.clone(), + notifier.clone(), + webrender::RendererOptions { + clear_color: Some(api::ColorF::new(0., 0., 0., 0.)), + device_pixel_ratio, + ..webrender::RendererOptions::default() + }, + None, + ).unwrap(); + let api = sender.create_api(); + + Rectangle { + visual, + renderer: Some(renderer), + document_id: api.add_document(size), + api, + size, + color: api::ColorF { r, g, b, a }, + } + } + + fn render(&mut self, device_pixel_ratio: f32, rx: &mpsc::Receiver<()>) { + self.visual.make_current(); + + let pipeline_id = api::PipelineId(0, 0); + let layout_size = self.size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut builder = api::DisplayListBuilder::new(pipeline_id); + + let rect = euclid::Rect::new(euclid::Point2D::zero(), layout_size); + + let region = api::ComplexClipRegion::new( + rect, + api::BorderRadius::uniform(20.), + api::ClipMode::Clip + ); + let clip_id = builder.define_clip_rounded_rect( + &api::SpaceAndClipInfo::root_scroll(pipeline_id), + region, + ); + + builder.push_rect( + &api::CommonItemProperties::new( + rect, + api::SpaceAndClipInfo { + spatial_id: api::SpatialId::root_scroll_node(pipeline_id), + clip_id, + }, + ), + rect, + self.color, + ); + + let mut transaction = Transaction::new(); + transaction.set_display_list( + api::Epoch(0), + None, + layout_size, + builder.finalize(), + true, + ); + transaction.set_root_pipeline(pipeline_id); + transaction.generate_frame(0); + self.api.send_transaction(self.document_id, transaction); + rx.recv().unwrap(); + let renderer = self.renderer.as_mut().unwrap(); + renderer.update(); + renderer.render(self.size, 0).unwrap(); + let _ = renderer.flush_pipeline_info(); + self.visual.present(); + } +} + +impl Drop for Rectangle { + fn drop(&mut self) { + self.renderer.take().unwrap().deinit() + } +} + +#[derive(Clone)] +struct Notifier { + events_proxy: winit::EventsLoopProxy, + tx: mpsc::Sender<()>, +} + +impl api::RenderNotifier for Notifier { + fn clone(&self) -> Box<dyn api::RenderNotifier> { + Box::new(Clone::clone(self)) + } + + fn wake_up(&self, _composite_needed: bool) { + self.tx.send(()).unwrap(); + let _ = self.events_proxy.wakeup(); + } + + fn new_frame_ready(&self, + _: api::DocumentId, + _: bool, + composite_needed: bool, + _: Option<u64>) { + self.wake_up(composite_needed); + } +} |