Optimize resource upload in D3D11 (#48282)

John Tur created

Currently, each time we draw a primitive batch, we fully overwrite the
instance buffer with the contents of the new batch. Since we use a
write-only mapping to do this, the GPU driver may handle synchronization
hazards by transparently creating new allocations if the previous
allocation is still in use. We draw many primitive batches in one frame,
which stress-tests this mechanism somewhat. If internal driver limits
are hit, the resource update will start to block until the GPU catches
up and releases in-use allocations. This would result in a significant
reduction in framerate.

To avoid this, we upload the data for all primitive batches at once at
the beginning of the frame. Each primitive batch draw then binds the
relevant sub-array of the instance buffer. This way, there are no
mid-frame resource updates.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/blade/blade_renderer.rs     |  33 
crates/gpui/src/platform/mac/metal_renderer.rs       |  59 +-
crates/gpui/src/platform/windows/directx_renderer.rs | 266 ++++++++++---
crates/gpui/src/scene.rs                             |  62 +--
4 files changed, 256 insertions(+), 164 deletions(-)

Detailed changes

crates/gpui/src/platform/blade/blade_renderer.rs 🔗

@@ -719,7 +719,8 @@ impl BladeRenderer {
         profiling::scope!("render pass");
         for batch in scene.batches() {
             match batch {
-                PrimitiveBatch::Quads(quads) => {
+                PrimitiveBatch::Quads(range) => {
+                    let quads = &scene.quads[range];
                     let instance_buf = unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
                     let mut encoder = pass.with(&self.pipelines.quads);
                     encoder.bind(
@@ -731,7 +732,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, quads.len() as u32);
                 }
-                PrimitiveBatch::Shadows(shadows) => {
+                PrimitiveBatch::Shadows(range) => {
+                    let shadows = &scene.shadows[range];
                     let instance_buf =
                         unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
                     let mut encoder = pass.with(&self.pipelines.shadows);
@@ -744,7 +746,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, shadows.len() as u32);
                 }
-                PrimitiveBatch::Paths(paths) => {
+                PrimitiveBatch::Paths(range) => {
+                    let paths = &scene.paths[range];
                     let Some(first_path) = paths.first() else {
                         continue;
                     };
@@ -800,7 +803,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, sprites.len() as u32);
                 }
-                PrimitiveBatch::Underlines(underlines) => {
+                PrimitiveBatch::Underlines(range) => {
+                    let underlines = &scene.underlines[range];
                     let instance_buf =
                         unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
                     let mut encoder = pass.with(&self.pipelines.underlines);
@@ -813,10 +817,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, underlines.len() as u32);
                 }
-                PrimitiveBatch::MonochromeSprites {
-                    texture_id,
-                    sprites,
-                } => {
+                PrimitiveBatch::MonochromeSprites { texture_id, range } => {
+                    let sprites = &scene.monochrome_sprites[range];
                     let tex_info = self.atlas.get_texture_info(texture_id);
                     let instance_buf =
                         unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
@@ -836,10 +838,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, sprites.len() as u32);
                 }
-                PrimitiveBatch::PolychromeSprites {
-                    texture_id,
-                    sprites,
-                } => {
+                PrimitiveBatch::PolychromeSprites { texture_id, range } => {
+                    let sprites = &scene.polychrome_sprites[range];
                     let tex_info = self.atlas.get_texture_info(texture_id);
                     let instance_buf =
                         unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
@@ -855,10 +855,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, sprites.len() as u32);
                 }
-                PrimitiveBatch::SubpixelSprites {
-                    texture_id,
-                    sprites,
-                } => {
+                PrimitiveBatch::SubpixelSprites { texture_id, range } => {
+                    let sprites = &scene.subpixel_sprites[range];
                     let tex_info = self.atlas.get_texture_info(texture_id);
                     let instance_buf =
                         unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
@@ -878,7 +876,8 @@ impl BladeRenderer {
                     );
                     encoder.draw(0, 4, 0, sprites.len() as u32);
                 }
-                PrimitiveBatch::Surfaces(surfaces) => {
+                PrimitiveBatch::Surfaces(range) => {
+                    let surfaces = &scene.surfaces[range];
                     let mut _encoder = pass.with(&self.pipelines.surfaces);
 
                     for surface in surfaces {

crates/gpui/src/platform/mac/metal_renderer.rs 🔗

@@ -548,21 +548,22 @@ impl MetalRenderer {
 
         for batch in scene.batches() {
             let ok = match batch {
-                PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
-                    shadows,
+                PrimitiveBatch::Shadows(range) => self.draw_shadows(
+                    &scene.shadows[range],
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
                     command_encoder,
                 ),
-                PrimitiveBatch::Quads(quads) => self.draw_quads(
-                    quads,
+                PrimitiveBatch::Quads(range) => self.draw_quads(
+                    &scene.quads[range],
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
                     command_encoder,
                 ),
-                PrimitiveBatch::Paths(paths) => {
+                PrimitiveBatch::Paths(range) => {
+                    let paths = &scene.paths[range];
                     command_encoder.end_encoding();
 
                     let did_draw = self.draw_paths_to_intermediate(
@@ -594,37 +595,33 @@ impl MetalRenderer {
                         false
                     }
                 }
-                PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
-                    underlines,
+                PrimitiveBatch::Underlines(range) => self.draw_underlines(
+                    &scene.underlines[range],
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,
                     command_encoder,
                 ),
-                PrimitiveBatch::MonochromeSprites {
-                    texture_id,
-                    sprites,
-                } => self.draw_monochrome_sprites(
-                    texture_id,
-                    sprites,
-                    instance_buffer,
-                    &mut instance_offset,
-                    viewport_size,
-                    command_encoder,
-                ),
-                PrimitiveBatch::PolychromeSprites {
-                    texture_id,
-                    sprites,
-                } => self.draw_polychrome_sprites(
-                    texture_id,
-                    sprites,
-                    instance_buffer,
-                    &mut instance_offset,
-                    viewport_size,
-                    command_encoder,
-                ),
-                PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
-                    surfaces,
+                PrimitiveBatch::MonochromeSprites { texture_id, range } => self
+                    .draw_monochrome_sprites(
+                        texture_id,
+                        &scene.monochrome_sprites[range],
+                        instance_buffer,
+                        &mut instance_offset,
+                        viewport_size,
+                        command_encoder,
+                    ),
+                PrimitiveBatch::PolychromeSprites { texture_id, range } => self
+                    .draw_polychrome_sprites(
+                        texture_id,
+                        &scene.polychrome_sprites[range],
+                        instance_buffer,
+                        &mut instance_offset,
+                        viewport_size,
+                        command_encoder,
+                    ),
+                PrimitiveBatch::Surfaces(range) => self.draw_surfaces(
+                    &scene.surfaces[range],
                     instance_buffer,
                     &mut instance_offset,
                     viewport_size,

crates/gpui/src/platform/windows/directx_renderer.rs 🔗

@@ -315,28 +315,29 @@ impl DirectXRenderer {
             WindowBackgroundAppearance::Opaque => [1.0f32; 4],
             _ => [0.0f32; 4],
         })?;
+
+        self.upload_scene_buffers(scene)?;
+
         for batch in scene.batches() {
             match batch {
-                PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
-                PrimitiveBatch::Quads(quads) => self.draw_quads(quads),
-                PrimitiveBatch::Paths(paths) => {
+                PrimitiveBatch::Shadows(range) => self.draw_shadows(range.start, range.len()),
+                PrimitiveBatch::Quads(range) => self.draw_quads(range.start, range.len()),
+                PrimitiveBatch::Paths(range) => {
+                    let paths = &scene.paths[range];
                     self.draw_paths_to_intermediate(paths)?;
                     self.draw_paths_from_intermediate(paths)
                 }
-                PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines),
-                PrimitiveBatch::MonochromeSprites {
-                    texture_id,
-                    sprites,
-                } => self.draw_monochrome_sprites(texture_id, sprites),
-                PrimitiveBatch::SubpixelSprites {
-                    texture_id,
-                    sprites,
-                } => self.draw_subpixel_sprites(texture_id, sprites),
-                PrimitiveBatch::PolychromeSprites {
-                    texture_id,
-                    sprites,
-                } => self.draw_polychrome_sprites(texture_id, sprites),
-                PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces),
+                PrimitiveBatch::Underlines(range) => self.draw_underlines(range.start, range.len()),
+                PrimitiveBatch::MonochromeSprites { texture_id, range } => {
+                    self.draw_monochrome_sprites(texture_id, range.start, range.len())
+                }
+                PrimitiveBatch::SubpixelSprites { texture_id, range } => {
+                    self.draw_subpixel_sprites(texture_id, range.start, range.len())
+                }
+                PrimitiveBatch::PolychromeSprites { texture_id, range } => {
+                    self.draw_polychrome_sprites(texture_id, range.start, range.len())
+                }
+                PrimitiveBatch::Surfaces(range) => self.draw_surfaces(&scene.surfaces[range]),
             }
             .context(format!(
                 "scene too large:\
@@ -398,17 +399,67 @@ impl DirectXRenderer {
         Ok(())
     }
 
-    fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
-        if shadows.is_empty() {
+    fn upload_scene_buffers(&mut self, scene: &Scene) -> Result<()> {
+        let devices = self.devices.as_ref().context("devices missing")?;
+
+        if !scene.shadows.is_empty() {
+            self.pipelines.shadow_pipeline.update_buffer(
+                &devices.device,
+                &devices.device_context,
+                &scene.shadows,
+            )?;
+        }
+
+        if !scene.quads.is_empty() {
+            self.pipelines.quad_pipeline.update_buffer(
+                &devices.device,
+                &devices.device_context,
+                &scene.quads,
+            )?;
+        }
+
+        if !scene.underlines.is_empty() {
+            self.pipelines.underline_pipeline.update_buffer(
+                &devices.device,
+                &devices.device_context,
+                &scene.underlines,
+            )?;
+        }
+
+        if !scene.monochrome_sprites.is_empty() {
+            self.pipelines.mono_sprites.update_buffer(
+                &devices.device,
+                &devices.device_context,
+                &scene.monochrome_sprites,
+            )?;
+        }
+
+        if !scene.subpixel_sprites.is_empty() {
+            self.pipelines.subpixel_sprites.update_buffer(
+                &devices.device,
+                &devices.device_context,
+                &scene.subpixel_sprites,
+            )?;
+        }
+
+        if !scene.polychrome_sprites.is_empty() {
+            self.pipelines.poly_sprites.update_buffer(
+                &devices.device,
+                &devices.device_context,
+                &scene.polychrome_sprites,
+            )?;
+        }
+
+        Ok(())
+    }
+
+    fn draw_shadows(&mut self, start: usize, len: usize) -> Result<()> {
+        if len == 0 {
             return Ok(());
         }
         let devices = self.devices.as_ref().context("devices missing")?;
-        self.pipelines.shadow_pipeline.update_buffer(
+        self.pipelines.shadow_pipeline.draw_range(
             &devices.device,
-            &devices.device_context,
-            shadows,
-        )?;
-        self.pipelines.shadow_pipeline.draw(
             &devices.device_context,
             slice::from_ref(
                 &self
@@ -418,23 +469,19 @@ impl DirectXRenderer {
                     .viewport,
             ),
             slice::from_ref(&self.globals.global_params_buffer),
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
             4,
-            shadows.len() as u32,
+            start as u32,
+            len as u32,
         )
     }
 
-    fn draw_quads(&mut self, quads: &[Quad]) -> Result<()> {
-        if quads.is_empty() {
+    fn draw_quads(&mut self, start: usize, len: usize) -> Result<()> {
+        if len == 0 {
             return Ok(());
         }
         let devices = self.devices.as_ref().context("devices missing")?;
-        self.pipelines.quad_pipeline.update_buffer(
+        self.pipelines.quad_pipeline.draw_range(
             &devices.device,
-            &devices.device_context,
-            quads,
-        )?;
-        self.pipelines.quad_pipeline.draw(
             &devices.device_context,
             slice::from_ref(
                 &self
@@ -444,9 +491,9 @@ impl DirectXRenderer {
                     .viewport,
             ),
             slice::from_ref(&self.globals.global_params_buffer),
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
             4,
-            quads.len() as u32,
+            start as u32,
+            len as u32,
         )
     }
 
@@ -561,105 +608,92 @@ impl DirectXRenderer {
         )
     }
 
-    fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
-        if underlines.is_empty() {
+    fn draw_underlines(&mut self, start: usize, len: usize) -> Result<()> {
+        if len == 0 {
             return Ok(());
         }
         let devices = self.devices.as_ref().context("devices missing")?;
         let resources = self.resources.as_ref().context("resources missing")?;
-        self.pipelines.underline_pipeline.update_buffer(
+        self.pipelines.underline_pipeline.draw_range(
             &devices.device,
-            &devices.device_context,
-            underlines,
-        )?;
-        self.pipelines.underline_pipeline.draw(
             &devices.device_context,
             slice::from_ref(&resources.viewport),
             slice::from_ref(&self.globals.global_params_buffer),
-            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
             4,
-            underlines.len() as u32,
+            start as u32,
+            len as u32,
         )
     }
 
     fn draw_monochrome_sprites(
         &mut self,
         texture_id: AtlasTextureId,
-        sprites: &[MonochromeSprite],
+        start: usize,
+        len: usize,
     ) -> Result<()> {
-        if sprites.is_empty() {
+        if len == 0 {
             return Ok(());
         }
         let devices = self.devices.as_ref().context("devices missing")?;
         let resources = self.resources.as_ref().context("resources missing")?;
-
-        self.pipelines.mono_sprites.update_buffer(
-            &devices.device,
-            &devices.device_context,
-            sprites,
-        )?;
         let texture_view = self.atlas.get_texture_view(texture_id);
-        self.pipelines.mono_sprites.draw_with_texture(
+        self.pipelines.mono_sprites.draw_range_with_texture(
+            &devices.device,
             &devices.device_context,
             &texture_view,
             slice::from_ref(&resources.viewport),
             slice::from_ref(&self.globals.global_params_buffer),
             slice::from_ref(&self.globals.sampler),
-            sprites.len() as u32,
+            start as u32,
+            len as u32,
         )
     }
 
     fn draw_subpixel_sprites(
         &mut self,
         texture_id: AtlasTextureId,
-        sprites: &[SubpixelSprite],
+        start: usize,
+        len: usize,
     ) -> Result<()> {
-        if sprites.is_empty() {
+        if len == 0 {
             return Ok(());
         }
         let devices = self.devices.as_ref().context("devices missing")?;
         let resources = self.resources.as_ref().context("resources missing")?;
-
-        self.pipelines.subpixel_sprites.update_buffer(
-            &devices.device,
-            &devices.device_context,
-            &sprites,
-        )?;
         let texture_view = self.atlas.get_texture_view(texture_id);
-        self.pipelines.subpixel_sprites.draw_with_texture(
+        self.pipelines.subpixel_sprites.draw_range_with_texture(
+            &devices.device,
             &devices.device_context,
             &texture_view,
             slice::from_ref(&resources.viewport),
             slice::from_ref(&self.globals.global_params_buffer),
             slice::from_ref(&self.globals.sampler),
-            sprites.len() as u32,
+            start as u32,
+            len as u32,
         )
     }
 
     fn draw_polychrome_sprites(
         &mut self,
         texture_id: AtlasTextureId,
-        sprites: &[PolychromeSprite],
+        start: usize,
+        len: usize,
     ) -> Result<()> {
-        if sprites.is_empty() {
+        if len == 0 {
             return Ok(());
         }
-
         let devices = self.devices.as_ref().context("devices missing")?;
         let resources = self.resources.as_ref().context("resources missing")?;
-        self.pipelines.poly_sprites.update_buffer(
-            &devices.device,
-            &devices.device_context,
-            sprites,
-        )?;
         let texture_view = self.atlas.get_texture_view(texture_id);
-        self.pipelines.poly_sprites.draw_with_texture(
+        self.pipelines.poly_sprites.draw_range_with_texture(
+            &devices.device,
             &devices.device_context,
             &texture_view,
             slice::from_ref(&resources.viewport),
             slice::from_ref(&self.globals.global_params_buffer),
             slice::from_ref(&self.globals.sampler),
-            sprites.len() as u32,
+            start as u32,
+            len as u32,
         )
     }
 
@@ -1050,6 +1084,64 @@ impl<T> PipelineState<T> {
         }
         Ok(())
     }
+
+    fn draw_range(
+        &self,
+        device: &ID3D11Device,
+        device_context: &ID3D11DeviceContext,
+        viewport: &[D3D11_VIEWPORT],
+        global_params: &[Option<ID3D11Buffer>],
+        vertex_count: u32,
+        first_instance: u32,
+        instance_count: u32,
+    ) -> Result<()> {
+        let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?;
+        set_pipeline_state(
+            device_context,
+            slice::from_ref(&view),
+            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
+            viewport,
+            &self.vertex,
+            &self.fragment,
+            global_params,
+            &self.blend_state,
+        );
+        unsafe {
+            device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
+        }
+        Ok(())
+    }
+
+    fn draw_range_with_texture(
+        &self,
+        device: &ID3D11Device,
+        device_context: &ID3D11DeviceContext,
+        texture: &[Option<ID3D11ShaderResourceView>],
+        viewport: &[D3D11_VIEWPORT],
+        global_params: &[Option<ID3D11Buffer>],
+        sampler: &[Option<ID3D11SamplerState>],
+        first_instance: u32,
+        instance_count: u32,
+    ) -> Result<()> {
+        let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?;
+        set_pipeline_state(
+            device_context,
+            slice::from_ref(&view),
+            D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
+            viewport,
+            &self.vertex,
+            &self.fragment,
+            global_params,
+            &self.blend_state,
+        );
+        unsafe {
+            device_context.PSSetSamplers(0, Some(sampler));
+            device_context.VSSetShaderResources(0, Some(texture));
+            device_context.PSSetShaderResources(0, Some(texture));
+            device_context.DrawInstanced(4, instance_count, 0, 0);
+        }
+        Ok(())
+    }
 }
 
 #[derive(Clone, Copy)]
@@ -1410,6 +1502,32 @@ fn create_buffer_view(
     Ok(view)
 }
 
+#[inline]
+fn create_buffer_view_range(
+    device: &ID3D11Device,
+    buffer: &ID3D11Buffer,
+    first_element: u32,
+    num_elements: u32,
+) -> Result<Option<ID3D11ShaderResourceView>> {
+    let desc = D3D11_SHADER_RESOURCE_VIEW_DESC {
+        Format: DXGI_FORMAT_UNKNOWN,
+        ViewDimension: D3D11_SRV_DIMENSION_BUFFER,
+        Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 {
+            Buffer: D3D11_BUFFER_SRV {
+                Anonymous1: D3D11_BUFFER_SRV_0 {
+                    FirstElement: first_element,
+                },
+                Anonymous2: D3D11_BUFFER_SRV_1 {
+                    NumElements: num_elements,
+                },
+            },
+        },
+    };
+    let mut view = None;
+    unsafe { device.CreateShaderResourceView(buffer, Some(&desc), Some(&mut view)) }?;
+    Ok(view)
+}
+
 #[inline]
 fn update_buffer<T>(
     device_context: &ID3D11DeviceContext,

crates/gpui/src/scene.rs 🔗

@@ -151,30 +151,22 @@ impl Scene {
         ),
         allow(dead_code)
     )]
-    pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch<'_>> {
+    pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
         BatchIterator {
-            shadows: &self.shadows,
             shadows_start: 0,
             shadows_iter: self.shadows.iter().peekable(),
-            quads: &self.quads,
             quads_start: 0,
             quads_iter: self.quads.iter().peekable(),
-            paths: &self.paths,
             paths_start: 0,
             paths_iter: self.paths.iter().peekable(),
-            underlines: &self.underlines,
             underlines_start: 0,
             underlines_iter: self.underlines.iter().peekable(),
-            monochrome_sprites: &self.monochrome_sprites,
             monochrome_sprites_start: 0,
             monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
-            subpixel_sprites: &self.subpixel_sprites,
             subpixel_sprites_start: 0,
             subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
-            polychrome_sprites: &self.polychrome_sprites,
             polychrome_sprites_start: 0,
             polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
-            surfaces: &self.surfaces,
             surfaces_start: 0,
             surfaces_iter: self.surfaces.iter().peekable(),
         }
@@ -255,34 +247,26 @@ impl Primitive {
     allow(dead_code)
 )]
 struct BatchIterator<'a> {
-    shadows: &'a [Shadow],
     shadows_start: usize,
     shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
-    quads: &'a [Quad],
     quads_start: usize,
     quads_iter: Peekable<slice::Iter<'a, Quad>>,
-    paths: &'a [Path<ScaledPixels>],
     paths_start: usize,
     paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
-    underlines: &'a [Underline],
     underlines_start: usize,
     underlines_iter: Peekable<slice::Iter<'a, Underline>>,
-    monochrome_sprites: &'a [MonochromeSprite],
     monochrome_sprites_start: usize,
     monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
-    subpixel_sprites: &'a [SubpixelSprite],
     subpixel_sprites_start: usize,
     subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
-    polychrome_sprites: &'a [PolychromeSprite],
     polychrome_sprites_start: usize,
     polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
-    surfaces: &'a [PaintSurface],
     surfaces_start: usize,
     surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
 }
 
 impl<'a> Iterator for BatchIterator<'a> {
-    type Item = PrimitiveBatch<'a>;
+    type Item = PrimitiveBatch;
 
     fn next(&mut self) -> Option<Self::Item> {
         let mut orders_and_kinds = [
@@ -336,9 +320,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                     shadows_end += 1;
                 }
                 self.shadows_start = shadows_end;
-                Some(PrimitiveBatch::Shadows(
-                    &self.shadows[shadows_start..shadows_end],
-                ))
+                Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
             }
             PrimitiveKind::Quad => {
                 let quads_start = self.quads_start;
@@ -352,7 +334,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                     quads_end += 1;
                 }
                 self.quads_start = quads_end;
-                Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
+                Some(PrimitiveBatch::Quads(quads_start..quads_end))
             }
             PrimitiveKind::Path => {
                 let paths_start = self.paths_start;
@@ -366,7 +348,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                     paths_end += 1;
                 }
                 self.paths_start = paths_end;
-                Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
+                Some(PrimitiveBatch::Paths(paths_start..paths_end))
             }
             PrimitiveKind::Underline => {
                 let underlines_start = self.underlines_start;
@@ -380,9 +362,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                     underlines_end += 1;
                 }
                 self.underlines_start = underlines_end;
-                Some(PrimitiveBatch::Underlines(
-                    &self.underlines[underlines_start..underlines_end],
-                ))
+                Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
             }
             PrimitiveKind::MonochromeSprite => {
                 let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
@@ -402,7 +382,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                 self.monochrome_sprites_start = sprites_end;
                 Some(PrimitiveBatch::MonochromeSprites {
                     texture_id,
-                    sprites: &self.monochrome_sprites[sprites_start..sprites_end],
+                    range: sprites_start..sprites_end,
                 })
             }
             PrimitiveKind::SubpixelSprite => {
@@ -423,13 +403,13 @@ impl<'a> Iterator for BatchIterator<'a> {
                 self.subpixel_sprites_start = sprites_end;
                 Some(PrimitiveBatch::SubpixelSprites {
                     texture_id,
-                    sprites: &self.subpixel_sprites[sprites_start..sprites_end],
+                    range: sprites_start..sprites_end,
                 })
             }
             PrimitiveKind::PolychromeSprite => {
                 let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
                 let sprites_start = self.polychrome_sprites_start;
-                let mut sprites_end = self.polychrome_sprites_start + 1;
+                let mut sprites_end = sprites_start + 1;
                 self.polychrome_sprites_iter.next();
                 while self
                     .polychrome_sprites_iter
@@ -444,7 +424,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                 self.polychrome_sprites_start = sprites_end;
                 Some(PrimitiveBatch::PolychromeSprites {
                     texture_id,
-                    sprites: &self.polychrome_sprites[sprites_start..sprites_end],
+                    range: sprites_start..sprites_end,
                 })
             }
             PrimitiveKind::Surface => {
@@ -459,9 +439,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                     surfaces_end += 1;
                 }
                 self.surfaces_start = surfaces_end;
-                Some(PrimitiveBatch::Surfaces(
-                    &self.surfaces[surfaces_start..surfaces_end],
-                ))
+                Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
             }
         }
     }
@@ -475,25 +453,25 @@ impl<'a> Iterator for BatchIterator<'a> {
     ),
     allow(dead_code)
 )]
-pub(crate) enum PrimitiveBatch<'a> {
-    Shadows(&'a [Shadow]),
-    Quads(&'a [Quad]),
-    Paths(&'a [Path<ScaledPixels>]),
-    Underlines(&'a [Underline]),
+pub(crate) enum PrimitiveBatch {
+    Shadows(Range<usize>),
+    Quads(Range<usize>),
+    Paths(Range<usize>),
+    Underlines(Range<usize>),
     MonochromeSprites {
         texture_id: AtlasTextureId,
-        sprites: &'a [MonochromeSprite],
+        range: Range<usize>,
     },
     #[cfg_attr(target_os = "macos", allow(dead_code))]
     SubpixelSprites {
         texture_id: AtlasTextureId,
-        sprites: &'a [SubpixelSprite],
+        range: Range<usize>,
     },
     PolychromeSprites {
         texture_id: AtlasTextureId,
-        sprites: &'a [PolychromeSprite],
+        range: Range<usize>,
     },
-    Surfaces(&'a [PaintSurface]),
+    Surfaces(Range<usize>),
 }
 
 #[derive(Default, Debug, Clone)]