зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
02515b0066
Коммит
4338f2052b
12
gfx/2d/2D.h
12
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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче