Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support of Metal renderer on macOS. #2461

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,6 @@ windows = { version = "0.56.0", features = [
] }
windows-registry = "0.1.1"

[target.'cfg(not(target_os = "windows"))'.dependencies]
skia-safe = { version = "0.73.0", features = ["gl", "textlayout"] }

[target.'cfg(target_os = "macos")'.dependencies]
icrate = { version = "0.0.4", features = [
"apple",
Expand All @@ -123,6 +120,13 @@ icrate = { version = "0.0.4", features = [
"Foundation_NSArray",
] }
objc2 = "0.4.1"
metal = "0.28.0"
skia-safe = { version = "0.73.0", features = ["metal", "gl", "textlayout"] }
foreign-types-shared = "0.3.1"
core-graphics-types = "0.1.3"

[target.'cfg(not(any(target_os = "windows", target_os = "macos")))'.dependencies]
skia-safe = { version = "0.73.0", features = ["gl", "textlayout"] }

[target.'cfg(target_os = "windows")'.build-dependencies]
winres = "0.1.12"
Expand Down
2 changes: 1 addition & 1 deletion src/cmd_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ pub struct CmdLineSettings {
pub geometry: GeometryArgs,

/// Force opengl on Windows
#[cfg(target_os = "windows")]
#[cfg(any(target_os = "windows", target_os = "macos"))]
#[arg(long = "opengl", env = "NEOVIDE_OPENGL", action = ArgAction::SetTrue, value_parser = FalseyValueParser::new())]
pub opengl: bool,
}
Expand Down
199 changes: 199 additions & 0 deletions src/renderer/metal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use core_graphics_types::{base::CGFloat, geometry::CGSize};
use icrate::AppKit::NSWindow;
use metal::{
foreign_types::{ForeignType, ForeignTypeRef},
CommandQueue, Device, MTLPixelFormat, MetalDrawable, MetalLayer,
};
use objc2::{msg_send, rc::Id, runtime::AnyObject};
use raw_window_handle::HasRawWindowHandle;
use skia_safe::{
gpu::{
mtl::{BackendContext, Handle, TextureInfo},
surfaces::wrap_backend_render_target,
BackendRenderTarget, DirectContext, SurfaceOrigin,
},
Canvas, ColorType, Surface,
};
use winit::{event_loop::EventLoopProxy, window::Window};

use crate::{profiling::tracy_gpu_zone, window::UserEvent};

use super::{vsync::VSyncMacosDisplayLink, SkiaRenderer, VSync};

struct MetalDrawableSurface {
pub drawable: MetalDrawable,
pub surface: Surface,
}

impl MetalDrawableSurface {
fn new(drawable: MetalDrawable, context: &mut DirectContext) -> MetalDrawableSurface {
tracy_gpu_zone!("MetalDrawableSurface.new");

let texture = drawable.texture();
let texture_info = unsafe { TextureInfo::new(texture.as_ptr() as Handle) };
let backend_render_target = BackendRenderTarget::new_metal(
(texture.width() as i32, texture.height() as i32),
&texture_info,
);

let surface = wrap_backend_render_target(
context,
&backend_render_target,
SurfaceOrigin::TopLeft,
ColorType::BGRA8888,
None,
None,
)
.expect("Failed to create skia surface with metal drawable.");

MetalDrawableSurface { drawable, surface }
}

fn new_from_next_drawable_of_metal_layer(
metal_layer: &mut MetalLayer,
context: &mut DirectContext,
) -> MetalDrawableSurface {
tracy_gpu_zone!("MetalDrawableSurface.new_from_next_drawable_of_metal_layer");

let drawable = metal_layer
.next_drawable()
.expect("Failed to get next drawable of metal layer.")
.to_owned();

Self::new(drawable, context)
}
}

pub struct MetalSkiaRenderer {
window: Window,
_device: Device,
metal_layer: MetalLayer,
command_queue: CommandQueue,
_backend: BackendContext,
context: DirectContext,
metal_drawable_surface: Option<MetalDrawableSurface>,
}

impl MetalSkiaRenderer {
pub fn new(window: Window) -> Self {
log::info!("Initialize MetalSkiaRenderer...");

let device = Device::system_default().expect("No metal device found.");

let draw_size = window.inner_size();

let metal_layer = {
let metal_layer = MetalLayer::new();
metal_layer.set_device(&device);
metal_layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
metal_layer.set_presents_with_transaction(false);
metal_layer.set_framebuffer_only(false);
metal_layer.set_display_sync_enabled(false);
metal_layer.set_opaque(false);

unsafe {
let ns_window = match window.raw_window_handle() {
raw_window_handle::RawWindowHandle::AppKit(handle) => {
Id::retain(handle.ns_window as *mut NSWindow).unwrap()
}
_ => panic!("Not an AppKit window."),
};
let ns_view = ns_window.contentView().unwrap();
ns_view.setWantsLayer(true);
let _: () = msg_send![&ns_view, setLayer:(metal_layer.as_ptr() as * mut AnyObject)];
}

metal_layer
.set_drawable_size(CGSize::new(draw_size.width as f64, draw_size.height as f64));
metal_layer
};

let command_queue = device.new_command_queue();

let backend = unsafe {
BackendContext::new(
device.as_ptr() as Handle,
command_queue.as_ptr() as Handle,
std::ptr::null(),
)
};

let context = DirectContext::new_metal(&backend, None).unwrap();

MetalSkiaRenderer {
window,
_device: device,
metal_layer,
command_queue,
_backend: backend,
context,
metal_drawable_surface: None,
}
}

fn move_to_next_frame(&mut self) {
tracy_gpu_zone!("move_to_next_frame");

self.metal_drawable_surface =
Some(MetalDrawableSurface::new_from_next_drawable_of_metal_layer(
&mut self.metal_layer,
&mut self.context,
));
}
}

impl SkiaRenderer for MetalSkiaRenderer {
fn window(&self) -> &Window {
&self.window
}

fn flush(&mut self) {
tracy_gpu_zone!("flush");

self.context.flush_and_submit();
}

fn swap_buffers(&mut self) {
tracy_gpu_zone!("swap buffers");

let command_buffer = self.command_queue.new_command_buffer();
command_buffer.present_drawable(
self.metal_drawable_surface
.as_mut()
.expect("Not metal drawable surface now.")
.drawable
.as_ref(),
);
command_buffer.commit();

self.metal_drawable_surface = None;
}

fn canvas(&mut self) -> &Canvas {
tracy_gpu_zone!("canvas");

self.move_to_next_frame();

self.metal_drawable_surface
.as_mut()
.expect("Not metal drawable surface now.")
.surface
.canvas()
}

fn resize(&mut self) {
tracy_gpu_zone!("resize");

let window_size = self.window.inner_size();
self.metal_layer.set_drawable_size(CGSize::new(
window_size.width as CGFloat,
window_size.height as CGFloat,
));

self.window.request_redraw();
}

fn create_vsync(&self, proxy: EventLoopProxy<UserEvent>) -> VSync {
VSync::MacosDisplayLink(VSyncMacosDisplayLink::new(self.window(), proxy))
}
}
21 changes: 20 additions & 1 deletion src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ mod vsync;
#[cfg(target_os = "windows")]
pub mod d3d;

#[cfg(target_os = "macos")]
mod metal;

use std::{
cmp::Ordering,
collections::{hash_map::Entry, HashMap},
Expand Down Expand Up @@ -523,6 +526,8 @@ pub enum WindowConfigType {
OpenGL(glutin::config::Config),
#[cfg(target_os = "windows")]
Direct3D,
#[cfg(target_os = "macos")]
Metal,
}

pub struct WindowConfig {
Expand All @@ -546,7 +551,19 @@ pub fn build_window_config<TE>(
}
}

#[cfg(not(target_os = "windows"))]
#[cfg(target_os = "macos")]
{
let cmd_line_settings = SETTINGS.get::<CmdLineSettings>();
if cmd_line_settings.opengl {
opengl::build_window(winit_window_builder, event_loop)
} else {
let window = winit_window_builder.build(event_loop).unwrap();
let config = WindowConfigType::Metal;
WindowConfig { window, config }
}
}

#[cfg(not(any(target_os = "windows", target_os = "macos")))]
{
opengl::build_window(winit_window_builder, event_loop)
}
Expand Down Expand Up @@ -574,6 +591,8 @@ pub fn create_skia_renderer(
}
#[cfg(target_os = "windows")]
WindowConfigType::Direct3D => Box::new(d3d::D3DSkiaRenderer::new(window.window)),
#[cfg(target_os = "macos")]
WindowConfigType::Metal => Box::new(metal::MetalSkiaRenderer::new(window.window)),
};
tracy_create_gpu_context("main_render_context", renderer.as_ref());
renderer
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/opengl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use winit::{
pub use super::vsync::VSyncWinDwm;

#[cfg(target_os = "macos")]
pub use super::vsync::VSyncMacos;
pub use super::vsync::VSyncMacosDisplayLink;

use super::{RendererSettings, SkiaRenderer, VSync, WindowConfig, WindowConfigType};

Expand Down Expand Up @@ -205,7 +205,7 @@ impl SkiaRenderer for OpenGLSkiaRenderer {

#[cfg(target_os = "macos")]
{
VSync::Macos(VSyncMacos::new(self.window(), proxy))
VSync::MacosDisplayLink(VSyncMacosDisplayLink::new(self.window(), proxy))
}
}

Expand Down
Loading
Loading