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::api::*;
20 use webrender::api::units::*;
21 #[cfg(target_os = "windows")]
22 use compositor_windows as compositor;
23 use std::{env, f32, process};
24 
25 // A very hacky integration with DirectComposite. It proxies calls from the compositor
26 // interface to a simple C99 library which does the DirectComposition / D3D11 / ANGLE
27 // interfacing. This is a very unsafe impl due to the way the window pointer is passed
28 // around!
29 struct DirectCompositeInterface {
30     window: *mut compositor::Window,
31 }
32 
33 impl DirectCompositeInterface {
new(window: *mut compositor::Window) -> Self34     fn new(window: *mut compositor::Window) -> Self {
35         DirectCompositeInterface {
36             window,
37         }
38     }
39 }
40 
41 impl webrender::Compositor for DirectCompositeInterface {
create_surface( &mut self, id: webrender::NativeSurfaceId, tile_size: DeviceIntSize, is_opaque: bool, )42     fn create_surface(
43         &mut self,
44         id: webrender::NativeSurfaceId,
45         tile_size: DeviceIntSize,
46         is_opaque: bool,
47     ) {
48         compositor::create_surface(
49             self.window,
50             id.0,
51             tile_size.width,
52             tile_size.height,
53             is_opaque,
54         );
55     }
56 
destroy_surface( &mut self, id: webrender::NativeSurfaceId, )57     fn destroy_surface(
58         &mut self,
59         id: webrender::NativeSurfaceId,
60     ) {
61         compositor::destroy_surface(self.window, id.0);
62     }
63 
create_tile( &mut self, id: webrender::NativeTileId, )64     fn create_tile(
65         &mut self,
66         id: webrender::NativeTileId,
67     ) {
68         compositor::create_tile(
69             self.window,
70             id.surface_id.0,
71             id.x,
72             id.y,
73         );
74     }
75 
destroy_tile( &mut self, id: webrender::NativeTileId, )76     fn destroy_tile(
77         &mut self,
78         id: webrender::NativeTileId,
79     ) {
80         compositor::destroy_tile(
81             self.window,
82             id.surface_id.0,
83             id.x,
84             id.y,
85         );
86     }
87 
bind( &mut self, id: webrender::NativeTileId, dirty_rect: DeviceIntRect, ) -> webrender::NativeSurfaceInfo88     fn bind(
89         &mut self,
90         id: webrender::NativeTileId,
91         dirty_rect: DeviceIntRect,
92     ) -> webrender::NativeSurfaceInfo {
93         let (fbo_id, x, y) = compositor::bind_surface(
94             self.window,
95             id.surface_id.0,
96             id.x,
97             id.y,
98             dirty_rect.origin.x,
99             dirty_rect.origin.y,
100             dirty_rect.size.width,
101             dirty_rect.size.height,
102         );
103 
104         webrender::NativeSurfaceInfo {
105             origin: DeviceIntPoint::new(x, y),
106             fbo_id,
107         }
108     }
109 
unbind(&mut self)110     fn unbind(&mut self) {
111         compositor::unbind_surface(self.window);
112     }
113 
begin_frame(&mut self)114     fn begin_frame(&mut self) {
115         compositor::begin_transaction(self.window);
116     }
117 
add_surface( &mut self, id: webrender::NativeSurfaceId, position: DeviceIntPoint, clip_rect: DeviceIntRect, )118     fn add_surface(
119         &mut self,
120         id: webrender::NativeSurfaceId,
121         position: DeviceIntPoint,
122         clip_rect: DeviceIntRect,
123     ) {
124         compositor::add_surface(
125             self.window,
126             id.0,
127             position.x,
128             position.y,
129             clip_rect.origin.x,
130             clip_rect.origin.y,
131             clip_rect.size.width,
132             clip_rect.size.height,
133         );
134     }
135 
end_frame(&mut self)136     fn end_frame(&mut self) {
137         compositor::end_transaction(self.window);
138     }
139 }
140 
141 // Simplisitic implementation of the WR notifier interface to know when a frame
142 // has been prepared and can be rendered.
143 struct Notifier {
144     tx: mpsc::Sender<()>,
145 }
146 
147 impl Notifier {
new(tx: mpsc::Sender<()>) -> Self148     fn new(tx: mpsc::Sender<()>) -> Self {
149         Notifier {
150             tx,
151         }
152     }
153 }
154 
155 impl RenderNotifier for Notifier {
clone(&self) -> Box<dyn RenderNotifier>156     fn clone(&self) -> Box<dyn RenderNotifier> {
157         Box::new(Notifier {
158             tx: self.tx.clone()
159         })
160     }
161 
wake_up(&self)162     fn wake_up(&self) {
163     }
164 
new_frame_ready(&self, _: DocumentId, _scrolled: bool, _composite_needed: bool, _render_time: Option<u64>)165     fn new_frame_ready(&self,
166                        _: DocumentId,
167                        _scrolled: bool,
168                        _composite_needed: bool,
169                        _render_time: Option<u64>) {
170         self.tx.send(()).ok();
171     }
172 }
173 
push_rotated_rect( builder: &mut DisplayListBuilder, rect: LayoutRect, color: ColorF, spatial_id: SpatialId, root_pipeline_id: PipelineId, angle: f32, time: f32, )174 fn push_rotated_rect(
175     builder: &mut DisplayListBuilder,
176     rect: LayoutRect,
177     color: ColorF,
178     spatial_id: SpatialId,
179     root_pipeline_id: PipelineId,
180     angle: f32,
181     time: f32,
182 ) {
183     let color = color.scale_rgb(time);
184     let rotation = LayoutTransform::create_rotation(
185         0.0,
186         0.0,
187         1.0,
188         Angle::radians(2.0 * std::f32::consts::PI * angle),
189     );
190     let transform_origin = LayoutVector3D::new(
191         rect.origin.x + rect.size.width * 0.5,
192         rect.origin.y + rect.size.height * 0.5,
193         0.0,
194     );
195     let transform = rotation
196         .pre_translate(-transform_origin)
197         .post_translate(transform_origin);
198     let spatial_id = builder.push_reference_frame(
199         LayoutPoint::zero(),
200         spatial_id,
201         TransformStyle::Flat,
202         PropertyBinding::Value(transform),
203         ReferenceFrameKind::Transform,
204     );
205     builder.push_rect(
206         &CommonItemProperties::new(
207             rect,
208             SpaceAndClipInfo {
209                 spatial_id,
210                 clip_id: ClipId::root(root_pipeline_id),
211             },
212         ),
213         rect,
214         color,
215     );
216 }
217 
build_display_list( builder: &mut DisplayListBuilder, scroll_id: ExternalScrollId, root_pipeline_id: PipelineId, layout_size: LayoutSize, time: f32, invalidations: Invalidations, )218 fn build_display_list(
219     builder: &mut DisplayListBuilder,
220     scroll_id: ExternalScrollId,
221     root_pipeline_id: PipelineId,
222     layout_size: LayoutSize,
223     time: f32,
224     invalidations: Invalidations,
225 ) {
226     let size_factor = match invalidations {
227         Invalidations::Small => 0.1,
228         Invalidations::Large | Invalidations::Scrolling => 1.0,
229     };
230 
231     let fixed_space_info = SpaceAndClipInfo {
232         spatial_id: SpatialId::root_scroll_node(root_pipeline_id),
233         clip_id: ClipId::root(root_pipeline_id),
234     };
235 
236     let scroll_space_info = builder.define_scroll_frame(
237         &fixed_space_info,
238         Some(scroll_id),
239         LayoutRect::new(LayoutPoint::zero(), layout_size),
240         LayoutRect::new(LayoutPoint::zero(), layout_size),
241         ScrollSensitivity::Script,
242         LayoutVector2D::zero(),
243     );
244 
245     builder.push_rect(
246         &CommonItemProperties::new(
247             LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0),
248             fixed_space_info,
249         ),
250         LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0),
251         ColorF::new(0.8, 0.8, 0.8, 1.0),
252     );
253 
254     push_rotated_rect(
255         builder,
256         LayoutRect::new(
257             LayoutPoint::new(100.0, 100.0),
258             LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
259         ),
260         ColorF::new(1.0, 0.0, 0.0, 1.0),
261         scroll_space_info.spatial_id,
262         root_pipeline_id,
263         time,
264         time,
265     );
266 
267     push_rotated_rect(
268         builder,
269         LayoutRect::new(
270             LayoutPoint::new(800.0, 100.0),
271             LayoutSize::new(size_factor * 100.0, size_factor * 600.0),
272         ),
273         ColorF::new(0.0, 1.0, 0.0, 1.0),
274         fixed_space_info.spatial_id,
275         root_pipeline_id,
276         0.2,
277         time,
278     );
279 
280     push_rotated_rect(
281         builder,
282         LayoutRect::new(
283             LayoutPoint::new(700.0, 200.0),
284             LayoutSize::new(size_factor * 300.0, size_factor * 300.0),
285         ),
286         ColorF::new(0.0, 0.0, 1.0, 1.0),
287         scroll_space_info.spatial_id,
288         root_pipeline_id,
289         0.1,
290         time,
291     );
292 }
293 
294 #[derive(Debug, Copy, Clone)]
295 enum Invalidations {
296     Large,
297     Small,
298     Scrolling,
299 }
300 
301 #[repr(C)]
302 #[derive(Debug, Copy, Clone)]
303 enum Sync {
304     None = 0,
305     Swap = 1,
306     Commit = 2,
307     Flush = 3,
308     Query = 4,
309 }
310 
main()311 fn main() {
312     let args: Vec<String> = env::args().collect();
313 
314     if args.len() != 6 {
315         println!("USAGE: compositor [native|none] [small|large|scroll] [none|swap|commit|flush|query] width height");
316         process::exit(0);
317     }
318 
319     let enable_compositor = match args[1].parse::<String>().unwrap().as_str() {
320         "native" => true,
321         "none" => false,
322         _ => panic!("invalid compositor [native, none]"),
323     };
324 
325     let inv_mode = match args[2].parse::<String>().unwrap().as_str() {
326         "small" => Invalidations::Small,
327         "large" => Invalidations::Large,
328         "scroll" => Invalidations::Scrolling,
329         _ => panic!("invalid invalidations [small, large, scroll]"),
330     };
331 
332     let sync_mode = match args[3].parse::<String>().unwrap().as_str() {
333         "none" => Sync::None,
334         "swap" => Sync::Swap,
335         "commit" => Sync::Commit,
336         "flush" => Sync::Flush,
337         "query" => Sync::Query,
338         _ => panic!("invalid sync mode [none, swap, commit, flush, query]"),
339     };
340 
341     let width = args[4].parse().unwrap();
342     let height = args[5].parse().unwrap();
343     let device_size = DeviceIntSize::new(width, height);
344 
345     // Load GL, construct WR and the native compositor interface.
346     let window = compositor::create_window(
347         device_size.width,
348         device_size.height,
349         enable_compositor,
350         sync_mode as i32,
351     );
352     let debug_flags = DebugFlags::empty();
353     let compositor_config = if enable_compositor {
354         webrender::CompositorConfig::Native {
355             max_update_rects: 1,
356             compositor: Box::new(DirectCompositeInterface::new(window)),
357         }
358     } else {
359         webrender::CompositorConfig::Draw {
360             max_partial_present_rects: 0,
361         }
362     };
363     let opts = webrender::RendererOptions {
364         clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
365         debug_flags,
366         enable_picture_caching: true,
367         compositor_config,
368         ..webrender::RendererOptions::default()
369     };
370     let (tx, rx) = mpsc::channel();
371     let notifier = Box::new(Notifier::new(tx));
372     let gl = unsafe {
373         gl::GlesFns::load_with(
374             |symbol| {
375                 let symbol = CString::new(symbol).unwrap();
376                 let ptr = compositor::get_proc_address(symbol.as_ptr());
377                 ptr
378             }
379         )
380     };
381     let (mut renderer, sender) = webrender::Renderer::new(
382         gl.clone(),
383         notifier,
384         opts,
385         None,
386         device_size,
387     ).unwrap();
388     let api = sender.create_api();
389     let document_id = api.add_document(device_size, 0);
390     let device_pixel_ratio = 1.0;
391     let mut current_epoch = Epoch(0);
392     let root_pipeline_id = PipelineId(0, 0);
393     let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio);
394     let mut time = 0.0;
395     let scroll_id = ExternalScrollId(3, root_pipeline_id);
396 
397     // Kick off first transaction which will mean we get a notify below to build the DL and render.
398     let mut txn = Transaction::new();
399     txn.set_root_pipeline(root_pipeline_id);
400 
401     if let Invalidations::Scrolling = inv_mode {
402         let mut root_builder = DisplayListBuilder::new(root_pipeline_id, layout_size);
403 
404         build_display_list(
405             &mut root_builder,
406             scroll_id,
407             root_pipeline_id,
408             layout_size,
409             1.0,
410             inv_mode,
411         );
412 
413         txn.set_display_list(
414             current_epoch,
415             None,
416             layout_size,
417             root_builder.finalize(),
418             true,
419         );
420     }
421 
422     txn.generate_frame();
423     api.send_transaction(document_id, txn);
424 
425     // Tick the compositor (in this sample, we don't block on UI events)
426     while compositor::tick(window) {
427         // If there is a new frame ready to draw
428         if let Ok(..) = rx.try_recv() {
429             // Update and render. This will invoke the native compositor interface implemented above
430             // as required.
431             renderer.update();
432             renderer.render(device_size).unwrap();
433             let _ = renderer.flush_pipeline_info();
434 
435             // Construct a simple display list that can be drawn and composited by DC.
436             let mut txn = Transaction::new();
437 
438             match inv_mode {
439                 Invalidations::Small | Invalidations::Large => {
440                     let mut root_builder = DisplayListBuilder::new(root_pipeline_id, layout_size);
441 
442                     build_display_list(
443                         &mut root_builder,
444                         scroll_id,
445                         root_pipeline_id,
446                         layout_size,
447                         time,
448                         inv_mode,
449                     );
450 
451                     txn.set_display_list(
452                         current_epoch,
453                         None,
454                         layout_size,
455                         root_builder.finalize(),
456                         true,
457                     );
458                 }
459                 Invalidations::Scrolling => {
460                     let d = 0.5 - 0.5 * (2.0 * f32::consts::PI * 5.0 * time).cos();
461                     txn.scroll_node_with_id(
462                         LayoutPoint::new(0.0, (d * 100.0).round()),
463                         scroll_id,
464                         ScrollClamping::NoClamping,
465                     );
466                 }
467             }
468 
469             txn.generate_frame();
470             api.send_transaction(document_id, txn);
471             current_epoch.0 += 1;
472             time += 0.001;
473             if time > 1.0 {
474                 time = 0.0;
475             }
476 
477             // This does nothing when native compositor is enabled
478             compositor::swap_buffers(window);
479         }
480     }
481 
482     renderer.deinit();
483     compositor::destroy_window(window);
484 }
485