///|
fn WebGpuLayerCommandCache::new() -> WebGpuLayerCommandCache {
{ entries: [], tick: 0 }
}
///|
fn WebGpuLayerCommandCache::expand_frame_commands(
self : WebGpuLayerCommandCache,
commands : Array[@core.DrawCommand],
) -> WebGpuLayerCommandExpansion {
self.tick = self.tick + 1
let output : Array[@core.DrawCommand] = []
let captures : Array[WebGpuLayerCommandCapture] = []
let mut hits = 0
let mut misses = 0
let mut updates = 0
let mut index = 0
while index < commands.length() {
match commands[index] {
@core.DrawCommand::BeginRetainedLayer(spec) =>
match
webgpu_find_retained_layer_end(commands, start=index + 1, spec.key) {
Some(layer_end) =>
match self.lookup(spec) {
Some(cached) => {
hits = hits + 1
let origin = spec.frame.origin
for cached_command in cached {
append_webgpu_layer_cache_command(
output,
captures,
webgpu_offset_draw_command(
cached_command,
dx=origin.x,
dy=origin.y,
),
)
}
index = layer_end + 1
}
None => {
misses = misses + 1
let layer = @core.DrawCommand::PushLayer(
@core.LayerSpec::new(
mask=@core.LayerMask::RectMask(spec.frame),
offscreen=true,
),
)
append_webgpu_layer_cache_command(output, captures, layer)
captures.push({ spec, commands: [layer] })
index = index + 1
}
}
None => {
append_webgpu_layer_cache_command(
output,
captures,
@core.DrawCommand::BeginRetainedLayer(spec),
)
index = index + 1
}
}
@core.DrawCommand::EndRetainedLayer(key) => {
append_webgpu_layer_cache_command(
output,
captures,
@core.DrawCommand::PopLayer,
)
match captures.pop() {
Some(capture) =>
if capture.spec.key == key {
// Store in local coordinates relative to the capture frame so a
// later retained declaration can re-place the same content at a
// new origin without replaying stale absolute frames.
let origin = capture.spec.frame.origin
let local_commands = capture.commands.map(command => {
webgpu_offset_draw_command(command, dx=-origin.x, dy=-origin.y)
})
self.store(capture.spec, local_commands)
updates = updates + 1
}
None => ()
}
index = index + 1
}
command => {
append_webgpu_layer_cache_command(output, captures, command)
index = index + 1
}
}
}
{
commands: output,
cache_hit_count: hits,
cache_miss_count: misses,
cache_update_count: updates,
}
}
///|
fn webgpu_find_retained_layer_end(
commands : Array[@core.DrawCommand],
start~ : Int,
key : String,
) -> Int? {
let mut depth = 0
for index in start.. depth = depth + 1
@core.DrawCommand::EndRetainedLayer(end_key) =>
if depth == 0 {
if end_key == key {
return Some(index)
}
return None
} else {
depth = depth - 1
}
_ => ()
}
}
None
}
///|
fn append_webgpu_layer_cache_command(
output : Array[@core.DrawCommand],
captures : Array[WebGpuLayerCommandCapture],
command : @core.DrawCommand,
) -> Unit {
output.push(command)
for capture in captures {
capture.commands.push(command)
}
}
///|
fn WebGpuLayerCommandCache::lookup(
self : WebGpuLayerCommandCache,
spec : @core.RetainedLayerSpec,
) -> Array[@core.DrawCommand]? {
for entry in self.entries {
if webgpu_retained_layer_specs_match(entry.spec, spec) {
return Some(entry.commands)
}
}
None
}
///|
fn WebGpuLayerCommandCache::store(
self : WebGpuLayerCommandCache,
spec : @core.RetainedLayerSpec,
commands : Array[@core.DrawCommand],
) -> Unit {
for entry in self.entries {
if entry.spec.key == spec.key {
entry.spec = spec
entry.commands.clear()
entry.commands.append(commands)
return ()
}
}
self.entries.push({ spec, commands })
}
///|
fn webgpu_retained_layer_specs_match(
cached : @core.RetainedLayerSpec,
current : @core.RetainedLayerSpec,
) -> Bool {
cached.key == current.key &&
cached.content_revision == current.content_revision &&
cached.scale_factor == current.scale_factor &&
cached.frame.size == current.frame.size
}
///|
/// Translate absolute draw geometry by `(dx, dy)`. Used to convert a cached
/// layer into local coordinates for storage and back into paint coordinates
/// when replaying at a new frame origin.
fn webgpu_offset_draw_command(
command : @core.DrawCommand,
dx~ : Double,
dy~ : Double,
) -> @core.DrawCommand {
if dx == 0.0 && dy == 0.0 {
return command
}
match command {
@core.DrawCommand::Clear(_)
| @core.DrawCommand::PopClip
| @core.DrawCommand::PopRoundedClip
| @core.DrawCommand::PopTransform
| @core.DrawCommand::PopOpacity
| @core.DrawCommand::PopLayer
| @core.DrawCommand::EndRetainedLayer(_)
| @core.DrawCommand::PopFilter
| @core.DrawCommand::PushOpacity(_)
| @core.DrawCommand::PushFilter(_) => command
@core.DrawCommand::FillRect(rect, color) =>
@core.DrawCommand::FillRect(webgpu_offset_rect(rect, dx~, dy~), color)
@core.DrawCommand::StrokeRect(rect, color, width) =>
@core.DrawCommand::StrokeRect(
webgpu_offset_rect(rect, dx~, dy~),
color,
width,
)
@core.DrawCommand::FillRoundedRect(rect, color) =>
@core.DrawCommand::FillRoundedRect(
webgpu_offset_rounded_rect(rect, dx~, dy~),
color,
)
@core.DrawCommand::StrokeRoundedRect(rect, color, width) =>
@core.DrawCommand::StrokeRoundedRect(
webgpu_offset_rounded_rect(rect, dx~, dy~),
color,
width,
)
@core.DrawCommand::FillRoundedRectBrush(rect, brush) =>
@core.DrawCommand::FillRoundedRectBrush(
webgpu_offset_rounded_rect(rect, dx~, dy~),
webgpu_offset_brush(brush, dx~, dy~),
)
@core.DrawCommand::StrokeRoundedRectBrush(rect, brush, width) =>
@core.DrawCommand::StrokeRoundedRectBrush(
webgpu_offset_rounded_rect(rect, dx~, dy~),
webgpu_offset_brush(brush, dx~, dy~),
width,
)
@core.DrawCommand::DrawShadow(shadow) =>
@core.DrawCommand::DrawShadow({
..shadow,
rect: webgpu_offset_rounded_rect(shadow.rect, dx~, dy~),
})
@core.DrawCommand::DrawText(run) =>
@core.DrawCommand::DrawText({
..run,
frame: webgpu_offset_rect(run.frame, dx~, dy~),
})
@core.DrawCommand::DrawImage(run) =>
@core.DrawCommand::DrawImage({
..run,
frame: webgpu_offset_rect(run.frame, dx~, dy~),
})
@core.DrawCommand::PushClip(rect) =>
@core.DrawCommand::PushClip(webgpu_offset_rect(rect, dx~, dy~))
@core.DrawCommand::PushRoundedClip(rect) =>
@core.DrawCommand::PushRoundedClip(
webgpu_offset_rounded_rect(rect, dx~, dy~),
)
@core.DrawCommand::PushTransform(transform) =>
@core.DrawCommand::PushTransform({
..transform,
tx: transform.tx + dx,
ty: transform.ty + dy,
})
@core.DrawCommand::PushLayer(layer) =>
@core.DrawCommand::PushLayer({
..layer,
mask: webgpu_offset_layer_mask(layer.mask, dx~, dy~),
})
@core.DrawCommand::BeginRetainedLayer(spec) =>
@core.DrawCommand::BeginRetainedLayer({
..spec,
frame: webgpu_offset_rect(spec.frame, dx~, dy~),
})
@core.DrawCommand::DrawPath(path) =>
@core.DrawCommand::DrawPath({
..path,
verbs: path.verbs.map(verb => webgpu_offset_path_verb(verb, dx~, dy~)),
fill: webgpu_offset_optional_brush(path.fill, dx~, dy~),
stroke: webgpu_offset_optional_brush(path.stroke, dx~, dy~),
})
@core.DrawCommand::DrawShaderEffect(spec) =>
@core.DrawCommand::DrawShaderEffect({
..spec,
frame: webgpu_offset_rect(spec.frame, dx~, dy~),
fallback: webgpu_offset_brush(spec.fallback, dx~, dy~),
})
}
}
///|
fn webgpu_offset_rect(
rect : @core.Rect,
dx~ : Double,
dy~ : Double,
) -> @core.Rect {
rect.offset(dx~, dy~)
}
///|
fn webgpu_offset_point(
point : @core.Point,
dx~ : Double,
dy~ : Double,
) -> @core.Point {
@core.Point::new(x=point.x + dx, y=point.y + dy)
}
///|
fn webgpu_offset_rounded_rect(
rect : @core.RoundedRect,
dx~ : Double,
dy~ : Double,
) -> @core.RoundedRect {
{ ..rect, rect: webgpu_offset_rect(rect.rect, dx~, dy~) }
}
///|
fn webgpu_offset_layer_mask(
mask : @core.LayerMask,
dx~ : Double,
dy~ : Double,
) -> @core.LayerMask {
match mask {
@core.LayerMask::NoMask => mask
@core.LayerMask::RectMask(rect) =>
@core.LayerMask::RectMask(webgpu_offset_rect(rect, dx~, dy~))
@core.LayerMask::RoundedMask(rect) =>
@core.LayerMask::RoundedMask(webgpu_offset_rounded_rect(rect, dx~, dy~))
}
}
///|
fn webgpu_offset_path_verb(
verb : @core.PathVerb,
dx~ : Double,
dy~ : Double,
) -> @core.PathVerb {
match verb {
@core.PathVerb::Close => verb
@core.PathVerb::MoveTo(point) =>
@core.PathVerb::MoveTo(webgpu_offset_point(point, dx~, dy~))
@core.PathVerb::LineTo(point) =>
@core.PathVerb::LineTo(webgpu_offset_point(point, dx~, dy~))
@core.PathVerb::QuadTo(c, point) =>
@core.PathVerb::QuadTo(
webgpu_offset_point(c, dx~, dy~),
webgpu_offset_point(point, dx~, dy~),
)
@core.PathVerb::CubicTo(c1, c2, point) =>
@core.PathVerb::CubicTo(
webgpu_offset_point(c1, dx~, dy~),
webgpu_offset_point(c2, dx~, dy~),
webgpu_offset_point(point, dx~, dy~),
)
}
}
///|
fn webgpu_offset_brush(
brush : @core.Brush,
dx~ : Double,
dy~ : Double,
) -> @core.Brush {
match brush {
@core.Brush::Solid(_) => brush
@core.Brush::LinearGradient(spec) =>
@core.Brush::LinearGradient({
..spec,
start: webgpu_offset_point(spec.start, dx~, dy~),
end: webgpu_offset_point(spec.end, dx~, dy~),
})
@core.Brush::RadialGradient(spec) =>
@core.Brush::RadialGradient({
..spec,
center: webgpu_offset_point(spec.center, dx~, dy~),
})
}
}
///|
fn webgpu_offset_optional_brush(
brush : @core.Brush?,
dx~ : Double,
dy~ : Double,
) -> @core.Brush? {
match brush {
Some(value) => Some(webgpu_offset_brush(value, dx~, dy~))
None => None
}
}