From 53b24c95357341b2423ca496e7c8786eb41c9dcf Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 12:15:30 +1000 Subject: [PATCH 1/9] Update gleam to 0.4.8 (allows upload of array textures via PBO). --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 204a657d75..dcdb748b11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ dependencies = [ "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "font-loader 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)", @@ -139,7 +139,7 @@ name = "cgl" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -404,7 +404,7 @@ dependencies = [ [[package]] name = "gleam" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gl_generator 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -649,7 +649,7 @@ dependencies = [ "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "gl_generator 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libloading 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1045,7 +1045,7 @@ dependencies = [ "freetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "gamma-lut 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1071,7 +1071,7 @@ dependencies = [ "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "offscreen_gl_context 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1186,7 +1186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" "checksum gif 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f" "checksum gl_generator 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "92a61e91a1f4554abd82f7e8593930c5307a9702c0b2f2d3b1cfcd23b21752fa" -"checksum gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "917ee404f414ed77756c12cb44fdcc7cd02f207bf91e1dc91a3ce7da794ec361" +"checksum gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "bf887141f0c2a83eae026cbf3fba74f0a5cb0f01d20e5cdfcd8c4ad39295be1e" "checksum heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c7593b1522161003928c959c20a2ca421c68e940d63d75573316a009e48a6d4" "checksum image 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d61d2b3f000fb41d268312b92d4dd5ee7823163ceee71a67c676271585dfe598" "checksum inflate 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d1238524675af3938a7c74980899535854b88ba07907bb1c944abe5b8fc437e5" From 7d7087470a2d27518cc6fcb564be5ef370ba32b2 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 12:17:34 +1000 Subject: [PATCH 2/9] Remove coalesce benchmark that is no longer relevant. --- webrender/benches/coalesce.rs | 22 ---------------------- webrender/src/lib.rs | 3 --- 2 files changed, 25 deletions(-) delete mode 100644 webrender/benches/coalesce.rs diff --git a/webrender/benches/coalesce.rs b/webrender/benches/coalesce.rs deleted file mode 100644 index 69c02e28e1..0000000000 --- a/webrender/benches/coalesce.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![feature(test)] - -extern crate rand; -extern crate test; -extern crate webrender; - -use rand::Rng; -use test::Bencher; -use webrender::TexturePage; -use webrender::api::{DeviceUintSize as Size}; - -#[bench] -fn bench_coalesce(b: &mut Bencher) { - let mut rng = rand::thread_rng(); - let mut page = TexturePage::new_dummy(Size::new(10000, 10000)); - let mut test_page = TexturePage::new_dummy(Size::new(10000, 10000)); - while page.allocate(&Size::new(rng.gen_range(1, 100), rng.gen_range(1, 100))).is_some() {} - b.iter(|| { - test_page.fill_from(&page); - test_page.coalesce(); - }); -} diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 0464096d47..db54611555 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -80,9 +80,6 @@ mod texture_cache; mod tiling; mod util; -#[doc(hidden)] // for benchmarks -pub use texture_cache::TexturePage; - #[cfg(feature = "webgl")] mod webgl_types; From f40f3376b42cff5df6de044a6a6b6523a2329dda Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 12:19:00 +1000 Subject: [PATCH 3/9] Add support for weak handles to freelist. --- webrender/src/freelist.rs | 98 +++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/webrender/src/freelist.rs b/webrender/src/freelist.rs index cd026adfa7..811262e3ad 100644 --- a/webrender/src/freelist.rs +++ b/webrender/src/freelist.rs @@ -3,40 +3,31 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::marker::PhantomData; -use std::mem; - -// TODO(gw): Add a weak free list handle. This is like a strong -// free list handle below, but will contain an epoch -// field. Weak handles will use a get_opt style API -// which returns an Option instead of T. // TODO(gw): Add an occupied list head, for fast // iteration of the occupied list to implement // retain() style functionality. +#[derive(Debug, Copy, Clone, PartialEq)] +struct Epoch(u32); + #[derive(Debug)] pub struct FreeListHandle { index: u32, _marker: PhantomData, } -enum SlotValue { - Free, - Occupied(T), -} - -impl SlotValue { - fn take(&mut self) -> T { - match mem::replace(self, SlotValue::Free) { - SlotValue::Free => unreachable!(), - SlotValue::Occupied(data) => data, - } - } +#[derive(Debug)] +pub struct WeakFreeListHandle { + index: u32, + epoch: Epoch, + _marker: PhantomData, } struct Slot { next: Option, - value: SlotValue, + epoch: Epoch, + value: Option, } pub struct FreeList { @@ -44,6 +35,11 @@ pub struct FreeList { free_list_head: Option, } +pub enum UpsertResult { + Updated(T), + Inserted(FreeListHandle), +} + impl FreeList { pub fn new() -> FreeList { FreeList { @@ -52,17 +48,59 @@ impl FreeList { } } + #[allow(dead_code)] pub fn get(&self, id: &FreeListHandle) -> &T { - match self.slots[id.index as usize].value { - SlotValue::Free => unreachable!(), - SlotValue::Occupied(ref data) => data, - } + self.slots[id.index as usize] + .value + .as_ref() + .unwrap() } + #[allow(dead_code)] pub fn get_mut(&mut self, id: &FreeListHandle) -> &mut T { - match self.slots[id.index as usize].value { - SlotValue::Free => unreachable!(), - SlotValue::Occupied(ref mut data) => data, + self.slots[id.index as usize] + .value + .as_mut() + .unwrap() + } + + pub fn get_opt(&self, id: &WeakFreeListHandle) -> Option<&T> { + let slot = &self.slots[id.index as usize]; + if slot.epoch == id.epoch { + slot.value.as_ref() + } else { + None + } + } + + pub fn get_opt_mut(&mut self, id: &WeakFreeListHandle) -> Option<&mut T> { + let slot = &mut self.slots[id.index as usize]; + if slot.epoch == id.epoch { + slot.value.as_mut() + } else { + None + } + } + + pub fn create_weak_handle(&self, id: &FreeListHandle) -> WeakFreeListHandle { + let slot = &self.slots[id.index as usize]; + WeakFreeListHandle { + index: id.index, + epoch: slot.epoch, + _marker: PhantomData, + } + } + + pub fn upsert(&mut self, + id: &WeakFreeListHandle, + data: T) -> UpsertResult { + if self.slots[id.index as usize].epoch == id.epoch { + let slot = &mut self.slots[id.index as usize]; + let result = UpsertResult::Updated(slot.value.take().unwrap()); + slot.value = Some(data); + result + } else { + UpsertResult::Inserted(self.insert(data)) } } @@ -74,7 +112,7 @@ impl FreeList { // Remove from free list. self.free_list_head = slot.next; slot.next = None; - slot.value = SlotValue::Occupied(item); + slot.value = Some(item); FreeListHandle { index: free_index, @@ -86,7 +124,8 @@ impl FreeList { self.slots.push(Slot { next: None, - value: SlotValue::Occupied(item), + epoch: Epoch(0), + value: Some(item), }); FreeListHandle { @@ -100,7 +139,8 @@ impl FreeList { pub fn free(&mut self, id: FreeListHandle) -> T { let slot = &mut self.slots[id.index as usize]; slot.next = self.free_list_head; + slot.epoch = Epoch(slot.epoch.0 + 1); self.free_list_head = Some(id.index); - slot.value.take() + slot.value.take().unwrap() } } From 899f9807cae27a7e5524851699e54784fc187553 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 12:21:57 +1000 Subject: [PATCH 4/9] Add support for array textures that don't have FBOs. Simplify render targets (always array textures). Remove resize_texture which is now unused. Support uploading all texture types via PBO. Remove support for texture upload without PBO. --- webrender/src/device.rs | 349 +++++++++++++++------------------------- 1 file changed, 133 insertions(+), 216 deletions(-) diff --git a/webrender/src/device.rs b/webrender/src/device.rs index 66262679d4..693cedea8c 100644 --- a/webrender/src/device.rs +++ b/webrender/src/device.rs @@ -296,6 +296,7 @@ impl FBOId { struct Texture { gl: Rc, id: gl::GLuint, + layer_count: i32, format: ImageFormat, width: u32, height: u32, @@ -1014,6 +1015,7 @@ impl Device { id, width: 0, height: 0, + layer_count: 0, format: ImageFormat::Invalid, filter: TextureFilter::Nearest, mode: RenderTargetMode::None, @@ -1030,6 +1032,11 @@ impl Device { texture_ids } + pub fn get_texture_layer_count(&self, texture_id: TextureId) -> i32 { + let texture = &self.textures[&texture_id]; + texture.layer_count + } + pub fn get_texture_dimensions(&self, texture_id: TextureId) -> DeviceUintSize { let texture = &self.textures[&texture_id]; DeviceUintSize::new(texture.width, texture.height) @@ -1052,24 +1059,6 @@ impl Device { self.gl.tex_parameter_i(target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); } - fn upload_texture_image(&mut self, - target: gl::GLuint, - width: u32, - height: u32, - internal_format: u32, - format: u32, - type_: u32, - pixels: Option<&[u8]>) { - self.gl.tex_image_2d(target, - 0, - internal_format as gl::GLint, - width as gl::GLint, height as gl::GLint, - 0, - format, - type_, - pixels); - } - pub fn init_texture(&mut self, texture_id: TextureId, width: u32, @@ -1077,6 +1066,7 @@ impl Device { format: ImageFormat, filter: TextureFilter, mode: RenderTargetMode, + layer_count: i32, pixels: Option<&[u8]>) { debug_assert!(self.inside_frame); @@ -1088,6 +1078,7 @@ impl Device { texture.width = width; texture.height = height; texture.filter = filter; + texture.layer_count = layer_count; texture.mode = mode; } @@ -1095,22 +1086,10 @@ impl Device { let type_ = gl_type_for_texture_format(format); match mode { - RenderTargetMode::SimpleRenderTarget => { + RenderTargetMode::RenderTarget => { self.bind_texture(DEFAULT_TEXTURE, texture_id); self.set_texture_parameters(texture_id.target, filter); - self.upload_texture_image(texture_id.target, - width, - height, - internal_format as u32, - gl_format, - type_, - None); - self.update_texture_storage(texture_id, None, resized); - } - RenderTargetMode::LayerRenderTarget(layer_count) => { - self.bind_texture(DEFAULT_TEXTURE, texture_id); - self.set_texture_parameters(texture_id.target, filter); - self.update_texture_storage(texture_id, Some(layer_count), resized); + self.update_texture_storage(texture_id, layer_count, resized); } RenderTargetMode::None => { self.bind_texture(DEFAULT_TEXTURE, texture_id); @@ -1124,13 +1103,34 @@ impl Device { } else { pixels }; - self.upload_texture_image(texture_id.target, - width, - height, - internal_format as u32, - gl_format, - type_, - actual_pixels); + + match texture_id.target { + gl::TEXTURE_2D_ARRAY => { + self.gl.tex_image_3d(gl::TEXTURE_2D_ARRAY, + 0, + internal_format as gl::GLint, + width as gl::GLint, + height as gl::GLint, + layer_count, + 0, + gl_format, + type_, + actual_pixels); + } + gl::TEXTURE_2D | + gl::TEXTURE_RECTANGLE | + gl::TEXTURE_EXTERNAL_OES => { + self.gl.tex_image_2d(texture_id.target, + 0, + internal_format as gl::GLint, + width as gl::GLint, height as gl::GLint, + 0, + gl_format, + type_, + actual_pixels); + } + _ => panic!("BUG: Unexpected texture target!"), + } } } } @@ -1143,92 +1143,70 @@ impl Device { /// FBOs as required. pub fn update_texture_storage(&mut self, texture_id: TextureId, - layer_count: Option, + layer_count: i32, resized: bool) { let texture = self.textures.get_mut(&texture_id).unwrap(); - match layer_count { - Some(layer_count) => { - assert!(layer_count > 0); - assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY); + assert!(layer_count > 0); + assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY); - let current_layer_count = texture.fbo_ids.len() as i32; - // If the texture is already the required size skip. - if current_layer_count == layer_count && !resized { - return; - } + let current_layer_count = texture.fbo_ids.len() as i32; + // If the texture is already the required size skip. + if current_layer_count == layer_count && !resized { + return; + } - let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format); - let type_ = gl_type_for_texture_format(texture.format); - - self.gl.tex_image_3d(texture_id.target, - 0, - internal_format as gl::GLint, - texture.width as gl::GLint, - texture.height as gl::GLint, - layer_count, - 0, - gl_format, - type_, - None); - - let needed_layer_count = layer_count - current_layer_count; - if needed_layer_count > 0 { - // Create more framebuffers to fill the gap - let new_fbos = self.gl.gen_framebuffers(needed_layer_count); - texture.fbo_ids.extend(new_fbos.into_iter().map(|id| FBOId(id))); - } else if needed_layer_count < 0 { - // Remove extra framebuffers - for old in texture.fbo_ids.drain(layer_count as usize ..) { - self.gl.delete_framebuffers(&[old.0]); - } - } + let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format); + let type_ = gl_type_for_texture_format(texture.format); - let depth_rb = if let Some(rbo) = texture.depth_rb { - rbo.0 - } else { - let renderbuffer_ids = self.gl.gen_renderbuffers(1); - let depth_rb = renderbuffer_ids[0]; - texture.depth_rb = Some(RBOId(depth_rb)); - depth_rb - }; - self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb); - self.gl.renderbuffer_storage(gl::RENDERBUFFER, - gl::DEPTH_COMPONENT24, - texture.width as gl::GLsizei, - texture.height as gl::GLsizei); - - for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() { - self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0); - self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER, - gl::COLOR_ATTACHMENT0, - texture_id.name, - 0, - fbo_index as gl::GLint); - self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER, - gl::DEPTH_ATTACHMENT, - gl::RENDERBUFFER, - depth_rb); - } + self.gl.tex_image_3d(texture_id.target, + 0, + internal_format as gl::GLint, + texture.width as gl::GLint, + texture.height as gl::GLint, + layer_count, + 0, + gl_format, + type_, + None); + + let needed_layer_count = layer_count - current_layer_count; + if needed_layer_count > 0 { + // Create more framebuffers to fill the gap + let new_fbos = self.gl.gen_framebuffers(needed_layer_count); + texture.fbo_ids.extend(new_fbos.into_iter().map(|id| FBOId(id))); + } else if needed_layer_count < 0 { + // Remove extra framebuffers + for old in texture.fbo_ids.drain(layer_count as usize ..) { + self.gl.delete_framebuffers(&[old.0]); } - None => { - if texture.fbo_ids.is_empty() { - assert!(texture_id.target != gl::TEXTURE_2D_ARRAY); - - let new_fbo = self.gl.gen_framebuffers(1)[0]; - self.gl.bind_framebuffer(gl::FRAMEBUFFER, new_fbo); - - self.gl.framebuffer_texture_2d(gl::FRAMEBUFFER, - gl::COLOR_ATTACHMENT0, - texture_id.target, - texture_id.name, - 0); + } - texture.fbo_ids.push(FBOId(new_fbo)); - } else { - assert_eq!(texture.fbo_ids.len(), 1); - } - } + let depth_rb = if let Some(rbo) = texture.depth_rb { + rbo.0 + } else { + let renderbuffer_ids = self.gl.gen_renderbuffers(1); + let depth_rb = renderbuffer_ids[0]; + texture.depth_rb = Some(RBOId(depth_rb)); + depth_rb + }; + self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb); + self.gl.renderbuffer_storage(gl::RENDERBUFFER, + gl::DEPTH_COMPONENT24, + texture.width as gl::GLsizei, + texture.height as gl::GLsizei); + + for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() { + self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0); + self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + texture_id.name, + 0, + fbo_index as gl::GLint); + self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER, + gl::DEPTH_ATTACHMENT, + gl::RENDERBUFFER, + depth_rb); } // TODO(gw): Hack! Modify the code above to use the normal binding interfaces the device exposes. @@ -1263,52 +1241,6 @@ impl Device { gl::LINEAR); } - pub fn resize_texture(&mut self, - texture_id: TextureId, - new_width: u32, - new_height: u32, - format: ImageFormat, - filter: TextureFilter, - mode: RenderTargetMode) { - debug_assert!(self.inside_frame); - - let old_size = self.get_texture_dimensions(texture_id); - - let temp_texture_id = self.create_texture_ids(1, TextureTarget::Default)[0]; - self.init_texture(temp_texture_id, old_size.width, old_size.height, format, filter, mode, None); - self.update_texture_storage(temp_texture_id, None, true); - - self.bind_read_target(Some((texture_id, 0))); - self.bind_texture(DEFAULT_TEXTURE, temp_texture_id); - - self.gl.copy_tex_sub_image_2d(temp_texture_id.target, - 0, - 0, - 0, - 0, - 0, - old_size.width as i32, - old_size.height as i32); - - self.deinit_texture(texture_id); - self.init_texture(texture_id, new_width, new_height, format, filter, mode, None); - self.update_texture_storage(texture_id, None, true); - self.bind_read_target(Some((temp_texture_id, 0))); - self.bind_texture(DEFAULT_TEXTURE, texture_id); - - self.gl.copy_tex_sub_image_2d(texture_id.target, - 0, - 0, - 0, - 0, - 0, - old_size.width as i32, - old_size.height as i32); - - self.bind_read_target(None); - self.deinit_texture(temp_texture_id); - } - pub fn deinit_texture(&mut self, texture_id: TextureId) { debug_assert!(self.inside_frame); @@ -1340,6 +1272,7 @@ impl Device { texture.format = ImageFormat::Invalid; texture.width = 0; texture.height = 0; + texture.layer_count = 0; } pub fn create_program(&mut self, @@ -1610,48 +1543,17 @@ impl Device { y0: u32, width: u32, height: u32, + layer_index: i32, + stride: Option, offset: usize) { debug_assert!(self.inside_frame); - debug_assert_eq!(self.textures.get(&texture_id).unwrap().format, ImageFormat::RGBAF32); - - self.bind_texture(DEFAULT_TEXTURE, texture_id); - - self.gl.tex_sub_image_2d_pbo(texture_id.target, - 0, - x0 as gl::GLint, - y0 as gl::GLint, - width as gl::GLint, - height as gl::GLint, - gl::RGBA, - gl::FLOAT, - offset); - } - - pub fn update_texture(&mut self, - texture_id: TextureId, - x0: u32, - y0: u32, - width: u32, - height: u32, - stride: Option, - data: &[u8]) { - debug_assert!(self.inside_frame); - - let mut expanded_data = Vec::new(); - let (gl_format, bpp, data, data_type) = match self.textures.get(&texture_id).unwrap().format { - ImageFormat::A8 => { - if cfg!(any(target_arch="arm", target_arch="aarch64")) { - expanded_data.extend(data.iter().flat_map(|byte| repeat(*byte).take(4))); - (get_gl_format_bgra(self.gl()), 4, expanded_data.as_slice(), gl::UNSIGNED_BYTE) - } else { - (GL_FORMAT_A, 1, data, gl::UNSIGNED_BYTE) - } - } - ImageFormat::RGB8 => (gl::RGB, 3, data, gl::UNSIGNED_BYTE), - ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl()), 4, data, gl::UNSIGNED_BYTE), - ImageFormat::RG8 => (gl::RG, 2, data, gl::UNSIGNED_BYTE), - ImageFormat::RGBAF32 => (gl::RGBA, 16, data, gl::FLOAT), + let (gl_format, bpp, data_type) = match self.textures.get(&texture_id).unwrap().format { + ImageFormat::A8 => (GL_FORMAT_A, 1, gl::UNSIGNED_BYTE), + ImageFormat::RGB8 => (gl::RGB, 3, gl::UNSIGNED_BYTE), + ImageFormat::BGRA8 => (get_gl_format_bgra(self.gl()), 4, gl::UNSIGNED_BYTE), + ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE), + ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT), ImageFormat::Invalid => unreachable!(), }; @@ -1660,26 +1562,41 @@ impl Device { None => width, }; - // Take the stride into account for all rows, except the last one. - let len = bpp * row_length * (height - 1) - + width * bpp; - let data = &data[0..len as usize]; - if let Some(..) = stride { self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint); } self.bind_texture(DEFAULT_TEXTURE, texture_id); - self.gl.tex_sub_image_2d(texture_id.target, - 0, - x0 as gl::GLint, - y0 as gl::GLint, - width as gl::GLint, - height as gl::GLint, - gl_format, - data_type, - data); + match texture_id.target { + gl::TEXTURE_2D_ARRAY => { + self.gl.tex_sub_image_3d_pbo(texture_id.target, + 0, + x0 as gl::GLint, + y0 as gl::GLint, + layer_index, + width as gl::GLint, + height as gl::GLint, + 1, + gl_format, + data_type, + offset); + } + gl::TEXTURE_2D | + gl::TEXTURE_RECTANGLE | + gl::TEXTURE_EXTERNAL_OES => { + self.gl.tex_sub_image_2d_pbo(texture_id.target, + 0, + x0 as gl::GLint, + y0 as gl::GLint, + width as gl::GLint, + height as gl::GLint, + gl_format, + data_type, + offset); + } + _ => panic!("BUG: Unexpected texture target!"), + } // Reset row length to 0, otherwise the stride would apply to all texture uploads. if let Some(..) = stride { From c292804df2b8cd5110b2857c7e7f9f250c24a611 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 12:22:22 +1000 Subject: [PATCH 5/9] Switch debug renderer to use array textures. --- webrender/src/debug_render.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webrender/src/debug_render.rs b/webrender/src/debug_render.rs index 9b4b33e8b0..228f569300 100644 --- a/webrender/src/debug_render.rs +++ b/webrender/src/debug_render.rs @@ -90,13 +90,14 @@ impl DebugRenderer { let line_vao = device.create_vao(&DESC_COLOR, 32); let tri_vao = device.create_vao(&DESC_COLOR, 32); - let font_texture_id = device.create_texture_ids(1, TextureTarget::Default)[0]; + let font_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0]; device.init_texture(font_texture_id, debug_font_data::BMP_WIDTH, debug_font_data::BMP_HEIGHT, ImageFormat::A8, TextureFilter::Linear, RenderTargetMode::None, + 1, Some(&debug_font_data::FONT_BITMAP)); DebugRenderer { From 96dc9d28bde6b26c1d4a718a3bbc6aec2f4f4045 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 12:35:08 +1000 Subject: [PATCH 6/9] Update shaders that fetch from sColor* to use array samplers. --- webrender/res/cs_clip_image.fs.glsl | 2 +- webrender/res/cs_clip_image.glsl | 1 + webrender/res/cs_clip_image.vs.glsl | 1 + webrender/res/cs_text_run.glsl | 2 +- webrender/res/cs_text_run.vs.glsl | 2 +- webrender/res/debug_font.fs.glsl | 6 +----- webrender/res/prim_shared.glsl | 8 +++++--- webrender/res/ps_image.fs.glsl | 2 +- webrender/res/ps_image.glsl | 1 + webrender/res/ps_image.vs.glsl | 1 + webrender/res/ps_text_run.fs.glsl | 2 +- webrender/res/ps_text_run.glsl | 2 +- webrender/res/ps_text_run.vs.glsl | 2 +- webrender/res/ps_yuv_image.fs.glsl | 12 ++++++------ webrender/res/ps_yuv_image.glsl | 1 + webrender/res/ps_yuv_image.vs.glsl | 4 ++++ webrender/res/shared.glsl | 8 ++++---- 17 files changed, 32 insertions(+), 25 deletions(-) diff --git a/webrender/res/cs_clip_image.fs.glsl b/webrender/res/cs_clip_image.fs.glsl index 20d2bc3e9d..54768997f6 100644 --- a/webrender/res/cs_clip_image.fs.glsl +++ b/webrender/res/cs_clip_image.fs.glsl @@ -11,7 +11,7 @@ void main(void) { clamp(vClipMaskUv.xy, vec2(0.0, 0.0), vec2(1.0, 1.0)); vec2 source_uv = clamp(clamped_mask_uv * vClipMaskUvRect.zw + vClipMaskUvRect.xy, vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw); - float clip_alpha = texture(sColor0, source_uv).r; //careful: texture has type A8 + float clip_alpha = texture(sColor0, vec3(source_uv, vLayer)).r; //careful: texture has type A8 oFragColor = vec4(min(alpha, clip_alpha), 1.0, 1.0, 1.0); } diff --git a/webrender/res/cs_clip_image.glsl b/webrender/res/cs_clip_image.glsl index 0960a724bd..8a8f1e3cdd 100644 --- a/webrender/res/cs_clip_image.glsl +++ b/webrender/res/cs_clip_image.glsl @@ -7,3 +7,4 @@ varying vec3 vPos; flat varying vec4 vClipMaskUvRect; flat varying vec4 vClipMaskUvInnerRect; +flat varying float vLayer; diff --git a/webrender/res/cs_clip_image.vs.glsl b/webrender/res/cs_clip_image.vs.glsl index d992a7a6dd..904356ddd7 100644 --- a/webrender/res/cs_clip_image.vs.glsl +++ b/webrender/res/cs_clip_image.vs.glsl @@ -26,6 +26,7 @@ void main(void) { cci.segment_index); vPos = vi.local_pos; + vLayer = res.layer; vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0); vec2 texture_size = vec2(textureSize(sColor0, 0)); diff --git a/webrender/res/cs_text_run.glsl b/webrender/res/cs_text_run.glsl index 39ed226b26..5dcde8b661 100644 --- a/webrender/res/cs_text_run.glsl +++ b/webrender/res/cs_text_run.glsl @@ -3,5 +3,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -varying vec2 vUv; +varying vec3 vUv; flat varying vec4 vColor; diff --git a/webrender/res/cs_text_run.vs.glsl b/webrender/res/cs_text_run.vs.glsl index ab43c8c4e8..b018fdf4d0 100644 --- a/webrender/res/cs_text_run.vs.glsl +++ b/webrender/res/cs_text_run.vs.glsl @@ -47,7 +47,7 @@ void main(void) { local_rect.xy + local_rect.zw, aPosition.xy); - vUv = mix(st0, st1, aPosition.xy); + vUv = vec3(mix(st0, st1, aPosition.xy), res.layer); vColor = shadow.color; gl_Position = uTransform * vec4(pos, 0.0, 1.0); diff --git a/webrender/res/debug_font.fs.glsl b/webrender/res/debug_font.fs.glsl index 46acfb5b0e..e77ab29bed 100644 --- a/webrender/res/debug_font.fs.glsl +++ b/webrender/res/debug_font.fs.glsl @@ -7,10 +7,6 @@ varying vec4 vColor; void main(void) { -#ifdef SERVO_ES2 - float alpha = texture(sColor0, vColorTexCoord.xy).a; -#else - float alpha = texture(sColor0, vColorTexCoord.xy).r; -#endif + float alpha = texture(sColor0, vec3(vColorTexCoord.xy, 0.0)).r; oFragColor = vec4(vColor.xyz, vColor.w * alpha); } diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 90f89dc573..653826ff55 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -771,21 +771,23 @@ TransformVertexInfo write_transform_vertex(RectWithSize instance_rect, struct GlyphResource { vec4 uv_rect; + float layer; vec2 offset; }; GlyphResource fetch_glyph_resource(int address) { vec4 data[2] = fetch_from_resource_cache_2(address); - return GlyphResource(data[0], data[1].xy); + return GlyphResource(data[0], data[1].x, data[1].yz); } struct ImageResource { vec4 uv_rect; + float layer; }; ImageResource fetch_image_resource(int address) { - vec4 data = fetch_from_resource_cache_1(address); - return ImageResource(data); + vec4 data[2] = fetch_from_resource_cache_2(address); + return ImageResource(data[0], data[1].x); } struct Rectangle { diff --git a/webrender/res/ps_image.fs.glsl b/webrender/res/ps_image.fs.glsl index db45514ab0..d31a2bf41a 100644 --- a/webrender/res/ps_image.fs.glsl +++ b/webrender/res/ps_image.fs.glsl @@ -28,5 +28,5 @@ void main(void) { alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize)))); - oFragColor = vec4(alpha) * TEX_SAMPLE(sColor0, st); + oFragColor = vec4(alpha) * TEX_SAMPLE(sColor0, vec3(st, vLayer)); } diff --git a/webrender/res/ps_image.glsl b/webrender/res/ps_image.glsl index 85872f6c4b..93138dd09d 100644 --- a/webrender/res/ps_image.glsl +++ b/webrender/res/ps_image.glsl @@ -9,6 +9,7 @@ flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas flat varying vec2 vTextureSize; // Size of the image in the texture atlas. flat varying vec2 vTileSpacing; // Amount of space between tiled instances of this image. flat varying vec4 vStRect; // Rectangle of valid texture rect. +flat varying float vLayer; #ifdef WR_FEATURE_TRANSFORM varying vec3 vLocalPos; diff --git a/webrender/res/ps_image.vs.glsl b/webrender/res/ps_image.vs.glsl index 6fb33a5937..bf9e15d107 100644 --- a/webrender/res/ps_image.vs.glsl +++ b/webrender/res/ps_image.vs.glsl @@ -50,6 +50,7 @@ void main(void) { vec2 st0 = uv0 / texture_size_normalization_factor; vec2 st1 = uv1 / texture_size_normalization_factor; + vLayer = res.layer; vTextureSize = st1 - st0; vTextureOffset = st0; vTileSpacing = image.stretch_size_and_tile_spacing.zw; diff --git a/webrender/res/ps_text_run.fs.glsl b/webrender/res/ps_text_run.fs.glsl index b21ac22949..55ccdccfaa 100644 --- a/webrender/res/ps_text_run.fs.glsl +++ b/webrender/res/ps_text_run.fs.glsl @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ void main(void) { - vec2 tc = clamp(vUv, vUvBorder.xy, vUvBorder.zw); + vec3 tc = vec3(clamp(vUv.xy, vUvBorder.xy, vUvBorder.zw), vUv.z); #ifdef WR_FEATURE_SUBPIXEL_AA //note: the blend mode is not compatible with clipping oFragColor = texture(sColor0, tc); diff --git a/webrender/res/ps_text_run.glsl b/webrender/res/ps_text_run.glsl index a5915da61f..25f632bd50 100644 --- a/webrender/res/ps_text_run.glsl +++ b/webrender/res/ps_text_run.glsl @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ flat varying vec4 vColor; -varying vec2 vUv; +varying vec3 vUv; flat varying vec4 vUvBorder; #ifdef WR_FEATURE_TRANSFORM diff --git a/webrender/res/ps_text_run.vs.glsl b/webrender/res/ps_text_run.vs.glsl index 8e874e3a96..12ecf1b684 100644 --- a/webrender/res/ps_text_run.vs.glsl +++ b/webrender/res/ps_text_run.vs.glsl @@ -48,6 +48,6 @@ void main(void) { vec2 st1 = res.uv_rect.zw / texture_size; vColor = text.color; - vUv = mix(st0, st1, f); + vUv = vec3(mix(st0, st1, f), res.layer); vUvBorder = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy; } diff --git a/webrender/res/ps_yuv_image.fs.glsl b/webrender/res/ps_yuv_image.fs.glsl index 3698841d86..81b134e2c2 100644 --- a/webrender/res/ps_yuv_image.fs.glsl +++ b/webrender/res/ps_yuv_image.fs.glsl @@ -73,17 +73,17 @@ void main(void) { // "The Y, Cb and Cr color channels within the 422 data are mapped into // the existing green, blue and red color channels." // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt - yuv_value = TEX_SAMPLE(sColor0, st_y).gbr; + yuv_value = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).gbr; #elif defined(WR_FEATURE_NV12) - yuv_value.x = TEX_SAMPLE(sColor0, st_y).r; - yuv_value.yz = TEX_SAMPLE(sColor1, st_u).rg; + yuv_value.x = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).r; + yuv_value.yz = TEX_SAMPLE(sColor1, vec3(st_u, vLayers.y)).rg; #else // The yuv_planar format should have this third texture coordinate. vec2 st_v = vTextureOffsetV + uv_offset; - yuv_value.x = TEX_SAMPLE(sColor0, st_y).r; - yuv_value.y = TEX_SAMPLE(sColor1, st_u).r; - yuv_value.z = TEX_SAMPLE(sColor2, st_v).r; + yuv_value.x = TEX_SAMPLE(sColor0, vec3(st_y, vLayers.x)).r; + yuv_value.y = TEX_SAMPLE(sColor1, vec3(st_u, vLayers.y)).r; + yuv_value.z = TEX_SAMPLE(sColor2, vec3(st_v, vLayers.z)).r; #endif // See the YuvColorMatrix definition for an explanation of where the constants come from. diff --git a/webrender/res/ps_yuv_image.glsl b/webrender/res/ps_yuv_image.glsl index 05bb9a0ff5..9280c4014e 100644 --- a/webrender/res/ps_yuv_image.glsl +++ b/webrender/res/ps_yuv_image.glsl @@ -13,6 +13,7 @@ flat varying vec2 vTextureSizeUv; // Size of the u and v planes in the texture flat varying vec2 vStretchSize; flat varying vec2 vHalfTexelY; // Normalized length of the half of a Y texel. flat varying vec2 vHalfTexelUv; // Normalized length of the half of u and v texels. +flat varying vec3 vLayers; #ifdef WR_FEATURE_TRANSFORM varying vec3 vLocalPos; diff --git a/webrender/res/ps_yuv_image.vs.glsl b/webrender/res/ps_yuv_image.vs.glsl index 05fc50d7d8..65df2b64f4 100644 --- a/webrender/res/ps_yuv_image.vs.glsl +++ b/webrender/res/ps_yuv_image.vs.glsl @@ -26,10 +26,14 @@ void main(void) { write_clip(vi.screen_pos, prim.clip_area); ImageResource y_rect = fetch_image_resource(prim.user_data0); + vLayers = vec3(y_rect.layer, 0.0, 0.0); + #ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR // only 1 channel ImageResource u_rect = fetch_image_resource(prim.user_data1); + vLayers.y = u_rect.layer; #ifndef WR_FEATURE_NV12 // 2 channel ImageResource v_rect = fetch_image_resource(prim.user_data2); + vLayers.z = v_rect.layer; #endif #endif diff --git a/webrender/res/shared.glsl b/webrender/res/shared.glsl index b739b2697b..46973a232f 100644 --- a/webrender/res/shared.glsl +++ b/webrender/res/shared.glsl @@ -15,7 +15,7 @@ // // Use texture() instead. #if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT) -#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord) +#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy) #else // In normal case, we use textureLod(). We haven't used the lod yet. So, we always pass 0.0 now. #define TEX_SAMPLE(sampler, tex_coord) textureLod(sampler, tex_coord, 0.0) @@ -61,9 +61,9 @@ uniform samplerExternalOES sColor0; uniform samplerExternalOES sColor1; uniform samplerExternalOES sColor2; #else -uniform sampler2D sColor0; -uniform sampler2D sColor1; -uniform sampler2D sColor2; +uniform sampler2DArray sColor0; +uniform sampler2DArray sColor1; +uniform sampler2DArray sColor2; #endif #ifdef WR_FEATURE_DITHERING From 91671498bc1b454d9e271df79ca41fc94d3ef309 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 13:48:39 +1000 Subject: [PATCH 7/9] Refactor and improve the texture cache implementation. * The texture cache now acts more like a traditional cache, where the cache is a fixed size, and can evict items as required. * The allocator is switched to a slab style allocator, allowing deterministic behavior when allocating and freeing. * The slab allocator does not suffer from fragmentation like the previous allocator, and has no coalesce requirements. * The eviction policy is a lot smarter than the previous implementation. Items are evicted from the cache as required, and not before. * Since the shared cache is a fixed / bounded size, any allocations that are too large for the cache are created as standalone textures. * In the rare / edge case of the fixed sized cache being full, and the cache not being able to evict items, these items also fallback to the standalone texture allocator. * All textures using the sColor* samplers are now array textures. This will allow us to simplify the large image tiling code in the future. * All texture updates are performed via PBO, to eliminate CPU driver stalls when updating textures. --- webrender/Cargo.toml | 2 +- webrender/src/frame.rs | 2 +- webrender/src/frame_builder.rs | 6 +- webrender/src/glyph_cache.rs | 73 +- webrender/src/glyph_rasterizer.rs | 76 +- webrender/src/internal_types.rs | 41 +- webrender/src/prim_store.rs | 23 +- webrender/src/render_backend.rs | 3 +- webrender/src/renderer.rs | 194 ++-- webrender/src/resource_cache.rs | 233 ++-- webrender/src/texture_cache.rs | 1746 ++++++++++++++--------------- 11 files changed, 1096 insertions(+), 1303 deletions(-) diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml index 3a2422c008..e549791af3 100644 --- a/webrender/Cargo.toml +++ b/webrender/Cargo.toml @@ -19,7 +19,7 @@ bit-set = "0.4" byteorder = "1.0" euclid = "0.15.1" fxhash = "0.2.1" -gleam = "0.4.7" +gleam = "0.4.8" lazy_static = "0.2" log = "0.3" num-traits = "0.1.32" diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index 98a3647e87..8e67f33e60 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -21,7 +21,7 @@ use scene::{Scene, SceneProperties}; use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags}; use util::{ComplexClipRegionHelpers, subtract_rect}; -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)] pub struct FrameId(pub u32); static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 }; diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 8272c07127..36b7f46757 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -103,7 +103,6 @@ pub struct FrameBuilderConfig { pub enable_scrollbars: bool, pub default_font_render_mode: FontRenderMode, pub debug: bool, - pub cache_expiry_frames: u32, } pub struct FrameBuilder { @@ -1773,7 +1772,10 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> { if let Some(mask) = clip_source.image_mask() { // We don't add the image mask for resolution, because // layer masks are resolved later. - self.resource_cache.request_image(mask.image, ImageRendering::Auto, None); + self.resource_cache.request_image(mask.image, + ImageRendering::Auto, + None, + self.gpu_cache); } } } diff --git a/webrender/src/glyph_cache.rs b/webrender/src/glyph_cache.rs index 218375c536..cc08b11b9e 100644 --- a/webrender/src/glyph_cache.rs +++ b/webrender/src/glyph_cache.rs @@ -3,43 +3,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{FontInstanceKey, GlyphKey}; -use frame::FrameId; -use gpu_cache::GpuCache; use internal_types::FastHashMap; -use resource_cache::{Resource, ResourceClassCache}; -use texture_cache::{TextureCache, TextureCacheItemId}; +use resource_cache::ResourceClassCache; +use std::sync::Arc; +use texture_cache::TextureCacheHandle; pub struct CachedGlyphInfo { - pub texture_cache_id: Option, - pub last_access: FrameId, + pub texture_cache_handle: TextureCacheHandle, + pub glyph_bytes: Arc>, + pub width: u32, + pub height: u32, + pub left: f32, + pub top: f32, } -impl Resource for CachedGlyphInfo { - fn free(self, texture_cache: &mut TextureCache) { - if let Some(id) = self.texture_cache_id { - texture_cache.free(id); - } - } - fn get_last_access_time(&self) -> FrameId { - self.last_access - } - fn set_last_access_time(&mut self, frame_id: FrameId) { - self.last_access = frame_id; - } - fn add_to_gpu_cache(&self, - texture_cache: &mut TextureCache, - gpu_cache: &mut GpuCache) { - if let Some(texture_cache_id) = self.texture_cache_id.as_ref() { - let item = texture_cache.get_mut(texture_cache_id); - if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) { - request.push(item.uv_rect); - request.push([item.user_data[0], item.user_data[1], 0.0, 0.0]); - } - } - } -} - -pub type GlyphKeyCache = ResourceClassCache; +pub type GlyphKeyCache = ResourceClassCache>; pub struct GlyphCache { pub glyph_key_caches: FastHashMap, @@ -66,39 +44,16 @@ impl GlyphCache { .expect("BUG: Unable to find glyph key cache!") } - pub fn update(&mut self, - texture_cache: &mut TextureCache, - gpu_cache: &mut GpuCache, - current_frame_id: FrameId, - expiry_frame_id: FrameId) { - let mut caches_to_remove = Vec::new(); - - for (font, glyph_key_cache) in &mut self.glyph_key_caches { - glyph_key_cache.update(texture_cache, - gpu_cache, - current_frame_id, - expiry_frame_id); - - if glyph_key_cache.is_empty() { - caches_to_remove.push(font.clone()); - } - } - - for key in caches_to_remove { - self.glyph_key_caches.remove(&key).unwrap(); - } - } - - pub fn clear(&mut self, texture_cache: &mut TextureCache) { + pub fn clear(&mut self) { for (_, glyph_key_cache) in &mut self.glyph_key_caches { - glyph_key_cache.clear(texture_cache) + glyph_key_cache.clear() } // We use this in on_memory_pressure where retaining memory allocations // isn't desirable, so we completely remove the hash map instead of clearing it. self.glyph_key_caches = FastHashMap::default(); } - pub fn clear_fonts(&mut self, texture_cache: &mut TextureCache, key_fun: F) + pub fn clear_fonts(&mut self, key_fun: F) where for<'r> F: Fn(&'r &FontInstanceKey) -> bool { let caches_to_destroy = self.glyph_key_caches.keys() @@ -107,7 +62,7 @@ impl GlyphCache { .collect::>(); for key in caches_to_destroy { let mut cache = self.glyph_key_caches.remove(&key).unwrap(); - cache.clear(texture_cache); + cache.clear(); } } } diff --git a/webrender/src/glyph_rasterizer.rs b/webrender/src/glyph_rasterizer.rs index 2c468a4275..90d6b10c0a 100644 --- a/webrender/src/glyph_rasterizer.rs +++ b/webrender/src/glyph_rasterizer.rs @@ -5,8 +5,8 @@ #[cfg(test)] use app_units::Au; use device::TextureFilter; -use frame::FrameId; use glyph_cache::{CachedGlyphInfo, GlyphCache}; +use gpu_cache::GpuCache; use internal_types::FastHashSet; use platform::font::{FontContext, RasterizedGlyph}; use profiler::TextureCacheProfileCounters; @@ -16,7 +16,7 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::collections::hash_map::Entry; use std::mem; -use texture_cache::TextureCache; +use texture_cache::{TextureCache, TextureCacheHandle}; #[cfg(test)] use api::{ColorF, LayoutPoint, FontRenderMode, IdNamespace, SubpixelDirection}; use api::{FontInstanceKey}; @@ -145,9 +145,10 @@ impl GlyphRasterizer { pub fn request_glyphs( &mut self, glyph_cache: &mut GlyphCache, - current_frame_id: FrameId, font: FontInstanceKey, - glyph_keys: &[GlyphKey]) { + glyph_keys: &[GlyphKey], + texture_cache: &mut TextureCache, + gpu_cache: &mut GpuCache) { assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key)); let mut glyphs = Vec::new(); @@ -155,8 +156,31 @@ impl GlyphRasterizer { // select glyphs that have not been requested yet. for key in glyph_keys { - match glyph_key_cache.entry(key.clone(), current_frame_id) { - Entry::Occupied(..) => {} + match glyph_key_cache.entry(key.clone()) { + Entry::Occupied(mut entry) => { + if let &mut Some(ref mut glyph_info) = entry.get_mut() { + if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) { + // This case gets hit when we have already rasterized + // the glyph and stored it in CPU memory, the the glyph + // has been evicted from the texture cache. In which case + // we need to re-upload it to the GPU. + texture_cache.update(&mut glyph_info.texture_cache_handle, + ImageDescriptor { + width: glyph_info.width, + height: glyph_info.height, + stride: None, + format: ImageFormat::BGRA8, + is_opaque: false, + offset: 0, + }, + TextureFilter::Linear, + ImageData::Raw(glyph_info.glyph_bytes.clone()), + [glyph_info.left, glyph_info.top], + None, + gpu_cache); + } + } + } Entry::Vacant(..) => { let request = GlyphRequest::new(&font, key); if self.pending_glyphs.insert(request.clone()) { @@ -209,10 +233,10 @@ impl GlyphRasterizer { pub fn resolve_glyphs( &mut self, - current_frame_id: FrameId, glyph_cache: &mut GlyphCache, texture_cache: &mut TextureCache, - texture_cache_profile: &mut TextureCacheProfileCounters, + gpu_cache: &mut GpuCache, + _texture_cache_profile: &mut TextureCacheProfileCounters, ) { let mut rasterized_glyphs = Vec::with_capacity(self.pending_glyphs.len()); @@ -241,10 +265,14 @@ impl GlyphRasterizer { // Update the caches. for job in rasterized_glyphs { - let image_id = job.result.and_then( + let glyph_info = job.result.and_then( |glyph| if glyph.width > 0 && glyph.height > 0 { assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0)); - let image_id = texture_cache.insert( + let glyph_bytes = Arc::new(glyph.bytes); + let mut texture_cache_handle = TextureCacheHandle::new(); + texture_cache.request(&mut texture_cache_handle, gpu_cache); + texture_cache.update( + &mut texture_cache_handle, ImageDescriptor { width: glyph.width, height: glyph.height, @@ -254,11 +282,19 @@ impl GlyphRasterizer { offset: 0, }, TextureFilter::Linear, - ImageData::Raw(Arc::new(glyph.bytes)), + ImageData::Raw(glyph_bytes.clone()), [glyph.left, glyph.top], - texture_cache_profile, + None, + gpu_cache, ); - Some(image_id) + Some(CachedGlyphInfo { + texture_cache_handle, + glyph_bytes, + width: glyph.width, + height: glyph.height, + left: glyph.left, + top: glyph.top, + }) } else { None } @@ -266,10 +302,7 @@ impl GlyphRasterizer { let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(job.request.font); - glyph_key_cache.insert(job.request.key, CachedGlyphInfo { - texture_cache_id: image_id, - last_access: current_frame_id, - }); + glyph_key_cache.insert(job.request.key, glyph_info); } // Now that we are done with the critical path (rendering the glyphs), @@ -337,6 +370,8 @@ fn raterize_200_glyphs() { let workers = Arc::new(ThreadPool::new(Configuration::new()).unwrap()); let mut glyph_rasterizer = GlyphRasterizer::new(workers); let mut glyph_cache = GlyphCache::new(); + let mut gpu_cache = GpuCache::new(); + let mut texture_cache = TextureCache::new(2048); let mut font_file = File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file"); let mut font_data = vec![]; @@ -345,8 +380,6 @@ fn raterize_200_glyphs() { let font_key = FontKey::new(IdNamespace(0), 0); glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0)); - let frame_id = FrameId(1); - let font = FontInstanceKey { font_key, color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(), @@ -364,18 +397,19 @@ fn raterize_200_glyphs() { for i in 0..4 { glyph_rasterizer.request_glyphs( &mut glyph_cache, - frame_id, font.clone(), &glyph_keys[(50 * i)..(50 * (i + 1))], + &mut texture_cache, + &mut gpu_cache ); } glyph_rasterizer.delete_font(font_key); glyph_rasterizer.resolve_glyphs( - frame_id, &mut glyph_cache, &mut TextureCache::new(4096), + &mut gpu_cache, &mut TextureCacheProfileCounters::new(), ); } diff --git a/webrender/src/internal_types.rs b/webrender/src/internal_types.rs index 0dd36f7689..586e63934d 100644 --- a/webrender/src/internal_types.rs +++ b/webrender/src/internal_types.rs @@ -15,7 +15,7 @@ use tiling; use renderer::BlendMode; use api::{ClipId, DevicePoint, DeviceUintRect, DocumentId, Epoch}; use api::{ExternalImageData, ExternalImageId}; -use api::{ImageData, ImageFormat, PipelineId}; +use api::{ImageFormat, PipelineId}; pub type FastHashMap = HashMap>; pub type FastHashSet = HashSet>; @@ -99,42 +99,31 @@ pub const DEFAULT_TEXTURE: TextureSampler = TextureSampler::Color0; #[derive(Copy, Clone, Debug, PartialEq)] pub enum RenderTargetMode { None, - SimpleRenderTarget, - LayerRenderTarget(i32), // Number of texture layers + RenderTarget, +} + +#[derive(Debug)] +pub enum TextureUpdateSource { + External { id: ExternalImageId, channel_index: u8 }, + Bytes { data: Arc> }, } #[derive(Debug)] pub enum TextureUpdateOp { Create { - width: u32, - height: u32, - format: ImageFormat, - filter: TextureFilter, - mode: RenderTargetMode, - data: Option, - }, - Update { - page_pos_x: u32, // the texture page position which we want to upload - page_pos_y: u32, width: u32, height: u32, - data: Arc>, - stride: Option, - offset: u32, + format: ImageFormat, + filter: TextureFilter, + mode: RenderTargetMode, + layer_count: i32, }, - UpdateForExternalBuffer { + Update { rect: DeviceUintRect, - id: ExternalImageId, - channel_index: u8, stride: Option, offset: u32, - }, - Grow { - width: u32, - height: u32, - format: ImageFormat, - filter: TextureFilter, - mode: RenderTargetMode, + layer_index: i32, + source: TextureUpdateSource, }, Free, } diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index b6d2824972..65ce18a60f 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -538,7 +538,8 @@ impl TextRunPrimitiveCpu { resource_cache: &mut ResourceCache, device_pixel_ratio: f32, display_list: &BuiltDisplayList, - run_mode: TextRunMode) { + run_mode: TextRunMode, + gpu_cache: &mut GpuCache) { let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio); let render_mode = match run_mode { TextRunMode::Normal => self.normal_render_mode, @@ -590,7 +591,7 @@ impl TextRunPrimitiveCpu { } } - resource_cache.request_glyphs(font, &self.glyph_keys); + resource_cache.request_glyphs(font, &self.glyph_keys, gpu_cache); } fn write_gpu_blocks(&self, @@ -1130,7 +1131,10 @@ impl PrimitiveStore { for clip in &metadata.clips { if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip { - resource_cache.request_image(mask.image, ImageRendering::Auto, None); + resource_cache.request_image(mask.image, + ImageRendering::Auto, + None, + gpu_cache); } } } @@ -1177,14 +1181,18 @@ impl PrimitiveStore { text.prepare_for_render(resource_cache, device_pixel_ratio, display_list, - text_run_mode); + text_run_mode, + gpu_cache); } PrimitiveKind::Image => { let image_cpu = &mut self.cpu_images[cpu_prim_index.0]; match image_cpu.kind { ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, tile_spacing) => { - resource_cache.request_image(image_key, image_rendering, tile_offset); + resource_cache.request_image(image_key, + image_rendering, + tile_offset, + gpu_cache); // TODO(gw): This doesn't actually need to be calculated each frame. // It's cheap enough that it's not worth introducing a cache for images @@ -1204,7 +1212,10 @@ impl PrimitiveStore { let channel_num = image_cpu.format.get_plane_num(); debug_assert!(channel_num <= 3); for channel in 0..channel_num { - resource_cache.request_image(image_cpu.yuv_key[channel], image_cpu.image_rendering, None); + resource_cache.request_image(image_cpu.yuv_key[channel], + image_cpu.image_rendering, + None, + gpu_cache); } } PrimitiveKind::AlignedGradient | diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 2c1169becc..49b5778507 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -215,8 +215,7 @@ impl RenderBackend { let resource_cache = ResourceCache::new(texture_cache, workers, - blob_image_renderer, - frame_config.cache_expiry_frames); + blob_image_renderer); register_thread_with_profiler("Backend".to_string()); diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 777440d37f..4bba90524e 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -19,7 +19,7 @@ use frame_builder::FrameBuilderConfig; use gleam::gl; use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList}; use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp}; -use internal_types::{TextureUpdateList, RenderTargetMode}; +use internal_types::{TextureUpdateList, RenderTargetMode, TextureUpdateSource}; use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture}; use internal_types::{BatchTextures, TextureSampler}; use profiler::{Profiler, BackendProfileCounters}; @@ -48,7 +48,7 @@ use thread_profiler::{register_thread_with_profiler, write_profile}; use util::TransformedRectKind; use webgl_types::GLContextHandleWrapper; use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier, RenderDispatcher}; -use api::{ExternalImageId, ExternalImageType, ImageData, ImageFormat}; +use api::{ExternalImageId, ExternalImageType, ImageFormat}; use api::{DeviceIntRect, DeviceUintRect, DeviceIntPoint, DeviceIntSize, DeviceUintSize}; use api::{BlobImageRenderer, channel, FontRenderMode}; use api::VRCompositorHandler; @@ -324,6 +324,7 @@ impl CacheTexture { ImageFormat::RGBAF32, TextureFilter::Nearest, RenderTargetMode::None, + 1, None); // Copy the current texture into the newly resized texture. @@ -361,6 +362,8 @@ impl CacheTexture { row_index as u32, MAX_VERTEX_TEXTURE_WIDTH as u32, 1, + 0, + None, 0); // Orphan the PBO. This is the recommended way to hint to the @@ -456,6 +459,7 @@ impl GpuDataTexture { L::image_format(), L::texture_filter(), RenderTargetMode::None, + 1, Some(unsafe { mem::transmute(data.as_slice()) } )); } } @@ -781,6 +785,7 @@ pub struct Renderer { /// list reuses the texture cache ID. This saves having to /// use a hashmap, and allows a flat vector for performance. cache_texture_id_map: Vec, + texture_cache_upload_pbo: PBOId, /// A special 1x1 dummy cache texture used for shaders that expect to work /// with the cache but are actually running in the first pass @@ -1142,7 +1147,8 @@ impl Renderer { 1, ImageFormat::BGRA8, TextureFilter::Linear, - RenderTargetMode::LayerRenderTarget(1), + RenderTargetMode::RenderTarget, + 1, None); let dither_matrix_texture_id = if options.enable_dithering { @@ -1164,6 +1170,7 @@ impl Renderer { ImageFormat::A8, TextureFilter::Nearest, RenderTargetMode::None, + 1, Some(&dither_matrix)); Some(id) @@ -1210,6 +1217,8 @@ impl Renderer { let blur_vao_id = device.create_vao_with_new_instances(&DESC_BLUR, mem::size_of::() as i32, prim_vao_id); let clip_vao_id = device.create_vao_with_new_instances(&DESC_CLIP, mem::size_of::() as i32, prim_vao_id); + let texture_cache_upload_pbo = device.create_pbo(); + device.end_frame(); let main_thread_dispatcher = Arc::new(Mutex::new(None)); @@ -1236,7 +1245,6 @@ impl Renderer { enable_scrollbars: options.enable_scrollbars, default_font_render_mode, debug: options.debug, - cache_expiry_frames: options.cache_expiry_frames, }; let device_pixel_ratio = options.device_pixel_ratio; @@ -1339,6 +1347,7 @@ impl Renderer { cpu_profiles: VecDeque::new(), gpu_profiles: VecDeque::new(), gpu_cache_texture, + texture_cache_upload_pbo, }; let sender = RenderApiSender::new(api_tx, payload_tx); @@ -1607,109 +1616,65 @@ impl Renderer { fn update_texture_cache(&mut self) { let _gm = GpuMarker::new(self.device.rc_gl(), "texture cache update"); let mut pending_texture_updates = mem::replace(&mut self.pending_texture_updates, vec![]); + for update_list in pending_texture_updates.drain(..) { for update in update_list.updates { match update.op { - TextureUpdateOp::Create { width, height, format, filter, mode, data } => { + TextureUpdateOp::Create { width, height, layer_count, format, filter, mode } => { let CacheTextureId(cache_texture_index) = update.id; if self.cache_texture_id_map.len() == cache_texture_index { // Create a new native texture, as requested by the texture cache. let texture_id = self.device - .create_texture_ids(1, TextureTarget::Default)[0]; + .create_texture_ids(1, TextureTarget::Array)[0]; self.cache_texture_id_map.push(texture_id); } let texture_id = self.cache_texture_id_map[cache_texture_index]; - if let Some(image) = data { - match image { - ImageData::Raw(raw) => { - self.device.init_texture(texture_id, - width, - height, - format, - filter, - mode, - Some(raw.as_slice())); - } - ImageData::External(ext_image) => { - match ext_image.image_type { - ExternalImageType::ExternalBuffer => { - let handler = self.external_image_handler - .as_mut() - .expect("Found external image, but no handler set!"); - - match handler.lock(ext_image.id, ext_image.channel_index).source { - ExternalImageSource::RawData(raw) => { - self.device.init_texture(texture_id, - width, - height, - format, - filter, - mode, - Some(raw)); - } - _ => panic!("No external buffer found"), - }; - handler.unlock(ext_image.id, ext_image.channel_index); - } - ExternalImageType::Texture2DHandle | - ExternalImageType::TextureRectHandle | - ExternalImageType::TextureExternalHandle => { - panic!("External texture handle should not use TextureUpdateOp::Create."); - } - } - } - _ => { - panic!("No suitable image buffer for TextureUpdateOp::Create."); - } - } - } else { - self.device.init_texture(texture_id, - width, - height, - format, - filter, - mode, - None); - } - } - TextureUpdateOp::Grow { width, height, format, filter, mode } => { - let texture_id = self.cache_texture_id_map[update.id.0]; - self.device.resize_texture(texture_id, - width, - height, - format, - filter, - mode); + // Ensure no PBO is bound when creating the texture storage, + // or GL will attempt to read data from there. + self.device.bind_pbo(None); + self.device.init_texture(texture_id, + width, + height, + format, + filter, + mode, + layer_count, + None); } - TextureUpdateOp::Update { page_pos_x, page_pos_y, width, height, data, stride, offset } => { + TextureUpdateOp::Update { rect, source, stride, layer_index, offset } => { let texture_id = self.cache_texture_id_map[update.id.0]; - self.device.update_texture(texture_id, - page_pos_x, - page_pos_y, - width, height, stride, - &data[offset as usize..]); - } - TextureUpdateOp::UpdateForExternalBuffer { rect, id, channel_index, stride, offset } => { - let handler = self.external_image_handler - .as_mut() - .expect("Found external image, but no handler set!"); - let device = &mut self.device; - let cached_id = self.cache_texture_id_map[update.id.0]; - - match handler.lock(id, channel_index).source { - ExternalImageSource::RawData(data) => { - device.update_texture(cached_id, - rect.origin.x, - rect.origin.y, - rect.size.width, - rect.size.height, - stride, - &data[offset as usize..]); + + // Bind a PBO to do the texture upload. + // Updating the texture via PBO avoids CPU-side driver stalls. + self.device.bind_pbo(Some(self.texture_cache_upload_pbo)); + + match source { + TextureUpdateSource::Bytes { data } => { + self.device.update_pbo_data(&data[offset as usize..]); } - _ => panic!("No external buffer found"), - }; - handler.unlock(id, channel_index); + TextureUpdateSource::External { id, channel_index } => { + let handler = self.external_image_handler + .as_mut() + .expect("Found external image, but no handler set!"); + match handler.lock(id, channel_index).source { + ExternalImageSource::RawData(data) => { + self.device.update_pbo_data(&data[offset as usize..]); + } + _ => panic!("No external buffer found"), + }; + handler.unlock(id, channel_index); + } + } + + self.device.update_texture_from_pbo(texture_id, + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + layer_index, + stride, + 0); } TextureUpdateOp::Free => { let texture_id = self.cache_texture_id_map[update.id.0]; @@ -1718,6 +1683,9 @@ impl Renderer { } } } + + // Ensure that other texture updates won't read from this PBO. + self.device.bind_pbo(None); } fn draw_instanced_batch(&mut self, @@ -2209,7 +2177,8 @@ impl Renderer { block_count: 1, address: deferred_resolve.address, }; - let blocks = [ [image.u0, image.v0, image.u1, image.v1].into() ]; + + let blocks = [ [image.u0, image.v0, image.u1, image.v1].into(), [0.0; 4].into() ]; self.gpu_cache_texture.apply_patch(&update, &blocks); } } @@ -2264,7 +2233,8 @@ impl Renderer { frame.cache_size.height as u32, ImageFormat::BGRA8, TextureFilter::Linear, - RenderTargetMode::LayerRenderTarget(target_count as i32), + RenderTargetMode::RenderTarget, + target_count as i32, None); } if let Some(texture_id) = pass.alpha_texture_id { @@ -2274,7 +2244,8 @@ impl Renderer { frame.cache_size.height as u32, ImageFormat::A8, TextureFilter::Nearest, - RenderTargetMode::LayerRenderTarget(target_count as i32), + RenderTargetMode::RenderTarget, + target_count as i32, None); } } @@ -2443,7 +2414,12 @@ impl Renderer { let mut spacing = 16; let mut size = 512; let fb_width = framebuffer_size.width as i32; - let num_textures = self.cache_texture_id_map.len() as i32; + let num_textures: i32 = self.cache_texture_id_map + .iter() + .map(|id| { + self.device.get_texture_layer_count(*id) + }) + .sum(); if num_textures * (size + spacing) > fb_width { let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32; @@ -2451,17 +2427,23 @@ impl Renderer { spacing = (spacing as f32 * factor) as i32; } - for (i, texture_id) in self.cache_texture_id_map.iter().enumerate() { - let x = fb_width - (spacing + size) * (i as i32 + 1); + let mut i = 0; + for texture_id in &self.cache_texture_id_map { let y = spacing + if self.debug_flags.contains(RENDER_TARGET_DBG) { 528 } else { 0 }; - // If we have more targets than fit on one row in screen, just early exit. - if x > fb_width { - return; - } + let layer_count = self.device.get_texture_layer_count(*texture_id); + for layer_index in 0..layer_count { + let x = fb_width - (spacing + size) * (i as i32 + 1); - let dest_rect = rect(x, y, size, size); - self.device.blit_render_target(Some((*texture_id, 0)), None, dest_rect); + // If we have more targets than fit on one row in screen, just early exit. + if x > fb_width { + return; + } + + let dest_rect = rect(x, y, size, size); + self.device.blit_render_target(Some((*texture_id, layer_index)), None, dest_rect); + i += 1; + } } } @@ -2587,7 +2569,6 @@ pub struct RendererOptions { pub enable_clear_scissor: bool, pub enable_batcher: bool, pub max_texture_size: Option, - pub cache_expiry_frames: u32, pub workers: Option>, pub blob_image_renderer: Option>, pub recorder: Option>, @@ -2614,7 +2595,6 @@ impl Default for RendererOptions { enable_clear_scissor: true, enable_batcher: true, max_texture_size: None, - cache_expiry_frames: 600, // roughly, 10 seconds workers: None, blob_image_renderer: None, recorder: None, diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index 4dc975faf9..2202d41be9 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -8,13 +8,12 @@ use glyph_cache::GlyphCache; use gpu_cache::{GpuCache, GpuCacheHandle}; use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList}; use profiler::{ResourceProfileCounters, TextureCacheProfileCounters}; -use std::cmp; use std::collections::hash_map::Entry::{self, Occupied, Vacant}; use std::fmt::Debug; use std::hash::Hash; use std::mem; use std::sync::Arc; -use texture_cache::{TextureCache, TextureCacheItemId}; +use texture_cache::{TextureCache, TextureCacheHandle}; use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest}; use api::{BlobImageResources, BlobImageData, ResourceUpdates, ResourceUpdate, AddFont}; use api::{DevicePoint, DeviceIntSize, DeviceUintRect, DeviceUintSize}; @@ -102,85 +101,44 @@ impl ImageTemplates { } struct CachedImageInfo { - texture_cache_id: TextureCacheItemId, + texture_cache_handle: TextureCacheHandle, epoch: Epoch, - last_access: FrameId, } pub struct ResourceClassCache { resources: FastHashMap, } -impl ResourceClassCache where K: Clone + Hash + Eq + Debug, V: Resource { +impl ResourceClassCache where K: Clone + Hash + Eq + Debug { pub fn new() -> ResourceClassCache { ResourceClassCache { resources: FastHashMap::default(), } } - fn get(&self, key: &K, frame: FrameId) -> &V { - let resource = self.resources - .get(key) - .expect("Didn't find a cached resource with that ID!"); - - // This assert catches cases in which we accidentally request a resource that we forgot to - // mark as needed this frame. - debug_assert_eq!(frame, resource.get_last_access_time()); - - resource + fn get(&self, key: &K) -> &V { + self.resources + .get(key) + .expect("Didn't find a cached resource with that ID!") } pub fn insert(&mut self, key: K, value: V) { self.resources.insert(key, value); } - pub fn entry(&mut self, key: K, frame: FrameId) -> Entry { - let mut entry = self.resources.entry(key); - match entry { - Occupied(ref mut entry) => { - entry.get_mut().set_last_access_time(frame); - } - Vacant(..) => {} - } - entry + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.resources.get_mut(key) } - pub fn is_empty(&self) -> bool { - self.resources.is_empty() + pub fn entry(&mut self, key: K) -> Entry { + self.resources.entry(key) } - pub fn update(&mut self, - texture_cache: &mut TextureCache, - gpu_cache: &mut GpuCache, - current_frame_id: FrameId, - expiry_frame_id: FrameId) { - let mut resources_to_destroy = Vec::new(); - - for (key, resource) in &self.resources { - let last_access = resource.get_last_access_time(); - if last_access < expiry_frame_id { - resources_to_destroy.push(key.clone()); - } else if last_access == current_frame_id { - resource.add_to_gpu_cache(texture_cache, gpu_cache); - } - } - - for key in resources_to_destroy { - let resource = - self.resources - .remove(&key) - .expect("Resource was in `last_access_times` but not in `resources`!"); - resource.free(texture_cache); - } + pub fn clear(&mut self) { + self.resources.clear(); } - pub fn clear(&mut self, texture_cache: &mut TextureCache) { - for (_, resource) in self.resources.drain() { - resource.free(texture_cache); - } - } - - fn clear_keys(&mut self, texture_cache: &mut TextureCache, key_fun: F) + fn clear_keys(&mut self, key_fun: F) where for<'r> F: Fn(&'r &K) -> bool { let resources_to_destroy = self.resources.keys() @@ -188,8 +146,7 @@ impl ResourceClassCache where K: Clone + Hash + Eq + Debug, V: Resourc .cloned() .collect::>(); for key in resources_to_destroy { - let resource = self.resources.remove(&key).unwrap(); - resource.free(texture_cache); + self.resources.remove(&key).unwrap(); } } } @@ -253,15 +210,12 @@ pub struct ResourceCache { pending_image_requests: FastHashSet, blob_image_renderer: Option>, - - cache_expiry_frames: u32, } impl ResourceCache { pub fn new(texture_cache: TextureCache, workers: Arc, - blob_image_renderer: Option>, - cache_expiry_frames: u32) -> ResourceCache { + blob_image_renderer: Option>) -> ResourceCache { ResourceCache { cached_glyphs: GlyphCache::new(), cached_images: ResourceClassCache::new(), @@ -277,7 +231,6 @@ impl ResourceCache { pending_image_requests: FastHashSet::default(), glyph_rasterizer: GlyphRasterizer::new(workers), blob_image_renderer, - cache_expiry_frames, } } @@ -426,7 +379,7 @@ impl ResourceCache { pub fn delete_image_template(&mut self, image_key: ImageKey) { let value = self.resources.image_templates.remove(image_key); - self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key == image_key); + self.cached_images.clear_keys(|request| request.key == image_key); match value { Some(image) => { @@ -458,7 +411,8 @@ impl ResourceCache { pub fn request_image(&mut self, key: ImageKey, rendering: ImageRendering, - tile: Option) { + tile: Option, + gpu_cache: &mut GpuCache) { debug_assert_eq!(self.state, State::AddResources); let request = ImageRequest { @@ -477,14 +431,25 @@ impl ResourceCache { // If this image exists in the texture cache, *and* the epoch // in the cache matches that of the template, then it is // valid to use as-is. - match self.cached_images.entry(request, self.current_frame_id) { + let (entry, needs_update) = match self.cached_images.entry(request) { Occupied(entry) => { - let cached_image = entry.get(); - if cached_image.epoch == template.epoch { - return; - } + let needs_update = entry.get().epoch != template.epoch; + (entry.into_mut(), needs_update) } - Vacant(..) => {} + Vacant(entry) => { + (entry.insert(CachedImageInfo { + epoch: template.epoch, + texture_cache_handle: TextureCacheHandle::new(), + }), true) + } + }; + + let needs_upload = self.texture_cache + .request(&mut entry.texture_cache_handle, + gpu_cache); + + if !needs_upload && !needs_update { + return; } // We can start a worker thread rasterizing right now, if: @@ -527,14 +492,16 @@ impl ResourceCache { pub fn request_glyphs(&mut self, font: FontInstanceKey, - glyph_keys: &[GlyphKey]) { + glyph_keys: &[GlyphKey], + gpu_cache: &mut GpuCache) { debug_assert_eq!(self.state, State::AddResources); self.glyph_rasterizer.request_glyphs( &mut self.cached_glyphs, - self.current_frame_id, font, glyph_keys, + &mut self.texture_cache, + gpu_cache, ); } @@ -552,10 +519,8 @@ impl ResourceCache { let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font); for (loop_index, key) in glyph_keys.iter().enumerate() { - let glyph = glyph_key_cache.get(key, self.current_frame_id); - let cache_item = glyph.texture_cache_id - .as_ref() - .map(|image_id| self.texture_cache.get(image_id)); + let glyph = glyph_key_cache.get(key); + let cache_item = glyph.as_ref().map(|info| self.texture_cache.get(&info.texture_cache_handle)); if let Some(cache_item) = cache_item { f(loop_index, &cache_item.uv_rect_handle); debug_assert!(texture_id == None || @@ -564,7 +529,7 @@ impl ResourceCache { } } - texture_id.map_or(SourceTexture::Invalid, SourceTexture::TextureCache) + texture_id.unwrap_or(SourceTexture::Invalid) } pub fn get_glyph_dimensions(&mut self, @@ -595,12 +560,8 @@ impl ResourceCache { rendering: image_rendering, tile, }; - let image_info = &self.cached_images.get(&key, self.current_frame_id); - let item = self.texture_cache.get(&image_info.texture_cache_id); - CacheItem { - texture_id: SourceTexture::TextureCache(item.texture_id), - uv_rect_handle: item.uv_rect_handle, - } + let image_info = &self.cached_images.get(&key); + self.texture_cache.get(&image_info.texture_cache_handle) } pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties { @@ -650,6 +611,7 @@ impl ResourceCache { pub fn begin_frame(&mut self, frame_id: FrameId) { debug_assert_eq!(self.state, State::Idle); self.state = State::AddResources; + self.texture_cache.begin_frame(frame_id); self.current_frame_id = frame_id; } @@ -662,29 +624,20 @@ impl ResourceCache { self.state = State::QueryResources; self.glyph_rasterizer.resolve_glyphs( - self.current_frame_id, &mut self.cached_glyphs, &mut self.texture_cache, + gpu_cache, texture_cache_profile, ); // Apply any updates of new / updated images (incl. blobs) to the texture cache. - self.update_texture_cache(texture_cache_profile); - - // Expire any resources that haven't been used for `cache_expiry_frames`. - let num_frames_back = self.cache_expiry_frames; - let expiry_frame = FrameId(cmp::max(num_frames_back, self.current_frame_id.0) - num_frames_back); - self.cached_images.update(&mut self.texture_cache, - gpu_cache, - self.current_frame_id, - expiry_frame); - self.cached_glyphs.update(&mut self.texture_cache, - gpu_cache, - self.current_frame_id, - expiry_frame); - } - - fn update_texture_cache(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) { + self.update_texture_cache(gpu_cache, texture_cache_profile); + self.texture_cache.end_frame(); + } + + fn update_texture_cache(&mut self, + gpu_cache: &mut GpuCache, + _texture_cache_profile: &mut TextureCacheProfileCounters) { for request in self.pending_image_requests.drain() { let image_template = self.resources.image_templates.get_mut(request.key).unwrap(); debug_assert!(image_template.data.uses_texture_cache()); @@ -757,38 +710,15 @@ impl ResourceCache { image_template.descriptor.clone() }; - match self.cached_images.entry(request, self.current_frame_id) { - Occupied(mut entry) => { - let entry = entry.get_mut(); - - // We should only get to this code path if the image - // definitely needs to be updated. - debug_assert!(entry.epoch != image_template.epoch); - self.texture_cache.update(&entry.texture_cache_id, - descriptor, - filter, - image_data, - image_template.dirty_rect); - - // Update the cached epoch - debug_assert_eq!(self.current_frame_id, entry.last_access); - entry.epoch = image_template.epoch; - image_template.dirty_rect = None; - } - Vacant(entry) => { - let image_id = self.texture_cache.insert(descriptor, - filter, - image_data, - [0.0; 2], - texture_cache_profile); - - entry.insert(CachedImageInfo { - texture_cache_id: image_id, - epoch: image_template.epoch, - last_access: self.current_frame_id, - }); - } - }; + let entry = self.cached_images.get_mut(&request).unwrap(); + self.texture_cache.update(&mut entry.texture_cache_handle, + descriptor, + filter, + image_data, + [0.0; 2], + image_template.dirty_rect, + gpu_cache); + image_template.dirty_rect = None; } } @@ -806,8 +736,8 @@ impl ResourceCache { // The advantage of clearing the cache completely is that it gets rid of any // remaining fragmentation that could have persisted if we kept around the most // recently used resources. - self.cached_images.clear(&mut self.texture_cache); - self.cached_glyphs.clear(&mut self.texture_cache); + self.cached_images.clear(); + self.cached_glyphs.clear(); } pub fn clear_namespace(&mut self, namespace: IdNamespace) { @@ -828,37 +758,8 @@ impl ResourceCache { self.resources.font_templates.remove(key); } - self.cached_images.clear_keys(&mut self.texture_cache, |request| request.key.0 == namespace); - self.cached_glyphs.clear_fonts(&mut self.texture_cache, |font| font.font_key.0 == namespace); - } -} - -pub trait Resource { - fn free(self, texture_cache: &mut TextureCache); - fn get_last_access_time(&self) -> FrameId; - fn set_last_access_time(&mut self, frame_id: FrameId); - fn add_to_gpu_cache(&self, - texture_cache: &mut TextureCache, - gpu_cache: &mut GpuCache); -} - -impl Resource for CachedImageInfo { - fn free(self, texture_cache: &mut TextureCache) { - texture_cache.free(self.texture_cache_id); - } - fn get_last_access_time(&self) -> FrameId { - self.last_access - } - fn set_last_access_time(&mut self, frame_id: FrameId) { - self.last_access = frame_id; - } - fn add_to_gpu_cache(&self, - texture_cache: &mut TextureCache, - gpu_cache: &mut GpuCache) { - let item = texture_cache.get_mut(&self.texture_cache_id); - if let Some(mut request) = gpu_cache.request(&mut item.uv_rect_handle) { - request.push(item.uv_rect); - } + self.cached_images.clear_keys(|request| request.key.0 == namespace); + self.cached_glyphs.clear_fonts(|font| font.font_key.0 == namespace); } } diff --git a/webrender/src/texture_cache.rs b/webrender/src/texture_cache.rs index d664bf33ab..a769f6cef1 100644 --- a/webrender/src/texture_cache.rs +++ b/webrender/src/texture_cache.rs @@ -3,819 +3,944 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use device::TextureFilter; -use freelist::{FreeList, FreeListHandle}; -use gpu_cache::GpuCacheHandle; -use internal_types::{FastHashMap, TextureUpdate, TextureUpdateOp, UvRect}; -use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList}; -use profiler::TextureCacheProfileCounters; +use frame::FrameId; +use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle}; +use gpu_cache::{GpuCache, GpuCacheHandle}; +use internal_types::{SourceTexture, TextureUpdate, TextureUpdateOp}; +use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList, TextureUpdateSource}; +use resource_cache::CacheItem; use std::cmp; -use std::collections::hash_map::Entry; use std::mem; -use std::slice::Iter; -use time; -use util; use api::{ExternalImageType, ImageData, ImageFormat}; use api::{DeviceUintRect, DeviceUintSize, DeviceUintPoint}; -use api::{DevicePoint, ImageDescriptor}; +use api::{ImageDescriptor}; -/// The number of bytes we're allowed to use for a texture. -const MAX_BYTES_PER_TEXTURE: u32 = 1024 * 1024 * 256; // 256MB +// The fixed number of layers for the shared texture cache. +// There is one array texture per image format, allocated lazily. +const TEXTURE_ARRAY_LAYERS: i32 = 2; -/// The number of RGBA pixels we're allowed to use for a texture. -const MAX_RGBA_PIXELS_PER_TEXTURE: u32 = MAX_BYTES_PER_TEXTURE / 4; +// The dimensions of each layer in the texture cache. +const TEXTURE_LAYER_DIMENSIONS: u32 = 2048; -/// The desired initial size of each texture, in pixels. -const INITIAL_TEXTURE_SIZE: u32 = 1024; +// The size of each region (page) in a texture layer. +const TEXTURE_REGION_DIMENSIONS: u32 = 512; -/// The square root of the number of RGBA pixels we're allowed to use for a texture, rounded down. -/// to the next power of two. -const SQRT_MAX_RGBA_PIXELS_PER_TEXTURE: u32 = 8192; - -/// The minimum number of pixels on each side that we require for rects to be classified as -/// "medium" within the free list. -const MINIMUM_MEDIUM_RECT_SIZE: u32 = 16; - -/// The minimum number of pixels on each side that we require for rects to be classified as -/// "large" within the free list. -const MINIMUM_LARGE_RECT_SIZE: u32 = 32; +// Maintains a simple freelist of texture IDs that are mapped +// to real API-specific texture IDs in the renderer. +struct CacheTextureIdList { + free_list: Vec, + next_id: usize, +} -/// The amount of time in milliseconds we give ourselves to coalesce rects before giving up. -const COALESCING_TIMEOUT: u64 = 100; +impl CacheTextureIdList { + fn new() -> CacheTextureIdList { + CacheTextureIdList { + next_id: 0, + free_list: Vec::new(), + } + } -/// The number of items that we process in the coalescing work list before checking whether we hit -/// the timeout. -const COALESCING_TIMEOUT_CHECKING_INTERVAL: usize = 256; + fn allocate(&mut self) -> CacheTextureId { + // If nothing on the free list of texture IDs, + // allocate a new one. + match self.free_list.pop() { + Some(id) => { + id + } + None => { + let id = CacheTextureId(self.next_id); + self.next_id += 1; + id + } + } + } -pub type TextureCacheItemId = FreeListHandle; + fn free(&mut self, id: CacheTextureId) { + self.free_list.push(id); + } +} -enum CoalescingStatus { - Changed, - Unchanged, - Timeout, +// Items in the texture cache can either be standalone textures, +// or a sub-rect inside the shared cache. +#[derive(Debug)] +enum EntryKind { + Standalone, + Cache { + // Origin within the texture layer where this item exists. + origin: DeviceUintPoint, + // The layer index of the texture array. + layer_index: u16, + // The region that this entry belongs to in the layer. + region_index: u16, + } } -/// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See -/// sections 2.2 and 2.2.5 in "A Thousand Ways to Pack the Bin - A Practical Approach to Two- -/// Dimensional Rectangle Bin Packing": -/// -/// http://clb.demon.fi/files/RectangleBinPack.pdf -/// -/// This approach was chosen because of its simplicity, good performance, and easy support for -/// dynamic texture deallocation. -pub struct TexturePage { +// Stores information related to a single entry in the texture +// cache. This is stored for each item whether it's in the shared +// cache or a standalone texture. +#[derive(Debug)] +struct CacheEntry { + // Size the requested item, in device pixels. + size: DeviceUintSize, + // Details specific to standalone or shared items. + kind: EntryKind, + // Arbitrary user data associated with this item. + user_data: [f32; 2], + // The last frame this item was requested for rendering. + last_access: FrameId, + // Handle to the resource rect in the GPU cache. + uv_rect_handle: GpuCacheHandle, + // Image format of the item. + format: ImageFormat, + // The actual device texture ID this is part of. texture_id: CacheTextureId, - texture_size: DeviceUintSize, - free_list: FreeRectList, - coalesce_vec: Vec, - allocations: u32, - dirty: bool, } -impl TexturePage { - fn new(texture_id: CacheTextureId, texture_size: DeviceUintSize) -> TexturePage { - let mut page = TexturePage { +impl CacheEntry { + // Create a new entry for a standalone texture. + fn new_standalone(texture_id: CacheTextureId, + size: DeviceUintSize, + format: ImageFormat, + user_data: [f32; 2], + last_access: FrameId) -> CacheEntry { + CacheEntry { + size, + user_data, + last_access, + kind: EntryKind::Standalone, texture_id, - texture_size, - free_list: FreeRectList::new(), - coalesce_vec: Vec::new(), - allocations: 0, - dirty: false, - }; - page.clear(); - page - } - - fn find_index_of_best_rect_in_bin(&self, bin: FreeListBin, requested_dimensions: &DeviceUintSize) - -> Option { - let mut smallest_index_and_area = None; - for (candidate_index, candidate_rect) in self.free_list.iter(bin).enumerate() { - if !requested_dimensions.fits_inside(&candidate_rect.size) { - continue - } - - let candidate_area = candidate_rect.size.width * candidate_rect.size.height; - smallest_index_and_area = Some((candidate_index, candidate_area)); - break + format, + uv_rect_handle: GpuCacheHandle::new(), } - - smallest_index_and_area.map(|(index, _)| FreeListIndex(bin, index)) } - /// Find a suitable rect in the free list. We choose the smallest such rect - /// in terms of area (Best-Area-Fit, BAF). - fn find_index_of_best_rect(&self, requested_dimensions: &DeviceUintSize) - -> Option { - let bin = FreeListBin::for_size(requested_dimensions); - for &target_bin in &[FreeListBin::Small, FreeListBin::Medium, FreeListBin::Large] { - if bin <= target_bin { - if let Some(index) = self.find_index_of_best_rect_in_bin(target_bin, - requested_dimensions) { - return Some(index); + // Update the GPU cache for this texture cache entry. + // This ensures that the UV rect, and texture layer index + // are up to date in the GPU cache for vertex shaders + // to fetch from. + fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) { + if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) { + let (origin, layer_index) = match self.kind { + EntryKind::Standalone { .. } => { + (DeviceUintPoint::zero(), 0.0) } - } + EntryKind::Cache { origin, layer_index, .. } => { + (origin, layer_index as f32) + } + }; + request.push([origin.x as f32, + origin.y as f32, + (origin.x + self.size.width) as f32, + (origin.y + self.size.height) as f32]); + request.push([layer_index, + self.user_data[0], + self.user_data[1], + 0.0]); } - None } +} - fn can_allocate(&self, requested_dimensions: &DeviceUintSize) -> bool { - self.find_index_of_best_rect(requested_dimensions).is_some() - } +type WeakCacheEntryHandle = WeakFreeListHandle; - pub fn allocate(&mut self, - requested_dimensions: &DeviceUintSize) -> Option { - if requested_dimensions.width == 0 || requested_dimensions.height == 0 { - return Some(DeviceUintPoint::new(0, 0)) - } - let index = match self.find_index_of_best_rect(requested_dimensions) { - None => return None, - Some(index) => index, - }; +// A texture cache handle is a weak reference to a cache entry. +// If the handle has not been inserted into the cache yet, the +// value will be None. Even when the value is Some(), the location +// may not actually be valid if it has been evicted by the cache. +// In this case, the cache handle needs to re-upload this item +// to the texture cache (see request() below). +#[derive(Debug)] +pub struct TextureCacheHandle { + entry: Option, +} - // Remove the rect from the free list and decide how to guillotine it. We choose the split - // that results in the single largest area (Min Area Split Rule, MINAS). - let chosen_rect = self.free_list.remove(index); - let candidate_free_rect_to_right = - DeviceUintRect::new( - DeviceUintPoint::new(chosen_rect.origin.x + requested_dimensions.width, chosen_rect.origin.y), - DeviceUintSize::new(chosen_rect.size.width - requested_dimensions.width, requested_dimensions.height)); - let candidate_free_rect_to_bottom = - DeviceUintRect::new( - DeviceUintPoint::new(chosen_rect.origin.x, chosen_rect.origin.y + requested_dimensions.height), - DeviceUintSize::new(requested_dimensions.width, chosen_rect.size.height - requested_dimensions.height)); - let candidate_free_rect_to_right_area = candidate_free_rect_to_right.size.width * - candidate_free_rect_to_right.size.height; - let candidate_free_rect_to_bottom_area = candidate_free_rect_to_bottom.size.width * - candidate_free_rect_to_bottom.size.height; - - // Guillotine the rectangle. - let new_free_rect_to_right; - let new_free_rect_to_bottom; - if candidate_free_rect_to_right_area > candidate_free_rect_to_bottom_area { - new_free_rect_to_right = DeviceUintRect::new( - candidate_free_rect_to_right.origin, - DeviceUintSize::new(candidate_free_rect_to_right.size.width, - chosen_rect.size.height)); - new_free_rect_to_bottom = candidate_free_rect_to_bottom - } else { - new_free_rect_to_right = candidate_free_rect_to_right; - new_free_rect_to_bottom = - DeviceUintRect::new(candidate_free_rect_to_bottom.origin, - DeviceUintSize::new(chosen_rect.size.width, - candidate_free_rect_to_bottom.size.height)) +impl TextureCacheHandle { + pub fn new() -> TextureCacheHandle { + TextureCacheHandle { + entry: None, } + } +} - // Add the guillotined rects back to the free list. If any changes were made, we're now - // dirty since coalescing might be able to defragment. - if !util::rect_is_empty(&new_free_rect_to_right) { - self.free_list.push(&new_free_rect_to_right); - self.dirty = true - } - if !util::rect_is_empty(&new_free_rect_to_bottom) { - self.free_list.push(&new_free_rect_to_bottom); - self.dirty = true - } +pub struct TextureCache { + // A lazily allocated, fixed size, texture array for + // each format the texture cache supports. + // TODO(gw): Do we actually need RG8 and RGB8 or + // are they only used by external textures? + array_a8: TextureArray, + array_rgba8: TextureArray, + array_rg8: TextureArray, + array_rgb8: TextureArray, + + // Maximum texture size supported by hardware. + max_texture_size: u32, - // Bump the allocation counter. - self.allocations += 1; + // A list of texture IDs that represent native + // texture handles. This indirection allows the texture + // cache to create / destroy / reuse texture handles + // without knowing anything about the device code. + cache_textures: CacheTextureIdList, - // Return the result. - Some(chosen_rect.origin) - } + // A list of updates that need to be applied to the + // texture cache in the rendering thread this frame. + pending_updates: TextureUpdateList, - fn coalesce_impl(rects: &mut [DeviceUintRect], deadline: u64, fun_key: F, fun_union: U) - -> CoalescingStatus where - F: Fn(&DeviceUintRect) -> (u32, u32), - U: Fn(&mut DeviceUintRect, &mut DeviceUintRect) -> usize, - { - let mut num_changed = 0; - rects.sort_by_key(&fun_key); - - for work_index in 0..rects.len() { - if work_index % COALESCING_TIMEOUT_CHECKING_INTERVAL == 0 && - time::precise_time_ns() >= deadline { - return CoalescingStatus::Timeout - } + // The current frame ID. Used for cache eviction policies. + frame_id: FrameId, - let (left, candidates) = rects.split_at_mut(work_index + 1); - let mut item = left.last_mut().unwrap(); - if util::rect_is_empty(item) { - continue - } + // Maintains the list of all current items in + // the texture cache. + entries: FreeList, - let key = fun_key(item); - for candidate in candidates.iter_mut() - .take_while(|r| key == fun_key(r)) { - num_changed += fun_union(item, candidate); - } - } + // A list of the strong handles of items that were + // allocated in the standalone texture pool. Used + // for evicting old standalone textures. + standalone_entry_handles: Vec>, + + // A list of the strong handles of items that were + // allocated in the shared texture cache. Used + // for evicting old cache items. + shared_entry_handles: Vec>, +} - if num_changed > 0 { - CoalescingStatus::Changed - } else { - CoalescingStatus::Unchanged +impl TextureCache { + pub fn new(max_texture_size: u32) -> TextureCache { + TextureCache { + max_texture_size, + array_a8: TextureArray::new(ImageFormat::A8), + array_rgba8: TextureArray::new(ImageFormat::BGRA8), + array_rg8: TextureArray::new(ImageFormat::RG8), + array_rgb8: TextureArray::new(ImageFormat::RGB8), + cache_textures: CacheTextureIdList::new(), + pending_updates: TextureUpdateList::new(), + frame_id: FrameId(0), + entries: FreeList::new(), + standalone_entry_handles: Vec::new(), + shared_entry_handles: Vec::new(), + } + } + + pub fn begin_frame(&mut self, frame_id: FrameId) { + self.frame_id = frame_id; + } + + pub fn end_frame(&mut self) { + self.expire_old_standalone_entries(); + } + + // Request an item in the texture cache. All images that will + // be used on a frame *must* have request() called on their + // handle, to update the last used timestamp and ensure + // that resources are not flushed from the cache too early. + // + // Returns true if the image needs to be uploaded to the + // texture cache (either never uploaded, or has been + // evicted on a previous frame). + pub fn request(&mut self, + handle: &mut TextureCacheHandle, + gpu_cache: &mut GpuCache) -> bool { + match handle.entry { + Some(ref handle) => { + match self.entries.get_opt_mut(handle) { + // If an image is requested that is already in the cache, + // refresh the GPU cache data associated with this item. + Some(entry) => { + entry.last_access = self.frame_id; + entry.update_gpu_cache(gpu_cache); + false + } + None => { + true + } + } + } + None => { + true + } } } - /// Combine rects that have the same width and are adjacent. - fn coalesce_horisontal(rects: &mut [DeviceUintRect], deadline: u64) -> CoalescingStatus { - Self::coalesce_impl(rects, deadline, - |item| (item.size.width, item.origin.x), - |item, candidate| { - if item.origin.y == candidate.max_y() || item.max_y() == candidate.origin.y { - *item = item.union(candidate); - candidate.size.width = 0; - 1 - } else { 0 } - }) + pub fn max_texture_size(&self) -> u32 { + self.max_texture_size } - /// Combine rects that have the same height and are adjacent. - fn coalesce_vertical(rects: &mut [DeviceUintRect], deadline: u64) -> CoalescingStatus { - Self::coalesce_impl(rects, deadline, - |item| (item.size.height, item.origin.y), - |item, candidate| { - if item.origin.x == candidate.max_x() || item.max_x() == candidate.origin.x { - *item = item.union(candidate); - candidate.size.height = 0; - 1 - } else { 0 } - }) + pub fn pending_updates(&mut self) -> TextureUpdateList { + mem::replace(&mut self.pending_updates, TextureUpdateList::new()) } - pub fn coalesce(&mut self) -> bool { - if !self.dirty { - return false - } - - // Iterate to a fixed point or until a timeout is reached. - let deadline = time::precise_time_ns() + COALESCING_TIMEOUT; - self.free_list.copy_to_vec(&mut self.coalesce_vec); - let mut changed = false; - - //Note: we might want to consider try to use the last sorted order first - // but the elements get shuffled around a bit anyway during the bin placement - - match Self::coalesce_horisontal(&mut self.coalesce_vec, deadline) { - CoalescingStatus::Changed => changed = true, - CoalescingStatus::Unchanged => (), - CoalescingStatus::Timeout => { - self.free_list.init_from_slice(&self.coalesce_vec); - return true + // Update the data stored by a given texture cache handle. + pub fn update( + &mut self, + handle: &mut TextureCacheHandle, + descriptor: ImageDescriptor, + filter: TextureFilter, + data: ImageData, + user_data: [f32; 2], + mut dirty_rect: Option, + gpu_cache: &mut GpuCache) { + + // Determine if we need to allocate texture cache memory + // for this item. We need to reallocate if any of the following + // is true: + // - Never been in the cache + // - Has been in the cache but was evicted. + // - Exists in the cache but dimensions / format have changed. + let realloc = match handle.entry { + Some(ref handle) => { + match self.entries.get_opt(handle) { + Some(entry) => { + entry.size.width != descriptor.width || + entry.size.height != descriptor.height || + entry.format != descriptor.format + } + None => { + // Was previously allocated but has been evicted. + true + } + } } - } - - match Self::coalesce_vertical(&mut self.coalesce_vec, deadline) { - CoalescingStatus::Changed => changed = true, - CoalescingStatus::Unchanged => (), - CoalescingStatus::Timeout => { - self.free_list.init_from_slice(&self.coalesce_vec); - return true + None => { + // This handle has not been allocated yet. + true } - } - - if changed { - self.free_list.init_from_slice(&self.coalesce_vec); - } - self.dirty = changed; - changed - } + }; - fn clear(&mut self) { - self.free_list = FreeRectList::new(); - self.free_list.push(&DeviceUintRect::new( - DeviceUintPoint::zero(), - self.texture_size)); - self.allocations = 0; - self.dirty = false; - } + if realloc { + self.allocate(handle, + descriptor, + filter, + user_data); - fn free(&mut self, rect: &DeviceUintRect) { - if util::rect_is_empty(rect) { - return - } - debug_assert!(self.allocations > 0); - self.allocations -= 1; - if self.allocations == 0 { - self.clear(); - return + // If we reallocated, we need to upload the whole item again. + dirty_rect = None; } - self.free_list.push(rect); - self.dirty = true - } + let entry = self.entries + .get_opt_mut(handle.entry.as_ref().unwrap()) + .expect("BUG: handle must be valid now"); - fn grow(&mut self, new_texture_size: DeviceUintSize) { - assert!(new_texture_size.width >= self.texture_size.width); - assert!(new_texture_size.height >= self.texture_size.height); + // Invalidate the contents of the resource rect in the GPU cache. + // This ensures that the update_gpu_cache below will add + // the new information to the GPU cache. + gpu_cache.invalidate(&entry.uv_rect_handle); - let new_rects = [ - DeviceUintRect::new(DeviceUintPoint::new(self.texture_size.width, 0), - DeviceUintSize::new(new_texture_size.width - self.texture_size.width, - new_texture_size.height)), + // Upload the resource rect and texture array layer. + entry.update_gpu_cache(gpu_cache); - DeviceUintRect::new(DeviceUintPoint::new(0, self.texture_size.height), - DeviceUintSize::new(self.texture_size.width, - new_texture_size.height - self.texture_size.height)), - ]; + // Create an update command, which the render thread processes + // to upload the new image data into the correct location + // in GPU memory. + let (layer_index, origin) = match entry.kind { + EntryKind::Standalone { .. } => { + (0, DeviceUintPoint::zero()) + } + EntryKind::Cache { layer_index, origin, .. } => { + (layer_index, origin) + } + }; - for rect in &new_rects { - if rect.size.width > 0 && rect.size.height > 0 { - self.free_list.push(rect); + let op = TextureUpdate::new_update(data, + &descriptor, + origin, + entry.size, + entry.texture_id, + layer_index as i32, + dirty_rect); + self.pending_updates.push(op); + } + + // Get a specific region by index from a shared texture array. + fn get_region_mut(&mut self, format: ImageFormat, region_index: u16) -> &mut TextureRegion { + let texture_array = match format { + ImageFormat::A8 => &mut self.array_a8, + ImageFormat::BGRA8 => &mut self.array_rgba8, + ImageFormat::RGB8 => &mut self.array_rgb8, + ImageFormat::RG8 => &mut self.array_rg8, + ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(), + }; + + &mut texture_array.regions[region_index as usize] + } + + // Retrieve the details of an item in the cache. This is used + // during batch creation to provide the resource rect address + // to the shaders and texture ID to the batching logic. + // This function will asssert in debug modes if the caller + // tries to get a handle that was not requested this frame. + pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem { + match handle.entry { + Some(ref handle) => { + let entry = self.entries + .get_opt(handle) + .expect("BUG: was dropped from cache or not updated!"); + debug_assert_eq!(entry.last_access, self.frame_id); + CacheItem { + uv_rect_handle: entry.uv_rect_handle, + texture_id: SourceTexture::TextureCache(entry.texture_id), + } + } + None => { + panic!("BUG: handle not requested earlier in frame") } } - - self.texture_size = new_texture_size } - fn can_grow(&self, max_size: u32) -> bool { - self.texture_size.width < max_size || self.texture_size.height < max_size - } -} + // Expire old standalone textures. + fn expire_old_standalone_entries(&mut self) { + let mut eviction_candidates = Vec::new(); + let mut retained_entries = Vec::new(); -// testing functionality -impl TexturePage { - #[doc(hidden)] - pub fn new_dummy(size: DeviceUintSize) -> TexturePage { - Self::new(CacheTextureId(0), size) - } + // Build a list of eviction candidates (which are + // anything not used this frame). + for handle in self.standalone_entry_handles.drain(..) { + let entry = self.entries.get(&handle); + if entry.last_access == self.frame_id { + retained_entries.push(handle); + } else { + eviction_candidates.push(handle); + } + } - #[doc(hidden)] - pub fn fill_from(&mut self, other: &TexturePage) { - self.dirty = true; - self.free_list.small.clear(); - self.free_list.small.extend_from_slice(&other.free_list.small); - self.free_list.medium.clear(); - self.free_list.medium.extend_from_slice(&other.free_list.medium); - self.free_list.large.clear(); - self.free_list.large.extend_from_slice(&other.free_list.large); - } -} + // Sort by access time so we remove the oldest ones first. + eviction_candidates.sort_by_key(|handle| { + let entry = self.entries.get(handle); + entry.last_access + }); -/// A binning free list. Binning is important to avoid sifting through lots of small strips when -/// allocating many texture items. -struct FreeRectList { - small: Vec, - medium: Vec, - large: Vec, -} + // We only allow an arbitrary number of unused + // standalone textures to remain in GPU memory. + // TODO(gw): We should make this a better heuristic, + // for example based on total memory size. + if eviction_candidates.len() > 32 { + let entries_to_keep = eviction_candidates.split_off(32); + retained_entries.extend(entries_to_keep); + } -impl FreeRectList { - fn new() -> FreeRectList { - FreeRectList { - small: vec![], - medium: vec![], - large: vec![], + // Free the selected items + for handle in eviction_candidates { + let entry = self.entries.free(handle); + self.free(entry); } + + // Keep a record of the remaining handles for next frame. + self.standalone_entry_handles = retained_entries; } - fn init_from_slice(&mut self, rects: &[DeviceUintRect]) { - self.small.clear(); - self.medium.clear(); - self.large.clear(); - for rect in rects { - if !util::rect_is_empty(rect) { - self.push(rect) + // Expire old shared items. Pass in the allocation size + // that is being requested, so we know when we've evicted + // enough items to guarantee we can fit this allocation in + // the cache. + fn expire_old_shared_entries(&mut self, required_alloc: &ImageDescriptor) { + let mut eviction_candidates = Vec::new(); + let mut retained_entries = Vec::new(); + + // Build a list of eviction candidates (which are + // anything not used this frame). + for handle in self.shared_entry_handles.drain(..) { + let entry = self.entries.get(&handle); + if entry.last_access == self.frame_id { + retained_entries.push(handle); + } else { + eviction_candidates.push(handle); } } - } - fn push(&mut self, rect: &DeviceUintRect) { - match FreeListBin::for_size(&rect.size) { - FreeListBin::Small => self.small.push(*rect), - FreeListBin::Medium => self.medium.push(*rect), - FreeListBin::Large => self.large.push(*rect), + // Sort by access time so we remove the oldest ones first. + eviction_candidates.sort_by_key(|handle| { + let entry = self.entries.get(handle); + entry.last_access + }); + + // Doing an eviction is quite expensive, so we don't want to + // do it all the time. To avoid this, try and evict a + // significant number of items each cycle. However, we don't + // want to evict everything we can, since that will result in + // more items being uploaded than necessary. + // Instead, we say we will keep evicting until both of these + // consitions are met: + // - We have evicted some arbitrary number of items (512 currently). + // AND + // - We have freed an item that will definitely allow us to + // fit the currently requested allocation. + let needed_slab_size = SlabSize::new(required_alloc.width, + required_alloc.height).get_size(); + let mut found_matching_slab = false; + let mut freed_complete_page = false; + let mut evicted_items = 0; + + for handle in eviction_candidates { + if evicted_items > 512 && + (found_matching_slab || freed_complete_page) { + retained_entries.push(handle); + } else { + let entry = self.entries.free(handle); + if let Some(region) = self.free(entry) { + found_matching_slab = found_matching_slab || + region.slab_size == needed_slab_size; + freed_complete_page = freed_complete_page || + region.is_empty(); + } + evicted_items += 1; + } } - } - fn remove(&mut self, index: FreeListIndex) -> DeviceUintRect { - match index.0 { - FreeListBin::Small => self.small.swap_remove(index.1), - FreeListBin::Medium => self.medium.swap_remove(index.1), - FreeListBin::Large => self.large.swap_remove(index.1), - } + // Keep a record of the remaining handles for next eviction cycle. + self.shared_entry_handles = retained_entries; } - fn iter(&self, bin: FreeListBin) -> Iter { - match bin { - FreeListBin::Small => self.small.iter(), - FreeListBin::Medium => self.medium.iter(), - FreeListBin::Large => self.large.iter(), + // Free a cache entry from the standalone list or shared cache. + fn free(&mut self, entry: CacheEntry) -> Option<&TextureRegion> { + match entry.kind { + EntryKind::Standalone { .. } => { + // This is a standalone texture allocation. Just push it back onto the free + // list. + self.pending_updates.push(TextureUpdate { + id: entry.texture_id, + op: TextureUpdateOp::Free, + }); + self.cache_textures.free(entry.texture_id); + None + } + EntryKind::Cache { origin, region_index, .. } => { + // Free the block in the given region. + let region = self.get_region_mut(entry.format, region_index); + region.free(origin); + Some(region) + } } } - fn copy_to_vec(&self, rects: &mut Vec) { - rects.clear(); - rects.extend_from_slice(&self.small); - rects.extend_from_slice(&self.medium); - rects.extend_from_slice(&self.large); - } -} + // Attempt to allocate a block from the shared cache. + fn allocate_from_shared_cache(&mut self, + descriptor: &ImageDescriptor, + user_data: [f32; 2]) -> Option { + // Work out which cache it goes in, based on format. + let texture_array = match descriptor.format { + ImageFormat::A8 => &mut self.array_a8, + ImageFormat::BGRA8 => &mut self.array_rgba8, + ImageFormat::RGB8 => &mut self.array_rgb8, + ImageFormat::RG8 => &mut self.array_rg8, + ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(), + }; -#[derive(Debug, Clone, Copy)] -struct FreeListIndex(FreeListBin, usize); + // Lazy initialize this texture array if required. + if texture_array.texture_id.is_none() { + let texture_id = self.cache_textures.allocate(); -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] -enum FreeListBin { - Small, - Medium, - Large, -} + let update_op = TextureUpdate { + id: texture_id, + op: TextureUpdateOp::Create { + width: TEXTURE_LAYER_DIMENSIONS, + height: TEXTURE_LAYER_DIMENSIONS, + format: descriptor.format, + filter: TextureFilter::Linear, + layer_count: TEXTURE_ARRAY_LAYERS, + mode: RenderTargetMode::RenderTarget, // todo: !!!! remove me!? + } + }; + self.pending_updates.push(update_op); -impl FreeListBin { - fn for_size(size: &DeviceUintSize) -> FreeListBin { - if size.width >= MINIMUM_LARGE_RECT_SIZE && size.height >= MINIMUM_LARGE_RECT_SIZE { - FreeListBin::Large - } else if size.width >= MINIMUM_MEDIUM_RECT_SIZE && - size.height >= MINIMUM_MEDIUM_RECT_SIZE { - FreeListBin::Medium - } else { - debug_assert!(size.width > 0 && size.height > 0); - FreeListBin::Small + texture_array.texture_id = Some(texture_id); } + + // Do the allocation. This can fail and return None + // if there are no free slots or regions available. + texture_array.alloc(descriptor.width, + descriptor.height, + user_data, + self.frame_id) } -} -#[derive(Debug, Clone)] -pub struct TextureCacheItem { - // Identifies the texture and array slice - pub texture_id: CacheTextureId, + // Allocate storage for a given image. This attempts to allocate + // from the shared cache, but falls back to standalone texture + // if the image is too large, or the cache is full. + fn allocate(&mut self, + handle: &mut TextureCacheHandle, + descriptor: ImageDescriptor, + filter: TextureFilter, + user_data: [f32; 2]) { + assert!(descriptor.width > 0 && descriptor.height > 0); - // The texture coordinates for this item - pub uv_rect: UvRect, + // Work out if this image qualifies to go in the shared (batching) cache. + let mut allowed_in_shared_cache = true; + let mut allocated_in_shared_cache = true; + let mut new_cache_entry = None; + let size = DeviceUintSize::new(descriptor.width, descriptor.height); + let frame_id = self.frame_id; - // The size of the allocated rectangle. - pub allocated_rect: DeviceUintRect, + // TODO(gw): For now, anything that requests nearest filtering + // just fails to allocate in a texture page, and gets a standalone + // texture. This isn't ideal, as it causes lots of batch breaks, + // but is probably rare enough that it can be fixed up later (it's also + // fairly trivial to implement, just tedious). + if filter == TextureFilter::Nearest { + allowed_in_shared_cache = false; + } - // Handle to the location of the UV rect for this item in GPU cache. - pub uv_rect_handle: GpuCacheHandle, + // Anything larger than 512 goes in a standalone texture. + // TODO(gw): If we find pages that suffer from batch breaks in this + // case, add support for storing these in a standalone + // texture array. + if descriptor.width > 512 || descriptor.height > 512 { + allowed_in_shared_cache = false; + } - pub format: ImageFormat, + // If it's allowed in the cache, see if there is a spot for it. + if allowed_in_shared_cache { + new_cache_entry = self.allocate_from_shared_cache(&descriptor, user_data); - // Some arbitrary data associated with this item. - // In the case of glyphs, it is the top / left offset - // from the rasterized glyph. - pub user_data: [f32; 2], -} + // If we failed to allocate in the shared cache, run an + // eviction cycle, and then try to allocate again. + if new_cache_entry.is_none() { + self.expire_old_shared_entries(&descriptor); -impl TextureCacheItem { - fn new(texture_id: CacheTextureId, - rect: DeviceUintRect, - format: ImageFormat, - user_data: [f32; 2]) - -> TextureCacheItem { - TextureCacheItem { - texture_id, - uv_rect: UvRect { - uv0: DevicePoint::new(rect.origin.x as f32, - rect.origin.y as f32), - uv1: DevicePoint::new((rect.origin.x + rect.size.width) as f32, - (rect.origin.y + rect.size.height) as f32), - }, - allocated_rect: rect, - uv_rect_handle: GpuCacheHandle::new(), - format, - user_data, + new_cache_entry = self.allocate_from_shared_cache(&descriptor, user_data); + } } - } -} -struct TextureCacheArena { - pages_a8: Vec, - pages_rgb8: Vec, - pages_rgba8: Vec, - pages_rg8: Vec, -} + // If not allowed in the cache, or if the shared cache is full, then it + // will just have to be in a unique texture. This hurts batching but should + // only occur on a small number of images (or pathological test cases!). + if new_cache_entry.is_none() { + let texture_id = self.cache_textures.allocate(); -impl TextureCacheArena { - fn new() -> TextureCacheArena { - TextureCacheArena { - pages_a8: Vec::new(), - pages_rgb8: Vec::new(), - pages_rgba8: Vec::new(), - pages_rg8: Vec::new(), - } - } + // Create an update operation to allocate device storage + // of the right size / format. + let update_op = TextureUpdate { + id: texture_id, + op: TextureUpdateOp::Create { + width: descriptor.width, + height: descriptor.height, + format: descriptor.format, + filter, + mode: RenderTargetMode::RenderTarget, + layer_count: 1, + } + }; + self.pending_updates.push(update_op); + + new_cache_entry = Some(CacheEntry::new_standalone(texture_id, + size, + descriptor.format, + user_data, + frame_id)); + + allocated_in_shared_cache = false; + } + + let new_cache_entry = new_cache_entry.expect("BUG: must have allocated by now"); + + // We need to update the texture cache handle now, so that it + // points to the correct location. + let new_entry_handle = match handle.entry { + Some(ref existing_entry) => { + // If the handle already exists, there's two possibilities: + // 1) It points to a valid entry in the freelist. + // 2) It points to a stale entry in the freelist (i.e. has been evicted). + // + // For (1) we want to replace the cache entry with our + // newly updated location. We also need to ensure that + // the storage (region or standalone) associated with the + // previous entry here gets freed. + // + // For (2) we need to add the data to a new location + // in the freelist. + // + // This is managed with a database style upsert operation. + match self.entries.upsert(existing_entry, new_cache_entry) { + UpsertResult::Updated(old_entry) => { + self.free(old_entry); + None + } + UpsertResult::Inserted(new_handle) => { + Some(new_handle) + } + } + } + None => { + // This handle has never been allocated, so just + // insert a new cache entry. + Some(self.entries.insert(new_cache_entry)) + } + }; - fn texture_page_for_id(&mut self, id: CacheTextureId) -> Option<&mut TexturePage> { - for page in self.pages_a8.iter_mut().chain(self.pages_rgb8.iter_mut()) - .chain(self.pages_rgba8.iter_mut()) - .chain(self.pages_rg8.iter_mut()) { - if page.texture_id == id { - return Some(page) + // If the cache entry is new, update it in the cache handle. + if let Some(new_entry_handle) = new_entry_handle { + handle.entry = Some(self.entries.create_weak_handle(&new_entry_handle)); + // Store the strong handle in the list that we scan for + // cache evictions. + if allocated_in_shared_cache { + self.shared_entry_handles.push(new_entry_handle); + } else { + self.standalone_entry_handles.push(new_entry_handle); } } - None } } -pub struct CacheTextureIdList { - next_id: usize, - free_list: Vec, +// A list of the block sizes that a region can be initialized with. +#[derive(Copy, Clone, PartialEq)] +enum SlabSize { + Size16x16, + Size32x32, + Size64x64, + Size128x128, + Size256x256, + Size512x512, } -impl CacheTextureIdList { - fn new() -> CacheTextureIdList { - CacheTextureIdList { - next_id: 0, - free_list: Vec::new(), - } - } +impl SlabSize { + fn new(width: u32, height: u32) -> SlabSize { + // TODO(gw): Consider supporting non-square + // allocator sizes here. + let max_dim = cmp::max(width, height); - fn allocate(&mut self) -> CacheTextureId { - // If nothing on the free list of texture IDs, - // allocate a new one. - if self.free_list.is_empty() { - self.free_list.push(self.next_id); - self.next_id += 1; + match max_dim { + 0 => unreachable!(), + 1 ... 16 => SlabSize::Size16x16, + 17 ... 32 => SlabSize::Size32x32, + 33 ... 64 => SlabSize::Size64x64, + 65 ... 128 => SlabSize::Size128x128, + 129 ... 256 => SlabSize::Size256x256, + 257 ... 512 => SlabSize::Size512x512, + _ => panic!("Invalid dimensions for cache!"), } - - let id = self.free_list.pop().unwrap(); - CacheTextureId(id) } - fn free(&mut self, id: CacheTextureId) { - self.free_list.push(id.0); + fn get_size(&self) -> u32 { + match *self { + SlabSize::Size16x16 => 16, + SlabSize::Size32x32 => 32, + SlabSize::Size64x64 => 64, + SlabSize::Size128x128 => 128, + SlabSize::Size256x256 => 256, + SlabSize::Size512x512 => 512, + } } } -pub struct TextureCache { - cache_id_list: CacheTextureIdList, - free_texture_levels: FastHashMap>, - items: FreeList, - arena: TextureCacheArena, - pending_updates: TextureUpdateList, - max_texture_size: u32, +// The x/y location within a texture region of an allocation. +struct TextureLocation { + x: u8, + y: u8, } -#[derive(PartialEq, Eq, Debug)] -pub enum AllocationKind { - TexturePage, - Standalone, +impl TextureLocation { + fn new(x: u32, y: u32) -> TextureLocation { + debug_assert!(x < 256 && y < 256); + TextureLocation { + x: x as u8, + y: y as u8, + } + } } -#[derive(Debug)] -pub struct AllocationResult { - new_image_id: Option, - kind: AllocationKind, - item: TextureCacheItem, +// A region is a sub-rect of a texture array layer. +// All allocations within a region are of the same size. +struct TextureRegion { + layer_index: i32, + region_size: u32, + slab_size: u32, + free_slots: Vec, + slots_per_axis: u32, + total_slot_count: usize, + origin: DeviceUintPoint, } -impl TextureCache { - pub fn new(mut max_texture_size: u32) -> TextureCache { - if max_texture_size * max_texture_size > MAX_RGBA_PIXELS_PER_TEXTURE { - max_texture_size = SQRT_MAX_RGBA_PIXELS_PER_TEXTURE; +impl TextureRegion { + fn new(region_size: u32, + layer_index: i32, + origin: DeviceUintPoint) -> TextureRegion { + TextureRegion { + layer_index, + region_size, + slab_size: 0, + free_slots: Vec::new(), + slots_per_axis: 0, + total_slot_count: 0, + origin, + } + } + + // Initialize a region to be an allocator for a specific slab size. + fn init(&mut self, slab_size: SlabSize) { + debug_assert!(self.slab_size == 0); + debug_assert!(self.free_slots.is_empty()); + + self.slab_size = slab_size.get_size(); + self.slots_per_axis = self.region_size / self.slab_size; + + // Add each block to a freelist. + for y in 0..self.slots_per_axis { + for x in 0..self.slots_per_axis { + self.free_slots.push(TextureLocation::new(x, y)); + } } - TextureCache { - cache_id_list: CacheTextureIdList::new(), - free_texture_levels: FastHashMap::default(), - items: FreeList::new(), - pending_updates: TextureUpdateList::new(), - arena: TextureCacheArena::new(), - max_texture_size, - } + self.total_slot_count = self.free_slots.len(); } - pub fn max_texture_size(&self) -> u32 { - self.max_texture_size + // Deinit a region, allowing it to become a region with + // a different allocator size. + fn deinit(&mut self) { + self.slab_size = 0; + self.free_slots.clear(); + self.slots_per_axis = 0; + self.total_slot_count = 0; } - pub fn pending_updates(&mut self) -> TextureUpdateList { - mem::replace(&mut self.pending_updates, TextureUpdateList::new()) + fn is_empty(&self) -> bool { + self.slab_size == 0 } - pub fn allocate( - &mut self, - requested_width: u32, - requested_height: u32, - format: ImageFormat, - filter: TextureFilter, - user_data: [f32; 2], - profile: &mut TextureCacheProfileCounters - ) -> AllocationResult { - self.allocate_impl( - requested_width, - requested_height, - format, - filter, - user_data, - profile, - None, - ) + // Attempt to allocate a fixed size block from this region. + fn alloc(&mut self) -> Option { + self.free_slots + .pop() + .map(|location| { + DeviceUintPoint::new(self.origin.x + self.slab_size * location.x as u32, + self.origin.y + self.slab_size * location.y as u32) + }) } - // If existing_item_id is None, create a new id, otherwise reuse it. - fn allocate_impl( - &mut self, - requested_width: u32, - requested_height: u32, - format: ImageFormat, - filter: TextureFilter, - user_data: [f32; 2], - profile: &mut TextureCacheProfileCounters, - existing_item_id: Option<&TextureCacheItemId> - ) -> AllocationResult { - let requested_size = DeviceUintSize::new(requested_width, requested_height); - - // TODO(gw): For now, anything that requests nearest filtering - // just fails to allocate in a texture page, and gets a standalone - // texture. This isn't ideal, as it causes lots of batch breaks, - // but is probably rare enough that it can be fixed up later (it's also - // fairly trivial to implement, just tedious). - if filter == TextureFilter::Nearest { - // Fall back to standalone texture allocation. - let texture_id = self.cache_id_list.allocate(); - - let update_op = TextureUpdate { - id: texture_id, - op: texture_create_op(DeviceUintSize::new(requested_width, - requested_height), - format, - RenderTargetMode::None, - filter), - }; - self.pending_updates.push(update_op); - - let cache_item = TextureCacheItem::new( - texture_id, - DeviceUintRect::new(DeviceUintPoint::zero(), requested_size), - format, - user_data - ); - - let new_image_id = match existing_item_id { - Some(id) => { - *self.items.get_mut(id) = cache_item.clone(); - None - } - None => { - let id = self.items.insert(cache_item.clone()); - Some(id) - } - }; + // Free a block in this region. + fn free(&mut self, point: DeviceUintPoint) { + let x = (point.x - self.origin.x) / self.slab_size; + let y = (point.y - self.origin.y) / self.slab_size; + self.free_slots.push(TextureLocation::new(x, y)); - return AllocationResult { - item: cache_item, - kind: AllocationKind::Standalone, - new_image_id, - } + // If this region is completely unused, deinit it + // so that it can become a different slab size + // as required. + if self.free_slots.len() == self.total_slot_count { + self.deinit(); } + } +} - let mode = RenderTargetMode::SimpleRenderTarget; - let (page_list, page_profile) = match format { - ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8), - ImageFormat::BGRA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8), - ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8), - ImageFormat::RG8 => (&mut self.arena.pages_rg8, &mut profile.pages_rg8), - ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(), - }; - +// A texture array contains a number of texture layers, where +// each layer contains one or more regions that can act +// as slab allocators. +struct TextureArray { + format: ImageFormat, + is_allocated: bool, + regions: Vec, + texture_id: Option, +} - // TODO(gw): Handle this sensibly (support failing to render items that can't fit?) - assert!( - requested_size.width <= self.max_texture_size, - "Width {:?} > max texture size (format: {:?}).", - requested_size.width, format - ); - assert!( - requested_size.height <= self.max_texture_size, - "Height {:?} > max texture size (format: {:?}).", - requested_size.height, format - ); - - let mut page_id = None; //using ID here to please the borrow checker - for (i, page) in page_list.iter_mut().enumerate() { - if page.can_allocate(&requested_size) { - page_id = Some(i); - break; - } - // try to coalesce it - if page.coalesce() && page.can_allocate(&requested_size) { - page_id = Some(i); - break; +impl TextureArray { + fn new(format: ImageFormat) -> TextureArray { + TextureArray { + format, + is_allocated: false, + regions: Vec::new(), + texture_id: None, + } + } + + // Allocate space in this texture array. + fn alloc(&mut self, + width: u32, + height: u32, + user_data: [f32; 2], + frame_id: FrameId) -> Option { + // Lazily allocate the regions if not already created. + // This means that very rarely used image formats can be + // added but won't allocate a cache if never used. + if !self.is_allocated { + debug_assert!(TEXTURE_LAYER_DIMENSIONS % TEXTURE_REGION_DIMENSIONS == 0); + let regions_per_axis = TEXTURE_LAYER_DIMENSIONS / TEXTURE_REGION_DIMENSIONS; + for layer_index in 0..TEXTURE_ARRAY_LAYERS { + for y in 0..regions_per_axis { + for x in 0..regions_per_axis { + let origin = DeviceUintPoint::new(x * TEXTURE_REGION_DIMENSIONS, + y * TEXTURE_REGION_DIMENSIONS); + let region = TextureRegion::new(TEXTURE_REGION_DIMENSIONS, + layer_index, + origin); + self.regions.push(region); + } + } } - if page.can_grow(self.max_texture_size) { - // try to grow it - let new_width = cmp::min(page.texture_size.width * 2, self.max_texture_size); - let new_height = cmp::min(page.texture_size.height * 2, self.max_texture_size); - let texture_size = DeviceUintSize::new(new_width, new_height); - self.pending_updates.push(TextureUpdate { - id: page.texture_id, - op: texture_grow_op(texture_size, format, mode), - }); - - let extra_texels = new_width * new_height - page.texture_size.width * page.texture_size.height; - let extra_bytes = extra_texels * format.bytes_per_pixel().unwrap_or(0); - page_profile.inc(extra_bytes as usize); - - page.grow(texture_size); - - if page.can_allocate(&requested_size) { - page_id = Some(i); + self.is_allocated = true; + } + + // Quantize the size of the allocation to select a region to + // allocate from. + let slab_size = SlabSize::new(width, height); + let slab_size_bytes = slab_size.get_size(); + + // TODO(gw): For simplicity, the initial implementation just + // has a single vec<> of regions. We could easily + // make this more efficient by storing a list of + // regions for each slab size specifically... + + // Keep track of the location of an empty region, + // in case we need to select a new empty region + // after the loop. + let mut empty_region_index = None; + let mut entry_kind = None; + + // Run through the existing regions of this size, and see if + // we can find a free block in any of them. + for (i, region) in self.regions.iter_mut().enumerate() { + if region.slab_size == 0 { + empty_region_index = Some(i); + } else if region.slab_size == slab_size_bytes { + if let Some(location) = region.alloc() { + entry_kind = Some(EntryKind::Cache { + layer_index: region.layer_index as u16, + region_index: i as u16, + origin: location, + }); break; } } } - let mut page = match page_id { - Some(index) => &mut page_list[index], - None => { - let init_texture_size = initial_texture_size(self.max_texture_size); - let texture_size = DeviceUintSize::new(cmp::max(requested_width, init_texture_size.width), - cmp::max(requested_height, init_texture_size.height)); - let extra_bytes = texture_size.width * texture_size.height * format.bytes_per_pixel().unwrap_or(0); - page_profile.inc(extra_bytes as usize); - - let free_texture_levels_entry = self.free_texture_levels.entry(format); - let mut free_texture_levels = match free_texture_levels_entry { - Entry::Vacant(entry) => entry.insert(Vec::new()), - Entry::Occupied(entry) => entry.into_mut(), - }; - if free_texture_levels.is_empty() { - let texture_id = self.cache_id_list.allocate(); - - let update_op = TextureUpdate { - id: texture_id, - op: texture_create_op(texture_size, - format, - mode, - filter), - }; - self.pending_updates.push(update_op); - - free_texture_levels.push(FreeTextureLevel { - texture_id, + // Find a region of the right size and try to allocate from it. + if entry_kind.is_none() { + if let Some(empty_region_index) = empty_region_index { + let region = &mut self.regions[empty_region_index]; + region.init(slab_size); + if let Some(location) = region.alloc() { + entry_kind = Some(EntryKind::Cache { + layer_index: region.layer_index as u16, + region_index: empty_region_index as u16, + origin: location, }); } - let free_texture_level = free_texture_levels.pop().unwrap(); - let texture_id = free_texture_level.texture_id; - - let page = TexturePage::new(texture_id, texture_size); - page_list.push(page); - page_list.last_mut().unwrap() - }, - }; - - let location = page.allocate(&requested_size) - .expect("All the checks have passed till now, there is no way back."); - let cache_item = TextureCacheItem::new( - page.texture_id, - DeviceUintRect::new(location, requested_size), - format, - user_data - ); - - let new_image_id = match existing_item_id { - Some(id) => { - *self.items.get_mut(id) = cache_item.clone(); - None - } - None => { - let id = self.items.insert(cache_item.clone()); - Some(id) } - }; - - AllocationResult { - item: cache_item, - kind: AllocationKind::TexturePage, - new_image_id, } - } - pub fn update( - &mut self, - image_id: &TextureCacheItemId, - descriptor: ImageDescriptor, - filter: TextureFilter, - data: ImageData, - mut dirty_rect: Option, - ) { - let mut existing_item = self.items.get(image_id).clone(); - - if existing_item.allocated_rect.size.width != descriptor.width || - existing_item.allocated_rect.size.height != descriptor.height || - existing_item.format != descriptor.format { - - self.free_item_rect(existing_item.clone()); - - self.allocate_impl( - descriptor.width, - descriptor.height, - descriptor.format, - filter, - existing_item.user_data, - &mut TextureCacheProfileCounters::new(), - Some(image_id), - ); - - // Fetch the item again because the rect most likely changed during reallocation. - existing_item = self.items.get(image_id).clone(); - // If we reallocated, we need to upload the whole item again. - dirty_rect = None; - } + entry_kind.map(|kind| { + CacheEntry { + size: DeviceUintSize::new(width, height), + user_data, + last_access: frame_id, + kind, + uv_rect_handle: GpuCacheHandle::new(), + format: self.format, + texture_id: self.texture_id.unwrap(), + } + }) + } +} - let op = match data { +impl TextureUpdate { + // Constructs a TextureUpdate operation to be passed to the + // rendering thread in order to do an upload to the right + // location in the texture cache. + fn new_update(data: ImageData, + descriptor: &ImageDescriptor, + origin: DeviceUintPoint, + size: DeviceUintSize, + texture_id: CacheTextureId, + layer_index: i32, + dirty_rect: Option) -> TextureUpdate { + let data_src = match data { + ImageData::Blob(..) => { + panic!("The vector image should have been rasterized."); + } ImageData::External(ext_image) => { match ext_image.image_type { ExternalImageType::Texture2DHandle | @@ -824,256 +949,53 @@ impl TextureCache { panic!("External texture handle should not go through texture_cache."); } ExternalImageType::ExternalBuffer => { - TextureUpdateOp::UpdateForExternalBuffer { - rect: existing_item.allocated_rect, + TextureUpdateSource::External { id: ext_image.id, channel_index: ext_image.channel_index, - stride: descriptor.stride, - offset: descriptor.offset, } } } } - ImageData::Blob(..) => { - panic!("The vector image should have been rasterized into a raw image."); - } ImageData::Raw(bytes) => { - match dirty_rect { - Some(dirty) => { - let stride = descriptor.compute_stride(); - let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x; - TextureUpdateOp::Update { - page_pos_x: existing_item.allocated_rect.origin.x + dirty.origin.x, - page_pos_y: existing_item.allocated_rect.origin.y + dirty.origin.y, - width: dirty.size.width, - height: dirty.size.height, - data: bytes, - stride: Some(stride), - offset, - } - } - None => { - TextureUpdateOp::Update { - page_pos_x: existing_item.allocated_rect.origin.x, - page_pos_y: existing_item.allocated_rect.origin.y, - width: descriptor.width, - height: descriptor.height, - data: bytes, - stride: descriptor.stride, - offset: descriptor.offset, - } - } + let finish = descriptor.offset + + descriptor.width * descriptor.format.bytes_per_pixel().unwrap_or(0) + + (descriptor.height-1) * descriptor.compute_stride(); + assert!(bytes.len() >= finish as usize); + + TextureUpdateSource::Bytes { + data: bytes } } }; - let update_op = TextureUpdate { - id: existing_item.texture_id, - op, - }; - - self.pending_updates.push(update_op); - } - - pub fn insert(&mut self, - descriptor: ImageDescriptor, - filter: TextureFilter, - data: ImageData, - user_data: [f32; 2], - profile: &mut TextureCacheProfileCounters) -> TextureCacheItemId { - if let ImageData::Blob(..) = data { - panic!("must rasterize the vector image before adding to the cache"); - } - - let width = descriptor.width; - let height = descriptor.height; - let format = descriptor.format; - let stride = descriptor.stride; - - if let ImageData::Raw(ref vec) = data { - let finish = descriptor.offset + - width * format.bytes_per_pixel().unwrap_or(0) + - (height-1) * descriptor.compute_stride(); - assert!(vec.len() >= finish as usize); - } - - let result = self.allocate(width, - height, - format, - filter, - user_data, - profile); - - match result.kind { - AllocationKind::TexturePage => { - match data { - ImageData::External(ext_image) => { - match ext_image.image_type { - ExternalImageType::Texture2DHandle | - ExternalImageType::TextureRectHandle | - ExternalImageType::TextureExternalHandle => { - panic!("External texture handle should not go through texture_cache."); - } - ExternalImageType::ExternalBuffer => { - let update_op = TextureUpdate { - id: result.item.texture_id, - op: TextureUpdateOp::UpdateForExternalBuffer { - rect: result.item.allocated_rect, - id: ext_image.id, - channel_index: ext_image.channel_index, - stride, - offset: descriptor.offset, - }, - }; - - self.pending_updates.push(update_op); - } - } - } - ImageData::Blob(..) => { - panic!("The vector image should have been rasterized."); - } - ImageData::Raw(bytes) => { - let update_op = TextureUpdate { - id: result.item.texture_id, - op: TextureUpdateOp::Update { - page_pos_x: result.item.allocated_rect.origin.x, - page_pos_y: result.item.allocated_rect.origin.y, - width: result.item.allocated_rect.size.width, - height: result.item.allocated_rect.size.height, - data: bytes, - stride, - offset: descriptor.offset, - }, - }; - - self.pending_updates.push(update_op); - } + let update_op = match dirty_rect { + Some(dirty) => { + let stride = descriptor.compute_stride(); + let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x; + let origin = DeviceUintPoint::new(origin.x + dirty.origin.x, + origin.y + dirty.origin.y); + TextureUpdateOp::Update { + rect: DeviceUintRect::new(origin, dirty.size), + source: data_src, + stride: Some(stride), + offset, + layer_index, } } - AllocationKind::Standalone => { - match data { - ImageData::External(ext_image) => { - match ext_image.image_type { - ExternalImageType::Texture2DHandle | - ExternalImageType::TextureRectHandle | - ExternalImageType::TextureExternalHandle => { - panic!("External texture handle should not go through texture_cache."); - } - ExternalImageType::ExternalBuffer => { - let update_op = TextureUpdate { - id: result.item.texture_id, - op: TextureUpdateOp::Create { - width, - height, - format, - filter, - mode: RenderTargetMode::None, - data: Some(data), - }, - }; - - self.pending_updates.push(update_op); - } - } - } - _ => { - let update_op = TextureUpdate { - id: result.item.texture_id, - op: TextureUpdateOp::Create { - width, - height, - format, - filter, - mode: RenderTargetMode::None, - data: Some(data), - }, - }; - - self.pending_updates.push(update_op); - } + None => { + TextureUpdateOp::Update { + rect: DeviceUintRect::new(origin, size), + source: data_src, + stride: descriptor.stride, + offset: descriptor.offset, + layer_index, } } - } - - result.new_image_id.unwrap() - } - - pub fn get(&self, id: &TextureCacheItemId) -> &TextureCacheItem { - self.items.get(id) - } - - pub fn get_mut(&mut self, id: &TextureCacheItemId) -> &mut TextureCacheItem { - self.items.get_mut(id) - } - - pub fn free(&mut self, id: TextureCacheItemId) { - let item = self.items.free(id); - self.free_item_rect(item); - } + }; - fn free_item_rect(&mut self, item: TextureCacheItem) { - match self.arena.texture_page_for_id(item.texture_id) { - Some(texture_page) => texture_page.free(&item.allocated_rect), - None => { - // This is a standalone texture allocation. Just push it back onto the free - // list. - self.pending_updates.push(TextureUpdate { - id: item.texture_id, - op: TextureUpdateOp::Free, - }); - self.cache_id_list.free(item.texture_id); - } + TextureUpdate { + id: texture_id, + op: update_op, } } } - -fn texture_create_op(texture_size: DeviceUintSize, - format: ImageFormat, - mode: RenderTargetMode, - filter: TextureFilter) - -> TextureUpdateOp { - TextureUpdateOp::Create { - width: texture_size.width, - height: texture_size.height, - format, - filter, - mode, - data: None, - } -} - -fn texture_grow_op(texture_size: DeviceUintSize, - format: ImageFormat, - mode: RenderTargetMode) - -> TextureUpdateOp { - TextureUpdateOp::Grow { - width: texture_size.width, - height: texture_size.height, - format, - filter: TextureFilter::Linear, - mode, - } -} - -trait FitsInside { - fn fits_inside(&self, other: &Self) -> bool; -} - -impl FitsInside for DeviceUintSize { - fn fits_inside(&self, other: &DeviceUintSize) -> bool { - self.width <= other.width && self.height <= other.height - } -} - -/// FIXME(pcwalton): Would probably be more efficient as a bit vector. -#[derive(Clone, Copy)] -pub struct FreeTextureLevel { - texture_id: CacheTextureId, -} - -/// Returns the number of pixels on a side we start out with for our texture atlases. -fn initial_texture_size(max_texture_size: u32) -> DeviceUintSize { - let initial_size = cmp::min(max_texture_size, INITIAL_TEXTURE_SIZE); - DeviceUintSize::new(initial_size, initial_size) -} From 869babf069e1532981bbe977204c074883e71be7 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 14 Aug 2017 16:13:13 +1000 Subject: [PATCH 8/9] Fix shader validation error. --- webrender/res/prim_shared.glsl | 19 ------------------- webrender/res/shared.glsl | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 653826ff55..8e00266ab8 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -3,25 +3,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#if defined(GL_ES) - #if GL_ES == 1 - #ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp sampler2DArray; - #else - precision mediump sampler2DArray; - #endif - - // Sampler default precision is lowp on mobile GPUs. - // This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880). - // Define highp precision macro to allow lossless FLOAT texture sampling. - #define HIGHP_SAMPLER_FLOAT highp - #else - #define HIGHP_SAMPLER_FLOAT - #endif -#else - #define HIGHP_SAMPLER_FLOAT -#endif - #define PST_TOP_LEFT 0 #define PST_TOP 1 #define PST_TOP_RIGHT 2 diff --git a/webrender/res/shared.glsl b/webrender/res/shared.glsl index 46973a232f..aab9faaa8c 100644 --- a/webrender/res/shared.glsl +++ b/webrender/res/shared.glsl @@ -52,6 +52,25 @@ //====================================================================================== // Shared shader uniforms //====================================================================================== +#if defined(GL_ES) + #if GL_ES == 1 + #ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp sampler2DArray; + #else + precision mediump sampler2DArray; + #endif + + // Sampler default precision is lowp on mobile GPUs. + // This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880). + // Define highp precision macro to allow lossless FLOAT texture sampling. + #define HIGHP_SAMPLER_FLOAT highp + #else + #define HIGHP_SAMPLER_FLOAT + #endif +#else + #define HIGHP_SAMPLER_FLOAT +#endif + #ifdef WR_FEATURE_TEXTURE_RECT uniform sampler2DRect sColor0; uniform sampler2DRect sColor1; From 63ec62ce1ac04ee19c9a7cac293e42e740b576d6 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Tue, 15 Aug 2017 07:31:23 +1000 Subject: [PATCH 9/9] Address review comments. --- webrender/src/freelist.rs | 4 ++++ webrender/src/glyph_cache.rs | 8 +++----- webrender/src/glyph_rasterizer.rs | 16 +++++++--------- webrender/src/renderer.rs | 6 +++--- webrender/src/texture_cache.rs | 18 ++++++++---------- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/webrender/src/freelist.rs b/webrender/src/freelist.rs index 811262e3ad..d43d7145a7 100644 --- a/webrender/src/freelist.rs +++ b/webrender/src/freelist.rs @@ -91,6 +91,10 @@ impl FreeList { } } + // Perform a database style UPSERT operation. If the provided + // handle is a valid entry, update the value and return the + // previous data. If the provided handle is invalid, then + // insert the data into a new slot and return the new handle. pub fn upsert(&mut self, id: &WeakFreeListHandle, data: T) -> UpsertResult { diff --git a/webrender/src/glyph_cache.rs b/webrender/src/glyph_cache.rs index cc08b11b9e..4d0d163e64 100644 --- a/webrender/src/glyph_cache.rs +++ b/webrender/src/glyph_cache.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{FontInstanceKey, GlyphKey}; +use api::{DevicePoint, DeviceUintSize, FontInstanceKey, GlyphKey}; use internal_types::FastHashMap; use resource_cache::ResourceClassCache; use std::sync::Arc; @@ -11,10 +11,8 @@ use texture_cache::TextureCacheHandle; pub struct CachedGlyphInfo { pub texture_cache_handle: TextureCacheHandle, pub glyph_bytes: Arc>, - pub width: u32, - pub height: u32, - pub left: f32, - pub top: f32, + pub size: DeviceUintSize, + pub offset: DevicePoint, } pub type GlyphKeyCache = ResourceClassCache>; diff --git a/webrender/src/glyph_rasterizer.rs b/webrender/src/glyph_rasterizer.rs index 90d6b10c0a..e46128c46c 100644 --- a/webrender/src/glyph_rasterizer.rs +++ b/webrender/src/glyph_rasterizer.rs @@ -19,7 +19,7 @@ use std::mem; use texture_cache::{TextureCache, TextureCacheHandle}; #[cfg(test)] use api::{ColorF, LayoutPoint, FontRenderMode, IdNamespace, SubpixelDirection}; -use api::{FontInstanceKey}; +use api::{DevicePoint, DeviceUintSize, FontInstanceKey}; use api::{FontKey, FontTemplate}; use api::{ImageData, ImageDescriptor, ImageFormat}; use api::{GlyphKey, GlyphDimensions}; @@ -158,7 +158,7 @@ impl GlyphRasterizer { for key in glyph_keys { match glyph_key_cache.entry(key.clone()) { Entry::Occupied(mut entry) => { - if let &mut Some(ref mut glyph_info) = entry.get_mut() { + if let Some(ref mut glyph_info) = *entry.get_mut() { if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) { // This case gets hit when we have already rasterized // the glyph and stored it in CPU memory, the the glyph @@ -166,8 +166,8 @@ impl GlyphRasterizer { // we need to re-upload it to the GPU. texture_cache.update(&mut glyph_info.texture_cache_handle, ImageDescriptor { - width: glyph_info.width, - height: glyph_info.height, + width: glyph_info.size.width, + height: glyph_info.size.height, stride: None, format: ImageFormat::BGRA8, is_opaque: false, @@ -175,7 +175,7 @@ impl GlyphRasterizer { }, TextureFilter::Linear, ImageData::Raw(glyph_info.glyph_bytes.clone()), - [glyph_info.left, glyph_info.top], + [glyph_info.offset.x, glyph_info.offset.y], None, gpu_cache); } @@ -290,10 +290,8 @@ impl GlyphRasterizer { Some(CachedGlyphInfo { texture_cache_handle, glyph_bytes, - width: glyph.width, - height: glyph.height, - left: glyph.left, - top: glyph.top, + size: DeviceUintSize::new(glyph.width, glyph.height), + offset: DevicePoint::new(glyph.left, glyph.top), }) } else { None diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 4bba90524e..8b1a4abf15 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -2414,15 +2414,15 @@ impl Renderer { let mut spacing = 16; let mut size = 512; let fb_width = framebuffer_size.width as i32; - let num_textures: i32 = self.cache_texture_id_map + let num_layers: i32 = self.cache_texture_id_map .iter() .map(|id| { self.device.get_texture_layer_count(*id) }) .sum(); - if num_textures * (size + spacing) > fb_width { - let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32; + if num_layers * (size + spacing) > fb_width { + let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32; size = (size as f32 * factor) as i32; spacing = (spacing as f32 * factor) as i32; } diff --git a/webrender/src/texture_cache.rs b/webrender/src/texture_cache.rs index a769f6cef1..13883e801e 100644 --- a/webrender/src/texture_cache.rs +++ b/webrender/src/texture_cache.rs @@ -474,10 +474,8 @@ impl TextureCache { } else { let entry = self.entries.free(handle); if let Some(region) = self.free(entry) { - found_matching_slab = found_matching_slab || - region.slab_size == needed_slab_size; - freed_complete_page = freed_complete_page || - region.is_empty(); + found_matching_slab |= region.slab_size == needed_slab_size; + freed_complete_page |= region.is_empty(); } evicted_items += 1; } @@ -867,7 +865,7 @@ impl TextureArray { // Quantize the size of the allocation to select a region to // allocate from. let slab_size = SlabSize::new(width, height); - let slab_size_bytes = slab_size.get_size(); + let slab_size_dim = slab_size.get_size(); // TODO(gw): For simplicity, the initial implementation just // has a single vec<> of regions. We could easily @@ -885,7 +883,7 @@ impl TextureArray { for (i, region) in self.regions.iter_mut().enumerate() { if region.slab_size == 0 { empty_region_index = Some(i); - } else if region.slab_size == slab_size_bytes { + } else if region.slab_size == slab_size_dim { if let Some(location) = region.alloc() { entry_kind = Some(EntryKind::Cache { layer_index: region.layer_index as u16, @@ -902,13 +900,13 @@ impl TextureArray { if let Some(empty_region_index) = empty_region_index { let region = &mut self.regions[empty_region_index]; region.init(slab_size); - if let Some(location) = region.alloc() { - entry_kind = Some(EntryKind::Cache { + entry_kind = region.alloc().map(|location| { + EntryKind::Cache { layer_index: region.layer_index as u16, region_index: empty_region_index as u16, origin: location, - }); - } + } + }); } }