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 use api::{DeviceIntPoint, DeviceIntRect};
6 use api::{LayerPoint, LayerRect, LayerToWorldScale, LayerVector2D};
7 use api::{ColorF, FilterOp, MixBlendMode, PipelineId};
8 use api::{PremultipliedColorF, Shadow};
9 use box_shadow::{BLUR_SAMPLE_SCALE};
10 use clip_scroll_tree::ClipScrollNodeIndex;
11 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
12 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
13 use gpu_types::{PictureType};
14 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
15 use prim_store::ScrollNodeAndClipChain;
16 use render_task::{ClearMode, RenderTask};
17 use render_task::{RenderTaskId, RenderTaskLocation, to_cache_size};
18 use scene::{FilterOpHelpers, SceneProperties};
19 use tiling::RenderTargetKind;
20 
21 /*
22  A picture represents a dynamically rendered image. It consists of:
23 
24  * A number of primitives that are drawn onto the picture.
25  * A composite operation describing how to composite this
26    picture into its parent.
27  * A configuration describing how to draw the primitives on
28    this picture (e.g. in screen space or local space).
29  */
30 
31 /// Specifies how this Picture should be composited
32 /// onto the target it belongs to.
33 #[derive(Debug, Copy, Clone, PartialEq)]
34 pub enum PictureCompositeMode {
35     /// Apply CSS mix-blend-mode effect.
36     MixBlend(MixBlendMode),
37     /// Apply a CSS filter.
38     Filter(FilterOp),
39     /// Draw to intermediate surface, copy straight across. This
40     /// is used for CSS isolation, and plane splitting.
41     Blit,
42 }
43 
44 /// Configure whether the content to be drawn by a picture
45 /// in local space rasterization or the screen space.
46 #[derive(Debug, Copy, Clone, PartialEq)]
47 #[cfg_attr(feature = "capture", derive(Serialize))]
48 #[cfg_attr(feature = "replay", derive(Deserialize))]
49 pub enum ContentOrigin {
50     Local(LayerPoint),
51     Screen(DeviceIntPoint),
52 }
53 
54 #[derive(Debug)]
55 pub enum PictureKind {
56     TextShadow {
57         offset: LayerVector2D,
58         color: ColorF,
59         blur_radius: f32,
60         content_rect: LayerRect,
61     },
62     Image {
63         // If a mix-blend-mode, contains the render task for
64         // the readback of the framebuffer that we use to sample
65         // from in the mix-blend-mode shader.
66         // For drop-shadow filter, this will store the original
67         // picture task which would be rendered on screen after
68         // blur pass.
69         secondary_render_task_id: Option<RenderTaskId>,
70         /// How this picture should be composited.
71         /// If None, don't composite - just draw directly on parent surface.
72         composite_mode: Option<PictureCompositeMode>,
73         // If true, this picture is part of a 3D context.
74         is_in_3d_context: bool,
75         // If requested as a frame output (for rendering
76         // pages to a texture), this is the pipeline this
77         // picture is the root of.
78         frame_output_pipeline_id: Option<PipelineId>,
79         // The original reference frame ID for this picture.
80         // It is only different if this is part of a 3D
81         // rendering context.
82         reference_frame_index: ClipScrollNodeIndex,
83         real_local_rect: LayerRect,
84         // An optional cache handle for storing extra data
85         // in the GPU cache, depending on the type of
86         // picture.
87         extra_gpu_data_handle: GpuCacheHandle,
88     },
89 }
90 
91 #[derive(Debug)]
92 pub struct PicturePrimitive {
93     // If this picture is drawn to an intermediate surface,
94     // the associated target information.
95     pub surface: Option<RenderTaskId>,
96 
97     // Details specific to this type of picture.
98     pub kind: PictureKind,
99 
100     // List of primitive runs that make up this picture.
101     pub runs: Vec<PrimitiveRun>,
102 
103     // The pipeline that the primitives on this picture belong to.
104     pub pipeline_id: PipelineId,
105 
106     // If true, apply visibility culling to primitives on this
107     // picture. For text shadows and box shadows, we want to
108     // unconditionally draw them.
109     pub cull_children: bool,
110 
111     // The brush primitive that will be used to draw this
112     // picture.
113     // TODO(gw): Having a brush primitive embedded here
114     //           makes the code complex in a few places.
115     //           Consider a better way to structure this.
116     //           Maybe embed the PicturePrimitive inside
117     //           the BrushKind enum instead?
118     pub brush: BrushPrimitive,
119 }
120 
121 impl PicturePrimitive {
new_text_shadow(shadow: Shadow, pipeline_id: PipelineId) -> Self122     pub fn new_text_shadow(shadow: Shadow, pipeline_id: PipelineId) -> Self {
123         PicturePrimitive {
124             runs: Vec::new(),
125             surface: None,
126             kind: PictureKind::TextShadow {
127                 offset: shadow.offset,
128                 color: shadow.color,
129                 blur_radius: shadow.blur_radius,
130                 content_rect: LayerRect::zero(),
131             },
132             pipeline_id,
133             cull_children: false,
134             brush: BrushPrimitive::new(
135                 BrushKind::Picture,
136                 None,
137             ),
138         }
139     }
140 
resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool141     pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
142         match self.kind {
143             PictureKind::Image { ref mut composite_mode, .. } => {
144                 match composite_mode {
145                     &mut Some(PictureCompositeMode::Filter(ref mut filter)) => {
146                         match filter {
147                             &mut FilterOp::Opacity(ref binding, ref mut value) => {
148                                 *value = properties.resolve_float(binding, *value);
149                             }
150                             _ => {}
151                         }
152 
153                         filter.is_visible()
154                     }
155                     _ => true,
156                 }
157             }
158             _ => true
159         }
160     }
161 
new_image( composite_mode: Option<PictureCompositeMode>, is_in_3d_context: bool, pipeline_id: PipelineId, reference_frame_index: ClipScrollNodeIndex, frame_output_pipeline_id: Option<PipelineId>, ) -> Self162     pub fn new_image(
163         composite_mode: Option<PictureCompositeMode>,
164         is_in_3d_context: bool,
165         pipeline_id: PipelineId,
166         reference_frame_index: ClipScrollNodeIndex,
167         frame_output_pipeline_id: Option<PipelineId>,
168     ) -> Self {
169         PicturePrimitive {
170             runs: Vec::new(),
171             surface: None,
172             kind: PictureKind::Image {
173                 secondary_render_task_id: None,
174                 composite_mode,
175                 is_in_3d_context,
176                 frame_output_pipeline_id,
177                 reference_frame_index,
178                 real_local_rect: LayerRect::zero(),
179                 extra_gpu_data_handle: GpuCacheHandle::new(),
180             },
181             pipeline_id,
182             cull_children: true,
183             brush: BrushPrimitive::new(
184                 BrushKind::Picture,
185                 None,
186             ),
187         }
188     }
189 
add_primitive( &mut self, prim_index: PrimitiveIndex, clip_and_scroll: ScrollNodeAndClipChain )190     pub fn add_primitive(
191         &mut self,
192         prim_index: PrimitiveIndex,
193         clip_and_scroll: ScrollNodeAndClipChain
194     ) {
195         if let Some(ref mut run) = self.runs.last_mut() {
196             if run.clip_and_scroll == clip_and_scroll &&
197                run.base_prim_index.0 + run.count == prim_index.0 {
198                 run.count += 1;
199                 return;
200             }
201         }
202 
203         self.runs.push(PrimitiveRun {
204             base_prim_index: prim_index,
205             count: 1,
206             clip_and_scroll,
207         });
208     }
209 
update_local_rect( &mut self, prim_run_rect: PrimitiveRunLocalRect, ) -> LayerRect210     pub fn update_local_rect(
211         &mut self,
212         prim_run_rect: PrimitiveRunLocalRect,
213     ) -> LayerRect {
214         let local_content_rect = prim_run_rect.local_rect_in_actual_parent_space;
215 
216         match self.kind {
217             PictureKind::Image { composite_mode, ref mut real_local_rect, .. } => {
218                 *real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
219 
220                 match composite_mode {
221                     Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
222                         let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
223                         local_content_rect.inflate(inflate_size, inflate_size)
224                     }
225                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
226                         let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
227                         local_content_rect.inflate(inflate_size, inflate_size)
228                                           .translate(&offset)
229                     }
230                     _ => {
231                         local_content_rect
232                     }
233                 }
234             }
235             PictureKind::TextShadow { offset, blur_radius, ref mut content_rect, .. } => {
236                 let blur_offset = blur_radius * BLUR_SAMPLE_SCALE;
237 
238                 *content_rect = local_content_rect.inflate(
239                     blur_offset,
240                     blur_offset,
241                 );
242 
243                 content_rect.translate(&offset)
244             }
245         }
246     }
247 
prepare_for_render( &mut self, prim_index: PrimitiveIndex, prim_screen_rect: &DeviceIntRect, prim_local_rect: &LayerRect, pic_state_for_children: PictureState, pic_state: &mut PictureState, frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, )248     pub fn prepare_for_render(
249         &mut self,
250         prim_index: PrimitiveIndex,
251         prim_screen_rect: &DeviceIntRect,
252         prim_local_rect: &LayerRect,
253         pic_state_for_children: PictureState,
254         pic_state: &mut PictureState,
255         frame_context: &FrameBuildingContext,
256         frame_state: &mut FrameBuildingState,
257     ) {
258         let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
259 
260         match self.kind {
261             PictureKind::Image {
262                 ref mut secondary_render_task_id,
263                 ref mut extra_gpu_data_handle,
264                 composite_mode,
265                 ..
266             } => {
267                 let content_origin = ContentOrigin::Screen(prim_screen_rect.origin);
268                 match composite_mode {
269                     Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
270                         let picture_task = RenderTask::new_picture(
271                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
272                             prim_index,
273                             RenderTargetKind::Color,
274                             content_origin,
275                             PremultipliedColorF::TRANSPARENT,
276                             ClearMode::Transparent,
277                             pic_state_for_children.tasks,
278                             PictureType::Image,
279                         );
280 
281                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
282                         let picture_task_id = frame_state.render_tasks.add(picture_task);
283 
284                         let blur_render_task = RenderTask::new_blur(
285                             blur_std_deviation,
286                             picture_task_id,
287                             frame_state.render_tasks,
288                             RenderTargetKind::Color,
289                             ClearMode::Transparent,
290                             PremultipliedColorF::TRANSPARENT,
291                         );
292 
293                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
294                         pic_state.tasks.push(render_task_id);
295                         self.surface = Some(render_task_id);
296                     }
297                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
298                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
299                         let mut picture_task = RenderTask::new_picture(
300                             RenderTaskLocation::Dynamic(None, rect.size),
301                             prim_index,
302                             RenderTargetKind::Color,
303                             ContentOrigin::Screen(rect.origin),
304                             PremultipliedColorF::TRANSPARENT,
305                             ClearMode::Transparent,
306                             pic_state_for_children.tasks,
307                             PictureType::Image,
308                         );
309                         picture_task.mark_for_saving();
310 
311                         let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
312                         let picture_task_id = frame_state.render_tasks.add(picture_task);
313 
314                         let blur_render_task = RenderTask::new_blur(
315                             blur_std_deviation.round(),
316                             picture_task_id,
317                             frame_state.render_tasks,
318                             RenderTargetKind::Color,
319                             ClearMode::Transparent,
320                             color.premultiplied(),
321                         );
322 
323                         *secondary_render_task_id = Some(picture_task_id);
324 
325                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
326                         pic_state.tasks.push(render_task_id);
327                         self.surface = Some(render_task_id);
328                     }
329                     Some(PictureCompositeMode::MixBlend(..)) => {
330                         let picture_task = RenderTask::new_picture(
331                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
332                             prim_index,
333                             RenderTargetKind::Color,
334                             content_origin,
335                             PremultipliedColorF::TRANSPARENT,
336                             ClearMode::Transparent,
337                             pic_state_for_children.tasks,
338                             PictureType::Image,
339                         );
340 
341                         let readback_task_id = frame_state.render_tasks.add(RenderTask::new_readback(*prim_screen_rect));
342 
343                         *secondary_render_task_id = Some(readback_task_id);
344                         pic_state.tasks.push(readback_task_id);
345 
346                         let render_task_id = frame_state.render_tasks.add(picture_task);
347                         pic_state.tasks.push(render_task_id);
348                         self.surface = Some(render_task_id);
349                     }
350                     Some(PictureCompositeMode::Filter(filter)) => {
351                         // If this filter is not currently going to affect
352                         // the picture, just collapse this picture into the
353                         // current render task. This most commonly occurs
354                         // when opacity == 1.0, but can also occur on other
355                         // filters and be a significant performance win.
356                         if filter.is_noop() {
357                             pic_state.tasks.extend(pic_state_for_children.tasks);
358                             self.surface = None;
359                         } else {
360 
361                             if let FilterOp::ColorMatrix(m) = filter {
362                                 if let Some(mut request) = frame_state.gpu_cache.request(extra_gpu_data_handle) {
363                                     for i in 0..5 {
364                                         request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
365                                     }
366                                 }
367                             }
368 
369                             let picture_task = RenderTask::new_picture(
370                                 RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
371                                 prim_index,
372                                 RenderTargetKind::Color,
373                                 content_origin,
374                                 PremultipliedColorF::TRANSPARENT,
375                                 ClearMode::Transparent,
376                                 pic_state_for_children.tasks,
377                                 PictureType::Image,
378                             );
379 
380                             let render_task_id = frame_state.render_tasks.add(picture_task);
381                             pic_state.tasks.push(render_task_id);
382                             self.surface = Some(render_task_id);
383                         }
384                     }
385                     Some(PictureCompositeMode::Blit) => {
386                         let picture_task = RenderTask::new_picture(
387                             RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
388                             prim_index,
389                             RenderTargetKind::Color,
390                             content_origin,
391                             PremultipliedColorF::TRANSPARENT,
392                             ClearMode::Transparent,
393                             pic_state_for_children.tasks,
394                             PictureType::Image,
395                         );
396 
397                         let render_task_id = frame_state.render_tasks.add(picture_task);
398                         pic_state.tasks.push(render_task_id);
399                         self.surface = Some(render_task_id);
400                     }
401                     None => {
402                         pic_state.tasks.extend(pic_state_for_children.tasks);
403                         self.surface = None;
404                     }
405                 }
406             }
407             PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
408                 // This is a shadow element. Create a render task that will
409                 // render the text run to a target, and then apply a gaussian
410                 // blur to that text run in order to build the actual primitive
411                 // which will be blitted to the framebuffer.
412                 let cache_size = to_cache_size(content_rect.size * content_scale);
413 
414                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
415                 // "the image that would be generated by applying to the shadow a
416                 // Gaussian blur with a standard deviation equal to half the blur radius."
417                 let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
418                 let blur_std_deviation = device_radius * 0.5;
419 
420                 let picture_task = RenderTask::new_picture(
421                     RenderTaskLocation::Dynamic(None, cache_size),
422                     prim_index,
423                     RenderTargetKind::Color,
424                     ContentOrigin::Local(content_rect.origin),
425                     color.premultiplied(),
426                     ClearMode::Transparent,
427                     Vec::new(),
428                     PictureType::TextShadow,
429                 );
430 
431                 let picture_task_id = frame_state.render_tasks.add(picture_task);
432 
433                 let blur_render_task = RenderTask::new_blur(
434                     blur_std_deviation,
435                     picture_task_id,
436                     frame_state.render_tasks,
437                     RenderTargetKind::Color,
438                     ClearMode::Transparent,
439                     color.premultiplied(),
440                 );
441 
442                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
443                 pic_state.tasks.push(render_task_id);
444                 self.surface = Some(render_task_id);
445             }
446         }
447     }
448 
write_gpu_blocks(&self, request: &mut GpuDataRequest)449     pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
450         // TODO(gw): It's unfortunate that we pay a fixed cost
451         //           of 5 GPU blocks / picture, just due to the size
452         //           of the color matrix. There aren't typically very
453         //           many pictures in a scene, but we should consider
454         //           making this more efficient for the common case.
455         match self.kind {
456             PictureKind::TextShadow { .. } => {
457                 request.push([0.0; 4]);
458             }
459             PictureKind::Image { composite_mode, .. } => {
460                 match composite_mode {
461                     Some(PictureCompositeMode::Filter(filter)) => {
462                         let amount = match filter {
463                             FilterOp::Contrast(amount) => amount,
464                             FilterOp::Grayscale(amount) => amount,
465                             FilterOp::HueRotate(angle) => 0.01745329251 * angle,
466                             FilterOp::Invert(amount) => amount,
467                             FilterOp::Saturate(amount) => amount,
468                             FilterOp::Sepia(amount) => amount,
469                             FilterOp::Brightness(amount) => amount,
470                             FilterOp::Opacity(_, amount) => amount,
471 
472                             // Go through different paths
473                             FilterOp::Blur(..) |
474                             FilterOp::DropShadow(..) |
475                             FilterOp::ColorMatrix(_) => 0.0,
476                         };
477 
478                         request.push([amount, 1.0 - amount, 0.0, 0.0]);
479                     }
480                     _ => {
481                         request.push([0.0; 4]);
482                     }
483                 }
484             }
485         }
486     }
487 }
488