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