tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit bd246bc20b5d2cb76e75464d310247acc9d0abc1
parent 10851eae0acbbfff35050b18d12bafe429a18a67
Author: Nicolas Silva <nical@fastmail.com>
Date:   Tue,  9 Dec 2025 08:19:03 +0000

Bug 1996818 - Introduce GpuBufferHandle to catch accidental gpu buffer address reuse. r=gw

Differential Revision: https://phabricator.services.mozilla.com/D275048

Diffstat:
Mgfx/wr/webrender/src/batch.rs | 4+++-
Mgfx/wr/webrender/src/frame_builder.rs | 4++--
Mgfx/wr/webrender/src/gpu_types.rs | 6+++---
Mgfx/wr/webrender/src/image_source.rs | 2+-
Mgfx/wr/webrender/src/picture_textures.rs | 6+++---
Mgfx/wr/webrender/src/prim_store/mod.rs | 4++--
Mgfx/wr/webrender/src/render_task.rs | 2+-
Mgfx/wr/webrender/src/render_task_graph.rs | 6+++---
Mgfx/wr/webrender/src/renderer/gpu_buffer.rs | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mgfx/wr/webrender/src/renderer/mod.rs | 4++--
Mgfx/wr/webrender/src/resource_cache.rs | 9+++++----
Mgfx/wr/webrender/src/texture_cache.rs | 17+++++++++--------
12 files changed, 131 insertions(+), 37 deletions(-)

diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs @@ -1905,6 +1905,7 @@ impl BatchBuilder { ctx.resource_cache.fetch_glyphs( font, &glyph_keys, + &gpu_buffer_builder.f32, &mut self.glyph_fetch_buffer, |texture_id, glyph_format, glyphs| { debug_assert_ne!(texture_id, TextureSource::Invalid); @@ -2603,7 +2604,8 @@ impl BatchBuilder { }, }; - let uv_rect_address = source.write_gpu_blocks(&mut gpu_buffer_builder.f32); + let uv_rect_handle = source.write_gpu_blocks(&mut gpu_buffer_builder.f32); + let uv_rect_address = gpu_buffer_builder.f32.resolve_handle(uv_rect_handle); self.add_brush_instance_to_batches( key, diff --git a/gfx/wr/webrender/src/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs @@ -658,8 +658,8 @@ impl FrameBuilder { let mut frame_memory = FrameMemory::new(chunk_pool, stamp.frame_id()); // TODO(gw): Recycle backing vec buffers for gpu buffer builder between frames let mut gpu_buffer_builder = GpuBufferBuilder { - f32: GpuBufferBuilderF::new(&frame_memory, 8 * 1024), - i32: GpuBufferBuilderI::new(&frame_memory, 2 * 1024), + f32: GpuBufferBuilderF::new(&frame_memory, 8 * 1024, stamp.frame_id()), + i32: GpuBufferBuilderI::new(&frame_memory, 2 * 1024, stamp.frame_id()), }; profile.set(profiler::PRIMITIVES, scene.prim_instances.len()); diff --git a/gfx/wr/webrender/src/gpu_types.rs b/gfx/wr/webrender/src/gpu_types.rs @@ -12,7 +12,7 @@ use crate::internal_types::{FastHashMap, FrameVec, FrameMemory}; use crate::prim_store::ClipData; use crate::render_task::RenderTaskAddress; use crate::render_task_graph::RenderTaskId; -use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF, GpuBufferWriterF, ShaderColorMode}; +use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF, GpuBufferHandle, GpuBufferWriterF, ShaderColorMode}; use std::i32; use crate::util::{MatrixHelpers, TransformedRectKind}; use glyph_rasterizer::SubpixelDirection; @@ -1006,10 +1006,10 @@ pub struct ImageSource { } impl ImageSource { - pub fn write_gpu_blocks(&self, gpu_buffer: &mut GpuBufferBuilderF) -> GpuBufferAddress { + pub fn write_gpu_blocks(&self, gpu_buffer: &mut GpuBufferBuilderF) -> GpuBufferHandle { let mut writer = gpu_buffer.write_blocks(6); self.push_gpu_blocks(&mut writer); - writer.finish() + writer.finish_with_handle() } pub fn push_gpu_blocks(&self, writer: &mut GpuBufferWriterF) { diff --git a/gfx/wr/webrender/src/image_source.rs b/gfx/wr/webrender/src/image_source.rs @@ -65,7 +65,7 @@ pub fn resolve_image( deferred_resolves.push(DeferredResolve { image_properties, - address: uv_rect_address, + handle: uv_rect_address, rendering: request.rendering, is_composited, }); diff --git a/gfx/wr/webrender/src/picture_textures.rs b/gfx/wr/webrender/src/picture_textures.rs @@ -14,7 +14,7 @@ use crate::internal_types::{ use crate::profiler::{self, TransactionProfile}; use crate::gpu_types::{ImageSource, UvRectKind}; use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle}; -use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF}; +use crate::renderer::{GpuBufferBuilderF, GpuBufferHandle}; #[derive(Debug, PartialEq)] @@ -43,7 +43,7 @@ pub struct PictureCacheEntry { // entirely in future (or move to EntryDetails::Picture). pub last_access: FrameStamp, /// Handle to the resource rect in the float GPU buffer. - pub uv_rect_handle: GpuBufferAddress, + pub uv_rect_handle: GpuBufferHandle, /// The actual device texture ID this is part of. pub texture_id: CacheTextureId, } @@ -218,7 +218,7 @@ impl PictureTextures { let cache_entry = PictureCacheEntry { size: tile_size, last_access: self.now, - uv_rect_handle: GpuBufferAddress::INVALID, + uv_rect_handle: GpuBufferHandle::INVALID, texture_id, }; diff --git a/gfx/wr/webrender/src/prim_store/mod.rs b/gfx/wr/webrender/src/prim_store/mod.rs @@ -13,7 +13,7 @@ use crate::composite::CompositorSurfaceKind; use crate::clip::ClipLeafId; use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState}; use crate::quad::QuadTileClassifier; -use crate::renderer::{GpuBufferAddress, GpuBufferWriterF}; +use crate::renderer::{GpuBufferAddress, GpuBufferHandle, GpuBufferWriterF}; use crate::segment::EdgeAaSegmentMask; use crate::border::BorderSegmentCacheKey; use crate::debug_item::{DebugItem, DebugMessage}; @@ -90,7 +90,7 @@ impl PrimitiveOpacity { #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct DeferredResolve { - pub address: GpuBufferAddress, + pub handle: GpuBufferHandle, pub image_properties: ImageProperties, pub rendering: ImageRendering, pub is_composited: bool, diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs @@ -2785,7 +2785,7 @@ impl RenderTask { uv_rect_kind: self.uv_rect_kind, }; - self.uv_rect_handle = image_source.write_gpu_blocks(&mut gpu_buffer.f32); + self.uv_rect_handle = image_source.write_gpu_blocks(&mut gpu_buffer.f32).address_unchecked(); } /// Called by the render task cache. diff --git a/gfx/wr/webrender/src/render_task_graph.rs b/gfx/wr/webrender/src/render_task_graph.rs @@ -645,7 +645,7 @@ impl RenderTaskGraphBuilder { // We'll handle it later and it's easier to not have to // deal with unexpected location variants like // RenderTaskLocation::CacheRequest when we do. - task.uv_rect_handle = cache_item.uv_rect_handle; + task.uv_rect_handle = gpu_buffers.f32.resolve_handle(cache_item.uv_rect_handle); if let RenderTaskLocation::CacheRequest { .. } = &task.location { let source = cache_item.texture_id; task.location = RenderTaskLocation::Static { @@ -1104,8 +1104,8 @@ impl RenderTaskGraphBuilder { let frame_memory = FrameMemory::fallback(); let mut gpu_buffers = GpuBufferBuilder { - f32: GpuBufferBuilderF::new(&frame_memory, 0), - i32: GpuBufferBuilderI::new(&frame_memory, 0), + f32: GpuBufferBuilderF::new(&frame_memory, 0, FrameId::first()), + i32: GpuBufferBuilderI::new(&frame_memory, 0, FrameId::first()), }; let g = self.end_frame(&mut rc, &mut gpu_buffers, &mut frame_memory.new_vec(), 2048, &frame_memory); g.print(); diff --git a/gfx/wr/webrender/src/renderer/gpu_buffer.rs b/gfx/wr/webrender/src/renderer/gpu_buffer.rs @@ -10,7 +10,7 @@ use std::i32; use crate::gpu_types::UvRectKind; -use crate::internal_types::{FrameMemory, FrameVec}; +use crate::internal_types::{FrameId, FrameMemory, FrameVec}; use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH; use crate::util::ScaleOffset; use api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceRect, LayoutRect, PictureRect}; @@ -68,6 +68,45 @@ pub struct GpuBufferBlockI { data: [i32; 4], } +/// GpuBuffer handle is similar to GpuBufferAddress with additional checks +/// to avoid accidentally using the same handle in multiple frames. +/// +/// Do not send GpuBufferHandle to the GPU directly. Instead use a GpuBuffer +/// or GpuBufferBuilder to resolve the handle into a GpuBufferAddress that +/// can be placed into GPU data. +/// +/// The extra checks consists into storing an 8 bit epoch in the upper 8 bits +/// of the handle. The epoch will be reused every 255 frames so this is not +/// a mechanism that one can rely on to store and reuse handles over multiple +/// frames. It is only a mechanism to catch mistakes where a handle is +/// accidentally used in the wrong frame and panic. +#[repr(transparent)] +#[derive(Copy, Clone, MallocSizeOf, Eq, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GpuBufferHandle(u32); + +impl GpuBufferHandle { + pub const INVALID: GpuBufferHandle = GpuBufferHandle(u32::MAX - 1); + const EPOCH_MASK: u32 = 0xFF000000; + + fn new(addr: u32, epoch: u32) -> Self { + Self(addr | epoch) + } + + pub fn address_unchecked(&self) -> GpuBufferAddress { + GpuBufferAddress(self.0 & !Self::EPOCH_MASK) + } +} + +impl std::fmt::Debug for GpuBufferHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let addr = self.0 & !Self::EPOCH_MASK; + let epoch = (self.0 & Self::EPOCH_MASK) >> 24; + write!(f, "#{addr}@{epoch}") + } +} + // TODO(gw): Temporarily encode GPU Cache addresses as a single int. // In the future, we can change the PrimitiveInstanceData struct // to use 2x u16 for the vertex attribute instead of an i32. @@ -239,6 +278,7 @@ pub struct GpuBufferWriter<'a, T> { deferred: &'a mut Vec<DeferredBlock>, index: usize, max_block_count: usize, + epoch: u32, } impl<'a, T> GpuBufferWriter<'a, T> where T: Texel { @@ -247,12 +287,14 @@ impl<'a, T> GpuBufferWriter<'a, T> where T: Texel { deferred: &'a mut Vec<DeferredBlock>, index: usize, max_block_count: usize, + epoch: u32, ) -> Self { GpuBufferWriter { buffer, deferred, index, max_block_count, + epoch, } } @@ -280,6 +322,13 @@ impl<'a, T> GpuBufferWriter<'a, T> where T: Texel { GpuBufferAddress(self.index as u32) } + + /// Close this writer, returning the GPU address of this set of block(s). + pub fn finish_with_handle(self) -> GpuBufferHandle { + assert!(self.buffer.len() <= self.index + self.max_block_count); + + GpuBufferHandle::new(self.index as u32, self.epoch) + } } impl<'a, T> Drop for GpuBufferWriter<'a, T> { @@ -295,13 +344,19 @@ pub struct GpuBufferBuilderImpl<T> { // `deferred` is only used during frame building and not sent with the // built frame, so it does not use the same allocator. deferred: Vec<DeferredBlock>, + + epoch: u32, } impl<T> GpuBufferBuilderImpl<T> where T: Texel + std::convert::From<DeviceIntRect> { - pub fn new(memory: &FrameMemory, capacity: usize) -> Self { + pub fn new(memory: &FrameMemory, capacity: usize, frame_id: FrameId) -> Self { + // Pick the first 8 bits of the frame id and store them in the upper bits + // of the handles. + let epoch = ((frame_id.as_u64() % 254) as u32 + 1) << 24; GpuBufferBuilderImpl { data: memory.new_vec_with_capacity(capacity), deferred: Vec::new(), + epoch, } } @@ -318,7 +373,7 @@ impl<T> GpuBufferBuilderImpl<T> where T: Texel + std::convert::From<DeviceIntRec self.data.extend_from_slice(blocks); - GpuBufferAddress(index as u32) + GpuBufferAddress(index as u32 | self.epoch) } /// Begin writing a specific number of blocks @@ -337,12 +392,13 @@ impl<T> GpuBufferBuilderImpl<T> where T: Texel + std::convert::From<DeviceIntRec &mut self.deferred, index, max_block_count, + self.epoch, ) } // Reserve space in the gpu buffer for data that will be written by the // renderer. - pub fn reserve_renderer_deferred_blocks(&mut self, block_count: usize) -> GpuBufferAddress { + pub fn reserve_renderer_deferred_blocks(&mut self, block_count: usize) -> GpuBufferHandle { ensure_row_capacity(&mut self.data, block_count); let index = self.data.len(); @@ -352,7 +408,7 @@ impl<T> GpuBufferBuilderImpl<T> where T: Texel + std::convert::From<DeviceIntRec self.data.push(Default::default()); } - GpuBufferAddress(index as u32) + GpuBufferHandle::new(index as u32, self.epoch) } pub fn finalize( @@ -399,7 +455,29 @@ impl<T> GpuBufferBuilderImpl<T> where T: Texel + std::convert::From<DeviceIntRec data: self.data, size: DeviceIntSize::new(MAX_VERTEX_TEXTURE_WIDTH as i32, (len / MAX_VERTEX_TEXTURE_WIDTH) as i32), format: T::image_format(), + epoch: self.epoch, + } + } + + pub fn resolve_handle(&self, handle: GpuBufferHandle) -> GpuBufferAddress { + if handle == GpuBufferHandle::INVALID { + return GpuBufferAddress::INVALID; + } + + let epoch = handle.0 & GpuBufferHandle::EPOCH_MASK; + assert!(self.epoch == epoch); + + GpuBufferAddress(handle.0 & !GpuBufferHandle::EPOCH_MASK) + } + + /// Panics if the handle cannot be used this frame. + #[allow(unused)] + pub fn check_handle(&self, handle: GpuBufferHandle) { + if handle == GpuBufferHandle::INVALID { + return; } + let epoch = handle.0 & GpuBufferHandle::EPOCH_MASK; + assert!(self.epoch == epoch); } } @@ -422,19 +500,31 @@ pub struct GpuBuffer<T> { pub data: FrameVec<T>, pub size: DeviceIntSize, pub format: ImageFormat, + epoch: u32, } impl<T> GpuBuffer<T> { pub fn is_empty(&self) -> bool { self.data.is_empty() } + + pub fn resolve_handle(&self, handle: GpuBufferHandle) -> GpuBufferAddress { + if handle == GpuBufferHandle::INVALID { + return GpuBufferAddress::INVALID; + } + + let epoch = handle.0 & GpuBufferHandle::EPOCH_MASK; + assert!(self.epoch == epoch); + + GpuBufferAddress(handle.0 & !GpuBufferHandle::EPOCH_MASK) + } } #[test] fn test_gpu_buffer_sizing_push() { let frame_memory = FrameMemory::fallback(); let render_task_graph = RenderTaskGraph::new_for_testing(); - let mut builder = GpuBufferBuilderF::new(&frame_memory, 0); + let mut builder = GpuBufferBuilderF::new(&frame_memory, 0, FrameId::first()); let row = vec![GpuBufferBlockF::EMPTY; MAX_VERTEX_TEXTURE_WIDTH]; builder.push(&row); @@ -450,7 +540,7 @@ fn test_gpu_buffer_sizing_push() { fn test_gpu_buffer_sizing_writer() { let frame_memory = FrameMemory::fallback(); let render_task_graph = RenderTaskGraph::new_for_testing(); - let mut builder = GpuBufferBuilderF::new(&frame_memory, 0); + let mut builder = GpuBufferBuilderF::new(&frame_memory, 0, FrameId::first()); let mut writer = builder.write_blocks(MAX_VERTEX_TEXTURE_WIDTH); for _ in 0 .. MAX_VERTEX_TEXTURE_WIDTH { diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs @@ -129,7 +129,7 @@ pub use debug::DebugRenderer; pub use shade::{PendingShadersToPrecache, Shaders, SharedShaders}; pub use vertex::{desc, VertexArrayKind, MAX_VERTEX_TEXTURE_WIDTH}; pub use gpu_buffer::{GpuBuffer, GpuBufferF, GpuBufferBuilderF, GpuBufferI, GpuBufferBuilderI}; -pub use gpu_buffer::{GpuBufferAddress, GpuBufferBuilder, GpuBufferWriterF, GpuBufferBlockF}; +pub use gpu_buffer::{GpuBufferHandle, GpuBufferAddress, GpuBufferBuilder, GpuBufferWriterF, GpuBufferBlockF}; /// The size of the array of each type of vertex data texture that /// is round-robin-ed each frame during bind_frame_data. Doing this @@ -4965,7 +4965,7 @@ impl Renderer { .external_images .insert(DeferredResolveIndex(i as u32), texture); - let addr = deferred_resolve.address; + let addr = gpu_buffer.resolve_handle(deferred_resolve.handle); let index = addr.as_u32() as usize; gpu_buffer.data[index] = image.uv.to_array().into(); gpu_buffer.data[index + 1] = [0f32; 4].into(); diff --git a/gfx/wr/webrender/src/resource_cache.rs b/gfx/wr/webrender/src/resource_cache.rs @@ -36,7 +36,7 @@ use crate::profiler::{self, TransactionProfile, bytes_to_mb}; use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder}; use crate::render_task_cache::{RenderTaskCache, RenderTaskCacheKey, RenderTaskParent}; use crate::render_task_cache::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle}; -use crate::renderer::{GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF}; +use crate::renderer::{GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF, GpuBufferHandle}; use crate::surface::SurfaceBuilder; use euclid::point2; use smallvec::SmallVec; @@ -83,7 +83,7 @@ pub struct GlyphFetchResult { #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct CacheItem { pub texture_id: TextureSource, - pub uv_rect_handle: GpuBufferAddress, + pub uv_rect_handle: GpuBufferHandle, pub uv_rect: DeviceIntRect, pub user_data: [f32; 4], } @@ -92,7 +92,7 @@ impl CacheItem { pub fn invalid() -> Self { CacheItem { texture_id: TextureSource::Invalid, - uv_rect_handle: GpuBufferAddress::INVALID, + uv_rect_handle: GpuBufferHandle::INVALID, uv_rect: DeviceIntRect::zero(), user_data: [0.0; 4], } @@ -1316,6 +1316,7 @@ impl ResourceCache { &self, mut font: FontInstance, glyph_keys: &[GlyphKey], + gpu_buffer: &GpuBufferBuilderF, fetch_buffer: &mut Vec<GlyphFetchResult>, mut f: F, ) where @@ -1348,7 +1349,7 @@ impl ResourceCache { } fetch_buffer.push(GlyphFetchResult { index_in_text_run: loop_index as i32, - uv_rect_address: cache_item.uv_rect_handle, + uv_rect_address: gpu_buffer.resolve_handle(cache_item.uv_rect_handle), offset: DevicePoint::new(cache_item.user_data[0], cache_item.user_data[1]), size: cache_item.uv_rect.size(), scale: cache_item.user_data[2], diff --git a/gfx/wr/webrender/src/texture_cache.rs b/gfx/wr/webrender/src/texture_cache.rs @@ -17,7 +17,7 @@ use crate::internal_types::{ }; use crate::lru_cache::LRUCache; use crate::profiler::{self, TransactionProfile}; -use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF}; +use crate::renderer::{GpuBufferBuilderF, GpuBufferHandle}; use crate::resource_cache::{CacheItem, CachedImageData}; use crate::texture_pack::{ AllocatorList, AllocId, AtlasAllocatorList, ShelfAllocator, ShelfAllocatorOptions, @@ -103,7 +103,7 @@ pub struct CacheEntry { // entirely in future (or move to PictureCacheEntry). pub last_access: FrameStamp, /// Address of the resource rect in the GPU cache. - pub uv_rect_handle: GpuBufferAddress, + pub uv_rect_handle: GpuBufferHandle, /// Image format of the data that the entry expects. pub input_format: ImageFormat, pub filter: TextureFilter, @@ -143,7 +143,7 @@ impl CacheEntry { input_format: params.descriptor.format, filter: params.filter, swizzle, - uv_rect_handle: GpuBufferAddress::INVALID, + uv_rect_handle: GpuBufferHandle::INVALID, eviction_notice: None, uv_rect_kind: params.uv_rect_kind, shader: TargetShader::Default, @@ -803,7 +803,7 @@ impl TextureCache { allocated_size_in_bytes: new_bytes, }; - entry.uv_rect_handle = GpuBufferAddress::INVALID; + entry.uv_rect_handle = GpuBufferHandle::INVALID; let src_rect = DeviceIntRect::from_origin_and_size(change.old_rect.min, entry.size); let dst_rect = DeviceIntRect::from_origin_and_size(change.new_rect.min, entry.size); @@ -1022,7 +1022,7 @@ impl TextureCache { pub fn try_get_cache_location( &self, handle: &TextureCacheHandle, - ) -> Option<(CacheTextureId, DeviceIntRect, Swizzle, GpuBufferAddress, [f32; 4])> { + ) -> Option<(CacheTextureId, DeviceIntRect, Swizzle, GpuBufferHandle, [f32; 4])> { let entry = self.get_entry_opt(handle)?; let origin = entry.details.describe(); Some(( @@ -1042,7 +1042,7 @@ impl TextureCache { pub fn get_cache_location( &self, handle: &TextureCacheHandle, - ) -> (CacheTextureId, DeviceIntRect, Swizzle, GpuBufferAddress, [f32; 4]) { + ) -> (CacheTextureId, DeviceIntRect, Swizzle, GpuBufferHandle, [f32; 4]) { self.try_get_cache_location(handle).expect("BUG: was dropped from cache or not updated!") } @@ -1349,7 +1349,7 @@ impl TextureCache { alloc_id, allocated_size_in_bytes, }, - uv_rect_handle: GpuBufferAddress::INVALID, + uv_rect_handle: GpuBufferHandle::INVALID, input_format: params.descriptor.format, filter: params.filter, swizzle, @@ -1648,6 +1648,7 @@ impl TextureCacheUpdate { #[cfg(test)] mod test_texture_cache { use crate::renderer::GpuBufferBuilderF; + use crate::internal_types::FrameId; #[test] fn check_allocation_size_balance() { @@ -1664,7 +1665,7 @@ mod test_texture_cache { use euclid::size2; let mut texture_cache = TextureCache::new_for_testing(2048, ImageFormat::BGRA8); let memory = FrameMemory::fallback(); - let mut gpu_buffer = GpuBufferBuilderF::new(&memory, 0); + let mut gpu_buffer = GpuBufferBuilderF::new(&memory, 0, FrameId::first()); let sizes: &[DeviceIntSize] = &[ size2(23, 27),