///|
priv struct SkiaCachedLayerPictureEntry {
identity_key : String
key : String
runtime_key : String
picture : @skia_native.Picture
scale_factor : Double
content_revision : Int
byte_size : Int64
mut hit_count : Int
mut last_used : Int
}
///|
fn SkiaRasterRenderer::draw_frame_to_picture_canvas(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
frame : @core.DrawFrame,
) -> Bool {
canvas.clear(skia_color(frame.clear_color))
let saved = canvas.save()
canvas.scale(
@moui_skia.Size::new(
Float::from_double(self.metrics.scale_factor),
Float::from_double(self.metrics.scale_factor),
),
)
self.unsupported_command_count = 0
self.unsupported_command_diagnostics.clear()
let scopes : Array[@render_common.PlanScopeKind] = []
let plan = @render_common.lower_draw_commands(frame.commands)
let stats = SkiaCachedLayerFrameStats::new()
let complete = self.render_picture_command_range(
canvas,
plan.ops,
start_index=0,
end_index=plan.ops.length(),
scopes,
stats,
)
if !scopes.is_empty() {
for scope in scopes {
self.record_unsupported_command(
@render_common.plan_scope_push_command(scope),
"picture command stream ended with an open \{@render_common.plan_scope_name(scope)} scope",
)
}
}
canvas.restore_to_count(saved)
complete
}
///|
fn SkiaRasterRenderer::render_picture_command_range(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
commands : Array[@render_common.PlanOp],
start_index~ : Int,
end_index~ : Int,
scopes : Array[@render_common.PlanScopeKind],
stats : SkiaCachedLayerFrameStats,
) -> 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.draw_picture_cached_layer(canvas, spec, stats) {
index = layer_end + 1
} else {
stats.cache_miss_count = stats.cache_miss_count + 1
if !self.update_picture_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",
"picture cache command stream ended without matching EndRetainedLayer for \{spec.key}",
)
return false
}
}
@render_common.PlanOp::EndRetainedLayer(_) => return true
command => {
self.render_command(canvas, command, scopes)
index = index + 1
}
}
}
true
}
///|
fn SkiaRasterRenderer::update_picture_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 budget = self.layer_cache_budget_bytes()
let estimated_bytes = skia_cached_layer_byte_size(physical_size)
if budget > 0L && estimated_bytes > budget {
return self.render_uncached_picture_layer(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
}
match @skia_native.PictureRecorder::new() {
None =>
self.render_uncached_picture_layer(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
Some(recorder) => {
let width = spec.frame.size.width.ceil().max(1.0).to_int()
let height = spec.frame.size.height.ceil().max(1.0).to_int()
match recorder.begin(width, height) {
None =>
self.render_uncached_picture_layer(
canvas,
spec,
commands,
start_index~,
end_index~,
stats,
)
Some(layer_canvas) => {
let saved = layer_canvas.save()
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_picture_command_range(
layer_canvas,
commands,
start_index~,
end_index~,
layer_scopes,
stats,
)
layer_canvas.restore_to_count(saved)
if !complete || !layer_scopes.is_empty() {
return false
}
match recorder.finish() {
None => false
Some(picture) => {
let identity_key = self.skia_layer_cache_identity_key(
spec, physical_size,
)
let key = self.skia_layer_cache_key(spec, physical_size)
let admission = self.layer_cache_admission_record(identity_key)
admission.record_update(spec.content_revision)
let byte_size = picture.approximate_bytes_used().max(1L)
match
self.cached_layer_admission_skip_reason(
admission, byte_size, budget,
) {
Some(reason) => {
admission.skip_reason = reason
self.remove_picture_cached_layers_by_identity(identity_key)
}
None => {
admission.skip_reason = ""
self.store_picture_cached_layer({
identity_key,
key,
runtime_key: spec.key,
picture,
scale_factor: self.metrics.scale_factor,
content_revision: spec.content_revision,
byte_size,
hit_count: admission.hit_count,
last_used: 0,
})
stats.cache_update_count = stats.cache_update_count + 1
stats.cache_evict_count += self.evict_picture_cached_layers_if_needed()
}
}
self.draw_picture_at_frame(canvas, picture, spec.frame)
true
}
}
}
}
}
}
}
///|
fn SkiaRasterRenderer::render_uncached_picture_layer(
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 scopes : Array[@render_common.PlanScopeKind] = []
let complete = self.render_picture_command_range(
canvas,
commands,
start_index~,
end_index~,
scopes,
stats,
)
canvas.restore_to_count(saved)
complete && scopes.is_empty()
}
///|
fn SkiaRasterRenderer::draw_picture_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.picture_layer_cache.get(key) {
None => false
Some(entry) => {
if entry.runtime_key != spec.key ||
entry.scale_factor != self.metrics.scale_factor ||
entry.content_revision != spec.content_revision {
return false
}
entry.last_used = self.next_layer_cache_tick()
entry.hit_count = entry.hit_count + 1
let admission = self.layer_cache_admission_record(entry.identity_key)
admission.hit_count = admission.hit_count + 1
stats.cache_hit_count = stats.cache_hit_count + 1
self.draw_picture_at_frame(canvas, entry.picture, spec.frame)
true
}
}
}
///|
fn SkiaRasterRenderer::draw_picture_at_frame(
self : SkiaRasterRenderer,
canvas : @skia_native.Canvas,
picture : @skia_native.Picture,
frame : @core.Rect,
) -> Unit {
let _ = self
let saved = canvas.save()
canvas.translate(
@moui_skia.Point::new(
Float::from_double(frame.origin.x),
Float::from_double(frame.origin.y),
),
)
canvas.draw_picture(picture)
canvas.restore_to_count(saved)
}
///|
fn SkiaRasterRenderer::store_picture_cached_layer(
self : SkiaRasterRenderer,
entry : SkiaCachedLayerPictureEntry,
) -> Unit {
self.remove_picture_cached_layers_by_identity(entry.identity_key)
let tick = self.next_layer_cache_tick()
let stored = { ..entry, last_used: tick }
self.picture_layer_cache[entry.key] = stored
self.picture_layer_cache_identity_index[entry.identity_key] = entry.key
self.picture_layer_cache_total_bytes += entry.byte_size
}
///|
fn SkiaRasterRenderer::remove_picture_cached_layers_by_identity(
self : SkiaRasterRenderer,
identity_key : String,
) -> Unit {
match self.picture_layer_cache_identity_index.get(identity_key) {
Some(key) => {
match self.picture_layer_cache.get(key) {
Some(entry) => self.picture_layer_cache_total_bytes -= entry.byte_size
None => ()
}
self.picture_layer_cache.remove(key)
self.picture_layer_cache_identity_index.remove(identity_key)
}
None => ()
}
}
///|
fn SkiaRasterRenderer::evict_picture_cached_layers_if_needed(
self : SkiaRasterRenderer,
) -> Int {
let budget = self.layer_cache_budget_bytes()
let mut evicted = 0
while budget >= 0L && self.picture_layer_cache_total_bytes > budget {
let mut oldest_key : String? = None
let mut oldest_tick = 2147483647
for key, entry in self.picture_layer_cache {
if entry.last_used < oldest_tick {
oldest_tick = entry.last_used
oldest_key = Some(key)
}
}
match oldest_key {
None => break
Some(key) => {
match self.picture_layer_cache.get(key) {
Some(entry) => {
self.picture_layer_cache_total_bytes -= entry.byte_size
self.picture_layer_cache_identity_index.remove(entry.identity_key)
}
None => ()
}
self.picture_layer_cache.remove(key)
evicted = evicted + 1
}
}
}
evicted
}