mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-28 08:55:27 +08:00
Remove block dependency from the tree (#65942)
Closes https://github.com/zed-industries/zed/issues/22408 See also https://github.com/zed-industries/scap/commit/b5c86ec58dd4b6b7732bf6ac8be03d6b1f190a82 that makes all macOS dependencies optional: we do not use these in Zed anyway, and have `default-features = false` that allow to remove all transitive `block` dependencies from the tree. Adjust the test to ensure no such dependencies can appear later. Release Notes: - [GPUI] Removed `block` dependecy from the tree
This commit is contained in:
1 parent
2a74c8dff6
commit
1808df02c0
12 files changed
+1484
-1004
No files matched your search
Generated
+7
-186
@@ -2304,12 +2304,6 @@ dependencies = [
|
||||
"core2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -3198,36 +3192,6 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"cocoa-foundation",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics 0.23.2",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa-foundation"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.13.0"
|
||||
@@ -3788,19 +3752,6 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.24.0"
|
||||
@@ -3809,35 +3760,11 @@ checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
"core-graphics-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-helmer-fork"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation 0.9.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.2.0"
|
||||
@@ -3865,7 +3792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics 0.24.0",
|
||||
"core-graphics",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
@@ -7509,12 +7436,10 @@ dependencies = [
|
||||
"collections",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation-sys",
|
||||
"core-graphics 0.24.0",
|
||||
"core-graphics",
|
||||
"core-text",
|
||||
"derive_more",
|
||||
"dispatch2",
|
||||
"etagere",
|
||||
"foreign-types 0.5.0",
|
||||
"futures 0.3.34",
|
||||
"gpui",
|
||||
"gpui_util",
|
||||
@@ -7522,9 +7447,9 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"mach2",
|
||||
"metal",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-text",
|
||||
"objc2-core-video",
|
||||
"objc2-foundation 0.3.2",
|
||||
@@ -7614,7 +7539,7 @@ dependencies = [
|
||||
"collections",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation-sys",
|
||||
"core-graphics 0.24.0",
|
||||
"core-graphics",
|
||||
"ctor",
|
||||
"dispatch2",
|
||||
"foreign-types 0.5.0",
|
||||
@@ -10583,15 +10508,6 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manatee"
|
||||
version = "0.8.0-alpha.5"
|
||||
@@ -10903,21 +10819,6 @@ dependencies = [
|
||||
"unicode-width 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"block",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types 0.5.0",
|
||||
"log",
|
||||
"objc",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
@@ -11686,27 +11587,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
"objc_exception",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.5"
|
||||
@@ -12063,24 +11943,6 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
@@ -15942,29 +15804,6 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
|
||||
[[package]]
|
||||
name = "screencapturekit"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e"
|
||||
dependencies = [
|
||||
"screencapturekit-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "screencapturekit-sys"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60"
|
||||
dependencies = [
|
||||
"block",
|
||||
"dispatch",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.12.0"
|
||||
@@ -17948,18 +17787,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||
|
||||
[[package]]
|
||||
name = "tao-core-video-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.5"
|
||||
@@ -22716,7 +22543,7 @@ dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"byteorder",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics 0.24.0",
|
||||
"core-graphics",
|
||||
"core-text",
|
||||
"dirs",
|
||||
"dwrote",
|
||||
@@ -22784,18 +22611,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zed-scap"
|
||||
version = "0.0.8-zed"
|
||||
source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac"
|
||||
source = "git+https://github.com/zed-industries/scap?rev=b5c86ec58dd4b6b7732bf6ac8be03d6b1f190a82#b5c86ec58dd4b6b7732bf6ac8be03d6b1f190a82"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cocoa",
|
||||
"core-graphics-helmer-fork",
|
||||
"log",
|
||||
"objc",
|
||||
"rand 0.8.6",
|
||||
"screencapturekit",
|
||||
"screencapturekit-sys",
|
||||
"sysinfo 0.31.4",
|
||||
"tao-core-video-sys",
|
||||
"windows 0.61.3",
|
||||
"windows-capture",
|
||||
"x11",
|
||||
|
||||
+1
-2
@@ -692,7 +692,6 @@ mach2 = "0.6"
|
||||
markup5ever_rcdom = "0.39"
|
||||
mdbook = { version = "=0.4.40", default-features = false, features = ["search"] }
|
||||
merman = { version = "=0.8.0-alpha.5", default-features = false }
|
||||
metal = "0.33"
|
||||
mime = "0.3.17"
|
||||
minidumper = "0.11"
|
||||
moka = { version = "0.12.10", features = ["sync"] }
|
||||
@@ -885,7 +884,7 @@ rustix = { version = "1.1", features = ["fs"] }
|
||||
rustls = "0.23.26"
|
||||
rustls-platform-verifier = "0.7"
|
||||
# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
|
||||
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
|
||||
scap = { git = "https://github.com/zed-industries/scap", rev = "b5c86ec58dd4b6b7732bf6ac8be03d6b1f190a82", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
|
||||
schemars = { version = "1.0", default-features = false, features = ["std", "indexmap2"] }
|
||||
sea-orm = { version = "=1.1.10", default-features = false }
|
||||
seccompiler = "0.5"
|
||||
|
||||
@@ -31,10 +31,8 @@ core-foundation.workspace = true
|
||||
core-foundation-sys.workspace = true
|
||||
core-graphics.workspace = true
|
||||
core-text = { workspace = true, optional = true }
|
||||
derive_more.workspace = true
|
||||
dispatch2.workspace = true
|
||||
dispatch2 = { workspace = true, features = ["block2"] }
|
||||
etagere.workspace = true
|
||||
foreign-types.workspace = true
|
||||
futures.workspace = true
|
||||
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
|
||||
font-kit = { workspace = true, optional = true, features = ["source"] }
|
||||
@@ -43,14 +41,34 @@ image.workspace = true
|
||||
libc.workspace = true
|
||||
log.workspace = true
|
||||
mach2.workspace = true
|
||||
metal.workspace = true
|
||||
objc2.workspace = true
|
||||
objc2-core-foundation = { workspace = true, features = ["std", "CFData", "CFDictionary", "CFString", "CFNumber", "CFBase"] }
|
||||
objc2-core-foundation = { workspace = true, features = ["std", "CFData", "CFDictionary", "CFString", "CFNumber", "CFBase", "CFCGTypes"] }
|
||||
objc2-core-graphics = { workspace = true, features = ["CGColorSpace"] }
|
||||
objc2-core-text = { workspace = true, optional = true }
|
||||
objc2-core-video = { workspace = true, features = ["CVPixelBuffer", "CVMetalTexture", "CVMetalTextureCache", "CVReturn", "objc2", "objc2-metal"] }
|
||||
objc2-metal = { version = "0.3", default-features = false, features = ["MTLDevice", "MTLPixelFormat", "MTLTexture"] }
|
||||
objc2-metal = { version = "0.3.2", default-features = false, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"MTLAllocation",
|
||||
"MTLBlitCommandEncoder",
|
||||
"MTLBuffer",
|
||||
"MTLCommandBuffer",
|
||||
"MTLCommandEncoder",
|
||||
"MTLCommandQueue",
|
||||
"MTLDevice",
|
||||
"MTLDrawable",
|
||||
"MTLLibrary",
|
||||
"MTLPixelFormat",
|
||||
"MTLRenderCommandEncoder",
|
||||
"MTLRenderPass",
|
||||
"MTLRenderPipeline",
|
||||
"MTLResource",
|
||||
"MTLTexture",
|
||||
"MTLTypes",
|
||||
] }
|
||||
objc2-foundation = { workspace = true, features = ["NSThread", "NSNotification", "NSOperation", "NSProcessInfo", "block2"] }
|
||||
objc2-quartz-core.workspace = true
|
||||
objc2-quartz-core = { workspace = true, features = ["objc2-core-foundation", "objc2-core-graphics", "objc2-metal"] }
|
||||
objc2-security.workspace = true
|
||||
parking_lot.workspace = true
|
||||
pathfinder_geometry = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use anyhow::{Context as _, Result, ensure};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use gpui::{
|
||||
AtlasBackend, AtlasKey, AtlasState, AtlasTextureId, AtlasTextureKind, AtlasTextureList,
|
||||
AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size,
|
||||
};
|
||||
use metal::Device;
|
||||
use objc2::{rc::Retained, runtime::ProtocolObject};
|
||||
use objc2_metal::{
|
||||
MTLDevice, MTLOrigin, MTLPixelFormat, MTLRegion, MTLSize, MTLStorageMode, MTLTexture,
|
||||
MTLTextureDescriptor, MTLTextureUsage,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, ptr::NonNull};
|
||||
|
||||
pub struct MetalAtlas(Mutex<AtlasState<MetalAtlasTextures>>);
|
||||
|
||||
impl MetalAtlas {
|
||||
pub(crate) fn new(device: Device, supports_shared_storage: bool) -> Self {
|
||||
pub(crate) fn new(
|
||||
device: Retained<ProtocolObject<dyn MTLDevice>>,
|
||||
supports_shared_storage: bool,
|
||||
) -> Self {
|
||||
MetalAtlas(Mutex::new(AtlasState::new(MetalAtlasTextures {
|
||||
device: AssertSend(device),
|
||||
device,
|
||||
supports_shared_storage,
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
monochrome_textures: AtlasTextureList::default(),
|
||||
polychrome_textures: AtlasTextureList::default(),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -25,13 +31,16 @@ impl MetalAtlas {
|
||||
/// has been removed. A scene can still reference such a texture when a
|
||||
/// cached view replays a paint from before the image was dropped, so
|
||||
/// callers must skip those sprites rather than assume the texture exists.
|
||||
pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
|
||||
pub(crate) fn metal_texture(
|
||||
&self,
|
||||
id: AtlasTextureId,
|
||||
) -> Option<Retained<ProtocolObject<dyn MTLTexture>>> {
|
||||
Some(self.0.lock().backend.texture(id)?.metal_texture.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct MetalAtlasTextures {
|
||||
device: AssertSend<Device>,
|
||||
device: Retained<ProtocolObject<dyn MTLDevice>>,
|
||||
supports_shared_storage: bool,
|
||||
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
@@ -58,11 +67,12 @@ impl AtlasBackend for MetalAtlasTextures {
|
||||
size: Size<DevicePixels>,
|
||||
bytes: &[u8],
|
||||
) -> Result<AtlasTile> {
|
||||
let tile = self.allocate(size, kind).context("failed to allocate")?;
|
||||
let upload = AtlasUpload::new(kind, size, bytes)?;
|
||||
let tile = self.allocate(size, kind)?;
|
||||
let texture = self
|
||||
.texture(tile.texture_id)
|
||||
.context("allocated tile refers to a missing texture")?;
|
||||
texture.upload(tile.bounds, bytes);
|
||||
.context("looking up allocated atlas texture")?;
|
||||
texture.upload(tile.bounds.origin, &upload)?;
|
||||
Ok(tile)
|
||||
}
|
||||
|
||||
@@ -100,7 +110,7 @@ impl MetalAtlasTextures {
|
||||
&mut self,
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> Option<AtlasTile> {
|
||||
) -> Result<AtlasTile> {
|
||||
{
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
@@ -113,19 +123,19 @@ impl MetalAtlasTextures {
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
{
|
||||
return Some(tile);
|
||||
return Ok(tile);
|
||||
}
|
||||
}
|
||||
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size)
|
||||
let texture = self.push_texture(size, texture_kind)?;
|
||||
texture.allocate(size).context("allocating atlas tile")
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
&mut self,
|
||||
min_size: Size<DevicePixels>,
|
||||
kind: AtlasTextureKind,
|
||||
) -> &mut MetalAtlasTexture {
|
||||
) -> Result<&mut MetalAtlasTexture> {
|
||||
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||
width: DevicePixels(1024),
|
||||
height: DevicePixels(1024),
|
||||
@@ -136,32 +146,38 @@ impl MetalAtlasTextures {
|
||||
height: DevicePixels(16384),
|
||||
};
|
||||
let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
|
||||
let texture_descriptor = metal::TextureDescriptor::new();
|
||||
texture_descriptor.set_width(size.width.into());
|
||||
texture_descriptor.set_height(size.height.into());
|
||||
let texture_descriptor = MTLTextureDescriptor::new();
|
||||
// SAFETY: Both dimensions are clamped to the existing 1024..=16384 atlas size limits.
|
||||
unsafe {
|
||||
texture_descriptor.setWidth(size.width.0 as usize);
|
||||
texture_descriptor.setHeight(size.height.0 as usize);
|
||||
}
|
||||
let pixel_format;
|
||||
let usage;
|
||||
match kind {
|
||||
AtlasTextureKind::Monochrome => {
|
||||
pixel_format = metal::MTLPixelFormat::A8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
pixel_format = MTLPixelFormat::A8Unorm;
|
||||
usage = MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Polychrome => {
|
||||
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
pixel_format = MTLPixelFormat::BGRA8Unorm;
|
||||
usage = MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
}
|
||||
texture_descriptor.set_pixel_format(pixel_format);
|
||||
texture_descriptor.set_usage(usage);
|
||||
texture_descriptor.setPixelFormat(pixel_format);
|
||||
texture_descriptor.setUsage(usage);
|
||||
// Shared memory mode can be used only on Apple GPU families
|
||||
// https://developer.apple.com/documentation/metal/mtlresourceoptions/storagemodeshared
|
||||
texture_descriptor.set_storage_mode(if self.supports_shared_storage {
|
||||
metal::MTLStorageMode::Shared
|
||||
texture_descriptor.setStorageMode(if self.supports_shared_storage {
|
||||
MTLStorageMode::Shared
|
||||
} else {
|
||||
metal::MTLStorageMode::Managed
|
||||
MTLStorageMode::Managed
|
||||
});
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
let metal_texture = self
|
||||
.device
|
||||
.newTextureWithDescriptor(&texture_descriptor)
|
||||
.context("allocating atlas texture")?;
|
||||
|
||||
let texture_list = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
@@ -169,28 +185,23 @@ impl MetalAtlasTextures {
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
|
||||
let index = texture_list.free_list.pop();
|
||||
let index = texture_list.free_list.pop().unwrap_or_else(|| {
|
||||
let index = texture_list.textures.len();
|
||||
texture_list.textures.push(None);
|
||||
index
|
||||
});
|
||||
|
||||
let atlas_texture = MetalAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: index.unwrap_or(texture_list.textures.len()) as u32,
|
||||
index: index as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size_to_etagere(size)),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
metal_texture,
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
|
||||
if let Some(ix) = index {
|
||||
texture_list.textures[ix] = Some(atlas_texture);
|
||||
texture_list.textures.get_mut(ix)
|
||||
} else {
|
||||
texture_list.textures.push(Some(atlas_texture));
|
||||
texture_list.textures.last_mut()
|
||||
}
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
Ok(texture_list.textures[index].insert(atlas_texture))
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> Option<&MetalAtlasTexture> {
|
||||
@@ -206,7 +217,7 @@ impl MetalAtlasTextures {
|
||||
struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
metal_texture: Retained<ProtocolObject<dyn MTLTexture>>,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
|
||||
@@ -226,28 +237,46 @@ impl MetalAtlasTexture {
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
let region = metal::MTLRegion::new_2d(
|
||||
bounds.origin.x.into(),
|
||||
bounds.origin.y.into(),
|
||||
bounds.size.width.into(),
|
||||
bounds.size.height.into(),
|
||||
fn upload(&self, origin: Point<DevicePixels>, upload: &AtlasUpload<'_>) -> Result<()> {
|
||||
ensure!(
|
||||
self.id.kind == upload.kind,
|
||||
"atlas upload kind does not match texture"
|
||||
);
|
||||
self.metal_texture.replace_region(
|
||||
region,
|
||||
0,
|
||||
bytes.as_ptr() as *const _,
|
||||
bounds.size.width.to_bytes(self.bytes_per_pixel()) as u64,
|
||||
ensure!(
|
||||
origin.x.0 >= 0
|
||||
&& origin.y.0 >= 0
|
||||
&& (origin.x.0 as usize)
|
||||
.checked_add(upload.size.width.0 as usize)
|
||||
.is_some_and(|end| end <= self.metal_texture.width())
|
||||
&& (origin.y.0 as usize)
|
||||
.checked_add(upload.size.height.0 as usize)
|
||||
.is_some_and(|end| end <= self.metal_texture.height()),
|
||||
"atlas upload is outside texture bounds"
|
||||
);
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
use metal::MTLPixelFormat::*;
|
||||
match self.metal_texture.pixel_format() {
|
||||
A8Unorm | R8Unorm => 1,
|
||||
RGBA8Unorm | BGRA8Unorm => 4,
|
||||
_ => unimplemented!(),
|
||||
let region = MTLRegion {
|
||||
origin: MTLOrigin {
|
||||
x: origin.x.0 as usize,
|
||||
y: origin.y.0 as usize,
|
||||
z: 0,
|
||||
},
|
||||
size: MTLSize {
|
||||
width: upload.size.width.0 as usize,
|
||||
height: upload.size.height.0 as usize,
|
||||
depth: 1,
|
||||
},
|
||||
};
|
||||
// SAFETY: Validated bytes cover every row of the checked region in this CPU-accessible texture.
|
||||
// The borrowed bytes remain live for the copy; GPU ordering is a separate caller requirement.
|
||||
unsafe {
|
||||
self.metal_texture
|
||||
.replaceRegion_mipmapLevel_withBytes_bytesPerRow(
|
||||
region,
|
||||
0,
|
||||
NonNull::from(upload.bytes).cast(),
|
||||
upload.bytes_per_row,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrement_ref_count(&mut self) {
|
||||
@@ -259,6 +288,44 @@ impl MetalAtlasTexture {
|
||||
}
|
||||
}
|
||||
|
||||
struct AtlasUpload<'a> {
|
||||
kind: AtlasTextureKind,
|
||||
size: Size<DevicePixels>,
|
||||
bytes: &'a [u8],
|
||||
bytes_per_row: usize,
|
||||
}
|
||||
|
||||
impl<'a> AtlasUpload<'a> {
|
||||
fn new(kind: AtlasTextureKind, size: Size<DevicePixels>, bytes: &'a [u8]) -> Result<Self> {
|
||||
ensure!(
|
||||
size.width.0 > 0 && size.height.0 > 0,
|
||||
"atlas dimensions must be positive"
|
||||
);
|
||||
let bytes_per_pixel = match kind {
|
||||
AtlasTextureKind::Monochrome => 1,
|
||||
AtlasTextureKind::Polychrome => 4,
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
let bytes_per_row = (size.width.0 as usize)
|
||||
.checked_mul(bytes_per_pixel)
|
||||
.context("calculating atlas row byte count")?;
|
||||
let byte_count = bytes_per_row
|
||||
.checked_mul(size.height.0 as usize)
|
||||
.context("calculating atlas byte count")?;
|
||||
ensure!(
|
||||
bytes.len() >= byte_count,
|
||||
"atlas upload byte length is {}, expected at least {byte_count}",
|
||||
bytes.len()
|
||||
);
|
||||
Ok(Self {
|
||||
kind,
|
||||
size,
|
||||
bytes,
|
||||
bytes_per_row,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn size_to_etagere(size: Size<DevicePixels>) -> etagere::Size {
|
||||
etagere::Size::new(size.width.into(), size.height.into())
|
||||
}
|
||||
@@ -270,39 +337,13 @@ fn point_from_etagere(value: etagere::Point) -> Point<DevicePixels> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct AssertSend<T>(T);
|
||||
|
||||
unsafe impl<T> Send for AssertSend<T> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::PlatformAtlas;
|
||||
use gpui::{PlatformAtlas, point, size};
|
||||
use objc2_metal::{MTLCreateSystemDefaultDevice, MTLGPUFamily};
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn create_atlas() -> Option<MetalAtlas> {
|
||||
let device = metal::Device::system_default()?;
|
||||
Some(MetalAtlas::new(device, true))
|
||||
}
|
||||
|
||||
fn make_image_key(image_id: usize, frame_index: usize) -> AtlasKey {
|
||||
AtlasKey::Image(gpui::RenderImageParams {
|
||||
image_id: gpui::ImageId(image_id),
|
||||
frame_index,
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_tile(atlas: &MetalAtlas, key: AtlasKey, size: Size<DevicePixels>) -> AtlasTile {
|
||||
atlas
|
||||
.get_or_insert_with(key, &mut || {
|
||||
let byte_count = (size.width.0 as usize) * (size.height.0 as usize) * 4;
|
||||
Ok(Some((size, Cow::Owned(vec![0u8; byte_count]))))
|
||||
})
|
||||
.expect("allocation should succeed")
|
||||
.expect("callback returns Some")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_clears_stale_keys_from_tiles_by_key() {
|
||||
let Some(atlas) = create_atlas() else {
|
||||
@@ -338,6 +379,7 @@ mod tests {
|
||||
// Re-inserting A must allocate a fresh tile on a new texture,
|
||||
// NOT return a stale tile referencing the deleted texture.
|
||||
let tile_a2 = insert_tile(&atlas, key_a, small);
|
||||
assert_eq!(tile_a2.texture_id, tile_a.texture_id);
|
||||
|
||||
// The texture must actually exist — this would panic before the fix.
|
||||
assert!(atlas.metal_texture(tile_a2.texture_id).is_some());
|
||||
@@ -402,4 +444,129 @@ mod tests {
|
||||
let key = make_image_key(999, 0);
|
||||
atlas.remove(&key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_upload_does_not_allocate() {
|
||||
let Some(atlas) = create_atlas() else {
|
||||
return;
|
||||
};
|
||||
let mut state = atlas.0.lock();
|
||||
for (kind, short_bytes, short_error) in [
|
||||
(
|
||||
AtlasTextureKind::Monochrome,
|
||||
&[][..],
|
||||
"atlas upload byte length is 0, expected at least 1",
|
||||
),
|
||||
(
|
||||
AtlasTextureKind::Polychrome,
|
||||
&[0][..],
|
||||
"atlas upload byte length is 1, expected at least 4",
|
||||
),
|
||||
] {
|
||||
for (width, height, bytes, expected_error) in [
|
||||
(1, 1, short_bytes, short_error),
|
||||
(0, 1, &[][..], "atlas dimensions must be positive"),
|
||||
(1, 0, &[][..], "atlas dimensions must be positive"),
|
||||
(i32::MIN, 1, &[][..], "atlas dimensions must be positive"),
|
||||
(1, i32::MIN, &[][..], "atlas dimensions must be positive"),
|
||||
] {
|
||||
assert_eq!(
|
||||
state
|
||||
.backend
|
||||
.insert(kind, size(DevicePixels(width), DevicePixels(height)), bytes)
|
||||
.err()
|
||||
.map(|error| error.to_string()),
|
||||
Some(expected_error.to_owned())
|
||||
);
|
||||
for textures in [
|
||||
&state.backend.monochrome_textures,
|
||||
&state.backend.polychrome_textures,
|
||||
] {
|
||||
assert_eq!((textures.textures.len(), textures.free_list.len()), (0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_texture_upload_preserves_pixel_layout() -> Result<()> {
|
||||
let Some(atlas) = create_atlas() else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut state = atlas.0.lock();
|
||||
for (kind, bytes_per_pixel) in [
|
||||
(AtlasTextureKind::Monochrome, 1),
|
||||
(AtlasTextureKind::Polychrome, 4),
|
||||
] {
|
||||
let texture = state
|
||||
.backend
|
||||
.push_texture(size(DevicePixels(8), DevicePixels(8)), kind)?;
|
||||
texture.upload(
|
||||
Point::default(),
|
||||
&AtlasUpload::new(
|
||||
kind,
|
||||
size(DevicePixels(8), DevicePixels(8)),
|
||||
&vec![0; 8 * 8 * bytes_per_pixel],
|
||||
)?,
|
||||
)?;
|
||||
let pixels = (1..=3 * 2 * bytes_per_pixel)
|
||||
.map(|value| value as u8)
|
||||
.collect::<Vec<_>>();
|
||||
texture.upload(
|
||||
point(DevicePixels(2), DevicePixels(3)),
|
||||
&AtlasUpload::new(kind, size(DevicePixels(3), DevicePixels(2)), &pixels)?,
|
||||
)?;
|
||||
let mut actual = vec![0; 8 * 8 * bytes_per_pixel];
|
||||
unsafe {
|
||||
texture
|
||||
.metal_texture
|
||||
.getBytes_bytesPerRow_fromRegion_mipmapLevel(
|
||||
NonNull::from(actual.as_mut_slice()).cast(),
|
||||
8 * bytes_per_pixel,
|
||||
MTLRegion {
|
||||
origin: MTLOrigin { x: 0, y: 0, z: 0 },
|
||||
size: MTLSize {
|
||||
width: 8,
|
||||
height: 8,
|
||||
depth: 1,
|
||||
},
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
let mut expected = vec![0; actual.len()];
|
||||
for row in 0..2 {
|
||||
let start = ((row + 3) * 8 + 2) * bytes_per_pixel;
|
||||
let source = row * 3 * bytes_per_pixel;
|
||||
expected[start..start + 3 * bytes_per_pixel]
|
||||
.copy_from_slice(&pixels[source..source + 3 * bytes_per_pixel]);
|
||||
}
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_atlas() -> Option<MetalAtlas> {
|
||||
let device = MTLCreateSystemDefaultDevice()?;
|
||||
let supports_shared_storage =
|
||||
cfg!(target_os = "ios") || device.supportsFamily(MTLGPUFamily::Apple1);
|
||||
Some(MetalAtlas::new(device, supports_shared_storage))
|
||||
}
|
||||
|
||||
fn make_image_key(image_id: usize, frame_index: usize) -> AtlasKey {
|
||||
AtlasKey::Image(gpui::RenderImageParams {
|
||||
image_id: gpui::ImageId(image_id),
|
||||
frame_index,
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_tile(atlas: &MetalAtlas, key: AtlasKey, size: Size<DevicePixels>) -> AtlasTile {
|
||||
atlas
|
||||
.get_or_insert_with(key, &mut || {
|
||||
let byte_count = (size.width.0 as usize) * (size.height.0 as usize) * 4;
|
||||
Ok(Some((size, Cow::Owned(vec![0u8; byte_count]))))
|
||||
})
|
||||
.expect("inserting atlas tile")
|
||||
.expect("retrieving inserted atlas tile")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -326,6 +326,16 @@ impl IosWindow {
|
||||
|
||||
let layer = view.layer();
|
||||
layer.setContentsScale(scale);
|
||||
let mut renderer = MetalRenderer::from_layer(
|
||||
MetalContext::default(),
|
||||
layer
|
||||
.downcast_ref::<CAMetalLayer>()
|
||||
.ok_or_else(|| anyhow::anyhow!("UIKit view must have a CAMetalLayer"))?,
|
||||
false,
|
||||
)?;
|
||||
let pixel_w = (screen_bounds_cg.size.width * scale) as i32;
|
||||
let pixel_h = (screen_bounds_cg.size.height * scale) as i32;
|
||||
renderer.update_drawable_size(size(DevicePixels(pixel_w), DevicePixels(pixel_h)));
|
||||
|
||||
// Auto-resize the Metal view when the parent view changes size
|
||||
// (e.g. rotation). UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
|
||||
@@ -406,17 +416,6 @@ impl IosWindow {
|
||||
text_input_view.setUserInteractionEnabled(true);
|
||||
view.addSubview(&text_input_view);
|
||||
|
||||
let pixel_w = (screen_bounds_cg.size.width * scale) as i32;
|
||||
let pixel_h = (screen_bounds_cg.size.height * scale) as i32;
|
||||
let mut renderer = MetalRenderer::from_layer(
|
||||
MetalContext::default(),
|
||||
layer
|
||||
.downcast_ref::<CAMetalLayer>()
|
||||
.ok_or_else(|| anyhow::anyhow!("UIKit view must have a CAMetalLayer"))?,
|
||||
false,
|
||||
);
|
||||
renderer.update_drawable_size(size(DevicePixels(pixel_w), DevicePixels(pixel_h)));
|
||||
|
||||
let state = IosWindowState {
|
||||
platform: Rc::downgrade(platform),
|
||||
keyboard_observers: RefCell::default(),
|
||||
|
||||
@@ -770,7 +770,7 @@ impl Platform for MacPlatform {
|
||||
background_executor,
|
||||
renderer_context,
|
||||
self.1,
|
||||
)))
|
||||
)?))
|
||||
}
|
||||
|
||||
fn window_appearance(&self) -> WindowAppearance {
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::{
|
||||
kTISPropertyInputSourceIsASCIICapable, kTISPropertyInputSourceType, kTISTypeKeyboardInputMode,
|
||||
ns_string, renderer,
|
||||
};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use anyhow::Result;
|
||||
use block2::RcBlock;
|
||||
use dispatch2::DispatchQueue;
|
||||
@@ -959,8 +958,9 @@ impl MacWindow {
|
||||
background_executor: BackgroundExecutor,
|
||||
renderer_context: renderer::Context,
|
||||
marker: MainThreadMarker,
|
||||
) -> Self {
|
||||
) -> Result<Self> {
|
||||
autoreleasepool(|_| unsafe {
|
||||
let renderer = renderer::new_renderer(renderer_context, false, use_display_p3)?;
|
||||
let allows_automatic_window_tabbing = tabbing_identifier.is_some();
|
||||
if allows_automatic_window_tabbing {
|
||||
let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: Bool::YES];
|
||||
@@ -1072,14 +1072,7 @@ impl MacWindow {
|
||||
cursor_style: CursorStyle::Arrow,
|
||||
cursor_visible,
|
||||
frame_source: None,
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
native_window as *mut _,
|
||||
native_view as *mut _,
|
||||
bounds.size.map(|pixels| pixels.as_f32()),
|
||||
false,
|
||||
use_display_p3,
|
||||
),
|
||||
renderer,
|
||||
request_frame_callback: None,
|
||||
event_callback: None,
|
||||
activate_callback: None,
|
||||
@@ -1285,7 +1278,7 @@ impl MacWindow {
|
||||
window_state.sheet_parent = sheet_parent;
|
||||
}
|
||||
|
||||
window
|
||||
Ok(window)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ runtime_shaders = ["gpui_macos/runtime_shaders", "gpui_ios/runtime_shaders"]
|
||||
wayland = ["gpui_linux/wayland"]
|
||||
x11 = ["gpui_linux/x11"]
|
||||
|
||||
[[bench]]
|
||||
name = "atlas_upload"
|
||||
harness = false
|
||||
required-features = ["bench-support"]
|
||||
|
||||
[[bench]]
|
||||
name = "focus_cleanup"
|
||||
path = "../gpui/benches/focus_cleanup.rs"
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
use std::{borrow::Cow, hint::black_box, sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use bench_metrics::MetricReport;
|
||||
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput};
|
||||
use gpui::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, BenchMeasurement, Bounds, ContentMask,
|
||||
Corners, DevicePixels, Hsla, ImageId, MonochromeSprite, PaddedBool32, PlatformAtlas,
|
||||
PlatformHeadlessRenderer, Point, PolychromeSprite, RenderImageParams, RenderSvgParams,
|
||||
ScaledPixels, Scene, SharedString, Size, TransformationMatrix, size,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
key: AtlasKey,
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
renderer: Box<dyn PlatformHeadlessRenderer>,
|
||||
atlas: Arc<dyn PlatformAtlas>,
|
||||
dimensions: Size<DevicePixels>,
|
||||
seed: Entry,
|
||||
entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
struct Batch<'a> {
|
||||
fixture: &'a Fixture,
|
||||
texture_id: AtlasTextureId,
|
||||
misses: bool,
|
||||
builder_calls: usize,
|
||||
tiles: Vec<AtlasTile>,
|
||||
}
|
||||
|
||||
fn atlas_upload(criterion: &mut Criterion<BenchMeasurement>) {
|
||||
assert!(cfg!(target_os = "macos"), "this benchmark requires Metal");
|
||||
assert!(!cfg!(debug_assertions), "use --profile release-fast");
|
||||
let mut group = criterion.benchmark_group("atlas_upload");
|
||||
for (kind, format) in [
|
||||
(AtlasTextureKind::Monochrome, "a8"),
|
||||
(AtlasTextureKind::Polychrome, "bgra8"),
|
||||
] {
|
||||
for (width, height, count) in [(1, 1, 128), (3, 2, 128), (16, 16, 128), (256, 256, 8)] {
|
||||
let dimensions = size(DevicePixels(width), DevicePixels(height));
|
||||
let mut fixture = Fixture::new(kind, dimensions, count).expect("create Metal fixture");
|
||||
fixture.validate_pixels().expect("validate atlas pixels");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
for (misses, mode) in [(true, "miss"), (false, "hit")] {
|
||||
let report = MetricReport::new();
|
||||
let id = BenchmarkId::new(
|
||||
format!("{mode}_{format}"),
|
||||
format!("{width}x{height}_{count}"),
|
||||
);
|
||||
group.bench_function(id, |bencher| {
|
||||
let mut iterations = 0;
|
||||
report.discard_pending();
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
iterations += 1;
|
||||
fixture.prepare(misses).expect("prepare atlas batch")
|
||||
},
|
||||
|batch| batch.run().expect("insert atlas batch"),
|
||||
BatchSize::PerIteration,
|
||||
);
|
||||
report.record_sample(iterations * count as u64);
|
||||
});
|
||||
eprintln!("{mode}_{format}/{width}x{height}: secondary metrics per atlas lookup");
|
||||
report.print(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new(kind: AtlasTextureKind, dimensions: Size<DevicePixels>, count: usize) -> Result<Self> {
|
||||
let renderer = gpui_platform::current_headless_renderer()?.context("Metal unavailable")?;
|
||||
let atlas = renderer.sprite_atlas();
|
||||
Ok(Self {
|
||||
renderer,
|
||||
atlas,
|
||||
dimensions,
|
||||
seed: make_entry(kind, dimensions, count),
|
||||
entries: (0..count)
|
||||
.map(|index| make_entry(kind, dimensions, index))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn insert(&self, entry: &Entry, builder_calls: &mut usize) -> Result<AtlasTile> {
|
||||
self.atlas
|
||||
.get_or_insert_with(black_box(entry.key.clone()), &mut || {
|
||||
*builder_calls += 1;
|
||||
Ok(Some((
|
||||
black_box(self.dimensions),
|
||||
Cow::Borrowed(black_box(&entry.bytes)),
|
||||
)))
|
||||
})?
|
||||
.context("missing atlas tile")
|
||||
}
|
||||
|
||||
fn prepare(&self, misses: bool) -> Result<Batch<'_>> {
|
||||
for entry in &self.entries {
|
||||
self.atlas.remove(&entry.key);
|
||||
}
|
||||
self.atlas.remove(&self.seed.key);
|
||||
let mut builder_calls = 0;
|
||||
let seed = self.insert(&self.seed, &mut builder_calls)?;
|
||||
assert_eq!(builder_calls, 1);
|
||||
if !misses {
|
||||
for entry in &self.entries {
|
||||
let tile = self.insert(entry, &mut builder_calls)?;
|
||||
assert_eq!(tile.texture_id, seed.texture_id);
|
||||
}
|
||||
assert_eq!(builder_calls, self.entries.len() + 1);
|
||||
}
|
||||
Ok(Batch {
|
||||
fixture: self,
|
||||
texture_id: seed.texture_id,
|
||||
misses,
|
||||
builder_calls: 0,
|
||||
tiles: Vec::with_capacity(self.entries.len()),
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_pixels(&mut self) -> Result<()> {
|
||||
let tiles = {
|
||||
let mut batch = self.prepare(true)?;
|
||||
batch.run()?;
|
||||
batch.tiles.clone()
|
||||
};
|
||||
for (entry, tile) in self.entries.iter().zip(tiles) {
|
||||
let scene = tile_scene(tile);
|
||||
let image = self
|
||||
.renderer
|
||||
.render_scene_to_image(&scene, self.dimensions)?;
|
||||
let expected = match tile.texture_id.kind {
|
||||
AtlasTextureKind::Monochrome => entry
|
||||
.bytes
|
||||
.iter()
|
||||
.flat_map(|&alpha| [alpha, alpha, alpha, 255])
|
||||
.collect::<Vec<_>>(),
|
||||
AtlasTextureKind::Polychrome => entry
|
||||
.bytes
|
||||
.chunks_exact(4)
|
||||
.flat_map(|pixel| [pixel[2], pixel[1], pixel[0], 255])
|
||||
.collect(),
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
assert_eq!(image.as_raw(), &expected);
|
||||
}
|
||||
let mut batch = self.prepare(false)?;
|
||||
batch.run()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Batch<'_> {
|
||||
fn run(&mut self) -> Result<()> {
|
||||
for entry in &self.fixture.entries {
|
||||
self.tiles
|
||||
.push(self.fixture.insert(entry, &mut self.builder_calls)?);
|
||||
}
|
||||
black_box(&self.tiles);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Batch<'_> {
|
||||
fn drop(&mut self) {
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
let count = self.fixture.entries.len();
|
||||
assert_eq!(self.tiles.len(), count);
|
||||
assert_eq!(self.builder_calls, if self.misses { count } else { 0 });
|
||||
for tile in &self.tiles {
|
||||
assert_eq!(tile.texture_id, self.texture_id);
|
||||
assert_eq!(tile.bounds.size, self.fixture.dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_entry(kind: AtlasTextureKind, dimensions: Size<DevicePixels>, index: usize) -> Entry {
|
||||
let key = match kind {
|
||||
AtlasTextureKind::Monochrome => AtlasKey::Svg(RenderSvgParams {
|
||||
path: SharedString::from(format!("atlas-upload-{index}")),
|
||||
size: dimensions,
|
||||
}),
|
||||
AtlasTextureKind::Polychrome => AtlasKey::Image(RenderImageParams {
|
||||
image_id: ImageId(index),
|
||||
frame_index: 0,
|
||||
}),
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
};
|
||||
let mut bytes = Vec::new();
|
||||
for row in 0..dimensions.height.0 {
|
||||
for column in 0..dimensions.width.0 {
|
||||
let value = (index as u8)
|
||||
.wrapping_mul(73)
|
||||
.wrapping_add((row as u8).wrapping_mul(31))
|
||||
.wrapping_add((column as u8).wrapping_mul(17));
|
||||
match kind {
|
||||
AtlasTextureKind::Monochrome => bytes.push(value),
|
||||
AtlasTextureKind::Polychrome => {
|
||||
bytes.extend([value, value.wrapping_add(53), value.wrapping_add(107), 255])
|
||||
}
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry { key, bytes }
|
||||
}
|
||||
|
||||
fn tile_scene(tile: AtlasTile) -> Scene {
|
||||
let bounds = Bounds {
|
||||
origin: Point::default(),
|
||||
size: size(
|
||||
ScaledPixels(tile.bounds.size.width.0 as f32),
|
||||
ScaledPixels(tile.bounds.size.height.0 as f32),
|
||||
),
|
||||
};
|
||||
let content_mask = ContentMask { bounds };
|
||||
let mut scene = Scene::default();
|
||||
match tile.texture_id.kind {
|
||||
AtlasTextureKind::Monochrome => scene.insert_primitive(MonochromeSprite {
|
||||
order: 0,
|
||||
pad: 0,
|
||||
tile,
|
||||
bounds,
|
||||
content_mask,
|
||||
color: Hsla {
|
||||
h: 0.0,
|
||||
s: 0.0,
|
||||
l: 1.0,
|
||||
a: 1.0,
|
||||
},
|
||||
transformation: TransformationMatrix::default(),
|
||||
}),
|
||||
AtlasTextureKind::Polychrome => scene.insert_primitive(PolychromeSprite {
|
||||
order: 0,
|
||||
pad: 0,
|
||||
tile,
|
||||
grayscale: PaddedBool32::from(false),
|
||||
opacity: 1.0,
|
||||
bounds,
|
||||
content_mask,
|
||||
corner_radii: Corners::default(),
|
||||
}),
|
||||
AtlasTextureKind::Subpixel => unreachable!(),
|
||||
}
|
||||
scene.finish();
|
||||
scene
|
||||
}
|
||||
|
||||
gpui::bench_group! {
|
||||
name = benches;
|
||||
config = Criterion::default()
|
||||
.sample_size(30)
|
||||
.warm_up_time(Duration::from_secs(1))
|
||||
.measurement_time(Duration::from_secs(2))
|
||||
.without_plots();
|
||||
targets = atlas_upload
|
||||
}
|
||||
gpui::bench_main!(benches);
|
||||
@@ -118,7 +118,7 @@ pub fn current_headless_renderer() -> anyhow::Result<Option<Box<dyn gpui::Platfo
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Ok(Some(Box::new(
|
||||
gpui_macos::metal_renderer::MetalHeadlessRenderer::new(),
|
||||
gpui_macos::metal_renderer::MetalHeadlessRenderer::new()?,
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
@@ -16,18 +16,18 @@ mod tests {
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use cargo_metadata::{DependencyKind, Metadata, MetadataCommand};
|
||||
use cargo_metadata::{CargoOpt, DependencyKind, Metadata, MetadataCommand};
|
||||
|
||||
use super::load_workspace;
|
||||
|
||||
/// Direct dependencies forbidden in workspace packages, paired with the reason shown on failure.
|
||||
const FORBIDDEN_DIRECT_DEPENDENCIES: &[(&str, &str)] = &[
|
||||
const FORBIDDEN_OBJC_DEPENDENCIES: &[(&str, &str)] = &[
|
||||
("block", "use `block2` instead"),
|
||||
(
|
||||
"cocoa",
|
||||
"use `objc2-app-kit` and `objc2-foundation` instead",
|
||||
),
|
||||
("cocoa-foundation", "use `objc2-foundation` instead"),
|
||||
("metal", "use `objc2-metal` and `objc2-quartz-core` instead"),
|
||||
("objc", "use `objc2` instead"),
|
||||
("objc-foundation", "use `objc2-foundation` instead"),
|
||||
("objc_id", "use `objc2::rc::Retained` instead"),
|
||||
@@ -53,21 +53,24 @@ mod tests {
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn no_forbidden_direct_dependencies() -> Result<()> {
|
||||
let workspace = load_workspace()?;
|
||||
let violations = forbidden_direct_dependency_violations(&workspace)?;
|
||||
fn no_forbidden_objc_dependencies() -> Result<()> {
|
||||
let workspace = MetadataCommand::new()
|
||||
.features(CargoOpt::AllFeatures)
|
||||
.exec()
|
||||
.context("loading cargo metadata with all workspace features")?;
|
||||
let violations = forbidden_objc_dependency_violations(&workspace)?;
|
||||
|
||||
assert_eq!(
|
||||
violations,
|
||||
Vec::<String>::new(),
|
||||
"forbidden direct dependencies:\n{}",
|
||||
"forbidden Objective-C dependencies:\n{}",
|
||||
violations.join("\n"),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_direct_dependencies_cover_manifest_declarations() -> Result<()> {
|
||||
fn forbidden_objc_dependencies_cover_manifests_and_transitive_packages() -> Result<()> {
|
||||
let fixture = tempfile::tempdir()?;
|
||||
for name in [
|
||||
"block",
|
||||
@@ -80,16 +83,21 @@ mod tests {
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
"objc2-quartz-core",
|
||||
"metal",
|
||||
"intermediate",
|
||||
"workspace",
|
||||
] {
|
||||
let directory = fixture.path().join(name);
|
||||
fs::create_dir(&directory)?;
|
||||
fs::write(directory.join("lib.rs"), "")?;
|
||||
let dependencies = if name == "metal" {
|
||||
"[dependencies]\nobjc = { path = '../objc' }\n"
|
||||
} else {
|
||||
""
|
||||
let dependencies = match name {
|
||||
"metal" => "[dependencies]\nobjc = { path = '../objc' }\n",
|
||||
"intermediate" => {
|
||||
"[dependencies]\nmetal = { path = '../metal', optional = true }\n"
|
||||
}
|
||||
_ => "",
|
||||
};
|
||||
fs::write(
|
||||
directory.join("Cargo.toml"),
|
||||
@@ -110,7 +118,8 @@ block2 = { path = "../block2" }
|
||||
objc2 = { path = "../objc2" }
|
||||
objc2-app-kit = { path = "../objc2-app-kit" }
|
||||
objc2-foundation = { path = "../objc2-foundation" }
|
||||
metal = { path = "../metal" }
|
||||
objc2-metal = { path = "../objc2-metal" }
|
||||
objc2-quartz-core = { path = "../objc2-quartz-core" }
|
||||
"#,
|
||||
);
|
||||
let forbidden = r#"
|
||||
@@ -122,6 +131,9 @@ path = "../cocoa"
|
||||
[dependencies.cocoa-foundation]
|
||||
path = "../cocoa-foundation"
|
||||
optional = true
|
||||
[dependencies.legacy_metal]
|
||||
package = "metal"
|
||||
path = "../metal"
|
||||
[target.'cfg(any())'.dependencies]
|
||||
objc = { path = "../objc" }
|
||||
[dev-dependencies]
|
||||
@@ -132,6 +144,7 @@ legacy_id = { workspace = true, optional = true }
|
||||
legacy_id = { package = "objc_id", path = "../objc_id" }
|
||||
objc = "0.1.0"
|
||||
unused_cocoa = { package = "cocoa", version = "0.1.0" }
|
||||
unused_metal = { package = "metal", version = "0.1.0" }
|
||||
unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
"#;
|
||||
fs::write(&manifest, format!("{allowed}{forbidden}"))?;
|
||||
@@ -139,7 +152,7 @@ unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
command
|
||||
.manifest_path(&manifest)
|
||||
.other_options(vec!["--offline".to_owned()]);
|
||||
let mut violations = forbidden_direct_dependency_violations(&command.exec()?)?;
|
||||
let mut violations = forbidden_objc_dependency_violations(&command.exec()?)?;
|
||||
violations.sort();
|
||||
assert_eq!(
|
||||
violations,
|
||||
@@ -147,18 +160,42 @@ unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
"Cargo.toml [workspace.dependencies]: `legacy_id` uses `objc_id`; use `objc2::rc::Retained` instead",
|
||||
"Cargo.toml [workspace.dependencies]: `objc` uses `objc`; use `objc2` instead",
|
||||
"Cargo.toml [workspace.dependencies]: `unused_cocoa` uses `cocoa`; use `objc2-app-kit` and `objc2-foundation` instead",
|
||||
"Cargo.toml [workspace.dependencies]: `unused_metal` uses `metal`; use `objc2-metal` and `objc2-quartz-core` instead",
|
||||
"workspace directly depends on `block`; use `block2` instead",
|
||||
"workspace directly depends on `cocoa-foundation`; use `objc2-foundation` instead",
|
||||
"workspace directly depends on `cocoa`; use `objc2-app-kit` and `objc2-foundation` instead",
|
||||
"workspace directly depends on `metal`; use `objc2-metal` and `objc2-quartz-core` instead",
|
||||
"workspace directly depends on `objc-foundation`; use `objc2-foundation` instead",
|
||||
"workspace directly depends on `objc_id`; use `objc2::rc::Retained` instead",
|
||||
"workspace directly depends on `objc`; use `objc2` instead",
|
||||
],
|
||||
"including renamed optional inactive-target build dependency",
|
||||
);
|
||||
fs::write(
|
||||
&manifest,
|
||||
format!(
|
||||
"{allowed}\n[target.'cfg(any())'.dependencies]\n\
|
||||
intermediate = {{ path = '../intermediate', optional = true }}\n\
|
||||
[features]\nlegacy = ['dep:intermediate', 'intermediate/metal']\n"
|
||||
),
|
||||
)?;
|
||||
assert_eq!(
|
||||
forbidden_objc_dependency_violations(&command.exec()?)?,
|
||||
Vec::<String>::new(),
|
||||
);
|
||||
command.features(CargoOpt::AllFeatures);
|
||||
let mut violations = forbidden_objc_dependency_violations(&command.exec()?)?;
|
||||
violations.sort();
|
||||
assert_eq!(
|
||||
violations,
|
||||
[
|
||||
"dependency graph contains `metal`; use `objc2-metal` and `objc2-quartz-core` instead",
|
||||
"dependency graph contains `objc`; use `objc2` instead",
|
||||
],
|
||||
);
|
||||
fs::write(&manifest, allowed)?;
|
||||
assert_eq!(
|
||||
forbidden_direct_dependency_violations(&command.exec()?)?,
|
||||
forbidden_objc_dependency_violations(&command.exec()?)?,
|
||||
Vec::<String>::new(),
|
||||
);
|
||||
Ok(())
|
||||
@@ -240,11 +277,12 @@ unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
);
|
||||
}
|
||||
|
||||
fn forbidden_direct_dependency_violations(workspace: &Metadata) -> Result<Vec<String>> {
|
||||
fn forbidden_objc_dependency_violations(workspace: &Metadata) -> Result<Vec<String>> {
|
||||
let mut violations = Vec::new();
|
||||
let mut reported_dependencies = BTreeSet::new();
|
||||
for package in workspace.workspace_packages() {
|
||||
for dependency in &package.dependencies {
|
||||
let Some((_, reason)) = FORBIDDEN_DIRECT_DEPENDENCIES
|
||||
let Some((_, reason)) = FORBIDDEN_OBJC_DEPENDENCIES
|
||||
.iter()
|
||||
.find(|(name, _)| *name == dependency.name)
|
||||
else {
|
||||
@@ -254,6 +292,21 @@ unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
"{} directly depends on `{}`; {reason}",
|
||||
package.name, dependency.name
|
||||
));
|
||||
reported_dependencies.insert(dependency.name.as_str());
|
||||
}
|
||||
}
|
||||
for package in &workspace.packages {
|
||||
let Some((_, reason)) = FORBIDDEN_OBJC_DEPENDENCIES
|
||||
.iter()
|
||||
.find(|(name, _)| *name == package.name.as_str())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if reported_dependencies.insert(package.name.as_str()) {
|
||||
violations.push(format!(
|
||||
"dependency graph contains `{}`; {reason}",
|
||||
package.name,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +324,7 @@ unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
.get("package")
|
||||
.and_then(toml::Value::as_str)
|
||||
.unwrap_or(alias);
|
||||
let Some((_, reason)) = FORBIDDEN_DIRECT_DEPENDENCIES
|
||||
let Some((_, reason)) = FORBIDDEN_OBJC_DEPENDENCIES
|
||||
.iter()
|
||||
.find(|(forbidden, _)| *forbidden == name)
|
||||
else {
|
||||
|
||||
Reference in new issue
Block a user