blade: Use BufferBelt from blade-utils (#12411)

Dzmitry Malyshau created

Release Notes:

- N/A

Follow-up to #12340
Carries https://github.com/kvark/blade/pull/122 and
https://github.com/kvark/blade/pull/119

Change summary

Cargo.lock                                       |  18 ++
Cargo.toml                                       |   5 
crates/gpui/Cargo.toml                           |   4 
crates/gpui/src/platform/blade.rs                |   3 
crates/gpui/src/platform/blade/blade_atlas.rs    |   8 
crates/gpui/src/platform/blade/blade_belt.rs     | 101 ------------------
crates/gpui/src/platform/blade/blade_renderer.rs |  21 +-
7 files changed, 36 insertions(+), 124 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1509,7 +1509,7 @@ dependencies = [
 [[package]]
 name = "blade-graphics"
 version = "0.4.0"
-source = "git+https://github.com/kvark/blade?rev=9c9cabf69e869fc7d9aef2fc76f7d5c354d5710a#9c9cabf69e869fc7d9aef2fc76f7d5c354d5710a"
+source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
 dependencies = [
  "ash",
  "ash-window",
@@ -1539,13 +1539,24 @@ dependencies = [
 [[package]]
 name = "blade-macros"
 version = "0.2.1"
-source = "git+https://github.com/kvark/blade?rev=9c9cabf69e869fc7d9aef2fc76f7d5c354d5710a#9c9cabf69e869fc7d9aef2fc76f7d5c354d5710a"
+source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn 2.0.59",
 ]
 
+[[package]]
+name = "blade-util"
+version = "0.1.0"
+source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
+dependencies = [
+ "blade-graphics",
+ "bytemuck",
+ "log",
+ "profiling",
+]
+
 [[package]]
 name = "block"
 version = "0.1.6"
@@ -3378,7 +3389,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
 dependencies = [
- "libloading 0.8.0",
+ "libloading 0.7.4",
 ]
 
 [[package]]
@@ -4689,6 +4700,7 @@ dependencies = [
  "bindgen 0.65.1",
  "blade-graphics",
  "blade-macros",
+ "blade-util",
  "block",
  "bytemuck",
  "calloop",

Cargo.toml 🔗

@@ -262,8 +262,9 @@ async-tar = "0.4.2"
 async-trait = "0.1"
 async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
 bitflags = "2.4.2"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "9c9cabf69e869fc7d9aef2fc76f7d5c354d5710a" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "9c9cabf69e869fc7d9aef2fc76f7d5c354d5710a" }
+blade-graphics = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
+blade-macros = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
+blade-util = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
 cap-std = "3.0"
 cargo_toml = "0.20"
 chrono = { version = "0.4", features = ["serde"] }

crates/gpui/Cargo.toml 🔗

@@ -14,7 +14,7 @@ workspace = true
 default = []
 test-support = ["backtrace", "collections/test-support", "util/test-support", "http/test-support"]
 runtime_shaders = []
-macos-blade = ["blade-graphics", "blade-macros", "bytemuck"]
+macos-blade = ["blade-graphics", "blade-macros", "blade-util", "bytemuck"]
 
 [lib]
 path = "src/gpui.rs"
@@ -26,6 +26,7 @@ async-task = "4.7"
 backtrace = { version = "0.3", optional = true }
 blade-graphics = { workspace = true, optional = true }
 blade-macros = { workspace = true, optional = true }
+blade-util = { workspace = true, optional = true }
 bytemuck = { version = "1", optional = true }
 collections.workspace = true
 ctor.workspace = true
@@ -96,6 +97,7 @@ flume = "0.11"
 #TODO: use these on all platforms
 blade-graphics.workspace = true
 blade-macros.workspace = true
+blade-util.workspace = true
 bytemuck = "1"
 cosmic-text = "0.11.2"
 copypasta = "0.10.1"

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

@@ -1,8 +1,5 @@
 mod blade_atlas;
-mod blade_belt;
 mod blade_renderer;
 
 pub(crate) use blade_atlas::*;
 pub(crate) use blade_renderer::*;
-
-use blade_belt::*;

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

@@ -1,10 +1,10 @@
-use super::{BladeBelt, BladeBeltDescriptor};
 use crate::{
     AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
     Point, Size,
 };
 use anyhow::Result;
 use blade_graphics as gpu;
+use blade_util::{BufferBelt, BufferBeltDescriptor};
 use collections::FxHashMap;
 use etagere::BucketedAtlasAllocator;
 use parking_lot::Mutex;
@@ -22,7 +22,7 @@ struct PendingUpload {
 
 struct BladeAtlasState {
     gpu: Arc<gpu::Context>,
-    upload_belt: BladeBelt,
+    upload_belt: BufferBelt,
     storage: BladeAtlasStorage,
     tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
     initializations: Vec<AtlasTextureId>,
@@ -48,7 +48,7 @@ impl BladeAtlas {
     pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
         BladeAtlas(Mutex::new(BladeAtlasState {
             gpu: Arc::clone(gpu),
-            upload_belt: BladeBelt::new(BladeBeltDescriptor {
+            upload_belt: BufferBelt::new(BufferBeltDescriptor {
                 memory: gpu::Memory::Upload,
                 min_chunk_size: 0x10000,
                 alignment: 64, // Vulkan `optimalBufferCopyOffsetAlignment` on Intel XE
@@ -212,7 +212,7 @@ impl BladeAtlasState {
     }
 
     fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
-        let data = unsafe { self.upload_belt.alloc_data(bytes, &self.gpu) };
+        let data = self.upload_belt.alloc_bytes(bytes, &self.gpu);
         self.uploads.push(PendingUpload { id, bounds, data });
     }
 

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

@@ -1,101 +0,0 @@
-use blade_graphics as gpu;
-use std::mem;
-
-struct ReusableBuffer {
-    raw: gpu::Buffer,
-    size: u64,
-}
-
-pub struct BladeBeltDescriptor {
-    pub memory: gpu::Memory,
-    pub min_chunk_size: u64,
-    pub alignment: u64,
-}
-
-/// A belt of buffers, used by the BladeAtlas to cheaply
-/// find staging space for uploads.
-pub struct BladeBelt {
-    desc: BladeBeltDescriptor,
-    buffers: Vec<(ReusableBuffer, gpu::SyncPoint)>,
-    active: Vec<(ReusableBuffer, u64)>,
-}
-
-impl BladeBelt {
-    pub fn new(desc: BladeBeltDescriptor) -> Self {
-        assert_ne!(desc.alignment, 0);
-        Self {
-            desc,
-            buffers: Vec::new(),
-            active: Vec::new(),
-        }
-    }
-
-    pub fn destroy(&mut self, gpu: &gpu::Context) {
-        for (buffer, _) in self.buffers.drain(..) {
-            gpu.destroy_buffer(buffer.raw);
-        }
-        for (buffer, _) in self.active.drain(..) {
-            gpu.destroy_buffer(buffer.raw);
-        }
-    }
-
-    #[profiling::function]
-    pub fn alloc(&mut self, size: u64, gpu: &gpu::Context) -> gpu::BufferPiece {
-        for &mut (ref rb, ref mut offset) in self.active.iter_mut() {
-            let aligned = offset.next_multiple_of(self.desc.alignment);
-            if aligned + size <= rb.size {
-                let piece = rb.raw.at(aligned);
-                *offset = aligned + size;
-                return piece;
-            }
-        }
-
-        let index_maybe = self
-            .buffers
-            .iter()
-            .position(|(rb, sp)| size <= rb.size && gpu.wait_for(sp, 0));
-        if let Some(index) = index_maybe {
-            let (rb, _) = self.buffers.remove(index);
-            let piece = rb.raw.into();
-            self.active.push((rb, size));
-            return piece;
-        }
-
-        let chunk_index = self.buffers.len() + self.active.len();
-        let chunk_size = size.max(self.desc.min_chunk_size);
-        let chunk = gpu.create_buffer(gpu::BufferDesc {
-            name: &format!("chunk-{}", chunk_index),
-            size: chunk_size,
-            memory: self.desc.memory,
-        });
-        let rb = ReusableBuffer {
-            raw: chunk,
-            size: chunk_size,
-        };
-        self.active.push((rb, size));
-        chunk.into()
-    }
-
-    // SAFETY: T should be zeroable and ordinary data, no references, pointers, cells or other complicated data type.
-    pub unsafe fn alloc_data<T>(&mut self, data: &[T], gpu: &gpu::Context) -> gpu::BufferPiece {
-        assert!(!data.is_empty());
-        let type_alignment = mem::align_of::<T>() as u64;
-        debug_assert_eq!(
-            self.desc.alignment % type_alignment,
-            0,
-            "Type alignment {} is too big",
-            type_alignment
-        );
-        let total_bytes = std::mem::size_of_val(data);
-        let bp = self.alloc(total_bytes as u64, gpu);
-        unsafe {
-            std::ptr::copy_nonoverlapping(data.as_ptr() as *const u8, bp.data(), total_bytes);
-        }
-        bp
-    }
-
-    pub fn flush(&mut self, sp: &gpu::SyncPoint) {
-        self.buffers
-            .extend(self.active.drain(..).map(|(rb, _)| (rb, sp.clone())));
-    }
-}

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

@@ -1,7 +1,7 @@
 // Doing `if let` gives you nice scoping with passes/encoders
 #![allow(irrefutable_let_patterns)]
 
-use super::{BladeAtlas, BladeBelt, BladeBeltDescriptor, PATH_TEXTURE_FORMAT};
+use super::{BladeAtlas, PATH_TEXTURE_FORMAT};
 use crate::{
     AtlasTextureKind, AtlasTile, Bounds, ContentMask, Hsla, MonochromeSprite, Path, PathId,
     PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
@@ -15,6 +15,7 @@ use media::core_video::CVMetalTextureCache;
 use std::{ffi::c_void, ptr::NonNull};
 
 use blade_graphics as gpu;
+use blade_util::{BufferBelt, BufferBeltDescriptor};
 use std::{mem, sync::Arc};
 
 const MAX_FRAME_TIME_MS: u32 = 1000;
@@ -346,7 +347,7 @@ pub struct BladeRenderer {
     command_encoder: gpu::CommandEncoder,
     last_sync_point: Option<gpu::SyncPoint>,
     pipelines: BladePipelines,
-    instance_belt: BladeBelt,
+    instance_belt: BufferBelt,
     path_tiles: HashMap<PathId, AtlasTile>,
     atlas: Arc<BladeAtlas>,
     atlas_sampler: gpu::Sampler,
@@ -371,7 +372,7 @@ impl BladeRenderer {
             buffer_count: 2,
         });
         let pipelines = BladePipelines::new(&gpu, surface_info);
-        let instance_belt = BladeBelt::new(BladeBeltDescriptor {
+        let instance_belt = BufferBelt::new(BufferBeltDescriptor {
             memory: gpu::Memory::Shared,
             min_chunk_size: 0x1000,
             alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
@@ -492,7 +493,7 @@ impl BladeRenderer {
                 pad: 0,
             };
 
-            let vertex_buf = unsafe { self.instance_belt.alloc_data(&vertices, &self.gpu) };
+            let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
             let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
                 colors: &[gpu::RenderTarget {
                     view: tex_info.raw_view,
@@ -557,7 +558,7 @@ impl BladeRenderer {
                 match batch {
                     PrimitiveBatch::Quads(quads) => {
                         let instance_buf =
-                            unsafe { self.instance_belt.alloc_data(quads, &self.gpu) };
+                            unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
                         let mut encoder = pass.with(&self.pipelines.quads);
                         encoder.bind(
                             0,
@@ -570,7 +571,7 @@ impl BladeRenderer {
                     }
                     PrimitiveBatch::Shadows(shadows) => {
                         let instance_buf =
-                            unsafe { self.instance_belt.alloc_data(shadows, &self.gpu) };
+                            unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
                         let mut encoder = pass.with(&self.pipelines.shadows);
                         encoder.bind(
                             0,
@@ -598,7 +599,7 @@ impl BladeRenderer {
                             }];
 
                             let instance_buf =
-                                unsafe { self.instance_belt.alloc_data(&sprites, &self.gpu) };
+                                unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
                             encoder.bind(
                                 0,
                                 &ShaderPathsData {
@@ -613,7 +614,7 @@ impl BladeRenderer {
                     }
                     PrimitiveBatch::Underlines(underlines) => {
                         let instance_buf =
-                            unsafe { self.instance_belt.alloc_data(underlines, &self.gpu) };
+                            unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
                         let mut encoder = pass.with(&self.pipelines.underlines);
                         encoder.bind(
                             0,
@@ -630,7 +631,7 @@ impl BladeRenderer {
                     } => {
                         let tex_info = self.atlas.get_texture_info(texture_id);
                         let instance_buf =
-                            unsafe { self.instance_belt.alloc_data(sprites, &self.gpu) };
+                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
                         let mut encoder = pass.with(&self.pipelines.mono_sprites);
                         encoder.bind(
                             0,
@@ -649,7 +650,7 @@ impl BladeRenderer {
                     } => {
                         let tex_info = self.atlas.get_texture_info(texture_id);
                         let instance_buf =
-                            unsafe { self.instance_belt.alloc_data(sprites, &self.gpu) };
+                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
                         let mut encoder = pass.with(&self.pipelines.poly_sprites);
                         encoder.bind(
                             0,