Skip to content

Commit

Permalink
Add support of metal Render backend.
Browse files Browse the repository at this point in the history
  • Loading branch information
crupest committed May 3, 2024
1 parent 5efb713 commit 8647815
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 19 deletions.
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 @@ -99,9 +99,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 @@ -120,6 +117,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 @@ -147,7 +147,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))
}
}
23 changes: 21 additions & 2 deletions 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 @@ -40,7 +43,7 @@ use crate::{
#[cfg(feature = "gpu_profiling")]
use crate::profiling::GpuCtx;

#[cfg(target_os = "windows")]
#[cfg(any(target_os = "windows", target_os = "macos"))]
use crate::CmdLineSettings;

use cursor_renderer::CursorRenderer;
Expand Down Expand Up @@ -480,6 +483,8 @@ pub enum WindowConfigType {
OpenGL(glutin::config::Config),
#[cfg(target_os = "windows")]
Direct3D,
#[cfg(target_os = "macos")]
Metal,
}

pub struct WindowConfig {
Expand All @@ -503,7 +508,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 @@ -531,6 +548,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::{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

0 comments on commit 8647815

Please sign in to comment.