Get basic graphics rendering via Metal

Also, handle window resize.
This commit is contained in:
Nathan Sobo 2021-03-20 22:15:04 -06:00
parent 292b41ad57
commit e5ffe43bb6
9 changed files with 328 additions and 120 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
.DS_Store
/zed.xcworkspace
.DS_Store

View File

@ -13,7 +13,6 @@ use pathfinder_geometry::{rect::RectF, vector::vec2f};
use smol::{channel, prelude::*};
use std::{
any::{type_name, Any, TypeId},
borrow,
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
fmt::{self, Debug},
@ -292,6 +291,7 @@ type ActionCallback =
type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext);
pub struct MutableAppContext {
weak_self: Option<rc::Weak<RefCell<Self>>>,
platform: Arc<dyn platform::App>,
fonts: Arc<FontCache>,
assets: Arc<AssetCache>,
@ -302,7 +302,6 @@ pub struct MutableAppContext {
next_entity_id: usize,
next_window_id: usize,
next_task_id: usize,
weak_self: Option<rc::Weak<RefCell<Self>>>,
subscriptions: HashMap<usize, Vec<Subscription>>,
observations: HashMap<usize, Vec<Observation>>,
window_invalidations: HashMap<usize, WindowInvalidation>,
@ -325,6 +324,7 @@ impl MutableAppContext {
asset_source: impl AssetSource,
) -> Self {
Self {
weak_self: None,
platform,
fonts: Arc::new(FontCache::new()),
assets: Arc::new(AssetCache::new(asset_source)),
@ -339,7 +339,6 @@ impl MutableAppContext {
next_entity_id: 0,
next_window_id: 0,
next_task_id: 0,
weak_self: None,
subscriptions: HashMap::new(),
observations: HashMap::new(),
window_invalidations: HashMap::new(),
@ -355,6 +354,10 @@ impl MutableAppContext {
}
}
pub fn upgrade(&self) -> App {
App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
}
pub fn downgrade(&self) -> &AppContext {
&self.ctx
}
@ -624,7 +627,7 @@ impl MutableAppContext {
self.foreground.clone(),
) {
Err(e) => log::error!("error opening window: {}", e),
Ok(window) => {
Ok(mut window) => {
let presenter = Rc::new(RefCell::new(Presenter::new(
window_id,
self.fonts.clone(),
@ -632,11 +635,26 @@ impl MutableAppContext {
self,
)));
{
let mut app = self.upgrade();
let presenter = presenter.clone();
window.on_resize(Box::new(move |window| {
app.update(|ctx| {
let scene = presenter.borrow_mut().build_scene(
window.size(),
window.scale_factor(),
ctx,
);
window.present_scene(scene);
})
}));
}
self.on_window_invalidated(window_id, move |invalidation, ctx| {
let mut presenter = presenter.borrow_mut();
presenter.invalidate(invalidation, ctx.downgrade());
let scene = presenter.build_scene(window.size(), window.scale_factor(), ctx);
window.render_scene(scene);
window.present_scene(scene);
});
}
}
@ -1909,7 +1927,7 @@ impl<T> Hash for ModelHandle<T> {
}
}
impl<T> borrow::Borrow<usize> for ModelHandle<T> {
impl<T> std::borrow::Borrow<usize> for ModelHandle<T> {
fn borrow(&self) -> &usize {
&self.model_id
}

View File

@ -33,7 +33,7 @@ impl platform::App for App {
&self,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
) -> Result<Rc<dyn platform::Window>> {
Ok(Rc::new(Window::open(options, executor)?))
) -> Result<Box<dyn platform::Window>> {
Ok(Box::new(Window::open(options, executor)?))
}
}

View File

@ -1,14 +1,21 @@
use anyhow::{anyhow, Result};
use std::{ffi::c_void, mem};
use crate::Scene;
use self::shaders::ToUchar4;
use super::window::RenderContext;
use crate::{color::ColorU, scene::Layer, Scene};
use anyhow::{anyhow, Result};
use metal::{MTLResourceOptions, NSRange};
use shaders::ToFloat2 as _;
const SHADERS_METALLIB: &'static [u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
const INSTANCE_BUFFER_SIZE: u64 = 1024 * 1024;
pub struct Renderer {
quad_pipeline_state: metal::RenderPipelineState,
quad_vertices: metal::Buffer,
instances: metal::Buffer,
}
impl Renderer {
@ -17,6 +24,22 @@ impl Renderer {
.new_library_with_data(SHADERS_METALLIB)
.map_err(|message| anyhow!("error building metal library: {}", message))?;
let quad_vertices = [
(0., 0.).to_float2(),
(1., 0.).to_float2(),
(0., 1.).to_float2(),
(0., 1.).to_float2(),
(1., 0.).to_float2(),
(1., 1.).to_float2(),
];
let quad_vertices = device.new_buffer_with_data(
quad_vertices.as_ptr() as *const c_void,
(quad_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
MTLResourceOptions::StorageModeManaged,
);
let instances =
device.new_buffer(INSTANCE_BUFFER_SIZE, MTLResourceOptions::StorageModeManaged);
Ok(Self {
quad_pipeline_state: build_pipeline_state(
device,
@ -26,10 +49,78 @@ impl Renderer {
"quad_fragment",
pixel_format,
)?,
quad_vertices,
instances,
})
}
pub fn render(&mut self, scene: &Scene, ctx: RenderContext) {}
pub fn render(&mut self, scene: &Scene, ctx: &RenderContext) {
ctx.command_encoder.set_viewport(metal::MTLViewport {
originX: 0.0,
originY: 0.0,
width: ctx.drawable_size.x() as f64,
height: ctx.drawable_size.y() as f64,
znear: 0.0,
zfar: 1.0,
});
for layer in scene.layers() {
self.render_quads(layer, ctx);
}
}
fn render_quads(&mut self, layer: &Layer, ctx: &RenderContext) {
ctx.command_encoder
.set_render_pipeline_state(&self.quad_pipeline_state);
ctx.command_encoder.set_vertex_buffer(
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
Some(&self.quad_vertices),
0,
);
ctx.command_encoder.set_vertex_buffer(
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexQuads as u64,
Some(&self.instances),
0,
);
ctx.command_encoder.set_vertex_bytes(
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
mem::size_of::<shaders::GPUIQuadUniforms>() as u64,
[shaders::GPUIQuadUniforms {
viewport_size: ctx.drawable_size.to_float2(),
}]
.as_ptr() as *const c_void,
);
let batch_size = self.instances.length() as usize / mem::size_of::<shaders::GPUIQuad>();
let buffer_contents = self.instances.contents() as *mut shaders::GPUIQuad;
for quad_batch in layer.quads().chunks(batch_size) {
for (ix, quad) in quad_batch.iter().enumerate() {
log::info!("render {} {:?}", ix, quad);
unsafe {
*(buffer_contents.offset(ix as isize)) = shaders::GPUIQuad {
origin: quad.bounds.origin().to_float2(),
size: quad.bounds.size().to_float2(),
background_color: quad
.background
.unwrap_or(ColorU::transparent_black())
.to_uchar4(),
};
}
}
self.instances.did_modify_range(NSRange {
location: 0,
length: (quad_batch.len() * mem::size_of::<shaders::GPUIQuad>()) as u64,
});
ctx.command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quad_batch.len() as u64,
);
}
}
}
fn build_pipeline_state(
@ -47,7 +138,7 @@ fn build_pipeline_state(
.get_function(fragment_fn_name, None)
.map_err(|message| anyhow!("error locating fragment function: {}", message))?;
let mut descriptor = metal::RenderPipelineDescriptor::new();
let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
@ -61,3 +152,57 @@ fn build_pipeline_state(
.new_render_pipeline_state(&descriptor)
.map_err(|message| anyhow!("could not create render pipeline state: {}", message))
}
mod shaders {
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::{color::ColorU, geometry::vector::Vector2F};
use std::mem;
include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
pub trait ToFloat2 {
fn to_float2(&self) -> vector_float2;
}
pub trait ToUchar4 {
fn to_uchar4(&self) -> vector_uchar4;
}
impl ToFloat2 for (f32, f32) {
fn to_float2(&self) -> vector_float2 {
unsafe {
let mut output = mem::transmute::<_, u32>(self.1.to_bits()) as vector_float2;
output <<= 32;
output |= mem::transmute::<_, u32>(self.0.to_bits()) as vector_float2;
output
}
}
}
impl ToFloat2 for Vector2F {
fn to_float2(&self) -> vector_float2 {
unsafe {
let mut output = mem::transmute::<_, u32>(self.y().to_bits()) as vector_float2;
output <<= 32;
output |= mem::transmute::<_, u32>(self.x().to_bits()) as vector_float2;
output
}
}
}
impl ToUchar4 for ColorU {
fn to_uchar4(&self) -> vector_uchar4 {
let mut vec = self.a as vector_uchar4;
vec <<= 8;
vec |= self.b as vector_uchar4;
vec <<= 8;
vec |= self.g as vector_uchar4;
vec <<= 8;
vec |= self.r as vector_uchar4;
vec
}
}
}

View File

@ -9,7 +9,7 @@ typedef enum {
typedef struct {
vector_float2 origin;
vector_float2 size;
vector_float4 background_color;
vector_uchar4 background_color;
} GPUIQuad;
typedef struct {

View File

@ -3,6 +3,10 @@
using namespace metal;
float4 coloru_to_colorf(uchar4 coloru) {
return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
}
struct QuadFragmentInput {
float4 position [[position]];
GPUIQuad quad;
@ -17,14 +21,15 @@ vertex QuadFragmentInput quad_vertex(
) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
GPUIQuad quad = quads[quad_id];
float4 position = float4((unit_vertex * quad.size + quad.origin) / (uniforms->viewport_size / 2.0), 0.0, 1.0);
float2 position = (unit_vertex * quad.size + quad.origin) / (uniforms->viewport_size / 2.0);
float4 device_position = float4(position * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
return QuadFragmentInput {
position,
device_position,
quad,
};
}
fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]]) {
return input.quad.background_color;
}
return coloru_to_colorf(input.quad.background_color);
}

View File

@ -1,7 +1,8 @@
use crate::{
executor,
geometry::vector::Vector2F,
platform::{self, Event},
platform::{self, Event, WindowContext},
util::post_inc,
Scene,
};
use anyhow::{anyhow, Result};
@ -27,7 +28,7 @@ use objc::{
use pathfinder_geometry::vector::vec2f;
use smol::Timer;
use std::{
cell::{Cell, RefCell},
cell::RefCell,
ffi::c_void,
mem, ptr,
rc::Rc,
@ -111,16 +112,16 @@ unsafe fn build_classes() {
};
}
pub struct Window(Rc<WindowState>);
pub struct Window(Rc<RefCell<WindowState>>);
struct WindowState {
native_window: id,
event_callback: RefCell<Option<Box<dyn FnMut(Event) -> bool>>>,
resize_callback: RefCell<Option<Box<dyn FnMut(Vector2F, f32)>>>,
synthetic_drag_counter: Cell<usize>,
event_callback: Option<Box<dyn FnMut(Event, &mut dyn platform::WindowContext)>>,
resize_callback: Option<Box<dyn FnMut(&mut dyn platform::WindowContext)>>,
synthetic_drag_counter: usize,
executor: Rc<executor::Foreground>,
scene_to_render: RefCell<Option<Scene>>,
renderer: RefCell<Renderer>,
scene_to_render: Option<Scene>,
renderer: Renderer,
command_queue: metal::CommandQueue,
device: metal::Device,
layer: id,
@ -181,18 +182,18 @@ impl Window {
return Err(anyhow!("view return nil from initializer"));
}
let window = Self(Rc::new(WindowState {
let window = Self(Rc::new(RefCell::new(WindowState {
native_window,
event_callback: RefCell::new(None),
resize_callback: RefCell::new(None),
synthetic_drag_counter: Cell::new(0),
event_callback: None,
resize_callback: None,
synthetic_drag_counter: 0,
executor,
scene_to_render: Default::default(),
renderer: RefCell::new(Renderer::new(&device, PIXEL_FORMAT)?),
renderer: Renderer::new(&device, PIXEL_FORMAT)?,
command_queue: device.new_command_queue(),
device,
layer,
}));
})));
(*native_window).set_ivar(
WINDOW_STATE_IVAR,
@ -236,46 +237,44 @@ impl Window {
pub fn zoom(&self) {
unsafe {
self.0.native_window.performZoom_(nil);
self.0.as_ref().borrow().native_window.performZoom_(nil);
}
}
pub fn on_event<F: 'static + FnMut(Event) -> bool>(&mut self, callback: F) {
*self.0.event_callback.borrow_mut() = Some(Box::new(callback));
}
pub fn on_resize<F: 'static + FnMut(Vector2F, f32)>(&mut self, callback: F) {
*self.0.resize_callback.borrow_mut() = Some(Box::new(callback));
}
}
impl Drop for Window {
fn drop(&mut self) {
unsafe {
self.0.native_window.close();
let _: () = msg_send![self.0.native_window.delegate(), release];
self.0.as_ref().borrow().native_window.close();
}
}
}
impl platform::Window for Window {
fn size(&self) -> Vector2F {
self.0.size()
fn on_event(&mut self, callback: Box<dyn FnMut(Event, &mut dyn platform::WindowContext)>) {
self.0.as_ref().borrow_mut().event_callback = Some(callback);
}
fn scale_factor(&self) -> f32 {
self.0.scale_factor()
}
fn render_scene(&self, scene: Scene) {
*self.0.scene_to_render.borrow_mut() = Some(scene);
unsafe {
let _: () = msg_send![self.0.native_window.contentView(), setNeedsDisplay: YES];
}
fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn platform::WindowContext)>) {
self.0.as_ref().borrow_mut().resize_callback = Some(callback);
}
}
impl WindowState {
impl platform::WindowContext for Window {
fn size(&self) -> Vector2F {
self.0.as_ref().borrow().size()
}
fn scale_factor(&self) -> f32 {
self.0.as_ref().borrow().scale_factor()
}
fn present_scene(&mut self, scene: Scene) {
self.0.as_ref().borrow_mut().present_scene(scene);
}
}
impl platform::WindowContext for WindowState {
fn size(&self) -> Vector2F {
let NSSize { width, height, .. } =
unsafe { NSView::frame(self.native_window.contentView()) }.size;
@ -289,16 +288,17 @@ impl WindowState {
}
}
fn next_synthetic_drag_id(&self) -> usize {
let next_id = self.synthetic_drag_counter.get() + 1;
self.synthetic_drag_counter.set(next_id);
next_id
fn present_scene(&mut self, scene: Scene) {
self.scene_to_render = Some(scene);
unsafe {
let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
}
}
}
unsafe fn window_state(object: &Object) -> Rc<WindowState> {
unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
let rc1 = Rc::from_raw(raw as *mut WindowState);
let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
let rc2 = rc1.clone();
mem::forget(rc1);
rc2
@ -328,23 +328,25 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) {
}
extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
let window = unsafe { window_state(this) };
let window_state = unsafe { get_window_state(this) };
let mut window_state_borrow = window_state.as_ref().borrow_mut();
let event = unsafe { Event::from_native(native_event, Some(window.size().y())) };
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
if let Some(event) = event {
match event {
Event::LeftMouseDragged { position } => schedule_synthetic_drag(&window, position),
Event::LeftMouseDragged { position } => {
schedule_synthetic_drag(&&window_state, position)
}
Event::LeftMouseUp { .. } => {
window.next_synthetic_drag_id();
post_inc(&mut window_state_borrow.synthetic_drag_counter);
}
_ => {}
}
if let Some(callback) = window.event_callback.borrow_mut().as_mut() {
if callback(event) {
return;
}
if let Some(mut callback) = window_state_borrow.event_callback.take() {
callback(event, &mut *window_state_borrow);
window_state_borrow.event_callback = Some(callback);
}
}
}
@ -356,55 +358,62 @@ extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
}
extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
let window = unsafe { window_state(this) };
window.layer
let window_state = unsafe { get_window_state(this) };
let window_state = window_state.as_ref().borrow();
window_state.layer
}
extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
let window;
let window_state = unsafe { get_window_state(this) };
let mut window_state = window_state.as_ref().borrow_mut();
unsafe {
window = window_state(this);
let _: () = msg_send![window.layer, setContentsScale: window.scale_factor() as f64];
let _: () =
msg_send![window_state.layer, setContentsScale: window_state.scale_factor() as f64];
}
if let Some(callback) = window.resize_callback.borrow_mut().as_mut() {
let size = window.size();
let scale_factor = window.scale_factor();
callback(size, scale_factor);
if let Some(mut callback) = window_state.resize_callback.take() {
callback(&mut *window_state);
window_state.resize_callback = Some(callback);
};
}
extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
let window;
unsafe {
window = window_state(this);
if window.size() == vec2f(size.width as f32, size.height as f32) {
return;
}
let window_state = unsafe { get_window_state(this) };
let mut window_state = window_state.as_ref().borrow_mut();
let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
let scale_factor = window.scale_factor() as f64;
let drawable_size: NSSize = NSSize {
width: size.width * scale_factor,
height: size.height * scale_factor,
};
let _: () = msg_send![window.layer, setDrawableSize: drawable_size];
if window_state.size() == vec2f(size.width as f32, size.height as f32) {
return;
}
if let Some(callback) = window.resize_callback.borrow_mut().as_mut() {
let size = window.size();
let scale_factor = window.scale_factor();
callback(size, scale_factor);
unsafe {
let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
}
let scale_factor = window_state.scale_factor() as f64;
let drawable_size: NSSize = NSSize {
width: size.width * scale_factor,
height: size.height * scale_factor,
};
unsafe {
let _: () = msg_send![window_state.layer, setDrawableSize: drawable_size];
}
if let Some(mut callback) = window_state.resize_callback.take() {
callback(&mut *window_state);
window_state.resize_callback = Some(callback);
};
}
extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
log::info!("display layer");
unsafe {
let window = window_state(this);
let window_state = get_window_state(this);
let mut window_state = window_state.as_ref().borrow_mut();
if let Some(scene) = window.scene_to_render.borrow_mut().take() {
let drawable: &metal::MetalDrawableRef = msg_send![window.layer, nextDrawable];
if let Some(scene) = window_state.scene_to_render.take() {
let drawable: &metal::MetalDrawableRef = msg_send![window_state.layer, nextDrawable];
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
@ -416,14 +425,19 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
color_attachment.set_store_action(MTLStoreAction::Store);
color_attachment.set_clear_color(MTLClearColor::new(0., 0., 0., 1.));
let command_buffer = window.command_queue.new_command_buffer();
let command_queue = window_state.command_queue.clone();
let command_buffer = command_queue.new_command_buffer();
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
window.renderer.borrow_mut().render(
let size = window_state.size();
let scale_factor = window_state.scale_factor();
let device = window_state.device.clone();
window_state.renderer.render(
&scene,
RenderContext {
drawable_size: window.size() * window.scale_factor(),
device: &window.device,
&RenderContext {
drawable_size: size * scale_factor,
device: &device,
command_encoder,
},
);
@ -432,23 +446,33 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
command_buffer.commit();
command_buffer.wait_until_completed();
drawable.present();
log::info!("present");
};
}
}
fn schedule_synthetic_drag(window_state: &Rc<WindowState>, position: Vector2F) {
let drag_id = window_state.next_synthetic_drag_id();
fn schedule_synthetic_drag(window_state: &Rc<RefCell<WindowState>>, position: Vector2F) {
let weak_window_state = Rc::downgrade(window_state);
let mut window_state = window_state.as_ref().borrow_mut();
let drag_id = post_inc(&mut window_state.synthetic_drag_counter);
let instant = Instant::now() + Duration::from_millis(16);
window_state
.executor
.spawn(async move {
Timer::at(instant).await;
if let Some(window_state) = weak_window_state.upgrade() {
if window_state.synthetic_drag_counter.get() == drag_id {
if let Some(callback) = window_state.event_callback.borrow_mut().as_mut() {
let mut window_state_borrow = window_state.as_ref().borrow_mut();
if window_state_borrow.synthetic_drag_counter == drag_id {
if let Some(mut callback) = window_state_borrow.event_callback.take() {
schedule_synthetic_drag(&window_state, position);
callback(Event::LeftMouseDragged { position });
callback(
Event::LeftMouseDragged { position },
&mut *window_state_borrow,
);
window_state_borrow.event_callback = Some(callback);
}
}
}

View File

@ -32,7 +32,7 @@ pub trait App {
&self,
options: WindowOptions,
executor: Rc<executor::Foreground>,
) -> Result<Rc<dyn Window>>;
) -> Result<Box<dyn Window>>;
}
pub trait Dispatcher: Send + Sync {
@ -40,10 +40,15 @@ pub trait Dispatcher: Send + Sync {
fn run_on_main_thread(&self, task: Runnable);
}
pub trait Window {
pub trait Window: WindowContext {
fn on_event(&mut self, callback: Box<dyn FnMut(Event, &mut dyn WindowContext)>);
fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn WindowContext)>);
}
pub trait WindowContext {
fn size(&self) -> Vector2F;
fn scale_factor(&self) -> f32;
fn render_scene(&self, scene: Scene);
fn present_scene(&mut self, scene: Scene);
}
pub struct WindowOptions<'a> {

View File

@ -6,12 +6,12 @@ pub struct Scene {
}
#[derive(Default)]
struct Layer {
pub struct Layer {
clip_bounds: Option<RectF>,
quads: Vec<Quad>,
}
#[derive(Default)]
#[derive(Default, Debug)]
pub struct Quad {
pub bounds: RectF,
pub background: Option<ColorU>,
@ -19,7 +19,7 @@ pub struct Quad {
pub corder_radius: f32,
}
#[derive(Clone, Copy, Default)]
#[derive(Clone, Copy, Default, Debug)]
pub struct Border {
pub width: f32,
pub color: Option<ColorU>,
@ -38,13 +38,19 @@ impl Scene {
}
}
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {}
pub fn pop_layer(&mut self) {
assert!(self.active_layer_stack.len() > 1);
self.active_layer_stack.pop();
pub fn layers(&self) -> &[Layer] {
self.layers.as_slice()
}
// pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
// }
// pub fn pop_layer(&mut self) {
// assert!(self.active_layer_stack.len() > 1);
// self.active_layer_stack.pop();
// }
pub fn push_quad(&mut self, quad: Quad) {
self.active_layer().push_quad(quad)
}
@ -58,6 +64,10 @@ impl Layer {
fn push_quad(&mut self, quad: Quad) {
self.quads.push(quad);
}
pub fn quads(&self) -> &[Quad] {
self.quads.as_slice()
}
}
impl Border {