diff --git a/gfx/wr/webrender/res/brush_mix_blend.glsl b/gfx/wr/webrender/res/brush_mix_blend.glsl index ff86e780f45c..f576a23764d6 100644 --- a/gfx/wr/webrender/res/brush_mix_blend.glsl +++ b/gfx/wr/webrender/res/brush_mix_blend.glsl @@ -7,16 +7,40 @@ #include shared,prim_shared,brush -// xy: uv coorinates. +// UV and bounds for the source image varying vec2 v_src_uv; +flat varying vec4 v_src_uv_sample_bounds; -// xy: uv coorinates. +// UV and bounds for the backdrop image varying vec2 v_backdrop_uv; +flat varying vec4 v_backdrop_uv_sample_bounds; +// mix-blend op, and perspective interpolation control +flat varying float v_perspective; flat varying int v_op; #ifdef WR_VERTEX_SHADER +void get_uv( + int res_address, + vec2 f, + ivec2 texture_size, + float perspective_f, + out vec2 out_uv, + out vec4 out_uv_sample_bounds +) { + ImageResource res = fetch_image_resource(res_address); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 inv_texture_size = vec2(1.0) / vec2(texture_size); + f = get_image_quad_uv(res_address, f); + vec2 uv = mix(uv0, uv1, f); + + out_uv = uv * inv_texture_size * perspective_f; + out_uv_sample_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) * inv_texture_size.xyxy; +} + void brush_vs( VertexInfo vi, int prim_address, @@ -29,25 +53,29 @@ void brush_vs( int brush_flags, vec4 unused ) { - //Note: this is unsafe for `vi.world_pos.w <= 0.0` - vec2 device_pos = vi.world_pos.xy * pic_task.device_pixel_scale / max(0.0, vi.world_pos.w); - vec2 backdrop_texture_size = vec2(textureSize(sColor0, 0)); - vec2 src_texture_size = vec2(textureSize(sColor1, 0)); + vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size; + float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0; + float perspective_f = mix(vi.world_pos.w, 1.0, perspective_interpolate); + v_perspective = perspective_interpolate; v_op = prim_user_data.x; - PictureTask src_task = fetch_picture_task(prim_user_data.z); - vec2 src_device_pos = vi.world_pos.xy * (src_task.device_pixel_scale / max(0.0, vi.world_pos.w)); - vec2 src_uv = src_device_pos + - src_task.common_data.task_rect.p0 - - src_task.content_origin; - v_src_uv = src_uv / src_texture_size; + get_uv( + prim_user_data.y, + f, + textureSize(sColor0, 0).xy, + 1.0, + v_backdrop_uv, + v_backdrop_uv_sample_bounds + ); - RenderTaskCommonData backdrop_task = fetch_render_task_common_data(prim_user_data.y); - float src_to_backdrop_scale = pic_task.device_pixel_scale / src_task.device_pixel_scale; - vec2 backdrop_uv = device_pos + - backdrop_task.task_rect.p0 - - src_task.content_origin * src_to_backdrop_scale; - v_backdrop_uv = backdrop_uv / backdrop_texture_size; + get_uv( + prim_user_data.z, + f, + textureSize(sColor1, 0).xy, + perspective_f, + v_src_uv, + v_src_uv_sample_bounds + ); } #endif @@ -210,8 +238,15 @@ const int MixBlendMode_Color = 14; const int MixBlendMode_Luminosity = 15; Fragment brush_fs() { - vec4 Cb = texture(sColor0, v_backdrop_uv); - vec4 Cs = texture(sColor1, v_src_uv); + float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective); + + vec2 src_uv = v_src_uv * perspective_divisor; + src_uv = clamp(src_uv, v_src_uv_sample_bounds.xy, v_src_uv_sample_bounds.zw); + + vec2 backdrop_uv = clamp(v_backdrop_uv, v_backdrop_uv_sample_bounds.xy, v_backdrop_uv_sample_bounds.zw); + + vec4 Cb = texture(sColor0, backdrop_uv); + vec4 Cs = texture(sColor1, src_uv); // The mix-blend-mode functions assume no premultiplied alpha if (Cb.a != 0.0) { diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs index 95824d7edfff..5da881509540 100644 --- a/gfx/wr/webrender/src/batch.rs +++ b/gfx/wr/webrender/src/batch.rs @@ -59,7 +59,6 @@ pub enum BrushBatchKind { Blend, MixBlend { task_id: RenderTaskId, - source_id: RenderTaskId, backdrop_id: RenderTaskId, }, YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange), @@ -1966,62 +1965,74 @@ impl BatchBuilder { let cache_task_id = ctx.resolve_surface(raster_config.surface_index); let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?"); - // TODO(gw): For now, mix-blend is not supported as a picture - // caching root, so we can safely assume there is - // only a single batcher present. - assert_eq!(self.batchers.len(), 1); - let color0 = render_tasks[backdrop_id].get_target_texture(); let color1 = render_tasks[cache_task_id].get_target_texture(); - let key = BatchKey::new( - BatchKind::Brush( - BrushBatchKind::MixBlend { - task_id: self.batchers[0].render_task_id, - source_id: cache_task_id, - backdrop_id, - }, - ), - BlendMode::PremultipliedAlpha, - BatchTextures { - input: TextureSet { - colors: [ - TextureSource::TextureCache( - color0, - Swizzle::default(), - ), - TextureSource::TextureCache( - color1, - Swizzle::default(), - ), - TextureSource::Invalid, - ], - }, - clip_mask: clip_mask_texture_id, - }, - ); - let backdrop_task_address: RenderTaskAddress = backdrop_id.into(); - let source_task_address: RenderTaskAddress = cache_task_id.into(); - let prim_header_index = prim_headers.push(&prim_header, z_id, [ - mode as u32 as i32, - backdrop_task_address.0 as i32, - source_task_address.0 as i32, - 0, - ]); + // Create a separate brush instance for each batcher. For most cases, + // there is only one batcher. However, in the case of drawing onto + // a picture cache, there is one batcher per tile. Although not + // currently used, the implementation of mix-blend-mode now supports + // doing partial readbacks per-tile. In future, this will be enabled + // and allow mix-blends to operate on picture cache surfaces without + // a separate isolated intermediate surface. - self.add_brush_instance_to_batches( - key, - batch_features, - bounding_rect, - z_id, - INVALID_SEGMENT_INDEX, - EdgeAaSegmentMask::empty(), - clip_task_address, - brush_flags, - prim_header_index, - 0, - prim_vis_mask, - ); + for batcher in &mut self.batchers { + if batcher.vis_mask.intersects(prim_vis_mask) { + let render_task_address = batcher.render_task_address; + + let batch_key = BatchKey::new( + BatchKind::Brush( + BrushBatchKind::MixBlend { + task_id: batcher.render_task_id, + backdrop_id, + }, + ), + BlendMode::PremultipliedAlpha, + BatchTextures { + input: TextureSet { + colors: [ + TextureSource::TextureCache( + color0, + Swizzle::default(), + ), + TextureSource::TextureCache( + color1, + Swizzle::default(), + ), + TextureSource::Invalid, + ], + }, + clip_mask: clip_mask_texture_id, + }, + ); + let src_uv_address = render_tasks[cache_task_id].get_texture_address(gpu_cache); + let readback_uv_address = render_tasks[backdrop_id].get_texture_address(gpu_cache); + let prim_header_index = prim_headers.push(&prim_header, z_id, [ + mode as u32 as i32, + readback_uv_address.as_int(), + src_uv_address.as_int(), + 0, + ]); + + let instance = BrushInstance { + segment_index: INVALID_SEGMENT_INDEX, + edge_flags: EdgeAaSegmentMask::empty(), + clip_task_address, + render_task_address, + brush_flags, + prim_header_index, + resource_address: 0, + }; + + batcher.push_single_instance( + batch_key, + batch_features, + bounding_rect, + z_id, + PrimitiveInstanceData::from(instance), + ); + } + } } PictureCompositeMode::Blit(_) => { let cache_task_id = ctx.resolve_surface(raster_config.surface_index); diff --git a/gfx/wr/webrender/src/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs index e4f0ca0af4af..e87198ada830 100644 --- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -210,10 +210,12 @@ impl<'a> FrameBuildingState<'a> { &mut self, surface_index: SurfaceIndex, tasks: Vec, + device_rect: DeviceRect, ) { let surface = &mut self.surfaces[surface_index.0]; assert!(surface.render_tasks.is_none()); surface.render_tasks = Some(SurfaceRenderTasks::Tiled(tasks)); + surface.device_rect = Some(device_rect); } /// Initialize render tasks for a simple surface, that contains only a @@ -223,10 +225,12 @@ impl<'a> FrameBuildingState<'a> { surface_index: SurfaceIndex, task_id: RenderTaskId, parent_surface_index: SurfaceIndex, + device_rect: DeviceRect, ) { let surface = &mut self.surfaces[surface_index.0]; assert!(surface.render_tasks.is_none()); surface.render_tasks = Some(SurfaceRenderTasks::Simple(task_id)); + surface.device_rect = Some(device_rect); self.add_child_render_task( parent_surface_index, @@ -243,10 +247,12 @@ impl<'a> FrameBuildingState<'a> { root_task_id: RenderTaskId, port_task_id: RenderTaskId, parent_surface_index: SurfaceIndex, + device_rect: DeviceRect, ) { let surface = &mut self.surfaces[surface_index.0]; assert!(surface.render_tasks.is_none()); surface.render_tasks = Some(SurfaceRenderTasks::Chained { root_task_id, port_task_id }); + surface.device_rect = Some(device_rect); self.add_child_render_task( parent_surface_index, diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs index f71cc57a439e..23511a79117b 100644 --- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -4067,6 +4067,8 @@ pub struct SurfaceInfo { pub device_pixel_scale: DevicePixelScale, /// The scale factors of the surface to raster transform. pub scale_factors: (f32, f32), + /// The allocated device rect for this surface + pub device_rect: Option, } impl SurfaceInfo { @@ -4105,8 +4107,13 @@ impl SurfaceInfo { inflation_factor, device_pixel_scale, scale_factors, + device_rect: None, } } + + pub fn get_device_rect(&self) -> DeviceRect { + self.device_rect.expect("bug: queried before surface was initialized") + } } #[derive(Debug)] @@ -5061,6 +5068,7 @@ impl PicturePrimitive { frame_state.init_surface_tiled( surface_index, surface_tasks, + device_clip_rect, ); } Some(ref mut raster_config) => { @@ -5268,6 +5276,7 @@ impl PicturePrimitive { blur_render_task_id, picture_task_id, parent_surface_index, + device_rect, ); } PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { @@ -5365,6 +5374,7 @@ impl PicturePrimitive { blur_render_task_id, picture_task_id, parent_surface_index, + device_rect, ); } PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => { @@ -5384,12 +5394,75 @@ impl PicturePrimitive { device_pixel_scale, ); - let readback_task_id = frame_state.rg_builder.add().init( - RenderTask::new_dynamic( - clipped.size.to_i32(), - RenderTaskKind::new_readback(), - ) + let parent_surface = &frame_state.surfaces[parent_surface_index.0]; + let parent_raster_spatial_node_index = parent_surface.raster_spatial_node_index; + let parent_device_pixel_scale = parent_surface.device_pixel_scale; + + // Create a space mapper that will allow mapping from the local rect + // of the mix-blend primitive into the space of the surface that we + // need to read back from. Note that we use the parent's raster spatial + // node here, so that we are in the correct device space of the parent + // surface, whether it establishes a raster root or not. + let map_pic_to_parent = SpaceMapper::new_with_target( + parent_raster_spatial_node_index, + self.spatial_node_index, + RasterRect::max_rect(), // TODO(gw): May need a conservative estimate? + frame_context.spatial_tree, ); + let pic_in_raster_space = map_pic_to_parent + .map(&pic_rect) + .expect("bug: unable to map mix-blend content into parent"); + + // Apply device pixel ratio for parent surface to get into device + // pixels for that surface. + let backdrop_rect = raster_rect_to_device_pixels( + pic_in_raster_space, + parent_device_pixel_scale, + ); + + let parent_surface_rect = parent_surface.get_device_rect(); + + // If there is no available parent surface to read back from (for example, if + // the parent surface is affected by a clip that doesn't affect the child + // surface), then create a dummy 16x16 readback. In future, we could alter + // the composite mode of this primitive to skip the mix-blend, but for simplicity + // we just create a dummy readback for now. + + let readback_task_id = match backdrop_rect.intersection(&parent_surface_rect) { + Some(available_rect) => { + // Calculate the UV coords necessary for the shader to sampler + // from the primitive rect within the readback region. This is + // 0..1 for aligned surfaces, but doing it this way allows + // accurate sampling if the primitive bounds have fractional values. + let backdrop_uv = calculate_uv_rect_kind( + &pic_rect, + &map_pic_to_parent.get_transform(), + &available_rect, + parent_device_pixel_scale, + ); + + frame_state.rg_builder.add().init( + RenderTask::new_dynamic( + available_rect.size.to_i32(), + RenderTaskKind::new_readback( + Some(available_rect.origin), + backdrop_uv, + ), + ) + ) + } + None => { + frame_state.rg_builder.add().init( + RenderTask::new_dynamic( + DeviceIntSize::new(16, 16), + RenderTaskKind::new_readback( + None, + UvRectKind::Rect, + ), + ) + ) + } + }; frame_state.add_child_render_task( parent_surface_index, @@ -5422,6 +5495,7 @@ impl PicturePrimitive { raster_config.surface_index, render_task_id, parent_surface_index, + clipped, ); } PictureCompositeMode::Filter(..) => { @@ -5466,6 +5540,7 @@ impl PicturePrimitive { raster_config.surface_index, render_task_id, parent_surface_index, + clipped, ); } PictureCompositeMode::ComponentTransferFilter(..) => { @@ -5509,6 +5584,7 @@ impl PicturePrimitive { raster_config.surface_index, render_task_id, parent_surface_index, + clipped, ); } PictureCompositeMode::MixBlend(..) | @@ -5553,6 +5629,7 @@ impl PicturePrimitive { raster_config.surface_index, render_task_id, parent_surface_index, + clipped, ); } PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => { @@ -5608,9 +5685,17 @@ impl PicturePrimitive { filter_task_id, picture_task_id, parent_surface_index, + clipped, ); } } + + // Update the device pixel ratio in the surface, in case it was adjusted due + // to the surface being too large. This ensures the correct scale is available + // in case it's used as input to a parent mix-blend-mode readback. + frame_state + .surfaces[raster_config.surface_index.0] + .device_pixel_scale = device_pixel_scale; } None => {} }; diff --git a/gfx/wr/webrender/src/render_target.rs b/gfx/wr/webrender/src/render_target.rs index 9e67179d5af7..c8e486f5e8fe 100644 --- a/gfx/wr/webrender/src/render_target.rs +++ b/gfx/wr/webrender/src/render_target.rs @@ -408,7 +408,7 @@ impl RenderTarget for ColorRenderTarget { RenderTaskKind::LineDecoration(..) => { panic!("Should not be added to color target!"); } - RenderTaskKind::Readback => {} + RenderTaskKind::Readback(..) => {} RenderTaskKind::Scaling(ref info) => { add_scaling_instances( info, @@ -530,7 +530,7 @@ impl RenderTarget for AlphaRenderTarget { let (target_rect, _) = task.get_target_rect(); match task.kind { - RenderTaskKind::Readback | + RenderTaskKind::Readback(..) | RenderTaskKind::Picture(..) | RenderTaskKind::Blit(..) | RenderTaskKind::Border(..) | @@ -752,7 +752,7 @@ impl TextureCacheRenderTarget { RenderTaskKind::Picture(..) | RenderTaskKind::ClipRegion(..) | RenderTaskKind::CacheMask(..) | - RenderTaskKind::Readback | + RenderTaskKind::Readback(..) | RenderTaskKind::Scaling(..) | RenderTaskKind::SvgFilter(..) => { panic!("BUG: unexpected task kind for texture cache target"); diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs index 445f045acae1..20e24ac78845 100644 --- a/gfx/wr/webrender/src/render_task.rs +++ b/gfx/wr/webrender/src/render_task.rs @@ -297,6 +297,20 @@ pub struct SvgFilterTask { pub uv_rect_kind: UvRectKind, } +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ReadbackTask { + // The offset of the rect that needs to be read back, in the + // device space of the surface that will be read back from. + // If this is None, there is no readback surface available + // and this is a dummy (empty) readback. + pub readback_origin: Option, + // UV rect of the readback rect within the readback task area + pub uv_rect_kind: UvRectKind, + // GPU cache handle that provides uv_rect_kind to shaders + pub uv_rect_handle: GpuCacheHandle, +} + #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] @@ -312,7 +326,7 @@ pub enum RenderTaskKind { ClipRegion(ClipRegionTask), VerticalBlur(BlurTask), HorizontalBlur(BlurTask), - Readback, + Readback(ReadbackTask), Scaling(ScalingTask), Blit(BlitTask), Border(BorderTask), @@ -331,7 +345,7 @@ impl RenderTaskKind { RenderTaskKind::ClipRegion(..) => "ClipRegion", RenderTaskKind::VerticalBlur(..) => "VerticalBlur", RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur", - RenderTaskKind::Readback => "Readback", + RenderTaskKind::Readback(..) => "Readback", RenderTaskKind::Scaling(..) => "Scaling", RenderTaskKind::Blit(..) => "Blit", RenderTaskKind::Border(..) => "Border", @@ -346,7 +360,7 @@ impl RenderTaskKind { pub fn target_kind(&self) -> RenderTargetKind { match *self { RenderTaskKind::LineDecoration(..) | - RenderTaskKind::Readback | + RenderTaskKind::Readback(..) | RenderTaskKind::Border(..) | RenderTaskKind::Gradient(..) | RenderTaskKind::Picture(..) | @@ -419,8 +433,17 @@ impl RenderTaskKind { }) } - pub fn new_readback() -> Self { - RenderTaskKind::Readback + pub fn new_readback( + readback_origin: Option, + uv_rect_kind: UvRectKind, + ) -> Self { + RenderTaskKind::Readback( + ReadbackTask { + readback_origin, + uv_rect_kind, + uv_rect_handle: GpuCacheHandle::new(), + } + ) } pub fn new_line_decoration( @@ -628,7 +651,7 @@ impl RenderTaskKind { task.blur_region.height as f32, ] } - RenderTaskKind::Readback | + RenderTaskKind::Readback(..) | RenderTaskKind::Scaling(..) | RenderTaskKind::Border(..) | RenderTaskKind::LineDecoration(..) | @@ -685,7 +708,9 @@ impl RenderTaskKind { RenderTaskKind::SvgFilter(ref mut info) => { (&mut info.uv_rect_handle, info.uv_rect_kind) } - RenderTaskKind::Readback | + RenderTaskKind::Readback(ref mut info) => { + (&mut info.uv_rect_handle, info.uv_rect_kind) + } RenderTaskKind::Scaling(..) | RenderTaskKind::Blit(..) | RenderTaskKind::ClipRegion(..) | @@ -1359,7 +1384,7 @@ impl RenderTask { pub fn uv_rect_kind(&self) -> UvRectKind { match self.kind { RenderTaskKind::CacheMask(..) | - RenderTaskKind::Readback => { + RenderTaskKind::Readback(..) => { unreachable!("bug: unexpected render task"); } @@ -1407,8 +1432,10 @@ impl RenderTask { RenderTaskKind::SvgFilter(ref info) => { gpu_cache.get_address(&info.uv_rect_handle) } + RenderTaskKind::Readback(ref info) => { + gpu_cache.get_address(&info.uv_rect_handle) + } RenderTaskKind::ClipRegion(..) | - RenderTaskKind::Readback | RenderTaskKind::Scaling(..) | RenderTaskKind::Blit(..) | RenderTaskKind::Border(..) | @@ -1508,7 +1535,7 @@ impl RenderTask { pt.new_level("HorizontalBlur".to_owned()); task.print_with(pt); } - RenderTaskKind::Readback => { + RenderTaskKind::Readback(..) => { pt.new_level("Readback".to_owned()); } RenderTaskKind::Scaling(ref kind) => { diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs index c78356294629..dfabcda3b4a7 100644 --- a/gfx/wr/webrender/src/renderer/mod.rs +++ b/gfx/wr/webrender/src/renderer/mod.rs @@ -84,7 +84,7 @@ use crate::profiler::{Profiler, add_event_marker, add_text_marker, thread_is_bei use crate::device::query::{GpuProfiler, GpuDebugMethod}; use crate::render_backend::{FrameId, RenderBackend}; use crate::render_task_graph::RenderTaskGraph; -use crate::render_task::{RenderTask, RenderTaskKind}; +use crate::render_task::{RenderTask, RenderTaskKind, ReadbackTask}; use crate::resource_cache::ResourceCache; use crate::scene_builder_thread::{SceneBuilderThread, SceneBuilderThreadChannels, LowPrioritySceneBuilderThread}; use crate::screen_capture::AsyncScreenshotGrabber; @@ -2489,10 +2489,22 @@ impl Renderer { &mut self, draw_target: DrawTarget, uses_scissor: bool, - source: &RenderTask, backdrop: &RenderTask, readback: &RenderTask, ) { + // Extract the rectangle in the backdrop surface's device space of where + // we need to read from. + let readback_origin = match readback.kind { + RenderTaskKind::Readback(ReadbackTask { readback_origin: Some(o), .. }) => o, + RenderTaskKind::Readback(ReadbackTask { readback_origin: None, .. }) => { + // If this is a dummy readback, just early out. We know that the + // clear of the target will ensure the task rect is already zero alpha, + // so it won't affect the rendering output. + return; + } + _ => unreachable!(), + }; + if uses_scissor { self.device.disable_scissor(); } @@ -2509,11 +2521,7 @@ impl Renderer { // composite operation in this batch. let (readback_rect, readback_layer) = readback.get_target_rect(); let (backdrop_rect, _) = backdrop.get_target_rect(); - let (backdrop_screen_origin, backdrop_scale) = match backdrop.kind { - RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale), - _ => panic!("bug: composite on non-picture?"), - }; - let (source_screen_origin, source_scale) = match source.kind { + let (backdrop_screen_origin, _) = match backdrop.kind { RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale), _ => panic!("bug: composite on non-picture?"), }; @@ -2528,31 +2536,56 @@ impl Renderer { false, ); - let source_in_backdrop_space = source_screen_origin * (backdrop_scale.0 / source_scale.0); - - let mut src = DeviceIntRect::new( - (source_in_backdrop_space + (backdrop_rect.origin.to_f32() - backdrop_screen_origin)).to_i32(), - readback_rect.size, + // Get the rect that we ideally want, in space of the parent surface + let wanted_rect = DeviceRect::new( + readback_origin, + readback_rect.size.to_f32(), ); - let mut dest = readback_rect.to_i32(); - let device_to_framebuffer = Scale::new(1i32); - // Need to invert the y coordinates and flip the image vertically when - // reading back from the framebuffer. - if draw_target.is_default() { - src.origin.y = draw_target.dimensions().height as i32 - src.size.height - src.origin.y; - dest.origin.y += dest.size.height; - dest.size.height = -dest.size.height; + // Get the rect that is available on the parent surface. It may be smaller + // than desired because this is a picture cache tile covering only part of + // the wanted rect and/or because the parent surface was clipped. + let avail_rect = DeviceRect::new( + backdrop_screen_origin, + backdrop_rect.size.to_f32(), + ); + + if let Some(int_rect) = wanted_rect.intersection(&avail_rect) { + // If there is a valid intersection, work out the correct origins and + // sizes of the copy rects, and do the blit. + let copy_size = int_rect.size.to_i32(); + + let src_origin = backdrop_rect.origin.to_f32() + + int_rect.origin.to_vector() - + backdrop_screen_origin.to_vector(); + + let src = DeviceIntRect::new( + src_origin.to_i32(), + copy_size, + ); + + let dest_origin = readback_rect.origin.to_f32() + + int_rect.origin.to_vector() - + readback_origin.to_vector(); + + let dest = DeviceIntRect::new( + dest_origin.to_i32(), + copy_size, + ); + + // Should always be drawing to picture cache tiles or off-screen surface! + debug_assert!(!draw_target.is_default()); + let device_to_framebuffer = Scale::new(1i32); + + self.device.blit_render_target( + draw_target.into(), + src * device_to_framebuffer, + cache_draw_target, + dest * device_to_framebuffer, + TextureFilter::Linear, + ); } - self.device.blit_render_target( - draw_target.into(), - src * device_to_framebuffer, - cache_draw_target, - dest * device_to_framebuffer, - TextureFilter::Linear, - ); - // Restore draw target to current pass render target + layer, and reset // the read target. self.device.bind_draw_target(draw_target); @@ -2899,14 +2932,13 @@ impl Renderer { } // Handle special case readback for composites. - if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind { + if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, backdrop_id }) = batch.key.kind { // composites can't be grouped together because // they may overlap and affect each other. debug_assert_eq!(batch.instances.len(), 1); self.handle_readback_composite( draw_target, uses_scissor, - &render_tasks[source_id], &render_tasks[task_id], &render_tasks[backdrop_id], ); diff --git a/gfx/wr/wrench/reftests/blend/mix-blend-invalid-backdrop-ref.yaml b/gfx/wr/wrench/reftests/blend/mix-blend-invalid-backdrop-ref.yaml new file mode 100644 index 000000000000..1e204cb33851 --- /dev/null +++ b/gfx/wr/wrench/reftests/blend/mix-blend-invalid-backdrop-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: red diff --git a/gfx/wr/wrench/reftests/blend/mix-blend-invalid-backdrop.yaml b/gfx/wr/wrench/reftests/blend/mix-blend-invalid-backdrop.yaml new file mode 100644 index 000000000000..1b0e6d079294 --- /dev/null +++ b/gfx/wr/wrench/reftests/blend/mix-blend-invalid-backdrop.yaml @@ -0,0 +1,23 @@ +# Test that if the parent surface is clipped such that there +# is no backdrop rect available, no crash occurs and output +# is as expected (a no-op mix-blend) +--- +root: + items: + - type: clip + id: 2 + bounds: [0, 0, 100, 100] + - type: stacking-context + blend-container: true + clip-node: 2 + items: + - type: rect + bounds: [0, 0, 100, 100] + color: red + - type: stacking-context + bounds: [100, 0, 100, 100] + mix-blend-mode: multiply + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green diff --git a/gfx/wr/wrench/reftests/blend/reftest.list b/gfx/wr/wrench/reftests/blend/reftest.list index b4826d68374b..e967a72cfea1 100644 --- a/gfx/wr/wrench/reftests/blend/reftest.list +++ b/gfx/wr/wrench/reftests/blend/reftest.list @@ -22,3 +22,4 @@ fuzzy(1,2502) == transparent-composite-1.yaml transparent-composite-1-ref.yaml fuzzy(1,2502) == transparent-composite-2.yaml transparent-composite-2-ref.yaml fuzzy(2,420) == multi-mix-blend-mode.yaml multi-mix-blend-mode-ref.yaml +== mix-blend-invalid-backdrop.yaml mix-blend-invalid-backdrop-ref.yaml diff --git a/gfx/wr/wrench/src/wrench.rs b/gfx/wr/wrench/src/wrench.rs index 058aefbca3f0..52c67473a631 100644 --- a/gfx/wr/wrench/src/wrench.rs +++ b/gfx/wr/wrench/src/wrench.rs @@ -266,7 +266,6 @@ impl Wrench { testing: true, max_texture_size: Some(8196), // Needed for rawtest::test_resize_image. allow_dual_source_blending: !disable_dual_source_blending, - allow_advanced_blend_equation: true, dump_shader_source, // SWGL doesn't support the GL_ALWAYS depth comparison function used by // `clear_caches_with_quads`, but scissored clears work well. diff --git a/gfx/wr/wrench/src/yaml_helper.rs b/gfx/wr/wrench/src/yaml_helper.rs index dc55b1e860ce..f9974efdc751 100644 --- a/gfx/wr/wrench/src/yaml_helper.rs +++ b/gfx/wr/wrench/src/yaml_helper.rs @@ -52,6 +52,8 @@ fn string_to_color(color: &str) -> Option { "white" => Some(ColorF::new(1.0, 1.0, 1.0, 1.0)), "black" => Some(ColorF::new(0.0, 0.0, 0.0, 1.0)), "yellow" => Some(ColorF::new(1.0, 1.0, 0.0, 1.0)), + "cyan" => Some(ColorF::new(0.0, 1.0, 1.0, 1.0)), + "magenta" => Some(ColorF::new(1.0, 0.0, 1.0, 1.0)), "transparent" => Some(ColorF::new(1.0, 1.0, 1.0, 0.0)), s => { let items: Vec = s.split_whitespace()