use crate::{DevicePixels, Size};
#[derive(Clone, Debug, Default)]
pub enum AppProfile {
#[default]
Desktop,
Utility,
Minimal,
Custom(AppResourceProfile),
}
impl AppProfile {
pub fn to_resource_profile(&self) -> AppResourceProfile {
match self {
AppProfile::Desktop => AppResourceProfile::desktop(),
AppProfile::Utility => AppResourceProfile::utility(),
AppProfile::Minimal => AppResourceProfile::minimal(),
AppProfile::Custom(profile) => profile.clone(),
}
}
}
impl From<AppProfile> for AppResourceProfile {
fn from(profile: AppProfile) -> Self {
profile.to_resource_profile()
}
}
#[derive(Clone, Debug)]
pub struct AppResourceProfile {
pub text: TextResourceBudget,
pub gpu: GpuResourceBudget,
pub element_arena_size: usize,
}
impl AppResourceProfile {
pub fn desktop() -> Self {
Self {
text: TextResourceBudget {
line_layout_cache_max_entries: 10_000,
line_layout_cache_low_watermark: 5_000,
raster_bounds_cache_max_entries: None,
},
gpu: GpuResourceBudget {
atlas_initial_size: 1024,
instance_buffer_initial_size: 2 * 1024 * 1024,
instance_buffer_max_size: 256 * 1024 * 1024,
},
element_arena_size: 1024 * 1024,
}
}
pub fn utility() -> Self {
Self {
text: TextResourceBudget {
line_layout_cache_max_entries: 3_000,
line_layout_cache_low_watermark: 2_000,
raster_bounds_cache_max_entries: Some(5_000),
},
gpu: GpuResourceBudget {
atlas_initial_size: 1024,
instance_buffer_initial_size: 1024 * 1024,
instance_buffer_max_size: 64 * 1024 * 1024,
},
element_arena_size: 512 * 1024,
}
}
pub fn minimal() -> Self {
Self {
text: TextResourceBudget {
line_layout_cache_max_entries: 500,
line_layout_cache_low_watermark: 400,
raster_bounds_cache_max_entries: Some(2_000),
},
gpu: GpuResourceBudget {
atlas_initial_size: 512,
instance_buffer_initial_size: 512 * 1024,
instance_buffer_max_size: 16 * 1024 * 1024,
},
element_arena_size: 256 * 1024,
}
}
}
impl Default for AppResourceProfile {
fn default() -> Self {
Self::desktop()
}
}
#[derive(Clone, Debug)]
pub struct TextResourceBudget {
pub line_layout_cache_max_entries: usize,
pub line_layout_cache_low_watermark: usize,
pub raster_bounds_cache_max_entries: Option<usize>,
}
#[cfg_attr(
not(any(target_os = "linux", target_os = "freebsd", test)),
allow(dead_code)
)]
const MIN_INSTANCE_BUFFER_CAPACITY: u64 = 16;
#[derive(Clone, Debug)]
pub struct GpuResourceBudget {
pub atlas_initial_size: u32,
pub instance_buffer_initial_size: usize,
pub instance_buffer_max_size: usize,
}
impl Default for GpuResourceBudget {
fn default() -> Self {
AppResourceProfile::desktop().gpu
}
}
impl GpuResourceBudget {
pub fn new(atlas_initial_size: u32, instance_buffer_initial_size: usize) -> Self {
Self {
atlas_initial_size,
instance_buffer_initial_size,
instance_buffer_max_size: 256 * 1024 * 1024,
}
}
pub fn atlas_size(&self) -> Size<DevicePixels> {
let s = self.atlas_initial_size as i32;
Size {
width: DevicePixels(s),
height: DevicePixels(s),
}
}
#[doc(hidden)]
pub fn normalize_instance_buffer_capacity(
instance_buffer_initial_size: usize,
max_buffer_size: u64,
) -> u64 {
(instance_buffer_initial_size as u64)
.max(MIN_INSTANCE_BUFFER_CAPACITY)
.min(max_buffer_size)
}
#[doc(hidden)]
pub fn normalize_instance_buffer_limits(
instance_buffer_initial_size: usize,
instance_buffer_max_size: usize,
device_max_buffer_size: u64,
) -> (u64, u64) {
let device_max = device_max_buffer_size.max(MIN_INSTANCE_BUFFER_CAPACITY);
let profile_max = (instance_buffer_max_size as u64)
.max(instance_buffer_initial_size as u64)
.max(MIN_INSTANCE_BUFFER_CAPACITY)
.min(device_max);
let initial =
Self::normalize_instance_buffer_capacity(instance_buffer_initial_size, profile_max);
(initial, profile_max)
}
#[doc(hidden)]
pub fn next_instance_buffer_capacity(
current: u64,
required: u64,
max_capacity: u64,
) -> Option<u64> {
if required > max_capacity {
return None;
}
if current >= required {
return Some(current);
}
let grown = current
.saturating_mul(2)
.max(required.next_power_of_two())
.min(max_capacity);
(grown >= required).then_some(grown)
}
#[doc(hidden)]
pub fn aligned_instance_allocation(data_len: u64, alignment: u64) -> u64 {
data_len
.max(MIN_INSTANCE_BUFFER_CAPACITY)
.next_multiple_of(alignment.max(1))
}
}
#[cfg(test)]
mod tests {
use super::{AppResourceProfile, GpuResourceBudget};
#[test]
fn instance_buffer_capacity_uses_the_requested_budget_within_device_limits() {
assert_eq!(
GpuResourceBudget::normalize_instance_buffer_capacity(768 * 1024, 8 * 1024 * 1024),
768 * 1024
);
assert_eq!(
GpuResourceBudget::normalize_instance_buffer_capacity(0, 8 * 1024 * 1024),
16
);
assert_eq!(
GpuResourceBudget::normalize_instance_buffer_capacity(
16 * 1024 * 1024,
8 * 1024 * 1024,
),
8 * 1024 * 1024
);
}
#[test]
fn instance_buffer_limits_keep_profile_watermarks_under_the_device_cap() {
let (initial, max) = GpuResourceBudget::normalize_instance_buffer_limits(
512 * 1024,
16 * 1024 * 1024,
8 * 1024 * 1024,
);
assert_eq!(initial, 512 * 1024);
assert_eq!(max, 8 * 1024 * 1024);
}
#[test]
fn instance_buffer_limits_raise_a_max_below_the_initial_budget() {
let (initial, max) = GpuResourceBudget::normalize_instance_buffer_limits(
2 * 1024 * 1024,
1024,
8 * 1024 * 1024,
);
assert_eq!(initial, 2 * 1024 * 1024);
assert_eq!(max, 2 * 1024 * 1024);
}
#[test]
fn instance_buffer_growth_starts_from_the_profile_allocation() {
assert_eq!(
GpuResourceBudget::next_instance_buffer_capacity(
512 * 1024,
600 * 1024,
16 * 1024 * 1024
),
Some(1024 * 1024)
);
assert_eq!(
GpuResourceBudget::next_instance_buffer_capacity(
512 * 1024,
3 * 1024 * 1024,
16 * 1024 * 1024,
),
Some(4 * 1024 * 1024)
);
assert_eq!(
GpuResourceBudget::next_instance_buffer_capacity(
512 * 1024,
32 * 1024 * 1024,
16 * 1024 * 1024,
),
None
);
}
#[test]
fn preset_profiles_cap_batch_upload_below_zed_desktop_ceiling() {
let desktop = AppResourceProfile::desktop().gpu;
let utility = AppResourceProfile::utility().gpu;
let minimal = AppResourceProfile::minimal().gpu;
assert_eq!(desktop.instance_buffer_max_size, 256 * 1024 * 1024);
assert_eq!(utility.instance_buffer_max_size, 64 * 1024 * 1024);
assert_eq!(minimal.instance_buffer_max_size, 16 * 1024 * 1024);
assert!(minimal.instance_buffer_max_size < utility.instance_buffer_max_size);
assert!(utility.instance_buffer_max_size < desktop.instance_buffer_max_size);
assert!(desktop.instance_buffer_initial_size <= desktop.instance_buffer_max_size);
}
#[test]
fn new_and_default_keep_a_two_field_construction_path() {
let custom = GpuResourceBudget::new(768, 768 * 1024);
assert_eq!(custom.atlas_initial_size, 768);
assert_eq!(custom.instance_buffer_initial_size, 768 * 1024);
assert_eq!(custom.instance_buffer_max_size, 256 * 1024 * 1024);
let patched = GpuResourceBudget {
atlas_initial_size: 512,
instance_buffer_initial_size: 512 * 1024,
..GpuResourceBudget::default()
};
assert_eq!(patched.instance_buffer_max_size, 256 * 1024 * 1024);
assert_eq!(
GpuResourceBudget::default().instance_buffer_initial_size,
AppResourceProfile::desktop()
.gpu
.instance_buffer_initial_size
);
}
#[test]
fn aligned_instance_allocation_reserves_the_binding_minimum() {
assert_eq!(GpuResourceBudget::aligned_instance_allocation(0, 256), 256);
assert_eq!(GpuResourceBudget::aligned_instance_allocation(16, 256), 256);
assert_eq!(
GpuResourceBudget::aligned_instance_allocation(300, 256),
512
);
}
}