From 4338f2052bdc8ebfe3367c62cfeff27d7a964cc9 Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Fri, 16 Mar 2018 19:20:26 -0400 Subject: [PATCH] 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 --- gfx/2d/2D.h | 12 + gfx/2d/DrawEventRecorder.cpp | 13 + gfx/2d/DrawTargetRecording.cpp | 18 ++ gfx/2d/DrawTargetRecording.h | 2 + gfx/layers/wr/WebRenderCommandBuilder.cpp | 2 +- gfx/webrender_bindings/Moz2DImageRenderer.cpp | 14 + gfx/webrender_bindings/src/moz2d_renderer.rs | 247 +++++++++++++++++- 7 files changed, 293 insertions(+), 15 deletions(-) diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h index fb1ec6e48629..71465aa40518 100644 --- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -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. * diff --git a/gfx/2d/DrawEventRecorder.cpp b/gfx/2d/DrawEventRecorder.cpp index fa0edd8fbe28..13981fd3974a 100644 --- a/gfx/2d/DrawEventRecorder.cpp +++ b/gfx/2d/DrawEventRecorder.cpp @@ -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 diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp index 103ca0740520..6a5a814446bd 100644 --- a/gfx/2d/DrawTargetRecording.cpp +++ b/gfx/2d/DrawTargetRecording.cpp @@ -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) { diff --git a/gfx/2d/DrawTargetRecording.h b/gfx/2d/DrawTargetRecording.h index 708e749a02d2..e22e699b9433 100644 --- a/gfx/2d/DrawTargetRecording.h +++ b/gfx/2d/DrawTargetRecording.h @@ -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. diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp index 8e3d0f23b20c..8ac17370981e 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.cpp +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -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) { diff --git a/gfx/webrender_bindings/Moz2DImageRenderer.cpp b/gfx/webrender_bindings/Moz2DImageRenderer.cpp index 443ff23507ae..9c419e2db5b4 100644 --- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp +++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp @@ -223,6 +223,19 @@ static bool Moz2DRenderCallback(const Range 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 aBlob, while (reader.pos < reader.len) { size_t end = reader.ReadSize(); size_t extra_end = reader.ReadSize(); + reader.SkipBounds(); gfx::InlineTranslator translator(dt); diff --git a/gfx/webrender_bindings/src/moz2d_renderer.rs b/gfx/webrender_bindings/src/moz2d_renderer.rs index f885be60f97a..8a4343b6c0c6 100644 --- a/gfx/webrender_bindings/src/moz2d_renderer.rs +++ b/gfx/webrender_bindings/src/moz2d_renderer.rs @@ -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, Option)>, @@ -54,15 +59,25 @@ fn convert_from_bytes(slice: &[u8]) -> T { ret } -struct BufReader<'a> -{ +use std::slice; + +fn convert_to_bytes(x: &T) -> &[u8] { + unsafe { + let ip: *const T = x; + let bp: *const u8 = ip as * const _; + slice::from_raw_parts(bp, mem::size_of::()) + } +} + + +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(&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::(); + 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, + index: Vec +} + +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 { + // 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 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 { + + 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) { + { + 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) { - 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) { + 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) { - 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::(); - - 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); } }