///|
priv struct SkiaCachedLayerImageEntry {
identity_key : String
key : String
runtime_key : String
image : @skia_native.Image
physical_size : @moui_skia.ISize
scale_factor : Double
surface_route : SkiaSurfaceRoute
content_revision : Int
byte_size : Int64
update_streak : Int
mut hit_count : Int
skip_reason : String
last_admitted_frame : Int
mut last_used : Int
}
///|
priv struct SkiaCachedLayerAdmissionRecord {
identity_key : String
mut update_streak : Int
mut hit_count : Int
mut skip_reason : String
mut last_admitted_frame : Int
mut last_revision : Int
}
///|
priv struct SkiaCachedLayerFrameStats {
mut cache_hit_count : Int
mut cache_miss_count : Int
mut cache_update_count : Int
mut cache_evict_count : Int
}
///|
fn SkiaCachedLayerFrameStats::new() -> SkiaCachedLayerFrameStats {
{
cache_hit_count: 0,
cache_miss_count: 0,
cache_update_count: 0,
cache_evict_count: 0,
}
}
///|
fn SkiaCachedLayerFrameStats::to_render_frame_result(
self : SkiaCachedLayerFrameStats,
presented~ : Bool,
) -> @render.RenderFrameResult {
@render.RenderFrameResult::new(
presented~,
cache_hit_count=self.cache_hit_count,
cache_miss_count=self.cache_miss_count,
cache_update_count=self.cache_update_count,
cache_evict_count=self.cache_evict_count,
)
}
///|
fn SkiaRasterRenderer::render_cached_command_range(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
commands : Array[@render_common.PlanOp],
start_index~ : Int,
end_index~ : Int,
canvas_scopes : Array[@render_common.PlanScopeKind],
stats : SkiaCachedLayerFrameStats,
damage? : @core.DamageRegion = @core.DamageRegion::full(
"offscreen layer render",
),
) -> Bool {
let mut index = start_index
while index < end_index {
match commands[index] {
@render_common.PlanOp::BeginRetainedLayer(spec) =>
match find_cached_layer_end(commands, index + 1, spec.key) {
Some(layer_end) =>
if self.should_skip_command_for_damage(spec.frame, damage) ||
self.draw_cached_layer(canvas, spec, stats) {
index = layer_end + 1
} else {
stats.cache_miss_count = stats.cache_miss_count + 1
if !self.update_cached_layer(
canvas,
spec,
commands,
start_index=index + 1,
end_index=layer_end,
stats,
) {
return false
}
index = layer_end + 1
}
None => {
self.record_unsupported_command(
"BeginRetainedLayer",
"cached layer command stream ended without matching EndRetainedLayer for \{spec.key}",
)
return false
}
}
@render_common.PlanOp::EndRetainedLayer(_) => return true
command => {
self.render_command(canvas, command, canvas_scopes)
index = index + 1
}
}
}
true
}
///|
fn SkiaRasterRenderer::should_skip_command_for_damage(
self : SkiaRasterRenderer,
bounds : @core.Rect,
damage : @core.DamageRegion,
) -> Bool {
let _ = self
match damage {
@core.DamageRegion::Empty => false
@core.DamageRegion::FullSurface(_) => false
@core.DamageRegion::Rects(rects) => {
let mut intersects = false
for rect in rects {
match bounds.intersection(rect) {
Some(overlap) =>
if !overlap.is_empty() {
intersects = true
break
}
None => ()
}
}
!intersects
}
}
}
///|
fn find_cached_layer_end(
commands : Array[@render_common.PlanOp],
start_index : Int,
key : String,
) -> Int? {
let mut depth = 0
for index in start_index.. depth = depth + 1
@render_common.PlanOp::EndRetainedLayer(end_key) =>
if depth == 0 {
if end_key == key {
return Some(index)
} else {
return None
}
} else {
depth = depth - 1
}
_ => ()
}
}
None
}
///|
fn SkiaRasterRenderer::update_cached_layer(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
spec : @core.RetainedLayerSpec,
commands : Array[@render_common.PlanOp],
start_index~ : Int,
end_index~ : Int,
stats : SkiaCachedLayerFrameStats,
) -> Bool {
if spec.frame.is_empty() {
return true
}
let physical_size = skia_cached_layer_physical_size(
spec.frame,
self.metrics.scale_factor,
)
let byte_size = skia_cached_layer_byte_size(physical_size)
let budget = self.layer_cache_budget_bytes()
if budget > 0L && byte_size > budget {
self.record_unsupported_command(
"BeginRetainedLayer",
"cached layer \{spec.key} exceeds Skia layer cache budget; rendered directly",
)
return self.render_uncached_layer_to_canvas(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
}
match self.create_layer_surface(physical_size) {
Some(layer_surface) =>
match layer_surface.canvas() {
Some(layer_canvas) => {
layer_canvas.clear(@moui_skia.Color::transparent())
let saved = layer_canvas.save()
layer_canvas.scale(
@moui_skia.Size::new(
Float::from_double(self.metrics.scale_factor),
Float::from_double(self.metrics.scale_factor),
),
)
layer_canvas.translate(
@moui_skia.Point::new(
Float::from_double(0.0 - spec.frame.origin.x),
Float::from_double(0.0 - spec.frame.origin.y),
),
)
let layer_scopes : Array[@render_common.PlanScopeKind] = []
let complete = self.render_cached_command_range(
layer_canvas,
commands,
start_index~,
end_index~,
layer_scopes,
stats,
)
if !layer_scopes.is_empty() {
for scope in layer_scopes {
self.record_unsupported_command(
@render_common.plan_scope_push_command(scope),
"cached layer command stream ended with an open \{@render_common.plan_scope_name(scope)} scope",
)
}
}
layer_canvas.restore_to_count(saved)
if !complete {
return false
}
if !layer_surface.flush_and_submit() {
self.record_unsupported_command(
"BeginRetainedLayer",
"cached layer surface flush failed for \{spec.key}; rendered directly",
)
return self.render_uncached_layer_to_canvas(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
}
match layer_surface.image_snapshot() {
Some(image) => {
let identity_key = self.skia_layer_cache_identity_key(
spec, physical_size,
)
let admission = self.layer_cache_admission_record(identity_key)
admission.record_update(spec.content_revision)
let entry = SkiaCachedLayerImageEntry::{
identity_key,
key: self.skia_layer_cache_key(spec, physical_size),
runtime_key: spec.key,
image,
physical_size,
scale_factor: self.metrics.scale_factor,
surface_route: self.surface_route,
content_revision: spec.content_revision,
byte_size,
update_streak: admission.update_streak,
hit_count: admission.hit_count,
skip_reason: admission.skip_reason,
last_admitted_frame: admission.last_admitted_frame,
last_used: 0,
}
match
self.cached_layer_admission_skip_reason(
admission, byte_size, budget,
) {
Some(reason) => {
admission.skip_reason = reason
self.remove_cached_layers_by_identity(entry.identity_key)
}
None =>
if self.store_cached_layer(entry, admission) {
admission.skip_reason = ""
stats.cache_update_count = stats.cache_update_count + 1
stats.cache_evict_count += self.evict_cached_layers_if_needed()
}
}
self.draw_cached_layer_image(
canvas,
image,
spec.frame,
physical_size,
)
true
}
None => {
self.record_unsupported_command(
"BeginRetainedLayer",
"cached layer snapshot failed for \{spec.key}; rendered directly",
)
self.render_uncached_layer_to_canvas(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
}
}
}
None => {
self.record_unsupported_command(
"BeginRetainedLayer",
"cached layer canvas unavailable for \{spec.key}; rendered directly",
)
self.render_uncached_layer_to_canvas(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
}
}
None => {
self.record_unsupported_command(
"BeginRetainedLayer",
"cached layer surface unavailable for \{spec.key}; rendered directly",
)
self.render_uncached_layer_to_canvas(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
}
}
}
///|
fn SkiaRasterRenderer::render_uncached_layer_to_canvas(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
spec : @core.RetainedLayerSpec,
commands : Array[@render_common.PlanOp],
start_index~ : Int,
end_index~ : Int,
stats : SkiaCachedLayerFrameStats,
) -> Bool {
let saved = canvas.save_count()
self.push_layer(
canvas,
@core.LayerSpec::new(
mask=@core.LayerMask::RectMask(spec.frame),
offscreen=true,
),
)
let layer_scopes : Array[@render_common.PlanScopeKind] = []
let complete = self.render_cached_command_range(
canvas,
commands,
start_index~,
end_index~,
layer_scopes,
stats,
)
if !layer_scopes.is_empty() {
for scope in layer_scopes {
self.record_unsupported_command(
@render_common.plan_scope_push_command(scope),
"uncached fallback layer command stream ended with an open \{@render_common.plan_scope_name(scope)} scope",
)
}
}
canvas.restore_to_count(saved)
complete
}
///|
fn SkiaRasterRenderer::draw_cached_layer(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
spec : @core.RetainedLayerSpec,
stats : SkiaCachedLayerFrameStats,
) -> Bool {
if spec.frame.is_empty() {
return true
}
let physical_size = skia_cached_layer_physical_size(
spec.frame,
self.metrics.scale_factor,
)
let key = self.skia_layer_cache_key(spec, physical_size)
match self.lookup_cached_layer(key) {
Some(entry) => {
// Cached layer pixels are recorded in layer-local coordinates; origin is
// the draw destination, so moving a same-size layer remains a cache hit.
if entry.runtime_key != spec.key ||
entry.scale_factor != self.metrics.scale_factor ||
entry.surface_route != self.surface_route ||
entry.content_revision != spec.content_revision {
return false
}
entry.last_used = self.next_layer_cache_tick()
entry.hit_count = entry.hit_count + 1
let identity_key = self.skia_layer_cache_identity_key(spec, physical_size)
let admission = self.layer_cache_admission_record(identity_key)
admission.hit_count = admission.hit_count + 1
stats.cache_hit_count = stats.cache_hit_count + 1
self.draw_cached_layer_image(
canvas,
entry.image,
spec.frame,
entry.physical_size,
)
true
}
// The native GPU producer stores cached layers as immutable pictures.
// Keep those entries usable when the same renderer falls back to raster.
None => self.draw_picture_cached_layer(canvas, spec, stats)
}
}
///|
fn SkiaRasterRenderer::draw_cached_layer_image(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
image : @skia_native.Image,
frame : @core.Rect,
physical_size : @moui_skia.ISize,
) -> Unit {
let _ = self
canvas.draw_image_rect(
image,
@moui_skia.Rect::from_xywh(
0.0,
0.0,
Float::from_int(physical_size.width),
Float::from_int(physical_size.height),
),
skia_rect(frame),
@moui_skia.Paint::new(anti_alias=true),
)
}
///|
fn SkiaRasterRenderer::create_layer_surface(
self : SkiaRasterRenderer,
physical_size : @moui_skia.ISize,
) -> @skia_native.Surface? {
let target = skia_surface_target_for_physical_size(
physical_size,
self.surface_route,
self.gpu_context,
)
match self.surface_route {
RasterSurfaceRoute => @skia_native.Surface::for_target(target)
MetalGpuSurfaceRoute =>
match self.gpu_context {
Some(gpu_context) =>
@skia_native.Surface::for_target_with_gpu_context(target, gpu_context)
None => None
}
Direct3DGpuSurfaceRoute =>
match self.gpu_context {
Some(gpu_context) =>
@skia_native.Surface::for_target_with_gpu_context(target, gpu_context)
None => None
}
VulkanGpuSurfaceRoute =>
match self.gpu_context {
Some(gpu_context) =>
@skia_native.Surface::for_target_with_gpu_context(target, gpu_context)
None => None
}
EglGpuSurfaceRoute =>
match self.gpu_context {
Some(gpu_context) =>
@skia_native.Surface::for_target_with_gpu_context(target, gpu_context)
None => None
}
}
}
///|
fn SkiaRasterRenderer::skia_layer_cache_key(
self : SkiaRasterRenderer,
spec : @core.RetainedLayerSpec,
physical_size : @moui_skia.ISize,
) -> String {
"\{spec.key}|route=\{self.surface_route.label()}|scale=\{self.metrics.scale_factor}|pw=\{physical_size.width}|ph=\{physical_size.height}|rev=\{spec.content_revision}"
}
///|
fn SkiaRasterRenderer::skia_layer_cache_identity_key(
self : SkiaRasterRenderer,
spec : @core.RetainedLayerSpec,
physical_size : @moui_skia.ISize,
) -> String {
"\{spec.key}|route=\{self.surface_route.label()}|scale=\{self.metrics.scale_factor}|pw=\{physical_size.width}|ph=\{physical_size.height}"
}
///|
fn SkiaRasterRenderer::layer_cache_admission_record(
self : SkiaRasterRenderer,
identity_key : String,
) -> SkiaCachedLayerAdmissionRecord {
match self.layer_cache_admissions.get(identity_key) {
Some(record) => record
None => {
let record = SkiaCachedLayerAdmissionRecord::{
identity_key,
update_streak: 0,
hit_count: 0,
skip_reason: "",
last_admitted_frame: 0,
last_revision: -1,
}
self.layer_cache_admissions[identity_key] = record
self.prune_layer_cache_admission_records(identity_key)
record
}
}
}
///|
fn skia_layer_cache_admission_limit() -> Int {
512
}
///|
fn SkiaRasterRenderer::prune_layer_cache_admission_records(
self : SkiaRasterRenderer,
protected_identity_key : String,
) -> Unit {
while self.layer_cache_admissions.length() >
skia_layer_cache_admission_limit() {
match self.layer_cache_admission_eviction_key(protected_identity_key) {
Some(key) => self.layer_cache_admissions.remove(key)
None => return
}
}
}
///|
fn SkiaRasterRenderer::layer_cache_admission_eviction_key(
self : SkiaRasterRenderer,
protected_identity_key : String,
) -> String? {
let mut candidate_key : String? = None
let mut candidate_live = true
let mut candidate_tick = 0
for key, record in self.layer_cache_admissions {
if key == protected_identity_key {
continue
}
let live = self.layer_cache_has_identity(record.identity_key)
let tick = record.last_admitted_frame
match candidate_key {
None => {
candidate_key = Some(key)
candidate_live = live
candidate_tick = tick
}
Some(_) =>
if (candidate_live && !live) ||
(candidate_live == live && tick < candidate_tick) {
candidate_key = Some(key)
candidate_live = live
candidate_tick = tick
}
}
}
candidate_key
}
///|
fn SkiaRasterRenderer::layer_cache_has_identity(
self : SkiaRasterRenderer,
identity_key : String,
) -> Bool {
self.layer_cache_identity_index.get(identity_key) is Some(_) ||
self.picture_layer_cache_identity_index.get(identity_key) is Some(_)
}
///|
fn SkiaCachedLayerAdmissionRecord::record_update(
self : SkiaCachedLayerAdmissionRecord,
revision : Int,
) -> Unit {
if self.last_revision != revision {
self.update_streak = self.update_streak + 1
self.last_revision = revision
}
}
///|
fn SkiaRasterRenderer::cached_layer_admission_skip_reason(
self : SkiaRasterRenderer,
admission : SkiaCachedLayerAdmissionRecord,
byte_size : Int64,
budget : Int64,
) -> String? {
let _ = self
if budget > 0L && byte_size * 10L > budget * 4L {
Some("large-layer")
} else if admission.update_streak > 2 && admission.hit_count == 0 {
Some("churn-without-hit")
} else {
None
}
}
///|
fn SkiaRasterRenderer::lookup_cached_layer(
self : SkiaRasterRenderer,
key : String,
) -> SkiaCachedLayerImageEntry? {
self.layer_cache.get(key)
}
///|
fn SkiaRasterRenderer::store_cached_layer(
self : SkiaRasterRenderer,
entry : SkiaCachedLayerImageEntry,
admission : SkiaCachedLayerAdmissionRecord,
) -> Bool {
if entry.byte_size > self.layer_cache_budget_bytes() {
return false
}
self.remove_stale_cached_layers_for_entry(entry)
let admitted_frame = self.next_layer_cache_tick()
admission.last_admitted_frame = admitted_frame
let new_entry = {
..entry,
hit_count: admission.hit_count,
skip_reason: admission.skip_reason,
last_admitted_frame: admitted_frame,
last_used: admitted_frame,
}
match self.layer_cache.get(entry.key) {
Some(old) => self.layer_cache_total_bytes -= old.byte_size
None => ()
}
self.layer_cache[entry.key] = new_entry
self.layer_cache_identity_index[entry.identity_key] = entry.key
self.layer_cache_total_bytes += new_entry.byte_size
true
}
///|
fn SkiaRasterRenderer::remove_stale_cached_layers_for_entry(
self : SkiaRasterRenderer,
entry : SkiaCachedLayerImageEntry,
) -> Unit {
match self.layer_cache_identity_index.get(entry.identity_key) {
Some(old_key) =>
if old_key != entry.key {
match self.layer_cache.get(old_key) {
Some(old) => self.layer_cache_total_bytes -= old.byte_size
None => ()
}
self.layer_cache.remove(old_key)
}
None => ()
}
}
///|
fn SkiaRasterRenderer::remove_cached_layers_by_identity(
self : SkiaRasterRenderer,
identity_key : String,
) -> Unit {
match self.layer_cache_identity_index.get(identity_key) {
Some(key) => {
match self.layer_cache.get(key) {
Some(entry) => self.layer_cache_total_bytes -= entry.byte_size
None => ()
}
self.layer_cache.remove(key)
self.layer_cache_identity_index.remove(identity_key)
}
None => ()
}
}
///|
fn SkiaRasterRenderer::evict_cached_layers_if_needed(
self : SkiaRasterRenderer,
) -> Int {
let budget = self.layer_cache_budget_bytes()
let mut evicted = 0
while budget > 0L &&
self.layer_cache_byte_size() > budget &&
self.layer_cache.length() > 0 {
let (key, entry) = self.cached_layer_eviction_entry(budget)
self.layer_cache_total_bytes -= entry.byte_size
self.layer_cache.remove(key)
self.layer_cache_identity_index.remove(entry.identity_key)
evicted = evicted + 1
}
evicted
}
///|
fn SkiaRasterRenderer::cached_layer_eviction_entry(
self : SkiaRasterRenderer,
budget : Int64,
) -> (String, SkiaCachedLayerImageEntry) {
let mut candidate_key : String? = None
let mut candidate_bytes = 0L
let mut candidate_last_used = 0
for key, entry in self.layer_cache {
let large_cold = entry.hit_count == 0 &&
entry.byte_size * 10L >= budget * 2L
let churny_cold = entry.hit_count == 0 &&
entry.update_streak > 1 &&
entry.skip_reason == ""
let entry_last_used = if entry.last_admitted_frame > 0 {
entry.last_admitted_frame
} else {
entry.last_used
}
if large_cold || churny_cold {
match candidate_key {
None => {
candidate_key = Some(key)
candidate_bytes = entry.byte_size
candidate_last_used = entry_last_used
}
Some(_) =>
if entry.byte_size > candidate_bytes ||
(
entry.byte_size == candidate_bytes &&
entry_last_used < candidate_last_used
) {
candidate_key = Some(key)
candidate_bytes = entry.byte_size
candidate_last_used = entry_last_used
}
}
}
}
match candidate_key {
Some(key) => (key, self.layer_cache[key])
None => self.oldest_cached_layer_entry()
}
}
///|
fn SkiaRasterRenderer::oldest_cached_layer_entry(
self : SkiaRasterRenderer,
) -> (String, SkiaCachedLayerImageEntry) {
let mut oldest_key : String? = None
let mut oldest_tick = 0
for key, entry in self.layer_cache {
match oldest_key {
None => {
oldest_key = Some(key)
oldest_tick = entry.last_used
}
Some(_) =>
if entry.last_used < oldest_tick {
oldest_key = Some(key)
oldest_tick = entry.last_used
}
}
}
let key = match oldest_key {
Some(k) => k
None => abort("oldest_cached_layer_entry called on empty layer cache")
}
(key, self.layer_cache[key])
}
///|
fn SkiaRasterRenderer::layer_cache_byte_size(
self : SkiaRasterRenderer,
) -> Int64 {
self.layer_cache_total_bytes
}
///|
fn SkiaRasterRenderer::layer_cache_budget_bytes(
self : SkiaRasterRenderer,
) -> Int64 {
let surface = skia_dim_to_int(self.metrics.physical_size.width).to_int64() *
skia_dim_to_int(self.metrics.physical_size.height).to_int64() *
4L *
3L
let cap = 64L * 1024L * 1024L
if surface < cap {
surface
} else {
cap
}
}
///|
fn SkiaRasterRenderer::next_layer_cache_tick(self : SkiaRasterRenderer) -> Int {
self.layer_cache_tick = self.layer_cache_tick + 1
self.layer_cache_tick
}
///|
fn SkiaRasterRenderer::clear_layer_cache(self : SkiaRasterRenderer) -> Unit {
self.layer_cache.clear()
self.layer_cache_identity_index.clear()
self.picture_layer_cache.clear()
self.picture_layer_cache_identity_index.clear()
self.layer_cache_admissions.clear()
self.layer_cache_total_bytes = 0L
self.picture_layer_cache_total_bytes = 0L
self.layer_cache_tick = 0
}
///|
fn skia_cached_layer_physical_size(
frame : @core.Rect,
scale_factor : Double,
) -> @moui_skia.ISize {
@moui_skia.ISize::new(
skia_dim_to_int(frame.size.width * scale_factor),
skia_dim_to_int(frame.size.height * scale_factor),
)
}
///|
fn skia_cached_layer_byte_size(size : @moui_skia.ISize) -> Int64 {
size.width.to_int64() * size.height.to_int64() * 4L
}