mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-21 05:31:11 +00:00
gpui_macos: Migrate screen capture to objc2 (#64251)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_remote_server_linux_x86_64 (push) Blocked by required conditions
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_remote_server_mac_aarch64 (push) Blocked by required conditions
run_tests / check_remote_server_windows_x86_64 (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_remote_server_linux_x86_64 (push) Blocked by required conditions
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_remote_server_mac_aarch64 (push) Blocked by required conditions
run_tests / check_remote_server_windows_x86_64 (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
# Objective Get rid of instances of the `block` crate, as it will not compile in a future version of Rust. This gets rid of the `block` crate inside of `gpui_macos`, which means were on the home stretch. ## Solution Migrate `screen_capture.rs` completely to `objc2`. While a more targeted fix is theoretically possible, there were enough upsides where I thought this was worth a full migration. Completely gets rid of the `media` crate, which had manual bindings to `core_media`, which `objc2` can provide. Gets rid of tons of unsafe code, and a lot of code in general. ## Testing Tested manual sharing inside of a Zed channel Release Notes: - N/A - [GPUI] Migrated macOS screen capture to `objc2`
This commit is contained in:
parent
7a01ac15c5
commit
4b47ceb9d3
15 changed files with 186 additions and 652 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -7533,6 +7533,8 @@ dependencies = [
|
|||
"lyon",
|
||||
"num_cpus",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-video",
|
||||
"objc2-metal 0.3.2",
|
||||
"parking",
|
||||
"parking_lot",
|
||||
|
|
@ -7651,7 +7653,6 @@ dependencies = [
|
|||
"accesskit_macos",
|
||||
"anyhow",
|
||||
"async-task",
|
||||
"block",
|
||||
"block2 0.6.2",
|
||||
"cocoa 0.26.0",
|
||||
"collections",
|
||||
|
|
@ -7671,11 +7672,11 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"mach2 0.5.0",
|
||||
"media",
|
||||
"metal",
|
||||
"objc",
|
||||
"objc2 0.6.3",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-media",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-screen-capture-kit",
|
||||
"objc2-user-notifications",
|
||||
|
|
@ -10306,6 +10307,7 @@ dependencies = [
|
|||
"livekit_api",
|
||||
"log",
|
||||
"nanoid",
|
||||
"objc2-core-foundation",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"rodio",
|
||||
|
|
@ -10863,19 +10865,6 @@ dependencies = [
|
|||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "media"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen",
|
||||
"core-foundation 0.10.0",
|
||||
"core-video",
|
||||
"foreign-types 0.5.0",
|
||||
"metal",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
|
|
@ -12048,6 +12037,21 @@ dependencies = [
|
|||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-media"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"dispatch2",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-video",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-services"
|
||||
version = "0.3.2"
|
||||
|
|
@ -12058,6 +12062,19 @@ dependencies = [
|
|||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-video"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
|
|
@ -12180,8 +12197,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-media",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ members = [
|
|||
"crates/lsp_locations",
|
||||
"crates/markdown",
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/mermaid_render",
|
||||
"crates/migrator",
|
||||
|
|
@ -405,7 +404,6 @@ lsp_command_selector = { path = "crates/lsp_command_selector" }
|
|||
lsp_locations = { path = "crates/lsp_locations" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
mermaid_render = { path = "crates/mermaid_render" }
|
||||
migrator = { path = "crates/migrator" }
|
||||
|
|
@ -717,6 +715,10 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [
|
|||
"block2",
|
||||
"objc2-core-foundation",
|
||||
] }
|
||||
objc2-core-foundation = { version = "=0.3.2", default-features = false }
|
||||
objc2-core-graphics = { version = "=0.3.2", default-features = false, features = ["CGDirectDisplay"] }
|
||||
objc2-core-media = { version = "=0.3.2", default-features = false, features = ["CMSampleBuffer", "objc2-core-video"] }
|
||||
objc2-core-video = { version = "=0.3.2", default-features = false, features = ["CVBuffer", "CVImageBuffer"] }
|
||||
objc2-foundation = { version = "=0.3.2", default-features = false, features = [
|
||||
"NSArray",
|
||||
"NSAttributedString",
|
||||
|
|
@ -745,8 +747,11 @@ objc2-foundation = { version = "=0.3.2", default-features = false, features = [
|
|||
] }
|
||||
objc2-screen-capture-kit = { version = "=0.3.2", default-features = false, features = [
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-media",
|
||||
"SCShareableContent",
|
||||
"SCStream",
|
||||
"std"
|
||||
] }
|
||||
objc2-user-notifications = "0.3"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ profiles = ["release"]
|
|||
# Own non-.rs files read at build time (protobufs, bindgen headers, doc/data).
|
||||
proto = ["proto/**"]
|
||||
livekit_api = ["vendored/protocol/**"]
|
||||
media = ["src/bindings.h"]
|
||||
gpui = [
|
||||
"README.md",
|
||||
"examples/image/exif-orientation-rotate-180.jpg",
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ uuid = { workspace = true, features = ["js"] }
|
|||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-video.workspace = true
|
||||
objc2-core-foundation.workspace = true
|
||||
objc2-core-video.workspace = true
|
||||
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "94b0f28166665e8fd2f53ff6d268a14955c82269", package = "zed-font-kit", version = "0.14.1-zed", optional = true }
|
||||
log.workspace = true
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
|
|||
#[cfg(not(feature = "screen-capture"))]
|
||||
pub(crate) type PlatformScreenCaptureFrame = ();
|
||||
#[cfg(all(target_os = "macos", feature = "screen-capture"))]
|
||||
pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
|
||||
pub(crate) type PlatformScreenCaptureFrame =
|
||||
objc2_core_foundation::CFRetained<objc2_core_video::CVImageBuffer>;
|
||||
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ accesskit.workspace = true
|
|||
accesskit_macos.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-task.workspace = true
|
||||
block.workspace = true
|
||||
block2.workspace = true
|
||||
cocoa.workspace = true
|
||||
collections.workspace = true
|
||||
|
|
@ -48,11 +47,11 @@ itertools.workspace = true
|
|||
libc.workspace = true
|
||||
log.workspace = true
|
||||
mach2.workspace = true
|
||||
media.workspace = true
|
||||
metal.workspace = true
|
||||
objc.workspace = true
|
||||
objc2.workspace = true
|
||||
objc2-app-kit.workspace = true
|
||||
objc2-core-graphics.workspace = true
|
||||
objc2-core-media.workspace = true
|
||||
objc2-foundation.workspace = true
|
||||
objc2-screen-capture-kit.workspace = true
|
||||
objc2-user-notifications.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,36 +1,25 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use block::ConcreteBlock;
|
||||
use block2::RcBlock;
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::NSArray,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use core_foundation::base::TCFType;
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayModeGetPixelHeight,
|
||||
CGDisplayModeGetPixelWidth, CGDisplayModeRelease,
|
||||
};
|
||||
use ctor::ctor;
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
|
||||
SharedString, SourceMetadata, size,
|
||||
};
|
||||
use media::core_media::{CMSampleBuffer, CMSampleBufferRef};
|
||||
use metal::NSInteger;
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
use objc2::{
|
||||
AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send,
|
||||
rc::Retained,
|
||||
runtime::{NSObject, ProtocolObject},
|
||||
};
|
||||
use objc2::{MainThreadMarker, rc::Retained};
|
||||
use objc2_app_kit::NSScreen;
|
||||
use objc2_foundation::{NSError, NSNumber, NSString};
|
||||
use objc2_screen_capture_kit::{SCDisplay, SCShareableContent};
|
||||
use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc};
|
||||
use objc2_core_graphics::{CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayMode};
|
||||
use objc2_core_media::CMSampleBuffer;
|
||||
use objc2_foundation::{NSArray, NSError, NSNumber, NSObjectProtocol, NSString};
|
||||
use objc2_screen_capture_kit::{
|
||||
SCContentFilter, SCDisplay, SCShareableContent, SCStream, SCStreamConfiguration,
|
||||
SCStreamOutput, SCStreamOutputType, SCWindow,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MacScreenCaptureSource {
|
||||
|
|
@ -39,49 +28,36 @@ pub struct MacScreenCaptureSource {
|
|||
}
|
||||
|
||||
pub struct MacScreenCaptureStream {
|
||||
sc_stream: id,
|
||||
sc_stream_output: id,
|
||||
sc_stream: Retained<SCStream>,
|
||||
sc_stream_output: Retained<StreamOutput>,
|
||||
meta: SourceMetadata,
|
||||
}
|
||||
|
||||
static mut DELEGATE_CLASS: *const Class = ptr::null();
|
||||
static mut OUTPUT_CLASS: *const Class = ptr::null();
|
||||
const FRAME_CALLBACK_IVAR: &str = "frame_callback";
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const SCStreamOutputTypeScreen: NSInteger = 0;
|
||||
|
||||
impl ScreenCaptureSource for MacScreenCaptureSource {
|
||||
fn metadata(&self) -> Result<SourceMetadata> {
|
||||
let (display_id, size) = unsafe {
|
||||
// SAFETY: `SCDisplay` is an Objective-C object, so objc2's object pointer has the
|
||||
// same ABI as objc 0.2's `id`. This only casts a borrowed pointer; it does not
|
||||
// transfer ownership or change the retain count. `self.sc_display` remains alive
|
||||
// for the entire message send, and `displayID` does not retain the receiver.
|
||||
let sc_display: id = Retained::as_ptr(&self.sc_display).cast_mut().cast();
|
||||
let display_id: CGDirectDisplayID = msg_send![sc_display, displayID];
|
||||
let display_mode_ref = CGDisplayCopyDisplayMode(display_id);
|
||||
let width = CGDisplayModeGetPixelWidth(display_mode_ref);
|
||||
let height = CGDisplayModeGetPixelHeight(display_mode_ref);
|
||||
CGDisplayModeRelease(display_mode_ref);
|
||||
|
||||
(
|
||||
display_id,
|
||||
size(DevicePixels(width as i32), DevicePixels(height as i32)),
|
||||
)
|
||||
};
|
||||
impl MacScreenCaptureSource {
|
||||
fn metadata(&self) -> SourceMetadata {
|
||||
let display_id = unsafe { self.sc_display.displayID() };
|
||||
let display_mode = CGDisplayCopyDisplayMode(display_id);
|
||||
let width = CGDisplayMode::pixel_width(display_mode.as_deref());
|
||||
let height = CGDisplayMode::pixel_height(display_mode.as_deref());
|
||||
let size = size(DevicePixels(width as i32), DevicePixels(height as i32));
|
||||
let (label, is_main) = self
|
||||
.meta
|
||||
.clone()
|
||||
.map(|meta| (meta.label, meta.is_main))
|
||||
.unzip();
|
||||
|
||||
Ok(SourceMetadata {
|
||||
SourceMetadata {
|
||||
id: display_id as u64,
|
||||
label,
|
||||
is_main,
|
||||
resolution: size,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreenCaptureSource for MacScreenCaptureSource {
|
||||
fn metadata(&self) -> Result<SourceMetadata> {
|
||||
Ok(self.metadata())
|
||||
}
|
||||
|
||||
fn stream(
|
||||
|
|
@ -89,86 +65,68 @@ impl ScreenCaptureSource for MacScreenCaptureSource {
|
|||
_foreground_executor: &ForegroundExecutor,
|
||||
frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
|
||||
) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let metadata = self.metadata();
|
||||
|
||||
unsafe {
|
||||
// SAFETY: `SCDisplay` is an Objective-C object, so objc2's object pointer has the
|
||||
// same ABI as objc 0.2's `id`. The cast removes pointer constness only because the
|
||||
// legacy `id` alias is mutable; it does not mutate the display, transfer ownership,
|
||||
// or change the retain count. The generated objc2 binding takes `display` as
|
||||
// `&SCDisplay`, confirming that this initializer borrows rather than consumes it.
|
||||
// `self.sc_display` remains alive through the call, and `SCContentFilter` must retain
|
||||
// the display or copy any state that it needs after initialization.
|
||||
let sc_display: id = Retained::as_ptr(&self.sc_display).cast_mut().cast();
|
||||
let stream: id = msg_send![class!(SCStream), alloc];
|
||||
let filter: id = msg_send![class!(SCContentFilter), alloc];
|
||||
let configuration: id = msg_send![class!(SCStreamConfiguration), alloc];
|
||||
let delegate: id = msg_send![DELEGATE_CLASS, alloc];
|
||||
let output: id = msg_send![OUTPUT_CLASS, alloc];
|
||||
|
||||
let excluded_windows = NSArray::array(nil);
|
||||
let filter: id =
|
||||
msg_send![filter, initWithDisplay:sc_display excludingWindows:excluded_windows];
|
||||
let configuration: id = msg_send![configuration, init];
|
||||
let _: id = msg_send![configuration, setScalesToFit: true];
|
||||
let _: id = msg_send![configuration, setPixelFormat: 0x42475241];
|
||||
// let _: id = msg_send![configuration, setShowsCursor: false];
|
||||
// let _: id = msg_send![configuration, setCaptureResolution: 3];
|
||||
let delegate: id = msg_send![delegate, init];
|
||||
let output: id = msg_send![output, init];
|
||||
|
||||
output.as_mut().unwrap().set_ivar(
|
||||
FRAME_CALLBACK_IVAR,
|
||||
Box::into_raw(Box::new(frame_callback)) as *mut c_void,
|
||||
let excluded_windows = NSArray::<SCWindow>::new();
|
||||
let filter = SCContentFilter::initWithDisplay_excludingWindows(
|
||||
SCContentFilter::alloc(),
|
||||
&self.sc_display,
|
||||
&excluded_windows,
|
||||
);
|
||||
let configuration = SCStreamConfiguration::new();
|
||||
configuration.setScalesToFit(true);
|
||||
configuration.setPixelFormat(0x42475241);
|
||||
configuration.setWidth(metadata.resolution.width.0 as usize);
|
||||
configuration.setHeight(metadata.resolution.height.0 as usize);
|
||||
|
||||
let meta = self.metadata().unwrap();
|
||||
let _: id = msg_send![configuration, setWidth: meta.resolution.width.0 as i64];
|
||||
let _: id = msg_send![configuration, setHeight: meta.resolution.height.0 as i64];
|
||||
let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate];
|
||||
let stream = SCStream::initWithFilter_configuration_delegate(
|
||||
SCStream::alloc(),
|
||||
&filter,
|
||||
&configuration,
|
||||
None,
|
||||
);
|
||||
let output = StreamOutput::new(frame_callback);
|
||||
let output_protocol = ProtocolObject::from_ref(&*output);
|
||||
|
||||
// Stream contains filter, configuration, and delegate internally so we release them here
|
||||
// to prevent a memory leak when steam is dropped
|
||||
let _: () = msg_send![filter, release];
|
||||
let _: () = msg_send![configuration, release];
|
||||
let _: () = msg_send![delegate, release];
|
||||
if let Err(error) = stream.addStreamOutput_type_sampleHandlerQueue_error(
|
||||
output_protocol,
|
||||
SCStreamOutputType::Screen,
|
||||
None,
|
||||
) {
|
||||
_ = sender.send(Err(anyhow!(
|
||||
"failed to add stream output: {}",
|
||||
error.localizedDescription()
|
||||
)));
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let mut error: id = nil;
|
||||
let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id];
|
||||
if error != nil {
|
||||
let message: id = msg_send![error, localizedDescription];
|
||||
let _: () = msg_send![stream, release];
|
||||
let _: () = msg_send![output, release];
|
||||
tx.send(Err(anyhow!("failed to add stream output {message:?}")))
|
||||
.ok();
|
||||
return rx;
|
||||
return receiver;
|
||||
}
|
||||
|
||||
let tx = Rc::new(RefCell::new(Some(tx)));
|
||||
let handler = ConcreteBlock::new({
|
||||
move |error: id| {
|
||||
let result = if error == nil {
|
||||
let stream = MacScreenCaptureStream {
|
||||
meta: meta.clone(),
|
||||
sc_stream: stream,
|
||||
sc_stream_output: output,
|
||||
};
|
||||
Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>)
|
||||
} else {
|
||||
let _: () = msg_send![stream, release];
|
||||
let _: () = msg_send![output, release];
|
||||
let message: id = msg_send![error, localizedDescription];
|
||||
Err(anyhow!("failed to start screen capture stream {message:?}"))
|
||||
};
|
||||
if let Some(tx) = tx.borrow_mut().take() {
|
||||
tx.send(result).ok();
|
||||
}
|
||||
}
|
||||
let state = Rc::new(RefCell::new(Some((sender, stream.clone(), output))));
|
||||
let handler = RcBlock::new(move |error: *mut NSError| {
|
||||
let Some((sender, stream, output)) = state.borrow_mut().take() else {
|
||||
return;
|
||||
};
|
||||
let result = if let Some(error) = error.as_ref() {
|
||||
Err(anyhow!(
|
||||
"failed to start screen capture stream: {}",
|
||||
error.localizedDescription()
|
||||
))
|
||||
} else {
|
||||
Ok(Box::new(MacScreenCaptureStream {
|
||||
meta: metadata.clone(),
|
||||
sc_stream: stream,
|
||||
sc_stream_output: output,
|
||||
}) as Box<dyn ScreenCaptureStream>)
|
||||
};
|
||||
|
||||
_ = sender.send(result);
|
||||
});
|
||||
let handler = handler.copy();
|
||||
let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler];
|
||||
rx
|
||||
stream.startCaptureWithCompletionHandler(Some(&handler));
|
||||
}
|
||||
|
||||
receiver
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,23 +139,27 @@ impl ScreenCaptureStream for MacScreenCaptureStream {
|
|||
impl Drop for MacScreenCaptureStream {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let mut error: id = nil;
|
||||
let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _];
|
||||
if error != nil {
|
||||
let message: id = msg_send![error, localizedDescription];
|
||||
log::error!("failed to add stream output {message:?}");
|
||||
let output = ProtocolObject::from_ref(&*self.sc_stream_output);
|
||||
if let Err(error) = self
|
||||
.sc_stream
|
||||
.removeStreamOutput_type_error(output, SCStreamOutputType::Screen)
|
||||
{
|
||||
log::error!(
|
||||
"failed to remove screen capture stream output: {}",
|
||||
error.localizedDescription()
|
||||
);
|
||||
}
|
||||
|
||||
let handler = ConcreteBlock::new(move |error: id| {
|
||||
if error != nil {
|
||||
let message: id = msg_send![error, localizedDescription];
|
||||
log::error!("failed to stop screen capture stream {message:?}");
|
||||
let handler = RcBlock::new(move |error: *mut NSError| {
|
||||
if let Some(error) = error.as_ref() {
|
||||
log::error!(
|
||||
"failed to stop screen capture stream: {}",
|
||||
error.localizedDescription()
|
||||
);
|
||||
}
|
||||
});
|
||||
let block = handler.copy();
|
||||
let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block];
|
||||
let _: () = msg_send![self.sc_stream, release];
|
||||
let _: () = msg_send![self.sc_stream_output, release];
|
||||
self.sc_stream
|
||||
.stopCaptureWithCompletionHandler(Some(&handler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -288,61 +250,42 @@ pub(crate) fn get_sources(
|
|||
rx
|
||||
}
|
||||
|
||||
#[ctor(unsafe)]
|
||||
unsafe fn build_classes() {
|
||||
let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap();
|
||||
unsafe {
|
||||
decl.add_method(
|
||||
sel!(outputVideoEffectDidStartForStream:),
|
||||
output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(outputVideoEffectDidStopForStream:),
|
||||
output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(stream:didStopWithError:),
|
||||
stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id),
|
||||
);
|
||||
DELEGATE_CLASS = decl.register();
|
||||
|
||||
let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap();
|
||||
decl.add_method(
|
||||
sel!(stream:didOutputSampleBuffer:ofType:),
|
||||
stream_did_output_sample_buffer_of_type
|
||||
as extern "C" fn(&Object, Sel, id, id, NSInteger),
|
||||
);
|
||||
decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR);
|
||||
|
||||
OUTPUT_CLASS = decl.register();
|
||||
}
|
||||
struct StreamOutputIvars {
|
||||
frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
|
||||
}
|
||||
|
||||
extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {}
|
||||
define_class!(
|
||||
// SAFETY: `NSObject` has no subclassing requirements, and `StreamOutput` does not
|
||||
// implement `Drop`; `define_class!` manages destruction of its ivars.
|
||||
#[unsafe(super(NSObject))]
|
||||
#[ivars = StreamOutputIvars]
|
||||
struct StreamOutput;
|
||||
|
||||
extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {}
|
||||
unsafe impl NSObjectProtocol for StreamOutput {}
|
||||
|
||||
extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {}
|
||||
unsafe impl SCStreamOutput for StreamOutput {
|
||||
#[unsafe(method(stream:didOutputSampleBuffer:ofType:))]
|
||||
fn stream_did_output_sample_buffer_of_type(
|
||||
&self,
|
||||
_stream: &SCStream,
|
||||
sample_buffer: &CMSampleBuffer,
|
||||
buffer_type: SCStreamOutputType,
|
||||
) {
|
||||
if buffer_type != SCStreamOutputType::Screen {
|
||||
return;
|
||||
}
|
||||
|
||||
extern "C" fn stream_did_output_sample_buffer_of_type(
|
||||
this: &Object,
|
||||
_: Sel,
|
||||
_stream: id,
|
||||
sample_buffer: id,
|
||||
buffer_type: NSInteger,
|
||||
) {
|
||||
if buffer_type != SCStreamOutputTypeScreen {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let sample_buffer = sample_buffer as CMSampleBufferRef;
|
||||
let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer);
|
||||
if let Some(buffer) = sample_buffer.image_buffer() {
|
||||
let callback: Box<Box<dyn Fn(ScreenCaptureFrame)>> =
|
||||
Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _);
|
||||
callback(ScreenCaptureFrame(buffer));
|
||||
mem::forget(callback);
|
||||
let Some(image_buffer) = (unsafe { sample_buffer.image_buffer() }) else {
|
||||
return;
|
||||
};
|
||||
(self.ivars().frame_callback)(ScreenCaptureFrame(image_buffer));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl StreamOutput {
|
||||
fn new(frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>) -> Retained<Self> {
|
||||
let this = Self::alloc().set_ivars(StreamOutputIvars { frame_callback });
|
||||
unsafe { msg_send![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ scap.workspace = true
|
|||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation.workspace = true
|
||||
core-video.workspace = true
|
||||
objc2-core-foundation.workspace = true
|
||||
coreaudio-rs = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -753,11 +753,15 @@ fn video_frame_buffer_from_webrtc(buffer: Box<dyn VideoBuffer>) -> Option<Remote
|
|||
#[cfg(target_os = "macos")]
|
||||
fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> {
|
||||
use livekit::webrtc;
|
||||
use objc2_core_foundation::CFRetained;
|
||||
|
||||
let pixel_buffer = frame.0.as_concrete_TypeRef();
|
||||
std::mem::forget(frame.0);
|
||||
let pixel_buffer = CFRetained::into_raw(frame.0);
|
||||
unsafe {
|
||||
Some(webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer(pixel_buffer as _))
|
||||
Some(
|
||||
webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer(
|
||||
pixel_buffer.as_ptr().cast(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
[package]
|
||||
name = "media"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
description = "Bindings to macos media handling APIs for Zed"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/media.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation.workspace = true
|
||||
foreign-types.workspace = true
|
||||
metal.workspace = true
|
||||
core-video.workspace = true
|
||||
objc.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72"
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../LICENSE-APACHE
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {
|
||||
use std::{env, path::PathBuf, process::Command};
|
||||
|
||||
let sdk_path = String::from_utf8(
|
||||
Command::new("xcrun")
|
||||
.args(["--sdk", "macosx", "--show-sdk-path"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
let sdk_path = sdk_path.trim_end();
|
||||
|
||||
println!("cargo:rerun-if-changed=src/bindings.h");
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("src/bindings.h")
|
||||
.clang_arg(format!("-isysroot{}", sdk_path))
|
||||
.clang_arg("-xobjective-c")
|
||||
.allowlist_type("CMItemIndex")
|
||||
.allowlist_type("CMSampleTimingInfo")
|
||||
.allowlist_type("CMVideoCodecType")
|
||||
.allowlist_type("VTEncodeInfoFlags")
|
||||
.allowlist_function("CMTimeMake")
|
||||
.allowlist_var("kCVPixelFormatType_.*")
|
||||
.allowlist_var("kCVReturn.*")
|
||||
.allowlist_var("VTEncodeInfoFlags_.*")
|
||||
.allowlist_var("kCMVideoCodecType_.*")
|
||||
.allowlist_var("kCMTime.*")
|
||||
.allowlist_var("kCMSampleAttachmentKey_.*")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.layout_tests(false)
|
||||
.generate()
|
||||
.expect("unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("couldn't write dispatch bindings");
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn main() {}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#import <CoreMedia/CMFormatDescription.h>
|
||||
#import <CoreMedia/CMSampleBuffer.h>
|
||||
#import <CoreVideo/CVPixelFormatDescription.h>
|
||||
#import <CoreVideo/CVReturn.h>
|
||||
#import <VideoToolbox/VTCompressionSession.h>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use objc::*;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
mod bindings;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod core_media {
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub use crate::bindings::{
|
||||
CMItemIndex, CMSampleTimingInfo, CMTime, CMTimeMake, CMVideoCodecType,
|
||||
kCMSampleAttachmentKey_NotSync, kCMTimeInvalid, kCMVideoCodecType_H264,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use core_foundation::{
|
||||
array::{CFArray, CFArrayRef},
|
||||
base::{CFTypeID, OSStatus, TCFType},
|
||||
declare_TCFType,
|
||||
dictionary::CFDictionary,
|
||||
impl_CFTypeDescription, impl_TCFType,
|
||||
string::CFString,
|
||||
};
|
||||
use core_video::image_buffer::{CVImageBuffer, CVImageBufferRef};
|
||||
use std::{ffi::c_void, ptr};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CMSampleBuffer(c_void);
|
||||
// The ref type must be a pointer to the underlying struct.
|
||||
pub type CMSampleBufferRef = *const __CMSampleBuffer;
|
||||
|
||||
declare_TCFType!(CMSampleBuffer, CMSampleBufferRef);
|
||||
impl_TCFType!(CMSampleBuffer, CMSampleBufferRef, CMSampleBufferGetTypeID);
|
||||
impl_CFTypeDescription!(CMSampleBuffer);
|
||||
|
||||
impl CMSampleBuffer {
|
||||
pub fn attachments(&self) -> Vec<CFDictionary<CFString>> {
|
||||
unsafe {
|
||||
let attachments =
|
||||
CMSampleBufferGetSampleAttachmentsArray(self.as_concrete_TypeRef(), true);
|
||||
CFArray::<CFDictionary>::wrap_under_get_rule(attachments)
|
||||
.into_iter()
|
||||
.map(|attachments| {
|
||||
CFDictionary::wrap_under_get_rule(attachments.as_concrete_TypeRef())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_buffer(&self) -> Option<CVImageBuffer> {
|
||||
unsafe {
|
||||
let ptr = CMSampleBufferGetImageBuffer(self.as_concrete_TypeRef());
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CVImageBuffer::wrap_under_get_rule(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_timing_info(&self, index: usize) -> Result<CMSampleTimingInfo> {
|
||||
unsafe {
|
||||
let mut timing_info = CMSampleTimingInfo {
|
||||
duration: kCMTimeInvalid,
|
||||
presentationTimeStamp: kCMTimeInvalid,
|
||||
decodeTimeStamp: kCMTimeInvalid,
|
||||
};
|
||||
let result = CMSampleBufferGetSampleTimingInfo(
|
||||
self.as_concrete_TypeRef(),
|
||||
index as CMItemIndex,
|
||||
&mut timing_info,
|
||||
);
|
||||
anyhow::ensure!(
|
||||
result == 0,
|
||||
"error getting sample timing info, code {result}"
|
||||
);
|
||||
Ok(timing_info)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_description(&self) -> CMFormatDescription {
|
||||
unsafe {
|
||||
CMFormatDescription::wrap_under_get_rule(CMSampleBufferGetFormatDescription(
|
||||
self.as_concrete_TypeRef(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> CMBlockBuffer {
|
||||
unsafe {
|
||||
CMBlockBuffer::wrap_under_get_rule(CMSampleBufferGetDataBuffer(
|
||||
self.as_concrete_TypeRef(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "CoreMedia", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CMSampleBufferGetTypeID() -> CFTypeID;
|
||||
fn CMSampleBufferGetSampleAttachmentsArray(
|
||||
buffer: CMSampleBufferRef,
|
||||
create_if_necessary: bool,
|
||||
) -> CFArrayRef;
|
||||
fn CMSampleBufferGetImageBuffer(buffer: CMSampleBufferRef) -> CVImageBufferRef;
|
||||
fn CMSampleBufferGetSampleTimingInfo(
|
||||
buffer: CMSampleBufferRef,
|
||||
index: CMItemIndex,
|
||||
timing_info_out: *mut CMSampleTimingInfo,
|
||||
) -> OSStatus;
|
||||
fn CMSampleBufferGetFormatDescription(buffer: CMSampleBufferRef) -> CMFormatDescriptionRef;
|
||||
fn CMSampleBufferGetDataBuffer(sample_buffer: CMSampleBufferRef) -> CMBlockBufferRef;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CMFormatDescription(c_void);
|
||||
pub type CMFormatDescriptionRef = *const __CMFormatDescription;
|
||||
|
||||
declare_TCFType!(CMFormatDescription, CMFormatDescriptionRef);
|
||||
impl_TCFType!(
|
||||
CMFormatDescription,
|
||||
CMFormatDescriptionRef,
|
||||
CMFormatDescriptionGetTypeID
|
||||
);
|
||||
impl_CFTypeDescription!(CMFormatDescription);
|
||||
|
||||
impl CMFormatDescription {
|
||||
pub fn h264_parameter_set_count(&self) -> usize {
|
||||
unsafe {
|
||||
let mut count = 0;
|
||||
let result = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
|
||||
self.as_concrete_TypeRef(),
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&mut count,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
assert_eq!(result, 0);
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
pub fn h264_parameter_set_at_index(&self, index: usize) -> Result<&[u8]> {
|
||||
unsafe {
|
||||
let mut bytes = ptr::null();
|
||||
let mut len = 0;
|
||||
let result = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
|
||||
self.as_concrete_TypeRef(),
|
||||
index,
|
||||
&mut bytes,
|
||||
&mut len,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
anyhow::ensure!(result == 0, "error getting parameter set, code: {result}");
|
||||
Ok(std::slice::from_raw_parts(bytes, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "CoreMedia", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CMFormatDescriptionGetTypeID() -> CFTypeID;
|
||||
fn CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
|
||||
video_desc: CMFormatDescriptionRef,
|
||||
parameter_set_index: usize,
|
||||
parameter_set_pointer_out: *mut *const u8,
|
||||
parameter_set_size_out: *mut usize,
|
||||
parameter_set_count_out: *mut usize,
|
||||
NALUnitHeaderLengthOut: *mut isize,
|
||||
) -> OSStatus;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CMBlockBuffer(c_void);
|
||||
pub type CMBlockBufferRef = *const __CMBlockBuffer;
|
||||
|
||||
declare_TCFType!(CMBlockBuffer, CMBlockBufferRef);
|
||||
impl_TCFType!(CMBlockBuffer, CMBlockBufferRef, CMBlockBufferGetTypeID);
|
||||
impl_CFTypeDescription!(CMBlockBuffer);
|
||||
|
||||
impl CMBlockBuffer {
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
unsafe {
|
||||
let mut bytes = ptr::null();
|
||||
let mut len = 0;
|
||||
let result = CMBlockBufferGetDataPointer(
|
||||
self.as_concrete_TypeRef(),
|
||||
0,
|
||||
&mut 0,
|
||||
&mut len,
|
||||
&mut bytes,
|
||||
);
|
||||
assert!(result == 0, "could not get block buffer data");
|
||||
std::slice::from_raw_parts(bytes, len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "CoreMedia", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CMBlockBufferGetTypeID() -> CFTypeID;
|
||||
fn CMBlockBufferGetDataPointer(
|
||||
buffer: CMBlockBufferRef,
|
||||
offset: usize,
|
||||
length_at_offset_out: *mut usize,
|
||||
total_length_out: *mut usize,
|
||||
data_pointer_out: *mut *const u8,
|
||||
) -> OSStatus;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod core_video {
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use core_foundation::{
|
||||
base::{CFTypeID, TCFType},
|
||||
declare_TCFType, impl_CFTypeDescription, impl_TCFType,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::bindings::{CVReturn, kCVReturnSuccess};
|
||||
pub use crate::bindings::{
|
||||
kCVPixelFormatType_32BGRA, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, kCVPixelFormatType_420YpCbCr8Planar,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use core_foundation::{
|
||||
base::kCFAllocatorDefault, dictionary::CFDictionaryRef, mach_port::CFAllocatorRef,
|
||||
};
|
||||
use foreign_types::ForeignTypeRef;
|
||||
|
||||
use metal::{MTLDevice, MTLPixelFormat};
|
||||
use std::ptr;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CVMetalTextureCache(c_void);
|
||||
pub type CVMetalTextureCacheRef = *const __CVMetalTextureCache;
|
||||
|
||||
declare_TCFType!(CVMetalTextureCache, CVMetalTextureCacheRef);
|
||||
impl_TCFType!(
|
||||
CVMetalTextureCache,
|
||||
CVMetalTextureCacheRef,
|
||||
CVMetalTextureCacheGetTypeID
|
||||
);
|
||||
impl_CFTypeDescription!(CVMetalTextureCache);
|
||||
|
||||
impl CVMetalTextureCache {
|
||||
/// # Safety
|
||||
///
|
||||
/// metal_device must be valid according to CVMetalTextureCacheCreate
|
||||
pub unsafe fn new(metal_device: *mut MTLDevice) -> Result<Self> {
|
||||
let mut this = ptr::null();
|
||||
let result = unsafe {
|
||||
CVMetalTextureCacheCreate(
|
||||
kCFAllocatorDefault,
|
||||
ptr::null(),
|
||||
metal_device,
|
||||
ptr::null(),
|
||||
&mut this,
|
||||
)
|
||||
};
|
||||
anyhow::ensure!(
|
||||
result == kCVReturnSuccess,
|
||||
"could not create texture cache, code: {result}"
|
||||
);
|
||||
unsafe { Ok(CVMetalTextureCache::wrap_under_create_rule(this)) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The arguments to this function must be valid according to CVMetalTextureCacheCreateTextureFromImage
|
||||
pub unsafe fn create_texture_from_image(
|
||||
&self,
|
||||
source: ::core_video::image_buffer::CVImageBufferRef,
|
||||
texture_attributes: CFDictionaryRef,
|
||||
pixel_format: MTLPixelFormat,
|
||||
width: usize,
|
||||
height: usize,
|
||||
plane_index: usize,
|
||||
) -> Result<CVMetalTexture> {
|
||||
let mut this = ptr::null();
|
||||
let result = unsafe {
|
||||
CVMetalTextureCacheCreateTextureFromImage(
|
||||
kCFAllocatorDefault,
|
||||
self.as_concrete_TypeRef(),
|
||||
source,
|
||||
texture_attributes,
|
||||
pixel_format,
|
||||
width,
|
||||
height,
|
||||
plane_index,
|
||||
&mut this,
|
||||
)
|
||||
};
|
||||
anyhow::ensure!(
|
||||
result == kCVReturnSuccess,
|
||||
"could not create texture, code: {result}"
|
||||
);
|
||||
unsafe { Ok(CVMetalTexture::wrap_under_create_rule(this)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CVMetalTextureCacheGetTypeID() -> CFTypeID;
|
||||
fn CVMetalTextureCacheCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
cache_attributes: CFDictionaryRef,
|
||||
metal_device: *const MTLDevice,
|
||||
texture_attributes: CFDictionaryRef,
|
||||
cache_out: *mut CVMetalTextureCacheRef,
|
||||
) -> CVReturn;
|
||||
fn CVMetalTextureCacheCreateTextureFromImage(
|
||||
allocator: CFAllocatorRef,
|
||||
texture_cache: CVMetalTextureCacheRef,
|
||||
source_image: ::core_video::image_buffer::CVImageBufferRef,
|
||||
texture_attributes: CFDictionaryRef,
|
||||
pixel_format: MTLPixelFormat,
|
||||
width: usize,
|
||||
height: usize,
|
||||
plane_index: usize,
|
||||
texture_out: *mut CVMetalTextureRef,
|
||||
) -> CVReturn;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CVMetalTexture(c_void);
|
||||
pub type CVMetalTextureRef = *const __CVMetalTexture;
|
||||
|
||||
declare_TCFType!(CVMetalTexture, CVMetalTextureRef);
|
||||
impl_TCFType!(CVMetalTexture, CVMetalTextureRef, CVMetalTextureGetTypeID);
|
||||
impl_CFTypeDescription!(CVMetalTexture);
|
||||
|
||||
impl CVMetalTexture {
|
||||
pub fn as_texture_ref(&self) -> &metal::TextureRef {
|
||||
unsafe {
|
||||
let texture = CVMetalTextureGetTexture(self.as_concrete_TypeRef());
|
||||
metal::TextureRef::from_ptr(texture as *mut _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CVMetalTextureGetTypeID() -> CFTypeID;
|
||||
fn CVMetalTextureGetTexture(texture: CVMetalTextureRef) -> *mut c_void;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue