Compare commits
3 Commits
inline-ass
...
metal-view
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0d47fda83 | ||
|
|
9c8737f643 | ||
|
|
ac471813fc |
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -7019,6 +7019,12 @@ dependencies = [
|
||||
"gix-validate 0.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
@@ -7168,6 +7174,7 @@ dependencies = [
|
||||
"font-kit",
|
||||
"foreign-types 0.5.0",
|
||||
"futures 0.3.31",
|
||||
"glam",
|
||||
"gpui_macros",
|
||||
"http_client",
|
||||
"image",
|
||||
|
||||
@@ -87,6 +87,7 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
derive_more.workspace = true
|
||||
etagere = "0.2"
|
||||
glam = "0.24"
|
||||
futures.workspace = true
|
||||
gpui_macros.workspace = true
|
||||
http_client = { optional = true, workspace = true }
|
||||
@@ -298,3 +299,7 @@ path = "examples/uniform_list.rs"
|
||||
[[example]]
|
||||
name = "window_shadow"
|
||||
path = "examples/window_shadow.rs"
|
||||
|
||||
[[example]]
|
||||
name = "metal_view"
|
||||
path = "examples/metal_view.rs"
|
||||
|
||||
254
crates/gpui/examples/metal_view.rs
Normal file
254
crates/gpui/examples/metal_view.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use gpui::{prelude::*, *};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use metal::{Device, MTLPrimitiveType, RenderCommandEncoderRef, RenderPipelineState, TextureRef};
|
||||
|
||||
struct MetalViewExample {
|
||||
start_time: Instant,
|
||||
#[cfg(target_os = "macos")]
|
||||
pipeline_state: Option<RenderPipelineState>,
|
||||
#[cfg(target_os = "macos")]
|
||||
device: Option<Device>,
|
||||
}
|
||||
|
||||
impl MetalViewExample {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(target_os = "macos")]
|
||||
pipeline_state: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
device: None,
|
||||
start_time: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn setup_metal(&mut self) {
|
||||
let device = Device::system_default().expect("no Metal device");
|
||||
|
||||
// Simplified shader for debugging
|
||||
let shader_source = r#"
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct Uniforms {
|
||||
float time;
|
||||
};
|
||||
|
||||
struct VertexOut {
|
||||
float4 position [[position]];
|
||||
float4 color;
|
||||
};
|
||||
|
||||
vertex VertexOut vertex_main(
|
||||
uint vid [[vertex_id]],
|
||||
constant Uniforms& uniforms [[buffer(0)]]
|
||||
) {
|
||||
VertexOut out;
|
||||
|
||||
// Define triangle vertices in normalized device coordinates
|
||||
float2 positions[3] = {
|
||||
float2( 0.0, 0.5), // Top
|
||||
float2(-0.5, -0.5), // Bottom left
|
||||
float2( 0.5, -0.5) // Bottom right
|
||||
};
|
||||
|
||||
float3 colors[3] = {
|
||||
float3(1.0, 0.0, 0.0), // Red
|
||||
float3(0.0, 1.0, 0.0), // Green
|
||||
float3(0.0, 0.0, 1.0) // Blue
|
||||
};
|
||||
|
||||
// Apply rotation
|
||||
float2 pos = positions[vid];
|
||||
float c = cos(uniforms.time);
|
||||
float s = sin(uniforms.time);
|
||||
float2 rotated = float2(
|
||||
pos.x * c - pos.y * s,
|
||||
pos.x * s + pos.y * c
|
||||
);
|
||||
|
||||
out.position = float4(rotated, 0.0, 1.0);
|
||||
out.color = float4(colors[vid], 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 fragment_main(VertexOut in [[stage_in]]) {
|
||||
return in.color;
|
||||
}
|
||||
"#;
|
||||
|
||||
let library = device
|
||||
.new_library_with_source(shader_source, &metal::CompileOptions::new())
|
||||
.expect("Failed to create shader library");
|
||||
|
||||
let vertex_function = library.get_function("vertex_main", None).unwrap();
|
||||
let fragment_function = library.get_function("fragment_main", None).unwrap();
|
||||
|
||||
// Create pipeline state - no vertex descriptor needed for vertex_id based rendering
|
||||
let pipeline_descriptor = metal::RenderPipelineDescriptor::new();
|
||||
pipeline_descriptor.set_vertex_function(Some(&vertex_function));
|
||||
pipeline_descriptor.set_fragment_function(Some(&fragment_function));
|
||||
|
||||
let color_attachment = pipeline_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
color_attachment.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
|
||||
|
||||
// Note: Depth testing is not enabled for now as it requires proper depth buffer setup
|
||||
// in the GPUI rendering pipeline
|
||||
|
||||
// Enable blending
|
||||
color_attachment.set_blending_enabled(true);
|
||||
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
|
||||
color_attachment
|
||||
.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
|
||||
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
|
||||
color_attachment
|
||||
.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
|
||||
|
||||
let pipeline_state = device
|
||||
.new_render_pipeline_state(&pipeline_descriptor)
|
||||
.expect("Failed to create pipeline state");
|
||||
|
||||
self.device = Some(device);
|
||||
self.pipeline_state = Some(pipeline_state);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn create_render_callback(&self, time_delta: f32) -> MetalRenderCallback {
|
||||
let pipeline_state = self.pipeline_state.clone().unwrap();
|
||||
|
||||
Arc::new(
|
||||
move |encoder: &RenderCommandEncoderRef,
|
||||
_target: &TextureRef,
|
||||
bounds: Bounds<Pixels>,
|
||||
scale_factor: f32| {
|
||||
// Set the pipeline state
|
||||
encoder.set_render_pipeline_state(&pipeline_state);
|
||||
|
||||
// Set viewport to match element bounds
|
||||
let viewport = metal::MTLViewport {
|
||||
originX: bounds.origin.x.0 as f64 * scale_factor as f64,
|
||||
originY: bounds.origin.y.0 as f64 * scale_factor as f64,
|
||||
width: bounds.size.width.0 as f64 * scale_factor as f64,
|
||||
height: bounds.size.height.0 as f64 * scale_factor as f64,
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
};
|
||||
encoder.set_viewport(viewport);
|
||||
|
||||
// Set scissor rectangle to clip to bounds
|
||||
let scissor_rect = metal::MTLScissorRect {
|
||||
x: (bounds.origin.x.0 * scale_factor) as u64,
|
||||
y: (bounds.origin.y.0 * scale_factor) as u64,
|
||||
width: (bounds.size.width.0 * scale_factor) as u64,
|
||||
height: (bounds.size.height.0 * scale_factor) as u64,
|
||||
};
|
||||
encoder.set_scissor_rect(scissor_rect);
|
||||
|
||||
// Pass time as uniform
|
||||
let time = time_delta * 2.0; // Scale for reasonable rotation speed
|
||||
#[repr(C)]
|
||||
struct Uniforms {
|
||||
time: f32,
|
||||
}
|
||||
let uniforms = Uniforms { time };
|
||||
encoder.set_vertex_bytes(
|
||||
0,
|
||||
std::mem::size_of::<Uniforms>() as u64,
|
||||
&uniforms as *const Uniforms as *const _,
|
||||
);
|
||||
|
||||
// Draw triangle using vertex_id - no vertex buffer needed
|
||||
encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 3);
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MetalViewExample {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
// Initialize Metal on first render if on macOS
|
||||
#[cfg(target_os = "macos")]
|
||||
if self.pipeline_state.is_none() {
|
||||
self.setup_metal();
|
||||
}
|
||||
|
||||
// Request animation frame
|
||||
window.request_animation_frame();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.bg(rgb(0x1e1e1e))
|
||||
.size_full()
|
||||
.p_8()
|
||||
.gap_6()
|
||||
.child(
|
||||
div()
|
||||
.child("Metal View Element")
|
||||
.text_2xl()
|
||||
.text_color(rgb(0xffffff)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child("While GPUI normally handles all Metal rendering for you, the metal_view() element gives you direct access to write custom Metal shaders and GPU drawing commands")
|
||||
.text_color(rgb(0xaaaaaa)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child("This example shows a rotating 3D cube - the 'Hello World' of 3D graphics programming")
|
||||
.text_sm()
|
||||
.text_color(rgb(0x888888)),
|
||||
)
|
||||
.child(div().overflow_hidden().child(
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let elapsed = self.start_time.elapsed().as_secs_f32();
|
||||
let callback = self.create_render_callback(elapsed);
|
||||
metal_view()
|
||||
.render_with_shared(callback)
|
||||
.w(px(600.0))
|
||||
.h(px(400.0))
|
||||
.bg(rgb(0x000000))
|
||||
},
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
div()
|
||||
.w(px(600.0))
|
||||
.h(px(400.0))
|
||||
.bg(rgb(0x222222))
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(div().child("Metal (macOS only)").text_color(rgb(0x666666)))
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let _ = cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
|
||||
None,
|
||||
size(px(900.0), px(600.0)),
|
||||
cx,
|
||||
))),
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: Some("Metal View Element".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_window, cx| cx.new(|_cx| MetalViewExample::new()),
|
||||
);
|
||||
|
||||
cx.activate(false);
|
||||
});
|
||||
}
|
||||
196
crates/gpui/src/elements/metal_view.rs
Normal file
196
crates/gpui/src/elements/metal_view.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use crate::{
|
||||
App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
|
||||
Pixels, Style, StyleRefinement, Styled, Window,
|
||||
};
|
||||
use refineable::Refineable;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use metal::{RenderCommandEncoderRef, TextureRef};
|
||||
|
||||
/// A callback for custom Metal rendering.
|
||||
///
|
||||
/// The callback receives:
|
||||
/// - command_encoder: The Metal command encoder to issue draw calls
|
||||
/// - target_texture: The texture to render into
|
||||
/// - bounds: The bounds of the element in pixels
|
||||
/// - scale_factor: The window's scale factor
|
||||
#[cfg(target_os = "macos")]
|
||||
pub type MetalRenderCallback =
|
||||
Arc<dyn Fn(&RenderCommandEncoderRef, &TextureRef, Bounds<Pixels>, f32) + Send + Sync + 'static>;
|
||||
|
||||
/// A view that allows custom Metal rendering.
|
||||
pub struct MetalView {
|
||||
#[cfg(target_os = "macos")]
|
||||
render_callback: Option<MetalRenderCallback>,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
/// Create a new Metal view element.
|
||||
pub fn metal_view() -> MetalView {
|
||||
MetalView {
|
||||
#[cfg(target_os = "macos")]
|
||||
render_callback: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
impl MetalView {
|
||||
/// Set the Metal render callback.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn render_with<F>(mut self, callback: F) -> Self
|
||||
where
|
||||
F: Fn(&RenderCommandEncoderRef, &TextureRef, Bounds<Pixels>, f32) + Send + Sync + 'static,
|
||||
{
|
||||
self.render_callback = Some(Arc::new(callback));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the Metal render callback using a shared callback.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn render_with_shared(mut self, callback: MetalRenderCallback) -> Self {
|
||||
self.render_callback = Some(callback);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for MetalView {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
let layout_id = window.request_layout(style, [], cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
_: &mut App,
|
||||
) {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(render_callback) = &self.render_callback {
|
||||
// TODO: This is a placeholder. In a real implementation, we would need to:
|
||||
// 1. Register this Metal view with the window's rendering system
|
||||
// 2. Ensure the callback is invoked during the Metal rendering pass
|
||||
// 3. Handle proper clipping and transformation matrices
|
||||
//
|
||||
// For now, we'll store the callback and bounds in the window's custom render queue
|
||||
window.paint_metal_view(bounds, render_callback.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for MetalView {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for MetalView {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for MetalView to provide platform-agnostic API
|
||||
pub trait MetalViewExt {
|
||||
/// Set a placeholder render function for non-macOS platforms
|
||||
fn render_placeholder<F>(self, callback: F) -> Self
|
||||
where
|
||||
F: Fn(Bounds<Pixels>) + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl MetalViewExt for MetalView {
|
||||
fn render_placeholder<F>(self, _callback: F) -> Self
|
||||
where
|
||||
F: Fn(Bounds<Pixels>) + Send + Sync + 'static,
|
||||
{
|
||||
// On non-macOS platforms, this could render a placeholder
|
||||
// or use a different rendering backend
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// Helper functions for creating common Metal render callbacks
|
||||
pub mod helpers {
|
||||
use super::*;
|
||||
use metal::*;
|
||||
|
||||
/// Helper to create a simple colored rectangle Metal renderer
|
||||
pub fn solid_color_renderer(r: f32, g: f32, b: f32, a: f32) -> MetalRenderCallback {
|
||||
Arc::new(move |encoder, _texture, bounds, _scale_factor| {
|
||||
// This is a simplified example. In practice, you would:
|
||||
// 1. Create or reuse a render pipeline state
|
||||
// 2. Set up vertex data for the bounds
|
||||
// 3. Issue draw calls
|
||||
// 4. Handle proper coordinate transformation
|
||||
|
||||
// For now, this is just a placeholder to show the API design
|
||||
let _ = (encoder, bounds, r, g, b, a);
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper to create a Metal renderer that draws a textured quad
|
||||
pub fn textured_quad_renderer(texture: Texture) -> MetalRenderCallback {
|
||||
Arc::new(move |encoder, _target, bounds, _scale_factor| {
|
||||
// Similar to above, this would set up a textured quad rendering
|
||||
let _ = (encoder, &texture, bounds);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
// ```rust
|
||||
// use gpui::elements::{metal_view, MetalViewExt};
|
||||
//
|
||||
// #[cfg(target_os = "macos")]
|
||||
// let view = metal_view()
|
||||
// .render_with(|encoder, target, bounds, scale_factor| {
|
||||
// // Custom Metal rendering code here
|
||||
// // You have full access to Metal command encoder
|
||||
// })
|
||||
// .size_full();
|
||||
//
|
||||
// #[cfg(not(target_os = "macos"))]
|
||||
// let view = metal_view()
|
||||
// .render_placeholder(|bounds| {
|
||||
// // Fallback rendering for non-macOS platforms
|
||||
// })
|
||||
// .size_full();
|
||||
// ```
|
||||
@@ -6,6 +6,9 @@ mod div;
|
||||
mod image_cache;
|
||||
mod img;
|
||||
mod list;
|
||||
/// Metal-based custom rendering for macOS
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod metal_view;
|
||||
mod surface;
|
||||
mod svg;
|
||||
mod text;
|
||||
@@ -19,6 +22,8 @@ pub use div::*;
|
||||
pub use image_cache::*;
|
||||
pub use img::*;
|
||||
pub use list::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use metal_view::*;
|
||||
pub use surface::*;
|
||||
pub use svg::*;
|
||||
pub use text::*;
|
||||
|
||||
@@ -129,6 +129,8 @@ pub use assets::*;
|
||||
pub use color::*;
|
||||
pub use ctor::ctor;
|
||||
pub use element::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use elements::metal_view::MetalRenderCallback;
|
||||
pub use elements::*;
|
||||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
|
||||
@@ -9,6 +9,8 @@ mod screen_capture;
|
||||
|
||||
#[cfg(not(feature = "macos-blade"))]
|
||||
mod metal_atlas;
|
||||
// #[cfg(not(feature = "macos-blade"))]
|
||||
// mod metal_render_pass;
|
||||
#[cfg(not(feature = "macos-blade"))]
|
||||
pub mod metal_renderer;
|
||||
|
||||
|
||||
343
crates/gpui/src/platform/mac/metal_render_pass.rs
Normal file
343
crates/gpui/src/platform/mac/metal_render_pass.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
use crate::{DevicePixels, PaintMetalView, PrimitiveBatch, ScaledPixels, Scene, Size};
|
||||
use metal::{
|
||||
CommandBufferRef, CommandQueue, Device, MTLLoadAction, MTLStoreAction, RenderCommandEncoderRef,
|
||||
};
|
||||
|
||||
/// Represents a single render command in the rendering pipeline
|
||||
#[derive(Debug)]
|
||||
pub enum RenderCommand<'a> {
|
||||
/// Begin a new render pass with the specified configuration
|
||||
BeginRenderPass { descriptor: RenderPassDescriptor },
|
||||
/// Draw a batch of GPUI primitives
|
||||
DrawPrimitives {
|
||||
batch: PrimitiveBatch<'a>,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
},
|
||||
/// Execute custom Metal rendering
|
||||
ExecuteMetalCallback {
|
||||
metal_view: &'a PaintMetalView,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
},
|
||||
/// End the current render pass
|
||||
EndRenderPass,
|
||||
}
|
||||
|
||||
/// Configuration for a render pass
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderPassDescriptor {
|
||||
pub texture: metal::Texture,
|
||||
pub load_action: MTLLoadAction,
|
||||
pub store_action: MTLStoreAction,
|
||||
pub clear_color: metal::MTLClearColor,
|
||||
pub viewport: metal::MTLViewport,
|
||||
}
|
||||
|
||||
/// State that needs to be preserved across render pass breaks
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderState {
|
||||
pub viewport: metal::MTLViewport,
|
||||
pub blend_mode: Option<BlendMode>,
|
||||
// Add other state that needs to be preserved
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum BlendMode {
|
||||
Normal,
|
||||
Multiply,
|
||||
Screen,
|
||||
// Add other blend modes as needed
|
||||
}
|
||||
|
||||
/// Context provided to Metal render callbacks
|
||||
pub struct MetalRenderContext<'a> {
|
||||
pub command_buffer: &'a CommandBufferRef,
|
||||
pub drawable_texture: &'a metal::TextureRef,
|
||||
pub viewport_size: Size<DevicePixels>,
|
||||
pub device: &'a Device,
|
||||
pub bounds: crate::Bounds<ScaledPixels>,
|
||||
pub scale_factor: f32,
|
||||
}
|
||||
|
||||
/// Manages the rendering pipeline with support for render pass breaks
|
||||
pub struct RenderPassManager {
|
||||
device: Device,
|
||||
command_queue: CommandQueue,
|
||||
current_state: RenderState,
|
||||
}
|
||||
|
||||
impl RenderPassManager {
|
||||
pub fn new(device: Device, command_queue: CommandQueue) -> Self {
|
||||
Self {
|
||||
device,
|
||||
command_queue,
|
||||
current_state: RenderState {
|
||||
viewport: metal::MTLViewport {
|
||||
originX: 0.0,
|
||||
originY: 0.0,
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
},
|
||||
blend_mode: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a scene into a list of render commands
|
||||
pub fn build_render_commands<'a>(
|
||||
&self,
|
||||
scene: &'a Scene,
|
||||
drawable_texture: &metal::TextureRef,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
is_opaque: bool,
|
||||
) -> Vec<RenderCommand<'a>> {
|
||||
let mut commands = Vec::new();
|
||||
|
||||
// Initial render pass configuration
|
||||
let alpha = if is_opaque { 1.0 } else { 0.0 };
|
||||
let descriptor = RenderPassDescriptor {
|
||||
texture: drawable_texture.to_owned(),
|
||||
load_action: MTLLoadAction::Clear,
|
||||
store_action: MTLStoreAction::Store,
|
||||
clear_color: metal::MTLClearColor::new(0.0, 0.0, 0.0, alpha),
|
||||
viewport: metal::MTLViewport {
|
||||
originX: 0.0,
|
||||
originY: 0.0,
|
||||
width: i32::from(viewport_size.width) as f64,
|
||||
height: i32::from(viewport_size.height) as f64,
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
},
|
||||
};
|
||||
|
||||
commands.push(RenderCommand::BeginRenderPass { descriptor });
|
||||
|
||||
// Process batches, inserting render pass breaks for MetalViews
|
||||
let mut in_render_pass = true;
|
||||
|
||||
for batch in scene.batches() {
|
||||
match batch {
|
||||
#[cfg(target_os = "macos")]
|
||||
PrimitiveBatch::MetalViews(metal_views) => {
|
||||
// End current render pass
|
||||
if in_render_pass {
|
||||
commands.push(RenderCommand::EndRenderPass);
|
||||
in_render_pass = false;
|
||||
}
|
||||
|
||||
// Add commands for each MetalView
|
||||
for metal_view in metal_views {
|
||||
commands.push(RenderCommand::ExecuteMetalCallback {
|
||||
metal_view,
|
||||
viewport_size,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Ensure we're in a render pass
|
||||
if !in_render_pass {
|
||||
let descriptor = RenderPassDescriptor {
|
||||
texture: drawable_texture.to_owned(),
|
||||
load_action: MTLLoadAction::Load, // Load existing content
|
||||
store_action: MTLStoreAction::Store,
|
||||
clear_color: metal::MTLClearColor::new(0.0, 0.0, 0.0, 0.0),
|
||||
viewport: self.current_state.viewport,
|
||||
};
|
||||
commands.push(RenderCommand::BeginRenderPass { descriptor });
|
||||
in_render_pass = true;
|
||||
}
|
||||
|
||||
// Add primitive drawing command
|
||||
commands.push(RenderCommand::DrawPrimitives {
|
||||
batch,
|
||||
viewport_size,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we end the final render pass
|
||||
if in_render_pass {
|
||||
commands.push(RenderCommand::EndRenderPass);
|
||||
}
|
||||
|
||||
commands
|
||||
}
|
||||
|
||||
/// Execute a list of render commands
|
||||
pub fn execute_commands<F>(
|
||||
&mut self,
|
||||
commands: &[RenderCommand],
|
||||
command_buffer: &CommandBufferRef,
|
||||
drawable_texture: &metal::TextureRef,
|
||||
mut draw_primitives: F,
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
F: FnMut(
|
||||
PrimitiveBatch,
|
||||
&RenderCommandEncoderRef,
|
||||
Size<DevicePixels>,
|
||||
) -> Result<(), anyhow::Error>,
|
||||
{
|
||||
let mut current_encoder: Option<metal::RenderCommandEncoder> = None;
|
||||
|
||||
for command in commands {
|
||||
match command {
|
||||
RenderCommand::BeginRenderPass { descriptor } => {
|
||||
// End any existing encoder
|
||||
if let Some(encoder) = current_encoder.take() {
|
||||
encoder.end_encoding();
|
||||
}
|
||||
|
||||
// Create new render pass
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
|
||||
color_attachment.set_texture(Some(&descriptor.texture));
|
||||
color_attachment.set_load_action(descriptor.load_action);
|
||||
color_attachment.set_store_action(descriptor.store_action);
|
||||
color_attachment.set_clear_color(descriptor.clear_color);
|
||||
|
||||
let encoder =
|
||||
command_buffer.new_render_command_encoder(&render_pass_descriptor);
|
||||
encoder.set_viewport(descriptor.viewport);
|
||||
self.current_state.viewport = descriptor.viewport;
|
||||
|
||||
current_encoder = Some(encoder);
|
||||
}
|
||||
|
||||
RenderCommand::DrawPrimitives {
|
||||
batch,
|
||||
viewport_size,
|
||||
} => {
|
||||
if let Some(ref encoder) = current_encoder {
|
||||
draw_primitives(*batch, encoder, *viewport_size)?;
|
||||
}
|
||||
}
|
||||
|
||||
RenderCommand::ExecuteMetalCallback {
|
||||
metal_view,
|
||||
viewport_size,
|
||||
} => {
|
||||
// End current encoder if any
|
||||
if let Some(encoder) = current_encoder.take() {
|
||||
encoder.end_encoding();
|
||||
}
|
||||
|
||||
// Create context for the callback
|
||||
let context = MetalRenderContext {
|
||||
command_buffer,
|
||||
drawable_texture,
|
||||
viewport_size: *viewport_size,
|
||||
device: &self.device,
|
||||
bounds: metal_view.bounds.clone(),
|
||||
scale_factor: 2.0, // TODO: Get actual scale factor
|
||||
};
|
||||
|
||||
// Create a new render command encoder for the callback
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
|
||||
color_attachment.set_texture(Some(drawable_texture));
|
||||
color_attachment.set_load_action(MTLLoadAction::Load);
|
||||
color_attachment.set_store_action(MTLStoreAction::Store);
|
||||
|
||||
let encoder =
|
||||
command_buffer.new_render_command_encoder(&render_pass_descriptor);
|
||||
|
||||
// Invoke the callback
|
||||
(metal_view.render_callback)(
|
||||
&encoder,
|
||||
drawable_texture,
|
||||
context.bounds.into(),
|
||||
context.scale_factor,
|
||||
);
|
||||
|
||||
encoder.end_encoding();
|
||||
}
|
||||
|
||||
RenderCommand::EndRenderPass => {
|
||||
if let Some(encoder) = current_encoder.take() {
|
||||
encoder.end_encoding();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure any remaining encoder is ended
|
||||
if let Some(encoder) = current_encoder {
|
||||
encoder.end_encoding();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Save the current render state
|
||||
pub fn save_state(&self) -> RenderState {
|
||||
self.current_state.clone()
|
||||
}
|
||||
|
||||
/// Restore a previously saved render state
|
||||
pub fn restore_state(&mut self, state: RenderState) {
|
||||
self.current_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for constructing render command lists
|
||||
pub struct RenderCommandBuilder<'a> {
|
||||
commands: Vec<RenderCommand<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> RenderCommandBuilder<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
commands: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_render_pass(mut self, descriptor: RenderPassDescriptor) -> Self {
|
||||
self.commands
|
||||
.push(RenderCommand::BeginRenderPass { descriptor });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn draw_primitives(
|
||||
mut self,
|
||||
batch: PrimitiveBatch<'a>,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
) -> Self {
|
||||
self.commands.push(RenderCommand::DrawPrimitives {
|
||||
batch,
|
||||
viewport_size,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn execute_metal_callback(
|
||||
mut self,
|
||||
metal_view: &'a PaintMetalView,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
) -> Self {
|
||||
self.commands.push(RenderCommand::ExecuteMetalCallback {
|
||||
metal_view,
|
||||
viewport_size,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn end_render_pass(mut self) -> Self {
|
||||
self.commands.push(RenderCommand::EndRenderPass);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Vec<RenderCommand<'a>> {
|
||||
self.commands
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use super::metal_atlas::MetalAtlas;
|
||||
use crate::{
|
||||
AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels,
|
||||
MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, px, size,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use block::ConcreteBlock;
|
||||
@@ -18,7 +18,7 @@ use core_video::{
|
||||
pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||
};
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use metal::{CAMetalLayer, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
@@ -97,7 +97,7 @@ pub(crate) struct MetalRenderer {
|
||||
device: metal::Device,
|
||||
layer: metal::MetalLayer,
|
||||
presents_with_transaction: bool,
|
||||
command_queue: CommandQueue,
|
||||
command_queue: metal::CommandQueue,
|
||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
shadows_pipeline_state: metal::RenderPipelineState,
|
||||
@@ -385,6 +385,7 @@ impl MetalRenderer {
|
||||
)
|
||||
.with_context(|| format!("rasterizing {} paths", scene.paths().len()))?;
|
||||
|
||||
// Create initial render pass
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
@@ -396,7 +397,7 @@ impl MetalRenderer {
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
|
||||
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
|
||||
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
let mut command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
|
||||
command_encoder.set_viewport(metal::MTLViewport {
|
||||
originX: 0.0,
|
||||
@@ -407,21 +408,53 @@ impl MetalRenderer {
|
||||
zfar: 1.0,
|
||||
});
|
||||
|
||||
let mut needs_new_encoder = false;
|
||||
|
||||
// Helper to create a continuation render encoder
|
||||
let create_continuation_encoder = || {
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
|
||||
color_attachment.set_texture(Some(drawable.texture()));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Load);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
|
||||
let encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
encoder.set_viewport(metal::MTLViewport {
|
||||
originX: 0.0,
|
||||
originY: 0.0,
|
||||
width: i32::from(viewport_size.width) as f64,
|
||||
height: i32::from(viewport_size.height) as f64,
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
});
|
||||
encoder
|
||||
};
|
||||
|
||||
for batch in scene.batches() {
|
||||
// Create a new encoder if needed
|
||||
if needs_new_encoder {
|
||||
command_encoder = create_continuation_encoder();
|
||||
needs_new_encoder = false;
|
||||
}
|
||||
|
||||
let ok = match batch {
|
||||
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
|
||||
shadows,
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Quads(quads) => self.draw_quads(
|
||||
quads,
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Paths(paths) => self.draw_paths(
|
||||
paths,
|
||||
@@ -429,14 +462,14 @@ impl MetalRenderer {
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
|
||||
underlines,
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
PrimitiveBatch::MonochromeSprites {
|
||||
texture_id,
|
||||
@@ -447,7 +480,7 @@ impl MetalRenderer {
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
PrimitiveBatch::PolychromeSprites {
|
||||
texture_id,
|
||||
@@ -458,15 +491,72 @@ impl MetalRenderer {
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
|
||||
surfaces,
|
||||
instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
&command_encoder,
|
||||
),
|
||||
#[cfg(target_os = "macos")]
|
||||
PrimitiveBatch::MetalViews(metal_views) => {
|
||||
// End current render pass
|
||||
command_encoder.end_encoding();
|
||||
|
||||
// Process each MetalView
|
||||
for metal_view in metal_views {
|
||||
// Create a render encoder for the callback
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
let color_attachment = render_pass_descriptor
|
||||
.color_attachments()
|
||||
.object_at(0)
|
||||
.unwrap();
|
||||
|
||||
color_attachment.set_texture(Some(drawable.texture()));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Load);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
|
||||
let callback_encoder =
|
||||
command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
callback_encoder.set_viewport(metal::MTLViewport {
|
||||
originX: 0.0,
|
||||
originY: 0.0,
|
||||
width: i32::from(viewport_size.width) as f64,
|
||||
height: i32::from(viewport_size.height) as f64,
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
});
|
||||
|
||||
// Invoke the Metal rendering callback
|
||||
let scale_factor = self.layer.contents_scale() as f32;
|
||||
// Convert bounds from ScaledPixels to Pixels
|
||||
let bounds = Bounds {
|
||||
origin: point(
|
||||
px(metal_view.bounds.origin.x.0 / scale_factor),
|
||||
px(metal_view.bounds.origin.y.0 / scale_factor),
|
||||
),
|
||||
size: size(
|
||||
px(metal_view.bounds.size.width.0 / scale_factor),
|
||||
px(metal_view.bounds.size.height.0 / scale_factor),
|
||||
),
|
||||
};
|
||||
|
||||
(metal_view.render_callback)(
|
||||
&callback_encoder,
|
||||
drawable.texture(),
|
||||
bounds,
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
callback_encoder.end_encoding();
|
||||
}
|
||||
|
||||
// Mark that we'll need a new encoder for subsequent primitives
|
||||
needs_new_encoder = true;
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if !ok {
|
||||
@@ -484,7 +574,10 @@ impl MetalRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
command_encoder.end_encoding();
|
||||
// End the encoder if we haven't already
|
||||
if !needs_new_encoder {
|
||||
command_encoder.end_encoding();
|
||||
}
|
||||
|
||||
instance_buffer.metal_buffer.did_modify_range(NSRange {
|
||||
location: 0,
|
||||
@@ -1134,6 +1227,9 @@ impl MetalRenderer {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// Note: draw_metal_views is no longer needed as we handle MetalViews
|
||||
// directly in draw_primitives with proper render pass management
|
||||
}
|
||||
|
||||
fn build_pipeline_state(
|
||||
|
||||
@@ -27,6 +27,8 @@ pub(crate) struct Scene {
|
||||
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
|
||||
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
|
||||
pub(crate) surfaces: Vec<PaintSurface>,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) metal_views: Vec<PaintMetalView>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
@@ -41,6 +43,8 @@ impl Scene {
|
||||
self.monochrome_sprites.clear();
|
||||
self.polychrome_sprites.clear();
|
||||
self.surfaces.clear();
|
||||
#[cfg(target_os = "macos")]
|
||||
self.metal_views.clear();
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -115,6 +119,11 @@ impl Scene {
|
||||
surface.order = order;
|
||||
self.surfaces.push(surface.clone());
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
Primitive::MetalView(metal_view) => {
|
||||
metal_view.order = order;
|
||||
self.metal_views.push(metal_view.clone());
|
||||
}
|
||||
}
|
||||
self.paint_operations
|
||||
.push(PaintOperation::Primitive(primitive));
|
||||
@@ -140,6 +149,8 @@ impl Scene {
|
||||
self.polychrome_sprites
|
||||
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
||||
self.surfaces.sort_by_key(|surface| surface.order);
|
||||
#[cfg(target_os = "macos")]
|
||||
self.metal_views.sort_by_key(|metal_view| metal_view.order);
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -172,6 +183,12 @@ impl Scene {
|
||||
surfaces: &self.surfaces,
|
||||
surfaces_start: 0,
|
||||
surfaces_iter: self.surfaces.iter().peekable(),
|
||||
#[cfg(target_os = "macos")]
|
||||
metal_views: &self.metal_views,
|
||||
#[cfg(target_os = "macos")]
|
||||
metal_views_start: 0,
|
||||
#[cfg(target_os = "macos")]
|
||||
metal_views_iter: self.metal_views.iter().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,6 +210,8 @@ pub(crate) enum PrimitiveKind {
|
||||
MonochromeSprite,
|
||||
PolychromeSprite,
|
||||
Surface,
|
||||
#[cfg(target_os = "macos")]
|
||||
MetalView,
|
||||
}
|
||||
|
||||
pub(crate) enum PaintOperation {
|
||||
@@ -210,6 +229,8 @@ pub(crate) enum Primitive {
|
||||
MonochromeSprite(MonochromeSprite),
|
||||
PolychromeSprite(PolychromeSprite),
|
||||
Surface(PaintSurface),
|
||||
#[cfg(target_os = "macos")]
|
||||
MetalView(PaintMetalView),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
@@ -222,6 +243,8 @@ impl Primitive {
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::Surface(surface) => &surface.bounds,
|
||||
#[cfg(target_os = "macos")]
|
||||
Primitive::MetalView(metal_view) => &metal_view.bounds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +257,8 @@ impl Primitive {
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::Surface(surface) => &surface.content_mask,
|
||||
#[cfg(target_os = "macos")]
|
||||
Primitive::MetalView(metal_view) => &metal_view.content_mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,13 +292,19 @@ struct BatchIterator<'a> {
|
||||
surfaces: &'a [PaintSurface],
|
||||
surfaces_start: usize,
|
||||
surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
|
||||
#[cfg(target_os = "macos")]
|
||||
metal_views: &'a [PaintMetalView],
|
||||
#[cfg(target_os = "macos")]
|
||||
metal_views_start: usize,
|
||||
#[cfg(target_os = "macos")]
|
||||
metal_views_iter: Peekable<slice::Iter<'a, PaintMetalView>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BatchIterator<'a> {
|
||||
type Item = PrimitiveBatch<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut orders_and_kinds = [
|
||||
let mut orders_and_kinds = vec![
|
||||
(
|
||||
self.shadows_iter.peek().map(|s| s.order),
|
||||
PrimitiveKind::Shadow,
|
||||
@@ -297,6 +328,12 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
PrimitiveKind::Surface,
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
orders_and_kinds.push((
|
||||
self.metal_views_iter.peek().map(|m| m.order),
|
||||
PrimitiveKind::MetalView,
|
||||
));
|
||||
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
|
||||
|
||||
let first = orders_and_kinds[0];
|
||||
@@ -426,6 +463,23 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
&self.surfaces[surfaces_start..surfaces_end],
|
||||
))
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
PrimitiveKind::MetalView => {
|
||||
let metal_views_start = self.metal_views_start;
|
||||
let mut metal_views_end = metal_views_start + 1;
|
||||
self.metal_views_iter.next();
|
||||
while self
|
||||
.metal_views_iter
|
||||
.next_if(|metal_view| (metal_view.order, batch_kind) < max_order_and_kind)
|
||||
.is_some()
|
||||
{
|
||||
metal_views_end += 1;
|
||||
}
|
||||
self.metal_views_start = metal_views_end;
|
||||
Some(PrimitiveBatch::MetalViews(
|
||||
&self.metal_views[metal_views_start..metal_views_end],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,6 +506,8 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
sprites: &'a [PolychromeSprite],
|
||||
},
|
||||
Surfaces(&'a [PaintSurface]),
|
||||
#[cfg(target_os = "macos")]
|
||||
MetalViews(&'a [PaintMetalView]),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@@ -668,12 +724,38 @@ pub(crate) struct PaintSurface {
|
||||
pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PaintMetalView {
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub render_callback: crate::MetalRenderCallback,
|
||||
}
|
||||
|
||||
impl Debug for PaintMetalView {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PaintMetalView")
|
||||
.field("order", &self.order)
|
||||
.field("bounds", &self.bounds)
|
||||
.field("content_mask", &self.content_mask)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaintSurface> for Primitive {
|
||||
fn from(surface: PaintSurface) -> Self {
|
||||
Primitive::Surface(surface)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl From<PaintMetalView> for Primitive {
|
||||
fn from(metal_view: PaintMetalView) -> Self {
|
||||
Primitive::MetalView(metal_view)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PathId(pub(crate) usize);
|
||||
|
||||
|
||||
@@ -2946,6 +2946,30 @@ impl Window {
|
||||
});
|
||||
}
|
||||
|
||||
/// Paint a custom Metal view.
|
||||
///
|
||||
/// This method should only be called as part of the paint phase of element drawing.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn paint_metal_view(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
render_callback: crate::MetalRenderCallback,
|
||||
) {
|
||||
use crate::PaintMetalView;
|
||||
|
||||
self.invalidator.debug_assert_paint();
|
||||
|
||||
let scale_factor = self.scale_factor();
|
||||
let bounds = bounds.scale(scale_factor);
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
self.next_frame.scene.insert_primitive(PaintMetalView {
|
||||
order: 0,
|
||||
bounds,
|
||||
content_mask,
|
||||
render_callback,
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes an image from the sprite atlas.
|
||||
pub fn drop_image(&mut self, data: Arc<RenderImage>) -> Result<()> {
|
||||
for frame_index in 0..data.frame_count() {
|
||||
|
||||
Reference in New Issue
Block a user