From ddd505f1e38fb65b21d785a614e043683f1344df Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 11 Feb 2016 20:26:54 -0800 Subject: [PATCH 1/2] Add a render target texture cache. --- src/device.rs | 39 +++++++++++++++--- src/frame.rs | 14 +++---- src/internal_types.rs | 2 +- src/renderer.rs | 13 +++--- src/resource_cache.rs | 4 +- src/texture_cache.rs | 93 +++++++++++++++++++++++++++++++++++++------ 6 files changed, 130 insertions(+), 35 deletions(-) diff --git a/src/device.rs b/src/device.rs index 6767f870a4..a055cdcbee 100644 --- a/src/device.rs +++ b/src/device.rs @@ -112,7 +112,7 @@ lazy_static! { }; } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum TextureFilter { Nearest, Linear, @@ -533,6 +533,8 @@ struct Texture { format: ImageFormat, width: u32, height: u32, + filter: TextureFilter, + mode: RenderTargetMode, fbo_ids: Vec, } @@ -981,6 +983,8 @@ impl Device { width: 0, height: 0, format: ImageFormat::Invalid, + filter: TextureFilter::Nearest, + mode: RenderTargetMode::None, fbo_ids: vec![], }; @@ -1083,10 +1087,14 @@ impl Device { pixels: Option<&[u8]>) { debug_assert!(self.inside_frame); - // TODO: ugh, messy! - self.textures.get_mut(&texture_id).unwrap().format = format; - self.textures.get_mut(&texture_id).unwrap().width = width; - self.textures.get_mut(&texture_id).unwrap().height = height; + { + let texture = self.textures.get_mut(&texture_id).expect("Didn't find texture!"); + texture.format = format; + texture.width = width; + texture.height = height; + texture.filter = filter; + texture.mode = mode + } let (internal_format, gl_format) = match format { ImageFormat::A8 => (GL_FORMAT_A, GL_FORMAT_A), @@ -1155,6 +1163,27 @@ impl Device { texture.fbo_ids.clear(); } + pub fn init_texture_if_necessary(&mut self, + texture_id: TextureId, + width: u32, + height: u32, + format: ImageFormat, + filter: TextureFilter, + mode: RenderTargetMode) { + debug_assert!(self.inside_frame); + { + let texture = self.textures.get_mut(&texture_id).expect("Didn't find texture!"); + if texture.format == format && + texture.width == width && + texture.height == height && + texture.filter == filter && + texture.mode == mode { + return + } + } + self.init_texture(texture_id, width, height, format, filter, mode, None) + } + pub fn create_program(&mut self, base_filename: &str) -> ProgramId { debug_assert!(self.inside_frame); diff --git a/src/frame.rs b/src/frame.rs index 5f8115ae5e..78a9501cf0 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -289,9 +289,8 @@ impl RenderTarget { fn reset(&mut self, pending_updates: &mut BatchUpdateList, resource_cache: &mut ResourceCache) { - for texture_id in self.texture_id_list.drain(..) { - resource_cache.free_render_target(texture_id); - } + self.texture_id_list.clear(); + resource_cache.free_old_render_targets(); for mut child in &mut self.children.drain(..) { child.reset(pending_updates, @@ -1009,10 +1008,11 @@ impl Frame { target_rect.size.height); let render_target_id = self.next_render_target_id(); - let (origin, texture_id) = target.allocate_target_rect(target_rect.size.width, - target_rect.size.height, - context.device_pixel_ratio, - context.resource_cache); + let (origin, texture_id) = + target.allocate_target_rect(target_rect.size.width, + target_rect.size.height, + context.device_pixel_ratio, + context.resource_cache); let mut new_target = RenderTarget::new(render_target_id, origin, diff --git a/src/internal_types.rs b/src/internal_types.rs index 8e94bb71fd..9e49b5fc58 100644 --- a/src/internal_types.rs +++ b/src/internal_types.rs @@ -341,7 +341,7 @@ impl DebugColorVertex { } } -#[derive(Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum RenderTargetMode { None, RenderTarget, diff --git a/src/renderer.rs b/src/renderer.rs index b9f02ee628..4882f0ca23 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1456,13 +1456,12 @@ impl Renderer { p0.x * render_context.device_pixel_ratio, inverted_y0 * render_context.device_pixel_ratio); - self.device.init_texture(self.temporary_fb_texture, - fb_rect_size.width as u32, - fb_rect_size.height as u32, - ImageFormat::RGBA8, - TextureFilter::Nearest, - RenderTargetMode::None, - None); + self.device.init_texture_if_necessary(self.temporary_fb_texture, + fb_rect_size.width as u32, + fb_rect_size.height as u32, + ImageFormat::RGBA8, + TextureFilter::Nearest, + RenderTargetMode::None); self.device.read_framebuffer_rect( self.temporary_fb_texture, 0, diff --git a/src/resource_cache.rs b/src/resource_cache.rs index 29f72d1cd6..7717a78ad1 100644 --- a/src/resource_cache.rs +++ b/src/resource_cache.rs @@ -377,8 +377,8 @@ impl ResourceCache { self.texture_cache.allocate_render_target(width, height, format) } - pub fn free_render_target(&mut self, texture_id: TextureId) { - self.texture_cache.free_render_target(texture_id) + pub fn free_old_render_targets(&mut self) { + self.texture_cache.free_old_render_targets() } pub fn pending_updates(&mut self) -> TextureUpdateList { diff --git a/src/texture_cache.rs b/src/texture_cache.rs index d5b3cf2941..684296df34 100644 --- a/src/texture_cache.rs +++ b/src/texture_cache.rs @@ -25,6 +25,9 @@ const MAX_BYTES_PER_TEXTURE: u32 = 64 * 1024 * 1024; /// 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 total number of RGBA pixels we're allowed to use for our render targets. +const MAX_RGBA_PIXELS_IN_CACHED_RENDER_TARGETS: u32 = 4096 * 4096 * 2; + /// 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 = 4096; @@ -543,6 +546,8 @@ pub struct TextureCache { // Vec, // BuildHasherDefault>, items: FreeList, + cached_render_targets: Vec, + total_pixel_count_of_cached_render_targets: u32, arena: TextureCacheArena, pending_updates: TextureUpdateList, } @@ -566,6 +571,8 @@ impl TextureCache { free_texture_levels: HashMap::with_hasher(Default::default()), alternate_free_texture_levels: HashMap::with_hasher(Default::default()), //render_target_free_texture_levels: HashMap::with_hasher(Default::default()), + cached_render_targets: vec![], + total_pixel_count_of_cached_render_targets: 0, items: FreeList::new(), pending_updates: TextureUpdateList::new(), arena: TextureCacheArena::new(), @@ -615,10 +622,32 @@ impl TextureCache { */ pub fn allocate_render_target(&mut self, - width: u32, - height: u32, - format: ImageFormat) -> TextureId { - let texture_id = self.free_texture_ids.pop().expect("TODO: Handle running out of texture IDs!"); + width: u32, + height: u32, + format: ImageFormat) + -> TextureId { + let mut cached_render_target_index = None; + for (i, cached_render_target) in self.cached_render_targets.iter().enumerate() { + if cached_render_target.width == width && + cached_render_target.height == height && + cached_render_target.format == format { + cached_render_target_index = Some(i); + break + } + } + if let Some(cached_render_target_index) = cached_render_target_index { + // Push to the end to mark as recently used. + let cached_render_target = self.cached_render_targets + .remove(cached_render_target_index); + self.cached_render_targets.push(cached_render_target); + return cached_render_target.texture_id + } + + self.total_pixel_count_of_cached_render_targets += width * height; + + let texture_id = self.free_texture_ids + .pop() + .expect("TODO: Handle running out of texture IDs!"); let op = TextureUpdateOp::Create(width, height, format, @@ -630,18 +659,48 @@ impl TextureCache { op: op, }; self.pending_updates.push(update_op); + + self.cached_render_targets.push(CachedRenderTarget { + texture_id: texture_id, + width: width, + height: height, + format: format, + }); + texture_id } - pub fn free_render_target(&mut self, texture_id: TextureId) { - let op = TextureUpdateOp::Update(0, 0, 0, 0, - TextureUpdateDetails::Blit(Vec::new())); - let update_op = TextureUpdate { - id: texture_id, - op: op, - }; - self.pending_updates.push(update_op); - self.free_texture_ids.push(texture_id); + pub fn free_old_render_targets(&mut self) { + if self.total_pixel_count_of_cached_render_targets <= + MAX_RGBA_PIXELS_IN_CACHED_RENDER_TARGETS { + return + } + + let mut cached_render_targets_to_destroy = 0; + for cached_render_target in &self.cached_render_targets { + let op = TextureUpdateOp::Update(0, 0, 0, 0, + TextureUpdateDetails::Blit(Vec::new())); + let update_op = TextureUpdate { + id: cached_render_target.texture_id, + op: op, + }; + self.pending_updates.push(update_op); + self.free_texture_ids.push(cached_render_target.texture_id); + + cached_render_targets_to_destroy += 1; + + self.total_pixel_count_of_cached_render_targets -= cached_render_target.width * + cached_render_target.height; + if self.total_pixel_count_of_cached_render_targets < + MAX_RGBA_PIXELS_IN_CACHED_RENDER_TARGETS { + break + } + } + + self.cached_render_targets = self.cached_render_targets[cached_render_targets_to_destroy..] + .iter() + .cloned() + .collect() } pub fn allocate(&mut self, @@ -1154,3 +1213,11 @@ pub enum TextureCacheItemKind { //RenderTarget, } +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct CachedRenderTarget { + texture_id: TextureId, + width: u32, + height: u32, + format: ImageFormat, +} + From 0e939e234b3a86b21ee8b43d13fecbb53d7721c0 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 11 Feb 2016 20:28:16 -0800 Subject: [PATCH 2/2] Cache VAOs. Seems to be about an 8x-10x compositing performance increase on browser.html. --- src/device.rs | 36 ++++++++++++++++++------------- src/renderer.rs | 56 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/device.rs b/src/device.rs index a055cdcbee..563e5f1bfd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -637,6 +637,7 @@ struct VAO { main_vbo_id: VBOId, aux_vbo_id: Option, ibo_id: IBOId, + owns_vbos: bool, } #[cfg(any(target_os = "android", target_os = "gonk"))] @@ -661,17 +662,20 @@ impl Drop for VAO { fn drop(&mut self) { gl::delete_vertex_arrays(&[self.id]); - // In the case of a rect batch, the main VBO is the shared quad VBO, so keep that around. - if self.vertex_format != VertexFormat::Rectangles { - gl::delete_buffers(&[self.main_vbo_id.0]); - } - if let Some(VBOId(aux_vbo_id)) = self.aux_vbo_id { - gl::delete_buffers(&[aux_vbo_id]); - } + if self.owns_vbos { + // In the case of a rect batch, the main VBO is the shared quad VBO, so keep that + // around. + if self.vertex_format != VertexFormat::Rectangles { + gl::delete_buffers(&[self.main_vbo_id.0]); + } + if let Some(VBOId(aux_vbo_id)) = self.aux_vbo_id { + gl::delete_buffers(&[aux_vbo_id]); + } - // todo(gw): maybe make these their own type with hashmap? - let IBOId(ibo_id) = self.ibo_id; - gl::delete_buffers(&[ibo_id]); + // todo(gw): maybe make these their own type with hashmap? + let IBOId(ibo_id) = self.ibo_id; + gl::delete_buffers(&[ibo_id]); + } } } @@ -1522,7 +1526,8 @@ impl Device { main_vbo_id: VBOId, aux_vbo_id: Option, ibo_id: IBOId, - _: u32) + _: u32, + owns_vbos: bool) -> VAOId { debug_assert!(self.inside_frame); @@ -1537,6 +1542,7 @@ impl Device { main_vbo_id: main_vbo_id, aux_vbo_id: aux_vbo_id, ibo_id: ibo_id, + owns_vbos: owns_vbos, }; let vao_id = VAOId(vao_id); @@ -1571,7 +1577,8 @@ impl Device { main_vbo_id: VBOId, aux_vbo_id: Option, ibo_id: IBOId, - offset: gl::GLuint) + offset: gl::GLuint, + owns_vbos: bool) -> VAOId { debug_assert!(self.inside_frame); @@ -1588,6 +1595,7 @@ impl Device { main_vbo_id: main_vbo_id, aux_vbo_id: aux_vbo_id, ibo_id: ibo_id, + owns_vbos: owns_vbos, }; gl::bind_vertex_array(0); @@ -1615,7 +1623,7 @@ impl Device { (VBOId(buffer_ids[1]), None) }; - self.create_vao_with_vbos(format, main_vbo_id, aux_vbo_id, ibo_id, 0) + self.create_vao_with_vbos(format, main_vbo_id, aux_vbo_id, ibo_id, 0, true) } #[inline(never)] @@ -1630,7 +1638,7 @@ impl Device { ibo_id, .. } = self.vaos.get(&source_vao_id).expect("Bad VAO ID in `create_similar_vao()`!"); - self.create_vao_with_vbos(format, main_vbo_id, aux_vbo_id, ibo_id, offset) + self.create_vao_with_vbos(format, main_vbo_id, aux_vbo_id, ibo_id, offset, false) } #[cfg(any(target_os = "android", target_os = "gonk"))] diff --git a/src/renderer.rs b/src/renderer.rs index 4882f0ca23..42656d8e8e 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -37,6 +37,8 @@ use util::RectHelpers; pub const BLUR_INFLATION_FACTOR: u32 = 3; pub const MAX_RASTER_OP_SIZE: u32 = 2048; +const MAX_CACHED_QUAD_VAOS: usize = 8; + // TODO(gw): HACK! Need to support lighten/darken mix-blend-mode properly on android... #[cfg(not(any(target_os = "android", target_os = "gonk")))] @@ -116,6 +118,9 @@ pub struct Renderer { vertex_buffers: HashMap, BuildHasherDefault>, raster_batches: Vec, quad_vertex_buffer: Option, + cached_quad_vaos: Vec, + simple_triangles_vao: Option, + raster_op_vao: Option, quad_program_id: ProgramId, u_quad_transform_array: UniformLocation, @@ -270,6 +275,9 @@ impl Renderer { vertex_buffers: HashMap::with_hasher(Default::default()), raster_batches: Vec::new(), quad_vertex_buffer: None, + simple_triangles_vao: None, + raster_op_vao: None, + cached_quad_vaos: Vec::new(), pending_texture_updates: Vec::new(), pending_batch_updates: Vec::new(), pending_shader_updates: Vec::new(), @@ -415,8 +423,14 @@ impl Renderer { self.quad_vertex_buffer = Some(self.device.create_quad_vertex_buffer()) } - let vao_id = self.device.create_vao(VertexFormat::Rectangles, - self.quad_vertex_buffer); + let vao_id = match self.cached_quad_vaos.pop() { + Some(quad_vao_id) => quad_vao_id, + None => { + self.device.create_vao(VertexFormat::Rectangles, + Some(self.quad_vertex_buffer.unwrap())) + } + }; + self.device.bind_vao(vao_id); self.device.update_vao_aux_vertices(vao_id, @@ -436,7 +450,12 @@ impl Renderer { let vertex_buffers_and_offsets = self.vertex_buffers.remove(&update.id).unwrap(); for vertex_buffer_and_offset in vertex_buffers_and_offsets.into_iter() { - self.device.delete_vao(vertex_buffer_and_offset.buffer.vao_id); + if self.cached_quad_vaos.len() < MAX_CACHED_QUAD_VAOS && + vertex_buffer_and_offset.offset == 0 { + self.cached_quad_vaos.push(vertex_buffer_and_offset.buffer.vao_id); + } else { + self.device.delete_vao(vertex_buffer_and_offset.buffer.vao_id); + } } } } @@ -951,7 +970,14 @@ impl Renderer { } fn perform_gl_texture_cache_update(&mut self, batch: RasterBatch) { - let vao_id = self.device.create_vao(VertexFormat::RasterOp, None); + let vao_id = match self.raster_op_vao { + Some(ref mut vao_id) => *vao_id, + None => { + let vao_id = self.device.create_vao(VertexFormat::RasterOp, None); + self.raster_op_vao = Some(vao_id); + vao_id + } + }; self.device.bind_vao(vao_id); self.device.update_vao_indices(vao_id, &batch.indices[..], VertexUsageHint::Dynamic); @@ -964,7 +990,6 @@ impl Renderer { //println!("drawing triangles due to GL texture cache update"); self.device.draw_triangles_u16(0, batch.indices.len() as gl::GLint); - self.device.delete_vao(vao_id); for blit_job in batch.blit_jobs { self.device.read_framebuffer_rect(blit_job.dest_texture_id, @@ -1220,7 +1245,8 @@ impl Renderer { let wvp = projection.mul(&mask.transform); self.device.bind_program(self.mask_program_id, &wvp); - draw_simple_triangles(&mut self.device, + draw_simple_triangles(&mut self.simple_triangles_vao, + &mut self.device, &mut self.profile_counters, &indices[..], &vertices[..], @@ -1556,7 +1582,8 @@ impl Renderer { } } - draw_simple_triangles(&mut self.device, + draw_simple_triangles(&mut self.simple_triangles_vao, + &mut self.device, &mut self.profile_counters, &indices[..], &vertices[..], @@ -1630,14 +1657,20 @@ pub struct RendererOptions { pub enable_profiler: bool, } -fn draw_simple_triangles(device: &mut Device, +fn draw_simple_triangles(simple_triangles_vao: &mut Option, + device: &mut Device, profile_counters: &mut RendererProfileCounters, indices: &[u16], vertices: &[PackedVertex], texture: TextureId) { - // TODO(glennw): Don't re-create this VAO all the time. Create it once and set positions - // via uniforms. - let vao_id = device.create_vao(VertexFormat::Triangles, None); + let vao_id = match *simple_triangles_vao { + Some(ref mut vao_id) => *vao_id, + None => { + let vao_id = device.create_vao(VertexFormat::Triangles, None); + *simple_triangles_vao = Some(vao_id); + vao_id + } + }; device.bind_color_texture(texture); device.bind_vao(vao_id); device.update_vao_indices(vao_id, &indices[..], VertexUsageHint::Dynamic); @@ -1647,7 +1680,6 @@ fn draw_simple_triangles(device: &mut Device, profile_counters.draw_calls.inc(); device.draw_triangles_u16(0, indices.len() as gl::GLint); - device.delete_vao(vao_id); } struct VertexBufferAndOffset {