///|
/// One window's direct-Metal renderer. It owns the FFI context handle and the
/// host `CAMetalLayer` handle the macOS backend created; it never creates a
/// window, a layer, or a device of its own.
priv struct MetalRenderer {
mut metrics : @core.SurfaceMetrics
mut context : UInt64
layer : UInt64
cache : @native_text.NativeTextCache
atlas : MetalGlyphAtlas
images : MetalImageCache
image_source : @render.HostImageSource
retained : MetalRetainedCache
mut presents : Int32
}
///|
fn MetalRenderer::new(
metrics~ : @core.SurfaceMetrics,
context~ : UInt64,
layer~ : UInt64,
cache~ : @native_text.NativeTextCache,
image_source~ : @render.HostImageSource,
) -> MetalRenderer {
{
metrics,
context,
layer,
cache,
atlas: new_metal_glyph_atlas(),
images: new_metal_image_cache(),
image_source,
retained: new_metal_retained_cache(),
presents: 0,
}
}
///|
fn MetalRenderer::resize(
self : MetalRenderer,
metrics : @core.SurfaceMetrics,
) -> Unit {
self.metrics = metrics
// Every pass target is sized to the surface the frame was built for, so none
// of the boundaries painted under the old size can be re-composited here.
clear_metal_retained_cache(self.retained)
}
///|
fn MetalRenderer::render_frame(
self : MetalRenderer,
frame : @core.DrawFrame,
) -> @render.RenderFrameResult {
// `frame.damage` is not consulted, and not by oversight: the render pass clears
// the drawable on load (`MTLLoadActionClear` in `moui_metal_pass`), so drawing
// only the damaged rect would leave everything outside it cleared rather than
// kept. Repainting a region needs a retained backbuffer — or `framebufferOnly =
// NO` plus a copy of the previous frame, the same cost family as the backdrop
// snapshot the `BlendMode` row declines for `Overlay`. A whole-surface repaint is
// therefore the correct behaviour here today, and the cost is stated instead of
// left for the next reader to discover by breaking it.
self.retained.begin_frame(self.context)
let draw_context = MetalFrameContext::{
cache: self.cache,
atlas: self.atlas,
images: self.images,
image_source: self.image_source,
context: self.context,
async_images: true,
image_budget_bytes: METAL_IMAGE_MAX_RESIDENT_BYTES,
retained: self.retained,
}
let geometry = build_metal_geometry(
@render_common.lower_draw_commands(frame.commands),
self.metrics,
draw_context,
)
let clear = frame.clear_color
let vertices = geometry.vertex_bytes()
let items = geometry.item_bytes()
let shapes = geometry.shape_bytes()
let layers = geometry.layer_bytes()
if self.context != 0UL {
// Per page, not per atlas: a frame that added one glyph to page 0 uploads
// 4 MB, not every page a dense document has grown.
let pages = metal_glyph_atlas_page_count(self.atlas)
for index in 0.. Int32 {
self.presents
}
///|
fn MetalRenderer::dispose(self : MetalRenderer) -> Unit {
if self.context != 0UL {
let context = self.context
self.context = 0UL
metal_context_release(context)
}
}