Ports the macOS-specific Zig code behind cfg(target_os = "macos"): - renderer: a GraphicsApi trait with generic::Renderer<G>; Metal backend (all renderer/metal/* files, shaders.metal verbatim, runtime library compile, IOSurface layer, CVDisplayLink), shadertoy -> MSL via naga; OpenGL remains the backend elsewhere. - font: CoreText face, shaper and discovery with build.rs backend selection (coretext default; coretext-freetype / coretext-harfbuzz / coretext-noshape features), no fontconfig/freetype/harfbuzz on Apple. - input: KeymapDarwin via UCKeyTranslate, option-as-alt, mac keycodes. - os: macos, mach, cf_release_thread, NSProcessInfo args and macOS branches; config: macOS defaults, keybinds, Application Support paths. - termio/core: login(1) wrapping, openpty/execvpe, Darwin child exit handling, os_log logging. Compile-checked for aarch64/x86_64-apple-darwin (clippy -D warnings clean) using the cross setup in docs/rewrite/notes/macos-M1.md; cannot run here. The embedded runtime/C API (embedded.zig) is the next step. Also includes the cross-platform integration edits shared by the crash, verification, Windows and macOS work (Cargo target-gating, thread crash-context hooks, apprt-gtk surface/IME refactor, termio/os cfg gates), a cfg fix so ghostty-config builds on wasm32, and an allow for the intentional recursion in the crash overflow test. Test Plan: - cargo test --workspace (3224 passed) - cargo clippy --workspace --all-targets -- -D warnings - cargo check --target aarch64-apple-darwin -p ghostty-renderer (with the documented cross env)
157 lines
4.2 KiB
Rust
157 lines
4.2 KiB
Rust
//! Wrapper for handling textures (`metal/Texture.zig`).
|
|
use std::{ffi::c_void, ptr::NonNull};
|
|
|
|
use objc2::{rc::Retained, runtime::ProtocolObject};
|
|
use objc2_metal::{
|
|
MTLDevice as _,
|
|
MTLOrigin,
|
|
MTLPixelFormat,
|
|
MTLRegion,
|
|
MTLResourceOptions,
|
|
MTLSize,
|
|
MTLTexture,
|
|
MTLTextureDescriptor,
|
|
MTLTextureUsage,
|
|
};
|
|
|
|
use super::{Device, Error};
|
|
use crate::{
|
|
graphics_api::{ApiError, GpuTexture},
|
|
metal_layout::{PixelFormat, ResourceOptions, TextureUsage},
|
|
};
|
|
|
|
/// Options for initializing a texture.
|
|
#[derive(Clone)]
|
|
pub struct Options {
|
|
/// MTLDevice
|
|
pub device: Device,
|
|
pub pixel_format: PixelFormat,
|
|
pub resource_options: ResourceOptions,
|
|
pub usage: TextureUsage,
|
|
}
|
|
|
|
/// A 2D texture.
|
|
pub struct Texture {
|
|
/// The underlying MTLTexture Object.
|
|
texture: Retained<ProtocolObject<dyn MTLTexture>>,
|
|
|
|
/// The width of this texture.
|
|
pub width: usize,
|
|
/// The height of this texture.
|
|
pub height: usize,
|
|
|
|
/// Bytes per pixel for this texture.
|
|
pub bpp: usize,
|
|
}
|
|
|
|
// SAFETY: MTLTexture objects may be used from any thread (the CPU side only
|
|
// calls `replaceRegion`, which the renderer serializes through its draw
|
|
// mutex).
|
|
unsafe impl Send for Texture {}
|
|
// SAFETY: see above.
|
|
unsafe impl Sync for Texture {}
|
|
|
|
impl Texture {
|
|
/// The underlying MTLTexture.
|
|
pub fn texture(&self) -> &ProtocolObject<dyn MTLTexture> {
|
|
&self.texture
|
|
}
|
|
|
|
/// Initialize a texture
|
|
pub fn init(
|
|
opts: &Options,
|
|
width: usize,
|
|
height: usize,
|
|
data: Option<&[u8]>,
|
|
) -> Result<Texture, Error> {
|
|
// Create our descriptor and set our properties
|
|
let desc = MTLTextureDescriptor::new();
|
|
desc.setPixelFormat(MTLPixelFormat(opts.pixel_format as usize));
|
|
// SAFETY: plain numbers, validated when the texture is created.
|
|
unsafe {
|
|
desc.setWidth(width);
|
|
desc.setHeight(height);
|
|
}
|
|
desc.setResourceOptions(MTLResourceOptions(opts.resource_options.bits() as usize));
|
|
desc.setUsage(MTLTextureUsage(opts.usage.bits() as usize));
|
|
|
|
// Initialize
|
|
let texture = opts
|
|
.device
|
|
.newTextureWithDescriptor(&desc)
|
|
.ok_or(Error::MetalFailed)?;
|
|
|
|
let this = Texture {
|
|
texture,
|
|
width,
|
|
height,
|
|
bpp: opts.pixel_format.bytes_per_pixel(),
|
|
};
|
|
|
|
// If we have data, we set it here.
|
|
if let Some(d) = data {
|
|
debug_assert_eq!(d.len(), width * height * this.bpp);
|
|
this.replace_region(0, 0, width, height, d)?;
|
|
}
|
|
|
|
Ok(this)
|
|
}
|
|
|
|
/// Replace a region of the texture with the provided data.
|
|
///
|
|
/// Zig does NOT check the dimensions of the data to ensure correctness;
|
|
/// since this is a safe function we reject data that is too short.
|
|
pub fn replace_region(
|
|
&self,
|
|
x: usize,
|
|
y: usize,
|
|
width: usize,
|
|
height: usize,
|
|
data: &[u8],
|
|
) -> Result<(), Error> {
|
|
let bytes_per_row = self.bpp * width;
|
|
if data.len() < bytes_per_row * height {
|
|
return Err(Error::MetalFailed);
|
|
}
|
|
let Some(ptr) = NonNull::new(data.as_ptr().cast_mut().cast::<c_void>()) else {
|
|
return Err(Error::MetalFailed);
|
|
};
|
|
let region = MTLRegion {
|
|
origin: MTLOrigin { x, y, z: 0 },
|
|
size: MTLSize {
|
|
width,
|
|
height,
|
|
depth: 1,
|
|
},
|
|
};
|
|
// SAFETY: `ptr` points to at least `bytes_per_row * height` readable
|
|
// bytes (checked above); Metal only reads from it.
|
|
unsafe {
|
|
self.texture
|
|
.replaceRegion_mipmapLevel_withBytes_bytesPerRow(region, 0, ptr, bytes_per_row);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl GpuTexture for Texture {
|
|
fn width(&self) -> usize {
|
|
self.width
|
|
}
|
|
|
|
fn height(&self) -> usize {
|
|
self.height
|
|
}
|
|
|
|
fn replace_region(
|
|
&self,
|
|
x: usize,
|
|
y: usize,
|
|
width: usize,
|
|
height: usize,
|
|
data: &[u8],
|
|
) -> Result<(), ApiError> {
|
|
Texture::replace_region(self, x, y, width, height, data).map_err(ApiError::new)
|
|
}
|
|
}
|