Bug 1690396 - Refactor mix-blend-mode impl to work correctly with raster roots. r=nical

This fixes incorrect rendering when either the source or backdrop
tasks establish a raster root.

By design, it also changes mix-blend backdrop readbacks to work in
a way that can handle readbacks from picture cache tiles, which is
a follow up optimization being worked on.

Differential Revision: https://phabricator.services.mozilla.com/D103853
This commit is contained in:
Glenn Watson 2021-02-04 01:16:41 +00:00
Родитель 9302cabffc
Коммит 5a2bff1c55
8 изменённых файлов: 158 добавлений и 64 удалений

Просмотреть файл

@ -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) {

Просмотреть файл

@ -59,7 +59,6 @@ pub enum BrushBatchKind {
Blend,
MixBlend {
task_id: RenderTaskId,
source_id: RenderTaskId,
backdrop_id: RenderTaskId,
},
YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange),
@ -1979,7 +1978,6 @@ impl BatchBuilder {
BatchKind::Brush(
BrushBatchKind::MixBlend {
task_id: self.batchers[0].render_task_id,
source_id: cache_task_id,
backdrop_id,
},
),
@ -2001,12 +1999,12 @@ impl BatchBuilder {
clip_mask: clip_mask_texture_id,
},
);
let backdrop_task_address: RenderTaskAddress = backdrop_id.into();
let source_task_address: RenderTaskAddress = cache_task_id.into();
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,
backdrop_task_address.0 as i32,
source_task_address.0 as i32,
readback_uv_address.as_int(),
src_uv_address.as_int(),
0,
]);

Просмотреть файл

@ -5415,10 +5415,50 @@ impl PicturePrimitive {
device_pixel_scale,
);
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,
);
// 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(),
&backdrop_rect,
parent_device_pixel_scale,
);
let readback_task_id = frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
clipped.size.to_i32(),
RenderTaskKind::new_readback(),
backdrop_rect.size.to_i32(),
RenderTaskKind::new_readback(
backdrop_rect.origin.to_i32(),
backdrop_uv,
),
)
);

Просмотреть файл

@ -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");

Просмотреть файл

@ -297,6 +297,18 @@ 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
pub readback_origin: DeviceIntPoint,
// 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 +324,7 @@ pub enum RenderTaskKind {
ClipRegion(ClipRegionTask),
VerticalBlur(BlurTask),
HorizontalBlur(BlurTask),
Readback,
Readback(ReadbackTask),
Scaling(ScalingTask),
Blit(BlitTask),
Border(BorderTask),
@ -331,7 +343,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 +358,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 +431,17 @@ impl RenderTaskKind {
})
}
pub fn new_readback() -> Self {
RenderTaskKind::Readback
pub fn new_readback(
readback_origin: DeviceIntPoint,
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 +649,7 @@ impl RenderTaskKind {
task.blur_region.height as f32,
]
}
RenderTaskKind::Readback |
RenderTaskKind::Readback(..) |
RenderTaskKind::Scaling(..) |
RenderTaskKind::Border(..) |
RenderTaskKind::LineDecoration(..) |
@ -685,7 +706,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 +1382,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 +1430,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 +1533,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) => {

Просмотреть файл

@ -2489,7 +2489,6 @@ impl Renderer {
&mut self,
draw_target: DrawTarget,
uses_scissor: bool,
source: &RenderTask,
backdrop: &RenderTask,
readback: &RenderTask,
) {
@ -2504,16 +2503,19 @@ impl Renderer {
let (cache_texture, _) = self.texture_resolver
.resolve(&texture_source).expect("bug: no source texture");
// 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(ref info) => info.readback_origin,
_ => unreachable!(),
};
// Before submitting the composite batch, do the
// framebuffer readbacks that are needed for each
// 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,28 +2530,22 @@ 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(),
let src = DeviceIntRect::new(
readback_origin +
backdrop_rect.origin.to_vector() -
backdrop_screen_origin.to_i32().to_vector(),
readback_rect.size,
);
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;
}
// 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,
readback_rect * device_to_framebuffer,
TextureFilter::Linear,
);
@ -2899,14 +2895,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],
);

Просмотреть файл

@ -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.

Просмотреть файл

@ -52,6 +52,8 @@ fn string_to_color(color: &str) -> Option<ColorF> {
"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<f32> = s.split_whitespace()