Skip to content
Open
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
15 changes: 13 additions & 2 deletions crates/anyrender_skia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ edition.workspace = true
pixels_window_renderer = ["dep:pixels_window_renderer"]
softbuffer_window_renderer = ["dep:softbuffer_window_renderer"]
log_frame_times = ["debug_timer/enable", "pixels_window_renderer?/log_frame_times", "softbuffer_window_renderer?/log_frame_times"]
vulkan = ["skia-safe/vulkan"]
vulkan = ["ganesh", "skia-safe?/vulkan"]
# GPU engines used by the window renderer on macOS/iOS (Linux/Windows always use Ganesh via OpenGL).
# rust-skia only publishes prebuilt binaries with one of these enabled: use
# `default-features = false, features = ["graphite"]` to use Graphite without building Skia from source.
ganesh = ["skia-safe?/ganesh"]
graphite = ["dep:skia-safe"]
default = ["ganesh"]

[dependencies]
anyrender = { workspace = true }
Expand All @@ -22,11 +28,12 @@ peniko = { workspace = true }
raw-window-handle = { workspace = true }
softbuffer_window_renderer = { workspace = true, optional = true }
pixels_window_renderer = { workspace = true, optional = true }
skia-safe = { version = "0.153.3", features = ["gl", "pdf"]}
skia-safe = { version = "0.153.3", features = ["pdf"] }
oaty = "0.2"
hashbrown = "0.17.0"

[target.'cfg(not(any(target_os = "macos", target_os = "ios")))'.dependencies]
skia-safe = { version = "0.153.3", features = ["gl"] }
glutin = { version = "0.32.3", features = ["default", "wgl", "egl"] }
gl = "0.14.0"
ash = "^0.38.0"
Expand All @@ -37,9 +44,13 @@ ash-window = "0.13.0"
skia-safe = { version = "0.153.3", features = ["textlayout"] }

[target.'cfg(target_os = "macos")'.dependencies]
# Optional so that the `graphite` feature only enables Skia's Graphite engine on Apple targets.
skia-safe = { version = "0.153.3", features = ["graphite"], optional = true }
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["objc2-quartz-core", "NSView", "NSResponder"] }

[target.'cfg(target_os = "ios")'.dependencies]
# Optional so that the `graphite` feature only enables Skia's Graphite engine on Apple targets.
skia-safe = { version = "0.153.3", features = ["graphite"], optional = true }
objc2-ui-kit = { version = "0.3.2", default-features = false, features = ["objc2-quartz-core", "UIView", "UIResponder"] }

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
Expand Down
16 changes: 8 additions & 8 deletions crates/anyrender_skia/src/image_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ impl ImageRenderer for SkiaImageRenderer {
// Clear surface with transparent background to allow transparency in rendered images
surface.canvas().clear(Color::TRANSPARENT);

draw_fn(&mut SkiaScenePainter {
inner: surface.canvas(),
cache: &mut self.scene_cache,
});
draw_fn(&mut SkiaScenePainter::new(
surface.canvas(),
&mut self.scene_cache,
));
timer.record_time("render");

self.scene_cache.next_gen();
Expand All @@ -91,10 +91,10 @@ impl ImageRenderer for SkiaImageRenderer {
// Clear surface with transparent background to allow transparency in rendered images
surface.canvas().clear(Color::TRANSPARENT);

draw_fn(&mut SkiaScenePainter {
inner: surface.canvas(),
cache: &mut self.scene_cache,
});
draw_fn(&mut SkiaScenePainter::new(
surface.canvas(),
&mut self.scene_cache,
));
timer.record_time("render");

self.scene_cache.next_gen();
Expand Down
5 changes: 5 additions & 0 deletions crates/anyrender_skia/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ mod window_renderer;
mod cache;
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod metal;
#[cfg(all(
any(target_os = "macos", target_os = "ios"),
not(any(feature = "ganesh", feature = "graphite"))
))]
compile_error!("anyrender_skia requires the `ganesh` or `graphite` feature on macOS/iOS");
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
mod opengl;
#[cfg(feature = "vulkan")]
Expand Down
193 changes: 54 additions & 139 deletions crates/anyrender_skia/src/metal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,151 +4,66 @@ use objc2::{rc::Retained, runtime::ProtocolObject};
#[cfg(target_os = "macos")]
use objc2_app_kit::NSView;
use objc2_core_foundation::CGSize;
use objc2_metal::{MTLCommandBuffer, MTLCommandQueue, MTLCreateSystemDefaultDevice, MTLDevice};
use objc2_quartz_core::{CAMetalDrawable, CAMetalLayer};
use objc2_metal::MTLDevice;
use objc2_quartz_core::CAMetalLayer;
#[cfg(target_os = "ios")]
use objc2_ui_kit::UIView;
use skia_safe::{
ColorType, Surface,
gpu::{self, DirectContext, SurfaceOrigin, backend_render_targets, mtl},
scalar,
};

use crate::window_renderer::SkiaBackend;

pub struct MetalBackend {
pub metal_layer: Retained<CAMetalLayer>,
pub command_queue: Retained<ProtocolObject<dyn MTLCommandQueue>>,
pub skia: DirectContext,
prepared_drawable: Option<Retained<ProtocolObject<dyn objc2_metal::MTLDrawable>>>,
}

impl MetalBackend {
pub fn new(
window: Arc<dyn anyrender::WindowHandle>,
width: u32,
height: u32,
composite_alpha_mode: anyrender::CompositeAlphaMode,
) -> Self {
let device = MTLCreateSystemDefaultDevice().expect("no device found");

let metal_layer = {
let layer = CAMetalLayer::new();
layer.setDevice(Some(&device));
layer.setPixelFormat(objc2_metal::MTLPixelFormat::BGRA8Unorm);
layer.setOpaque(matches!(
composite_alpha_mode,
anyrender::CompositeAlphaMode::Opaque | anyrender::CompositeAlphaMode::Auto
));
layer.setPresentsWithTransaction(false);
// Disabling this option allows Skia's Blend Mode to work.
// More about: https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
layer.setFramebufferOnly(false);
layer.setDrawableSize(CGSize::new(width as f64, height as f64));

let view_ptr = match window.window_handle().unwrap().as_raw() {
#[cfg(target_os = "macos")]
raw_window_handle::RawWindowHandle::AppKit(appkit) => {
appkit.ns_view.as_ptr() as *mut NSView
}
#[cfg(target_os = "ios")]
raw_window_handle::RawWindowHandle::UiKit(uikit) => {
uikit.ui_view.as_ptr() as *mut UIView
}
_ => panic!("Wrong window handle type"),
};
let view = unsafe { view_ptr.as_ref().unwrap() };

#[cfg(target_os = "macos")]
{
view.setWantsLayer(true);
view.setLayer(Some(&layer.clone().into_super()));
}

#[cfg(target_os = "ios")]
{
// TODO: consider using raw-window-metal crate. It synchronises some properties
// from the parent UIView layer to the child metal layer when they change
layer.setFrame(view.layer().frame());
view.layer().addSublayer(&layer)
}

layer
};

let command_queue = device
.newCommandQueue()
.expect("unable to get command queue");

let backend = unsafe {
mtl::BackendContext::new(
Retained::as_ptr(&device) as mtl::Handle,
Retained::as_ptr(&command_queue) as mtl::Handle,
)
};

let skia_context = gpu::direct_contexts::make_metal(&backend, None).unwrap();

Self {
metal_layer,
command_queue,
skia: skia_context,
prepared_drawable: None,
#[cfg(feature = "ganesh")]
mod ganesh;
#[cfg(feature = "graphite")]
mod graphite;

#[cfg(feature = "ganesh")]
pub(crate) use ganesh::MetalBackend;
#[cfg(feature = "graphite")]
pub(crate) use graphite::MetalGraphiteBackend;

/// Creates a `CAMetalLayer` for `device` and attaches it to the window's view.
fn create_metal_layer(
device: &ProtocolObject<dyn MTLDevice>,
window: Arc<dyn anyrender::WindowHandle>,
width: u32,
height: u32,
composite_alpha_mode: anyrender::CompositeAlphaMode,
) -> Retained<CAMetalLayer> {
let layer = CAMetalLayer::new();
layer.setDevice(Some(device));
layer.setPixelFormat(objc2_metal::MTLPixelFormat::BGRA8Unorm);
layer.setOpaque(matches!(
composite_alpha_mode,
anyrender::CompositeAlphaMode::Opaque | anyrender::CompositeAlphaMode::Auto
));
layer.setPresentsWithTransaction(false);
// Disabling this option allows Skia's Blend Mode to work.
// More about: https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
layer.setFramebufferOnly(false);
layer.setDrawableSize(CGSize::new(width as f64, height as f64));

let view_ptr = match window.window_handle().unwrap().as_raw() {
#[cfg(target_os = "macos")]
raw_window_handle::RawWindowHandle::AppKit(appkit) => {
appkit.ns_view.as_ptr() as *mut NSView
}
#[cfg(target_os = "ios")]
raw_window_handle::RawWindowHandle::UiKit(uikit) => uikit.ui_view.as_ptr() as *mut UIView,
_ => panic!("Wrong window handle type"),
};
let view = unsafe { view_ptr.as_ref().unwrap() };

#[cfg(target_os = "macos")]
{
view.setWantsLayer(true);
view.setLayer(Some(&layer.clone().into_super()));
}
}

impl SkiaBackend for MetalBackend {
fn set_size(&mut self, width: u32, height: u32) {
self.metal_layer
.setDrawableSize(CGSize::new(width as f64, height as f64));
#[cfg(target_os = "ios")]
{
// TODO: consider using raw-window-metal crate. It synchronises some properties
// from the parent UIView layer to the child metal layer when they change
layer.setFrame(view.layer().frame());
view.layer().addSublayer(&layer)
}

fn prepare(&mut self) -> Option<Surface> {
let drawable = self.metal_layer.nextDrawable()?;

let (drawable_width, drawable_height) = {
let size = self.metal_layer.drawableSize();
(size.width as scalar, size.height as scalar)
};

let surface = {
let texture_info = unsafe {
mtl::TextureInfo::new(Retained::as_ptr(&drawable.texture()) as mtl::Handle)
};

let backend_render_target = backend_render_targets::make_mtl(
(drawable_width as i32, drawable_height as i32),
&texture_info,
);

gpu::surfaces::wrap_backend_render_target(
&mut self.skia,
&backend_render_target,
SurfaceOrigin::TopLeft,
ColorType::BGRA8888,
None,
None,
)
.unwrap()
};

self.prepared_drawable = Some((&drawable).into());

Some(surface)
}

fn flush(&mut self, surface: Surface) {
self.skia.flush_and_submit();
drop(surface);
let command_buffer = self
.command_queue
.commandBuffer()
.expect("unable to get command buffer");

// TODO: save drawable
let drawable = self.prepared_drawable.take().unwrap();
command_buffer.presentDrawable(&drawable);
command_buffer.commit();
}
layer
}
Loading
Loading