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