Bug 1388842. Add support for updating blob images. r=mstange

Currently, we use a simple merging algorithm, because the more
complicated ones didn't work.

This code won't actually be used until we do blob image invalidation
in a follow up.

MozReview-Commit-ID: Q2Em3QC195
This commit is contained in:
Jeff Muizelaar 2018-03-16 19:20:26 -04:00
Родитель 02515b0066
Коммит 4338f2052b
7 изменённых файлов: 293 добавлений и 15 удалений

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

@ -1455,6 +1455,18 @@ public:
return mPermitSubpixelAA;
}
/**
* Mark the end of an Item in a DrawTargetRecording. These markers
* are used for merging recordings together.
*
* This should only be called on the 'root' DrawTargetRecording.
* Calling it on a child DrawTargetRecordings will cause confusion.
*
* Note: this is a bit of a hack. It might be better to just recreate
* the DrawTargetRecording.
*/
virtual void FlushItem(const IntRect &aBounds) {}
/**
* Ensures that no snapshot is still pointing to this DrawTarget's surface data.
*

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

@ -96,11 +96,24 @@ DrawEventRecorderMemory::Flush()
void
DrawEventRecorderMemory::FlushItem(IntRect aRect)
{
MOZ_RELEASE_ASSERT(!aRect.IsEmpty());
// Detatching our existing resources will add some
// destruction events to our stream so we need to do that
// first.
DetatchResources();
WriteElement(mIndex, mOutputStream.mLength);
// write out the fonts into the extra data section
mSerializeCallback(mOutputStream, mUnscaledFonts);
WriteElement(mIndex, mOutputStream.mLength);
WriteElement(mIndex, aRect.x);
WriteElement(mIndex, aRect.y);
WriteElement(mIndex, aRect.XMost());
WriteElement(mIndex, aRect.YMost());
ClearResources();
WriteHeader(mOutputStream);
}
void

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

@ -648,6 +648,24 @@ DrawTargetRecording::EnsurePathStored(const Path *aPath)
return pathRecording.forget();
}
// This should only be called on the 'root' DrawTargetRecording.
// Calling it on a child DrawTargetRecordings will cause confusion.
void
DrawTargetRecording::FlushItem(const IntRect &aBounds)
{
mRecorder->FlushItem(aBounds);
// Reinitialize the recorder (FlushItem will write a new recording header)
// Tell the new recording about our draw target
// This code should match what happens in the DrawTargetRecording constructor.
mRecorder->RecordEvent(RecordedDrawTargetCreation(this,
mFinalDT->GetBackendType(),
mSize,
mFinalDT->GetFormat(),
false, nullptr));
// Add the current transform to the new recording
mRecorder->RecordEvent(RecordedSetTransform(this, DrawTarget::GetTransform()));
}
void
DrawTargetRecording::EnsurePatternDependenciesStored(const Pattern &aPattern)
{

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

@ -38,6 +38,8 @@ public:
*/
virtual void Flush() override { mFinalDT->Flush(); }
virtual void FlushItem(const IntRect &aBounds) override;
/*
* Draw a surface to the draw target. Possibly doing partial drawing or
* applying scaling. No sampling happens outside the source.

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

@ -598,7 +598,7 @@ WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem,
}
bool isInvalidated = PaintItemByDrawTarget(aItem, dt, paintRect, offset, aDisplayListBuilder,
fallbackData->mBasicLayerManager, scale, highlight);
recorder->FlushItem(IntRect());
recorder->FlushItem(IntRect(0, 0, paintSize.width, paintSize.height));
recorder->Finish();
if (isInvalidated) {

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

@ -223,6 +223,19 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
pos += sizeof(ret);
return ret;
}
int ReadInt() {
int ret;
MOZ_RELEASE_ASSERT(pos + sizeof(ret) <= len);
memcpy(&ret, buf + pos, sizeof(ret));
pos += sizeof(ret);
return ret;
}
void SkipBounds() {
MOZ_RELEASE_ASSERT(pos + sizeof(int) * 4 <= len);
pos += sizeof(int) * 4;
}
};
//XXX: Make safe
size_t indexOffset = *(size_t*)(aBlob.end().get()-sizeof(size_t));
@ -233,6 +246,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
while (reader.pos < reader.len) {
size_t end = reader.ReadSize();
size_t extra_end = reader.ReadSize();
reader.SkipBounds();
gfx::InlineTranslator translator(dt);

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

@ -19,6 +19,11 @@ use foreign_types::ForeignType;
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
use std::ffi::CString;
macro_rules! dlog {
($($e:expr),*) => { {$(let _ = $e;)*} }
//($($t:tt)*) => { println!($($t)*) }
}
pub struct Moz2dImageRenderer {
blob_commands: HashMap<ImageKey, (Arc<BlobImageData>, Option<TileSize>)>,
@ -54,15 +59,25 @@ fn convert_from_bytes<T>(slice: &[u8]) -> T {
ret
}
struct BufReader<'a>
{
use std::slice;
fn convert_to_bytes<T>(x: &T) -> &[u8] {
unsafe {
let ip: *const T = x;
let bp: *const u8 = ip as * const _;
slice::from_raw_parts(bp, mem::size_of::<T>())
}
}
struct BufReader<'a> {
buf: &'a[u8],
pos: usize,
}
impl<'a> BufReader<'a> {
fn new(buf: &'a[u8]) -> BufReader<'a> {
BufReader{ buf: buf, pos: 0 }
BufReader { buf: buf, pos: 0 }
}
fn read<T>(&mut self) -> T {
@ -78,16 +93,223 @@ impl<'a> BufReader<'a> {
fn read_usize(&mut self) -> usize {
self.read()
}
fn has_more(&self) -> bool {
self.pos < self.buf.len()
}
}
/* Blob stream format:
* { data[..], index[..], offset in the stream of the index array }
*
* An 'item' has 'data' and 'extra_data'
* - In our case the 'data' is the stream produced by DrawTargetRecording
* and the 'extra_data' includes things like webrender font keys
*
* The index is an array of entries of the following form:
* { end, extra_end, bounds }
*
* - end is the offset of the end of an item's data
* an item's data goes from the begining of the stream or
* the begining of the last item til end
* - extra_end is the offset of the end of an item's extra data
* an item's extra data goes from 'end' until 'extra_end'
* - bounds is a set of 4 ints {x1, y1, x2, y2 }
*
* The offsets in the index should be monotonically increasing.
*
* Design rationale:
* - the index is smaller so we append it to the end of the data array
* during construction. This makes it more likely that we'll fit inside
* the data Vec
* - we use indices/offsets instead of sizes to avoid having to deal with any
* arithmetic that might overflow.
*/
struct BlobReader<'a> {
reader: BufReader<'a>,
begin: usize,
}
impl<'a> BlobReader<'a> {
fn new(buf: &'a[u8]) -> BlobReader<'a> {
// The offset of the index is at the end of the buffer.
let index_offset_pos = buf.len()-mem::size_of::<usize>();
let index_offset = to_usize(&buf[index_offset_pos..]);
BlobReader { reader: BufReader::new(&buf[index_offset..index_offset_pos]), begin: 0 }
}
fn read_entry(&mut self) -> (usize, usize, usize, Box2d) {
let end = self.reader.read();
let extra_end = self.reader.read();
let bounds = self.reader.read();
let ret = (self.begin, end, extra_end, bounds);
self.begin = extra_end;
ret
}
}
// This is used for writing new blob images.
// In our case this is the result of merging an old one and a new one
struct BlobWriter {
data: Vec<u8>,
index: Vec<u8>
}
impl BlobWriter {
fn new() -> BlobWriter {
BlobWriter { data: Vec::new(), index: Vec::new() }
}
fn new_entry(&mut self, extra_size: usize, bounds: Box2d, data: &[u8]) {
self.data.extend_from_slice(data);
// Write 'end' to the index: the offset where the regular data ends and the extra data starts.
self.index.extend_from_slice(convert_to_bytes(&(self.data.len() - extra_size)));
// Write 'extra_end' to the index: the offset where the extra data ends.
self.index.extend_from_slice(convert_to_bytes(&self.data.len()));
// XXX: we can aggregate these writes
// Write the bounds to the index.
self.index.extend_from_slice(convert_to_bytes(&bounds.x1));
self.index.extend_from_slice(convert_to_bytes(&bounds.y1));
self.index.extend_from_slice(convert_to_bytes(&bounds.x2));
self.index.extend_from_slice(convert_to_bytes(&bounds.y2));
}
fn finish(mut self) -> Vec<u8> {
// Append the index to the end of the buffer
// and then append the offset to the beginning of the index.
let index_begin = self.data.len();
self.data.extend_from_slice(&self.index);
self.data.extend_from_slice(convert_to_bytes(&index_begin));
self.data
}
}
// XXX: Do we want to allow negative values here or clamp to the image bounds?
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
struct Box2d {
x1: u32,
y1: u32,
x2: u32,
y2: u32
}
impl Box2d {
fn contained_by(&self, other: &Box2d) -> bool {
self.x1 >= other.x1 &&
self.x2 <= other.x2 &&
self.y1 >= other.y1 &&
self.y2 <= other.y2
}
}
impl From<DeviceUintRect> for Box2d {
fn from(rect: DeviceUintRect) -> Self {
Box2d{ x1: rect.min_x(), y1: rect.min_y(), x2: rect.max_x(), y2: rect.max_y() }
}
}
fn dump_blob_index(blob: &[u8], dirty_rect: Box2d) {
let mut index = BlobReader::new(blob);
while index.reader.has_more() {
let (_, _, _, bounds) = index.read_entry();
dlog!(" {:?} {}", bounds,
if bounds.contained_by(&dirty_rect) {
"*"
} else {
""
}
);
}
}
fn check_result(result: &[u8]) -> () {
let mut index = BlobReader::new(result);
assert!(index.reader.has_more(), "Unexpectedly empty result. This blob should just have been deleted");
while index.reader.has_more() {
let (_, end, extra, bounds) = index.read_entry();
dlog!("result bounds: {} {} {:?}", end, extra, bounds);
}
}
/* Merge a new partial blob image into an existing complete blob image.
All of the items not fully contained in the dirty_rect should match
in both new and old lists.
We continue to use the old content for these items.
Old items contained in the dirty_rect are dropped and new items
are retained.
*/
fn merge_blob_images(old: &[u8], new: &[u8], dirty_rect: Box2d) -> Vec<u8> {
let mut result = BlobWriter::new();
dlog!("dirty rect: {:?}", dirty_rect);
dlog!("old:");
dump_blob_index(old, dirty_rect);
dlog!("new:");
dump_blob_index(new, dirty_rect);
let mut old_reader = BlobReader::new(old);
let mut new_reader = BlobReader::new(new);
// Loop over both new and old entries merging them.
// Both new and old must have the same number of entries that
// overlap but are not contained by the dirty rect, and they
// must be in the same order.
while new_reader.reader.has_more() {
let (new_begin, new_end, new_extra, new_bounds) = new_reader.read_entry();
dlog!("bounds: {} {} {:?}", new_end, new_extra, new_bounds);
if new_bounds.contained_by(&dirty_rect) {
result.new_entry(new_extra - new_end, new_bounds, &new[new_begin..new_extra]);
} else {
loop {
assert!(old_reader.reader.has_more());
let (old_begin, old_end, old_extra, old_bounds) = old_reader.read_entry();
dlog!("new bounds: {} {} {:?}", old_end, old_extra, old_bounds);
if old_bounds.contained_by(&dirty_rect) {
// fully contained items will be discarded or replaced
} else {
assert_eq!(old_bounds, new_bounds);
// we found a matching item use the old data
result.new_entry(old_extra - old_end, old_bounds, &old[old_begin..old_extra]);
break;
}
}
}
}
// Include any remaining old items.
while old_reader.reader.has_more() {
let (_, old_end, old_extra, old_bounds) = old_reader.read_entry();
dlog!("new bounds: {} {} {:?}", old_end, old_extra, old_bounds);
assert!(old_bounds.contained_by(&dirty_rect));
}
let result = result.finish();
check_result(&result);
result
}
impl BlobImageRenderer for Moz2dImageRenderer {
fn add(&mut self, key: ImageKey, data: BlobImageData, tiling: Option<TileSize>) {
{
let index = BlobReader::new(&data);
assert!(index.reader.has_more());
}
self.blob_commands.insert(key, (Arc::new(data), tiling));
}
fn update(&mut self, key: ImageKey, data: BlobImageData, _dirty_rect: Option<DeviceUintRect>) {
let entry = self.blob_commands.get_mut(&key).unwrap();
entry.0 = Arc::new(data);
fn update(&mut self, key: ImageKey, data: BlobImageData, dirty_rect: Option<DeviceUintRect>) {
match self.blob_commands.entry(key) {
Entry::Occupied(mut e) => {
let old_data = &mut e.get_mut().0;
*old_data = Arc::new(merge_blob_images(&old_data, &data,
dirty_rect.unwrap().into()));
}
_ => { panic!("missing image key"); }
}
}
fn delete(&mut self, key: ImageKey) {
@ -99,7 +321,7 @@ impl BlobImageRenderer for Moz2dImageRenderer {
request: BlobImageRequest,
descriptor: &BlobImageDescriptor,
_dirty_rect: Option<DeviceUintRect>) {
debug_assert!(!self.rendered_images.contains_key(&request));
debug_assert!(!self.rendered_images.contains_key(&request), "{:?}", request);
// TODO: implement tiling.
// Add None in the map of rendered images. This makes it possible to differentiate
@ -149,14 +371,11 @@ impl BlobImageRenderer for Moz2dImageRenderer {
resources.get_font_data(key);
}
}
let index_offset_pos = commands.len()-mem::size_of::<usize>();
let index_offset = to_usize(&commands[index_offset_pos..]);
{
let mut index = BufReader::new(&commands[index_offset..index_offset_pos]);
while index.pos < index.buf.len() {
let end = index.read_usize();
let extra_end = index.read_usize();
let mut index = BlobReader::new(&commands);
assert!(index.reader.pos < index.reader.buf.len());
while index.reader.pos < index.reader.buf.len() {
let (_, end, extra_end, _) = index.read_entry();
process_fonts(BufReader::new(&commands[end..extra_end]), resources);
}
}