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:
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),