main.rs (16083B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* 6 7 An example of how to implement the Compositor trait that 8 allows picture caching surfaces to be composited by the operating 9 system. 10 11 The current example supports DirectComposite on Windows only. 12 13 */ 14 15 use euclid::Angle; 16 use gleam::gl; 17 use std::ffi::CString; 18 use std::sync::mpsc; 19 use webrender::{CompositorSurfaceTransform, Transaction, api::*}; 20 use webrender::api::units::*; 21 use webrender::Device; 22 #[cfg(target_os = "windows")] 23 use compositor_windows as compositor; 24 #[cfg(target_os = "linux")] 25 use compositor_wayland as compositor; 26 use std::{env, f32, process}; 27 28 // A very hacky integration with DirectComposite. It proxies calls from the compositor 29 // interface to a simple C99 library which does the DirectComposition / D3D11 / ANGLE 30 // interfacing. This is a very unsafe impl due to the way the window pointer is passed 31 // around! 32 struct DirectCompositeInterface { 33 window: *mut compositor::Window, 34 } 35 36 impl DirectCompositeInterface { 37 fn new(window: *mut compositor::Window) -> Self { 38 DirectCompositeInterface { window } 39 } 40 } 41 42 impl webrender::Compositor for DirectCompositeInterface { 43 fn create_surface( 44 &mut self, 45 _device: &mut Device, 46 id: webrender::NativeSurfaceId, 47 _virtual_offset: DeviceIntPoint, 48 tile_size: DeviceIntSize, 49 is_opaque: bool, 50 ) { 51 compositor::create_surface( 52 self.window, 53 id.0, 54 tile_size.width, 55 tile_size.height, 56 is_opaque, 57 ); 58 } 59 60 fn destroy_surface(&mut self, _device: &mut Device, id: webrender::NativeSurfaceId) { 61 compositor::destroy_surface(self.window, id.0); 62 } 63 64 fn create_tile(&mut self, _device: &mut Device, id: webrender::NativeTileId) { 65 compositor::create_tile(self.window, id.surface_id.0, id.x, id.y); 66 } 67 68 fn destroy_tile(&mut self, _device: &mut Device, id: webrender::NativeTileId) { 69 compositor::destroy_tile(self.window, id.surface_id.0, id.x, id.y); 70 } 71 72 fn bind( 73 &mut self, 74 _device: &mut Device, 75 id: webrender::NativeTileId, 76 dirty_rect: DeviceIntRect, 77 _valid_rect: DeviceIntRect, 78 ) -> webrender::NativeSurfaceInfo { 79 let (fbo_id, x, y) = compositor::bind_surface( 80 self.window, 81 id.surface_id.0, 82 id.x, 83 id.y, 84 dirty_rect.min.x, 85 dirty_rect.min.y, 86 dirty_rect.width(), 87 dirty_rect.height(), 88 ); 89 90 webrender::NativeSurfaceInfo { 91 origin: DeviceIntPoint::new(x, y), 92 fbo_id, 93 } 94 } 95 96 fn unbind(&mut self, _device: &mut Device) { 97 compositor::unbind_surface(self.window); 98 } 99 100 fn begin_frame(&mut self, _device: &mut Device) { 101 compositor::begin_transaction(self.window); 102 } 103 104 fn add_surface( 105 &mut self, 106 _device: &mut Device, 107 id: webrender::NativeSurfaceId, 108 transform: CompositorSurfaceTransform, 109 clip_rect: DeviceIntRect, 110 _image_rendering: ImageRendering, 111 ) { 112 compositor::add_surface( 113 self.window, 114 id.0, 115 transform.offset.x as i32, 116 transform.offset.y as i32, 117 clip_rect.min.x, 118 clip_rect.min.y, 119 clip_rect.width(), 120 clip_rect.height(), 121 ); 122 } 123 124 fn end_frame(&mut self, _device: &mut Device) { 125 compositor::end_transaction(self.window); 126 } 127 fn create_external_surface( 128 &mut self, 129 _device: &mut Device, 130 _id: webrender::NativeSurfaceId, 131 _: bool, 132 ) { 133 todo!() 134 } 135 136 fn attach_external_image( 137 &mut self, 138 _device: &mut Device, 139 _id: webrender::NativeSurfaceId, 140 _external_image: ExternalImageId, 141 ) { 142 todo!() 143 } 144 145 fn enable_native_compositor(&mut self, _device: &mut Device, _enable: bool) { 146 todo!() 147 } 148 149 fn deinit(&mut self, _device: &mut Device) { 150 compositor::deinit(self.window); 151 } 152 153 fn get_capabilities(&self, _device: &mut Device) -> webrender::CompositorCapabilities { 154 webrender::CompositorCapabilities { 155 virtual_surface_size: 1024 * 1024, 156 ..Default::default() 157 } 158 } 159 160 fn invalidate_tile( 161 &mut self, 162 _device: &mut Device, 163 _id: webrender::NativeTileId, 164 _valid_rect: DeviceIntRect, 165 ) { 166 } 167 168 fn start_compositing( 169 &mut self, 170 _device: &mut Device, 171 _color: webrender::webrender_api::ColorF, 172 _dirty_rects: &[DeviceIntRect], 173 _opaque_rects: &[DeviceIntRect], 174 ) { 175 } 176 177 fn create_backdrop_surface( 178 &mut self, 179 _device: &mut Device, 180 _id: webrender::NativeSurfaceId, 181 _color: webrender::webrender_api::ColorF, 182 ) { 183 todo!() 184 } 185 186 fn get_window_visibility(&self, _device: &mut Device) -> webrender::WindowVisibility { 187 todo!() 188 } 189 } 190 191 // Simplisitic implementation of the WR notifier interface to know when a frame 192 // has been prepared and can be rendered. 193 struct Notifier { 194 tx: mpsc::Sender<()>, 195 } 196 197 impl Notifier { 198 fn new(tx: mpsc::Sender<()>) -> Self { 199 Notifier { tx } 200 } 201 } 202 203 impl RenderNotifier for Notifier { 204 fn clone(&self) -> Box<dyn RenderNotifier> { 205 Box::new(Notifier { 206 tx: self.tx.clone(), 207 }) 208 } 209 210 fn wake_up(&self, _composite_needed: bool) {} 211 212 fn new_frame_ready(&self, _: DocumentId, _: FramePublishId, _params: &FrameReadyParams) { 213 self.tx.send(()).ok(); 214 } 215 } 216 217 fn push_rotated_rect( 218 builder: &mut DisplayListBuilder, 219 rect: LayoutRect, 220 color: ColorF, 221 spatial_id: SpatialId, 222 _root_pipeline_id: PipelineId, 223 angle: f32, 224 time: f32, 225 item_key: SpatialTreeItemKey, 226 ) { 227 let color = color.scale_rgb(time); 228 let rotation = LayoutTransform::rotation( 229 0.0, 230 0.0, 231 1.0, 232 Angle::radians(2.0 * std::f32::consts::PI * angle), 233 ); 234 let center = rect.center(); 235 let transform_origin = LayoutVector3D::new(center.x, center.y, 0.0); 236 let transform = rotation 237 .pre_translate(-transform_origin) 238 .then_translate(transform_origin); 239 let spatial_id = builder.push_reference_frame( 240 LayoutPoint::zero(), 241 spatial_id, 242 TransformStyle::Flat, 243 PropertyBinding::Value(transform), 244 ReferenceFrameKind::Transform { 245 is_2d_scale_translation: false, 246 should_snap: false, 247 paired_with_perspective: false, 248 }, 249 item_key, 250 ); 251 builder.push_rect( 252 &CommonItemProperties::new( 253 rect, 254 SpaceAndClipInfo { 255 spatial_id, 256 clip_chain_id: ClipChainId::INVALID, 257 }, 258 ), 259 rect, 260 color, 261 ); 262 } 263 264 fn build_display_list( 265 builder: &mut DisplayListBuilder, 266 scroll_id: ExternalScrollId, 267 root_pipeline_id: PipelineId, 268 layout_size: LayoutSize, 269 time: f32, 270 invalidations: Invalidations, 271 ) { 272 let size_factor = match invalidations { 273 Invalidations::Small => 0.1, 274 Invalidations::Large | Invalidations::Scrolling => 1.0, 275 }; 276 277 let fixed_space_info = SpaceAndClipInfo { 278 spatial_id: SpatialId::root_scroll_node(root_pipeline_id), 279 clip_chain_id: ClipChainId::INVALID, 280 }; 281 282 let scroll_spatial_id = builder.define_scroll_frame( 283 fixed_space_info.spatial_id, 284 scroll_id, 285 LayoutRect::from_size(layout_size), 286 LayoutRect::from_size(layout_size), 287 LayoutVector2D::zero(), 288 APZScrollGeneration::default(), 289 HasScrollLinkedEffect::No, 290 SpatialTreeItemKey::new(1, 0), 291 ); 292 293 builder.push_rect( 294 &CommonItemProperties::new( 295 LayoutRect::from_size(layout_size).inflate(-10.0, -10.0), 296 fixed_space_info, 297 ), 298 LayoutRect::from_size(layout_size).inflate(-10.0, -10.0), 299 ColorF::new(0.8, 0.8, 0.8, 1.0), 300 ); 301 302 push_rotated_rect( 303 builder, 304 LayoutRect::from_origin_and_size( 305 LayoutPoint::new(100.0, 100.0), 306 LayoutSize::new(size_factor * 400.0, size_factor * 400.0), 307 ), 308 ColorF::new(1.0, 0.0, 0.0, 1.0), 309 scroll_spatial_id, 310 root_pipeline_id, 311 time, 312 time, 313 SpatialTreeItemKey::new(1, 1), 314 ); 315 316 push_rotated_rect( 317 builder, 318 LayoutRect::from_origin_and_size( 319 LayoutPoint::new(800.0, 100.0), 320 LayoutSize::new(size_factor * 100.0, size_factor * 600.0), 321 ), 322 ColorF::new(0.0, 1.0, 0.0, 1.0), 323 fixed_space_info.spatial_id, 324 root_pipeline_id, 325 0.2, 326 time, 327 SpatialTreeItemKey::new(1, 2), 328 ); 329 330 push_rotated_rect( 331 builder, 332 LayoutRect::from_origin_and_size( 333 LayoutPoint::new(700.0, 200.0), 334 LayoutSize::new(size_factor * 300.0, size_factor * 300.0), 335 ), 336 ColorF::new(0.0, 0.0, 1.0, 1.0), 337 scroll_spatial_id, 338 root_pipeline_id, 339 0.1, 340 time, 341 SpatialTreeItemKey::new(1, 3), 342 ); 343 344 push_rotated_rect( 345 builder, 346 LayoutRect::from_origin_and_size( 347 LayoutPoint::new(100.0, 600.0), 348 LayoutSize::new(size_factor * 400.0, size_factor * 400.0), 349 ), 350 ColorF::new(1.0, 1.0, 0.0, 1.0), 351 scroll_spatial_id, 352 root_pipeline_id, 353 time, 354 time, 355 SpatialTreeItemKey::new(1, 4), 356 ); 357 358 push_rotated_rect( 359 builder, 360 LayoutRect::from_origin_and_size( 361 LayoutPoint::new(700.0, 600.0), 362 LayoutSize::new(size_factor * 400.0, size_factor * 400.0), 363 ), 364 ColorF::new(0.0, 1.0, 1.0, 1.0), 365 scroll_spatial_id, 366 root_pipeline_id, 367 time, 368 time, 369 SpatialTreeItemKey::new(1, 5), 370 ); 371 } 372 373 #[derive(Debug, Copy, Clone)] 374 enum Invalidations { 375 Large, 376 Small, 377 Scrolling, 378 } 379 380 #[repr(C)] 381 #[derive(Debug, Copy, Clone)] 382 enum Sync { 383 None = 0, 384 Swap = 1, 385 Commit = 2, 386 Flush = 3, 387 Query = 4, 388 } 389 390 fn main() { 391 let args: Vec<String> = env::args().collect(); 392 393 if args.len() != 6 { 394 println!("USAGE: compositor [native|none] [small|large|scroll] [none|swap|commit|flush|query] width height"); 395 process::exit(0); 396 } 397 398 let enable_compositor = match args[1].parse::<String>().unwrap().as_str() { 399 "native" => true, 400 "none" => false, 401 _ => panic!("invalid compositor [native, none]"), 402 }; 403 404 let inv_mode = match args[2].parse::<String>().unwrap().as_str() { 405 "small" => Invalidations::Small, 406 "large" => Invalidations::Large, 407 "scroll" => Invalidations::Scrolling, 408 _ => panic!("invalid invalidations [small, large, scroll]"), 409 }; 410 411 let sync_mode = match args[3].parse::<String>().unwrap().as_str() { 412 "none" => Sync::None, 413 "swap" => Sync::Swap, 414 "commit" => Sync::Commit, 415 "flush" => Sync::Flush, 416 "query" => Sync::Query, 417 _ => panic!("invalid sync mode [none, swap, commit, flush, query]"), 418 }; 419 420 let width = args[4].parse().unwrap(); 421 let height = args[5].parse().unwrap(); 422 let device_size = DeviceIntSize::new(width, height); 423 424 // Load GL, construct WR and the native compositor interface. 425 let window = compositor::create_window( 426 device_size.width, 427 device_size.height, 428 enable_compositor, 429 sync_mode as i32, 430 ); 431 let debug_flags = DebugFlags::empty(); 432 let compositor_config = if enable_compositor { 433 webrender::CompositorConfig::Native { 434 compositor: Box::new(DirectCompositeInterface::new(window)), 435 } 436 } else { 437 webrender::CompositorConfig::Draw { 438 max_partial_present_rects: 0, 439 draw_previous_partial_present_regions: false, 440 partial_present: None, 441 } 442 }; 443 let opts = webrender::WebRenderOptions { 444 clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0), 445 debug_flags, 446 compositor_config, 447 surface_origin_is_top_left: false, 448 ..webrender::WebRenderOptions::default() 449 }; 450 let (tx, rx) = mpsc::channel(); 451 let notifier = Box::new(Notifier::new(tx)); 452 let gl = unsafe { 453 gl::GlesFns::load_with(|symbol| { 454 let symbol = CString::new(symbol).unwrap(); 455 let ptr = compositor::get_proc_address(symbol.as_ptr()); 456 ptr 457 }) 458 }; 459 let (mut renderer, sender) = 460 webrender::create_webrender_instance(gl.clone(), notifier, opts, None).unwrap(); 461 let mut api = sender.create_api(); 462 let document_id = api.add_document(device_size); 463 let device_pixel_ratio = 1.0; 464 let mut current_epoch = Epoch(0); 465 let root_pipeline_id = PipelineId(0, 0); 466 let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio); 467 let mut time = 0.0; 468 let scroll_id = ExternalScrollId(3, root_pipeline_id); 469 470 // Kick off first transaction which will mean we get a notify below to build the DL and render. 471 let mut txn = Transaction::new(); 472 txn.set_root_pipeline(root_pipeline_id); 473 474 if let Invalidations::Scrolling = inv_mode { 475 let mut root_builder = DisplayListBuilder::new(root_pipeline_id); 476 root_builder.begin(); 477 478 build_display_list( 479 &mut root_builder, 480 scroll_id, 481 root_pipeline_id, 482 layout_size, 483 1.0, 484 inv_mode, 485 ); 486 487 txn.set_display_list(current_epoch, root_builder.end()); 488 } 489 490 txn.generate_frame(0, true, false, RenderReasons::empty()); 491 api.send_transaction(document_id, txn); 492 493 // Tick the compositor (in this sample, we don't block on UI events) 494 while compositor::tick(window) { 495 // If there is a new frame ready to draw 496 if let Ok(..) = rx.try_recv() { 497 // Update and render. This will invoke the native compositor interface implemented above 498 // as required. 499 renderer.update(); 500 renderer.render(device_size, 0).unwrap(); 501 let _ = renderer.flush_pipeline_info(); 502 503 // Construct a simple display list that can be drawn and composited by DC. 504 let mut txn = Transaction::new(); 505 506 match inv_mode { 507 Invalidations::Small | Invalidations::Large => { 508 let mut root_builder = DisplayListBuilder::new(root_pipeline_id); 509 root_builder.begin(); 510 511 build_display_list( 512 &mut root_builder, 513 scroll_id, 514 root_pipeline_id, 515 layout_size, 516 time, 517 inv_mode, 518 ); 519 520 txn.set_display_list(current_epoch, root_builder.end()); 521 } 522 Invalidations::Scrolling => { 523 let d = 0.5 - 0.5 * (2.0 * f32::consts::PI * 5.0 * time).cos(); 524 txn.set_scroll_offsets( 525 scroll_id, 526 vec![SampledScrollOffset { 527 offset: LayoutVector2D::new(0.0, (d * 100.0).round()), 528 generation: APZScrollGeneration::default(), 529 }], 530 ); 531 } 532 } 533 534 txn.generate_frame(0, true, false, RenderReasons::empty()); 535 api.send_transaction(document_id, txn); 536 current_epoch.0 += 1; 537 time += 0.001; 538 if time > 1.0 { 539 time = 0.0; 540 } 541 542 // This does nothing when native compositor is enabled 543 compositor::swap_buffers(window); 544 } 545 } 546 547 renderer.deinit(); 548 compositor::destroy_window(window); 549 }