Bug 1570081 - Support changing the blob image visible rect in WebRender. r=jrmuizel

Differential Revision: https://phabricator.services.mozilla.com/D39911

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Silva 2019-09-17 09:12:26 +00:00
Родитель e9850ca2ee
Коммит e9915eb02b
7 изменённых файлов: 258 добавлений и 94 удалений

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

@ -336,21 +336,23 @@ struct Reader {
};
static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
gfx::SurfaceFormat aFormat,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const mozilla::wr::LayoutIntRect* aRenderRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
Range<uint8_t> aOutput) {
IntSize size(aRenderRect->size.width, aRenderRect->size.height);
AUTO_PROFILER_TRACING("WebRender", "RasterizeSingleBlob", GRAPHICS);
MOZ_RELEASE_ASSERT(aSize.width > 0 && aSize.height > 0);
if (aSize.width <= 0 || aSize.height <= 0) {
MOZ_RELEASE_ASSERT(size.width > 0 && size.height > 0);
if (size.width <= 0 || size.height <= 0) {
return false;
}
auto stride = aSize.width * gfx::BytesPerPixel(aFormat);
auto stride = size.width * gfx::BytesPerPixel(aFormat);
if (aOutput.length() < static_cast<size_t>(aSize.height * stride)) {
if (aOutput.length() < static_cast<size_t>(size.height * stride)) {
return false;
}
@ -358,7 +360,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
bool uninitialized = false;
RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(
gfx::BackendType::SKIA, aOutput.begin().get(), aSize, stride, aFormat,
gfx::BackendType::SKIA, aOutput.begin().get(), size, stride, aFormat,
uninitialized);
if (!dt) {
@ -370,22 +372,18 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
size_t footerSize = sizeof(size_t) + sizeof(IntPoint);
MOZ_RELEASE_ASSERT(aBlob.length() >= footerSize);
size_t indexOffset = ConvertFromBytes<size_t>(aBlob.end().get() - footerSize);
IntPoint origin = ConvertFromBytes<IntPoint>(aBlob.end().get() - footerSize +
sizeof(size_t));
// Apply the visibleRect's offset to make (0, 0) in the DT correspond to (0,
// 0) in the texture
// aRenderRect is the part of the blob that we are currently rendering
// (for example a tile) in the same coordinate space as aVisibleRect.
IntPoint origin = gfx::IntPoint(aRenderRect->origin.x, aRenderRect->origin.y);
MOZ_RELEASE_ASSERT(indexOffset <= aBlob.length() - footerSize);
Reader reader(aBlob.begin().get() + indexOffset,
aBlob.length() - footerSize - indexOffset);
if (aTileOffset) {
origin +=
gfx::IntPoint(aTileOffset->x * *aTileSize, aTileOffset->y * *aTileSize);
}
dt = gfx::Factory::CreateOffsetDrawTarget(dt, origin);
auto bounds = gfx::IntRect(origin, aSize);
auto bounds = gfx::IntRect(origin, size);
if (aDirtyRect) {
gfx::Rect dirty(aDirtyRect->origin.x, aDirtyRect->origin.y,
@ -436,7 +434,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
float r = float(rand()) / float(RAND_MAX);
float g = float(rand()) / float(RAND_MAX);
float b = float(rand()) / float(RAND_MAX);
dt->FillRect(gfx::Rect(origin.x, origin.y, aSize.width, aSize.height),
dt->FillRect(gfx::Rect(origin.x, origin.y, size.width, size.height),
gfx::ColorPattern(gfx::Color(r, g, b, 0.5)));
}
@ -459,17 +457,19 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
extern "C" {
bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob, int32_t width,
int32_t height, mozilla::wr::ImageFormat aFormat,
bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
mozilla::wr::ImageFormat aFormat,
const mozilla::wr::LayoutIntRect* aRenderRect,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
mozilla::wr::MutByteSlice output) {
return mozilla::wr::Moz2DRenderCallback(
mozilla::wr::ByteSliceToRange(blob), mozilla::gfx::IntSize(width, height),
mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect, aTileSize,
aTileOffset, aDirtyRect, mozilla::wr::MutByteSliceToRange(output));
mozilla::wr::ByteSliceToRange(blob),
mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect,
aRenderRect, aTileSize, aTileOffset, aDirtyRect,
mozilla::wr::MutByteSliceToRange(output));
}
} // extern

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

@ -3258,9 +3258,8 @@ pub unsafe extern "C" fn wr_dec_ref_arc(arc: *const VecU8) {
extern "C" {
// TODO: figure out the API for tiled blob images.
pub fn wr_moz2d_render_cb(blob: ByteSlice,
width: i32,
height: i32,
format: ImageFormat,
render_rect: &LayoutIntRect,
visible_rect: &DeviceIntRect,
tile_size: Option<&u16>,
tile_offset: Option<&TileOffset>,

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

@ -553,9 +553,8 @@ fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
let result = unsafe {
if wr_moz2d_render_cb(
ByteSlice::new(&job.commands[..]),
descriptor.rect.size.width,
descriptor.rect.size.height,
descriptor.format,
&descriptor.rect,
&job.visible_rect,
job.tile_size.as_ref(),
job.request.tile.as_ref(),

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

@ -380,10 +380,6 @@ impl ClipNodeInfo {
rect.size,
);
// TODO: As a followup, if the image is a tiled blob, the device_image_rect below
// will be set to the blob's visible area.
let device_image_rect = DeviceIntRect::from_size(props.descriptor.size);
for Repetition { origin, .. } in repetitions {
let layout_image_rect = LayoutRect {
origin,
@ -392,7 +388,7 @@ impl ClipNodeInfo {
let tiles = image::tiles(
&layout_image_rect,
&visible_rect,
&device_image_rect,
&props.visible_rect,
tile_size as i32,
);
for tile in tiles {

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

@ -491,6 +491,37 @@ fn last_tile_size_1d(
)
}
pub fn compute_tile_rect(
image_rect: &DeviceIntRect,
regular_tile_size: TileSize,
tile: TileOffset,
) -> DeviceIntRect {
let regular_tile_size = regular_tile_size as i32;
DeviceIntRect {
origin: point2(
compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
),
size: size2(
compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32),
compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32),
),
}
}
fn compute_tile_origin_1d(
img_range: Range<i32>,
regular_tile_size: i32,
tile_offset: i32,
) -> i32 {
let tile_range = tile_range_1d(&img_range, regular_tile_size);
if tile_offset == tile_range.start {
img_range.start
} else {
tile_offset * regular_tile_size
}
}
// Compute the width and height in pixels of a tile depending on its position in the image.
pub fn compute_tile_size(
image_rect: &DeviceIntRect,
@ -551,6 +582,47 @@ pub fn for_each_tile_in_range(
}
}
pub fn compute_valid_tiles_if_bounds_change(
prev_rect: &DeviceIntRect,
new_rect: &DeviceIntRect,
tile_size: u16,
) -> Option<TileRange> {
let intersection = prev_rect.intersection(new_rect);
if intersection.is_none() {
return Some(TileRange::zero());
}
let intersection = intersection.unwrap_or(DeviceIntRect::zero());
let left = prev_rect.min_x() != new_rect.min_x();
let right = prev_rect.max_x() != new_rect.max_x();
let top = prev_rect.min_y() != new_rect.min_y();
let bottom = prev_rect.max_y() != new_rect.max_y();
if !left && !right && !top && !bottom {
// Bounds have not changed.
return None;
}
let tw = 1.0 / (tile_size as f32);
let th = 1.0 / (tile_size as f32);
let tiles = intersection
.cast::<f32>()
.scale(tw, th);
let min_x = if left { f32::ceil(tiles.min_x()) } else { f32::floor(tiles.min_x()) };
let min_y = if top { f32::ceil(tiles.min_y()) } else { f32::floor(tiles.min_y()) };
let max_x = if right { f32::floor(tiles.max_x()) } else { f32::ceil(tiles.max_x()) };
let max_y = if bottom { f32::floor(tiles.max_y()) } else { f32::ceil(tiles.max_y()) };
Some(TileRange {
origin: point2(min_x as i32, min_y as i32),
size: size2((max_x - min_x) as i32, (max_y - min_y) as i32),
})
}
#[cfg(test)]
mod tests {
use super::*;
@ -714,4 +786,26 @@ mod tests {
assert_eq!(result.first_tile_layout_size, 10.0);
assert_eq!(result.last_tile_layout_size, 10.0);
}
#[test]
fn smaller_than_tile_size_at_origin() {
let r = compute_tile_rect(
&rect(0, 0, 80, 80),
256,
point2(0, 0),
);
assert_eq!(r, rect(0, 0, 80, 80));
}
#[test]
fn smaller_than_tile_size_with_offset() {
let r = compute_tile_rect(
&rect(20, 20, 80, 80),
256,
point2(0, 0),
);
assert_eq!(r, rect(20, 20, 80, 80));
}
}

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

@ -2289,9 +2289,12 @@ impl PrimitiveStore {
frame_state.gpu_cache,
);
}
Some(ImageProperties { descriptor, tiling: Some(tile_size), .. }) => {
Some(ImageProperties { tiling: Some(tile_size), visible_rect, .. }) => {
image_instance.visible_tiles.clear();
let device_image_rect = DeviceIntRect::from_size(descriptor.size);
// TODO: rename the blob's visible_rect into something that doesn't conflict
// with the terminology we use during culling since it's not really the same
// thing.
let active_rect = visible_rect;
// Tighten the clip rect because decomposing the repeated image can
// produce primitives that are partially covering the original image
@ -2344,7 +2347,7 @@ impl PrimitiveStore {
let tiles = crate::image::tiles(
&layout_image_rect,
&visible_rect,
&device_image_rect,
&active_rect,
tile_size as i32,
);

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

@ -24,7 +24,8 @@ use crate::glyph_cache::GlyphCacheEntry;
use crate::glyph_rasterizer::{BaseFontInstance, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
use crate::gpu_types::UvRectKind;
use crate::image::{compute_tile_size, compute_tile_range, for_each_tile_in_range};
use crate::image::{compute_tile_size, compute_tile_rect, compute_tile_range, for_each_tile_in_range};
use crate::image::compute_valid_tiles_if_bounds_change;
use crate::internal_types::{FastHashMap, FastHashSet, TextureSource, TextureUpdateList};
use crate::profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
use crate::render_backend::{FrameId, FrameStamp};
@ -143,6 +144,9 @@ pub struct ImageProperties {
pub descriptor: ImageDescriptor,
pub external_image: Option<ExternalImageData>,
pub tiling: Option<TileSize>,
// Potentially a subset of the image's total rectangle. This rectangle is what
// we map to the (layout space) display item bounds.
pub visible_rect: DeviceIntRect,
}
#[derive(Debug, Copy, Clone, PartialEq)]
@ -164,13 +168,24 @@ struct BlobImageTemplate {
descriptor: ImageDescriptor,
tiling: Option<TileSize>,
dirty_rect: BlobDirtyRect,
viewport_tiles: Option<TileRange>,
/// See ImageResource::visible_rect.
visible_rect: DeviceIntRect,
// If the active rect of the blob changes, this represents the
// range of tiles that remain valid. This must be taken into
// account in addition to the valid rect when submitting blob
// rasterization requests.
// `None` means the bounds have not changed (tiles are still valid).
// `Some(TileRange::zero())` means all of the tiles are invalid.
valid_tiles_after_bounds_change: Option<TileRange>,
}
struct ImageResource {
data: CachedImageData,
descriptor: ImageDescriptor,
tiling: Option<TileSize>,
/// This is used to express images that are virtually very large
/// but with only a visible sub-set that is valid at a given time.
visible_rect: DeviceIntRect,
}
#[derive(Clone, Debug)]
@ -586,7 +601,13 @@ impl ResourceCache {
if let ImageData::Raw(ref bytes) = img.data {
profile_counters.image_templates.inc(bytes.len());
}
self.add_image_template(img.key, img.descriptor, img.data.into(), img.tiling);
self.add_image_template(
img.key,
img.descriptor,
img.data.into(),
&img.descriptor.size.into(),
img.tiling,
);
}
ResourceUpdate::UpdateImage(img) => {
self.update_image_template(img.key, img.descriptor, img.data.into(), &img.dirty_rect);
@ -596,6 +617,7 @@ impl ResourceCache {
img.key.as_image(),
img.descriptor,
CachedImageData::Blob,
&img.visible_rect,
img.tiling,
);
}
@ -608,7 +630,8 @@ impl ResourceCache {
&img.dirty_rect
),
);
self.discard_tiles_outside_visible_area(img.key, &img.visible_rect);
self.discard_tiles_outside_visible_area(img.key, &img.visible_rect); // TODO: remove?
self.set_image_visible_rect(img.key.as_image(), &img.visible_rect);
}
ResourceUpdate::DeleteImage(img) => {
self.delete_image_template(img);
@ -621,6 +644,7 @@ impl ResourceCache {
}
ResourceUpdate::SetBlobImageVisibleArea(key, area) => {
self.discard_tiles_outside_visible_area(key, &area);
self.set_image_visible_rect(key.as_image(), &area);
}
ResourceUpdate::AddFont(_) |
ResourceUpdate::AddFontInstance(_) => {
@ -657,12 +681,7 @@ impl ResourceCache {
}
ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => {
if let Some(template) = self.blob_image_templates.get_mut(&key) {
if let Some(tile_size) = template.tiling {
template.viewport_tiles = Some(compute_tile_range(
&area,
tile_size,
));
}
template.visible_rect = *area;
}
}
_ => {}
@ -773,6 +792,20 @@ impl ResourceCache {
if let RasterizedBlob::Tiled(ref mut tiles) = *image {
tiles.insert(tile, data);
}
match self.cached_images.try_get_mut(&request.key.as_image()) {
Some(&mut ImageResult::Multi(ref mut entries)) => {
let cached_key = CachedImageKey {
rendering: ImageRendering::Auto, // TODO(nical)
tile: Some(tile),
};
if let Some(entry) = entries.try_get_mut(&cached_key) {
entry.dirty_rect = DirtyRect::All;
}
}
_ => {}
}
} else {
if let RasterizedBlob::NonTiled(ref mut queue) = *image {
// If our new rasterized rect overwrites items in the queue, discard them.
@ -862,6 +895,7 @@ impl ResourceCache {
image_key: ImageKey,
descriptor: ImageDescriptor,
data: CachedImageData,
visible_rect: &DeviceIntRect,
mut tiling: Option<TileSize>,
) {
if tiling.is_none() && Self::should_tile(self.max_texture_size(), &descriptor, &data) {
@ -874,6 +908,7 @@ impl ResourceCache {
descriptor,
data,
tiling,
visible_rect: *visible_rect,
};
self.resources.image_templates.insert(image_key, resource);
@ -941,6 +976,7 @@ impl ResourceCache {
descriptor,
data,
tiling,
visible_rect: descriptor.size.into(),
};
}
@ -956,8 +992,6 @@ impl ResourceCache {
let max_texture_size = self.max_texture_size();
tiling = get_blob_tiling(tiling, descriptor, max_texture_size);
let viewport_tiles = tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
self.blob_image_handler.as_mut().unwrap().add(key, data, visible_rect, tiling);
self.blob_image_templates.insert(
@ -966,7 +1000,8 @@ impl ResourceCache {
descriptor: *descriptor,
tiling,
dirty_rect: DirtyRect::All,
viewport_tiles,
valid_tiles_after_bounds_change: None,
visible_rect: *visible_rect,
},
);
}
@ -990,13 +1025,32 @@ impl ResourceCache {
let tiling = get_blob_tiling(image.tiling, descriptor, max_texture_size);
let viewport_tiles = image.tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
let mut valid_tiles_after_bounds_change = None;
if let Some(tile_size) = image.tiling {
valid_tiles_after_bounds_change = compute_valid_tiles_if_bounds_change(
&image.visible_rect,
visible_rect,
tile_size,
);
}
match (image.valid_tiles_after_bounds_change, valid_tiles_after_bounds_change) {
(Some(old), Some(ref mut new)) => {
*new = new.intersection(&old).unwrap_or(TileRange::zero());
}
(Some(old), None) => {
valid_tiles_after_bounds_change = Some(old);
}
_ => {}
}
*image = BlobImageTemplate {
descriptor: *descriptor,
tiling,
dirty_rect: dirty_rect.union(&image.dirty_rect),
viewport_tiles,
valid_tiles_after_bounds_change,
visible_rect: *visible_rect,
};
}
@ -1166,14 +1220,11 @@ impl ResourceCache {
rect: match template.tiling {
Some(tile_size) => {
let tile = request.tile.unwrap();
LayoutIntRect {
origin: point2(tile.x, tile.y) * tile_size as i32,
size: blob_size(compute_tile_size(
&template.descriptor.size.into(),
tile_size,
tile,
)),
}
blob_rect(compute_tile_rect(
&template.visible_rect,
tile_size,
tile,
))
}
None => blob_size(template.descriptor.size).into(),
},
@ -1213,37 +1264,33 @@ impl ResourceCache {
if let Some(tile_size) = template.tiling {
// If we know that only a portion of the blob image is in the viewport,
// only request these visible tiles since blob images can be huge.
let mut tiles = template.viewport_tiles.unwrap_or_else(|| {
// Default to requesting the full range of tiles.
compute_tile_range(
&DeviceIntRect {
origin: point2(0, 0),
size: template.descriptor.size,
},
tile_size,
)
});
let mut tiles = compute_tile_range(
&template.visible_rect,
tile_size,
);
let image_dirty_rect = to_image_dirty_rect(&template.dirty_rect);
// Don't request tiles that weren't invalidated.
if let DirtyRect::Partial(dirty_rect) = image_dirty_rect {
let dirty_rect = DeviceIntRect {
origin: point2(
dirty_rect.origin.x,
dirty_rect.origin.y,
),
size: size2(
dirty_rect.size.width,
dirty_rect.size.height,
),
};
let dirty_tiles = compute_tile_range(
&dirty_rect,
tile_size,
);
let dirty_tiles = match image_dirty_rect {
DirtyRect::Partial(dirty_rect) => {
let dirty_rect = DeviceIntRect {
origin: point2(
dirty_rect.origin.x,
dirty_rect.origin.y,
),
size: size2(
dirty_rect.size.width,
dirty_rect.size.height,
),
};
tiles = tiles.intersection(&dirty_tiles).unwrap_or_else(TileRange::zero);
}
compute_tile_range(
&dirty_rect,
tile_size,
)
}
DirtyRect::All => tiles,
};
let original_tile_range = tiles;
@ -1284,16 +1331,22 @@ impl ResourceCache {
blob_tiles_clear_requests.push(clear_params);
}
for_each_tile_in_range(&tiles, |tile| {
let still_valid = template.valid_tiles_after_bounds_change
.map(|valid_tiles| valid_tiles.contains(tile))
.unwrap_or(true);
if still_valid && !dirty_tiles.contains(tile) {
return;
}
let descriptor = BlobImageDescriptor {
rect: LayoutIntRect {
origin: point2(tile.x, tile.y) * tile_size as i32,
size: blob_size(compute_tile_size(
&template.descriptor.size.into(),
tile_size,
tile,
)),
},
rect: blob_rect(compute_tile_rect(
&template.visible_rect,
tile_size,
tile,
)),
format: template.descriptor.format,
};
@ -1312,6 +1365,8 @@ impl ResourceCache {
}
);
});
template.valid_tiles_after_bounds_change = None;
} else {
let mut needs_upload = match self.cached_images.try_get(&key.as_image()) {
Some(&ImageResult::UntiledAuto(ref entry)) => {
@ -1320,7 +1375,7 @@ impl ResourceCache {
_ => true,
};
// If the queue of ratserized updates is growing it probably means that
// If the queue of rasterized updates is growing it probably means that
// the texture is not getting uploaded because the display item is off-screen.
// In that case we are better off
// - Either not kicking rasterization for that image (avoid wasted cpu work
@ -1417,6 +1472,12 @@ impl ResourceCache {
}
}
fn set_image_visible_rect(&mut self, key: ImageKey, rect: &DeviceIntRect) {
if let Some(image) = self.resources.image_templates.get_mut(key) {
image.visible_rect = * rect;
}
}
pub fn request_glyphs(
&mut self,
mut font: FontInstance,
@ -1555,6 +1616,7 @@ impl ResourceCache {
descriptor: image_template.descriptor,
external_image,
tiling: image_template.tiling,
visible_rect: image_template.visible_rect,
}
})
}
@ -1698,13 +1760,15 @@ impl ResourceCache {
if let Some(tile) = request.tile {
let tile_size = image_template.tiling.unwrap();
let clipped_tile_size = compute_tile_size(&descriptor.size.into(), tile_size, tile);
let clipped_tile_size = compute_tile_size(&image_template.visible_rect, tile_size, tile);
// The tiled image could be stored on the CPU as one large image or be
// already broken up into tiles. This affects the way we compute the stride
// and offset.
let tiled_on_cpu = image_template.data.is_blob();
if !tiled_on_cpu {
// we don't expect to have partial tiles at the top and left of non-blob
// images.
debug_assert_eq!(image_template.visible_rect.origin, point2(0, 0));
let bpp = descriptor.format.bytes_per_pixel();
let stride = descriptor.compute_stride();
descriptor.stride = Some(stride);
@ -2283,6 +2347,7 @@ impl ResourceCache {
data,
descriptor: template.descriptor,
tiling: template.tiling,
visible_rect: template.descriptor.size.into(),
});
}
@ -2298,3 +2363,11 @@ impl ResourceCache {
fn blob_size(device_size: DeviceIntSize) -> LayoutIntSize {
size2(device_size.width, device_size.height)
}
#[inline]
fn blob_rect(device_rect: DeviceIntRect) -> LayoutIntRect {
LayoutIntRect {
origin: point2(device_rect.origin.x, device_rect.origin.y),
size: blob_size(device_rect.size),
}
}