///|
fn build_native_draw_plan(
cache : NativeFontCache,
atlas : GlyphAtlas,
commands : Array[@render_common.PlanOp],
size : @core.Size,
scale_factor : Double,
) -> NativeDrawPlan {
build_native_draw_plan_range(
cache,
atlas,
commands,
start_index=0,
stop_at_layer=false,
stop_at_filter=false,
initial_state=native_paint_state_default(),
size,
scale_factor,
).plan
}
///|
priv struct NativeDrawPlanRangeResult {
plan : NativeDrawPlan
next_index : Int
}
///|
fn build_native_draw_plan_range(
cache : NativeFontCache,
atlas : GlyphAtlas,
commands : Array[@render_common.PlanOp],
start_index~ : Int,
stop_at_layer~ : Bool,
stop_at_filter~ : Bool,
initial_state~ : NativePaintState,
size : @core.Size,
scale_factor : Double,
) -> NativeDrawPlanRangeResult {
let was_dirty = atlas.dirty
for attempt in 0..<2 {
let visual_vertices : Array[VisualVertex] = []
let text_vertices : Array[TextVertex] = []
let image_vertices : Array[ImageVertex] = []
let items : Array[NativeDrawItem] = []
let mut state = initial_state
let state_stack : Array[NativePaintState] = []
let mut full = false
let mut index = start_index
while index < commands.length() {
let command = commands[index]
if !full {
match command {
@render_common.PlanOp::Clear(_) => ()
@render_common.PlanOp::FillRect(rect, color) =>
append_visual_item(
visual_vertices,
items,
rect,
radius=0.0,
brush=@core.Brush::solid(color),
mode=visual_mode_fill,
stroke_width=0.0,
blur_radius=0.0,
state~,
size~,
scale_factor~,
)
@render_common.PlanOp::StrokeRect(rect, color, width) =>
append_visual_item(
visual_vertices,
items,
rect,
radius=0.0,
brush=@core.Brush::solid(color),
mode=visual_mode_stroke,
stroke_width=width,
blur_radius=0.0,
state~,
size~,
scale_factor~,
)
@render_common.PlanOp::FillRoundedRect(rounded, color) =>
append_visual_item(
visual_vertices,
items,
rounded.rect,
radius=rounded.radius,
brush=@core.Brush::solid(color),
mode=visual_mode_fill,
stroke_width=0.0,
blur_radius=0.0,
state~,
size~,
scale_factor~,
)
@render_common.PlanOp::StrokeRoundedRect(rounded, color, width) =>
append_visual_item(
visual_vertices,
items,
rounded.rect,
radius=rounded.radius,
brush=@core.Brush::solid(color),
mode=visual_mode_stroke,
stroke_width=width,
blur_radius=0.0,
state~,
size~,
scale_factor~,
)
@render_common.PlanOp::FillRoundedRectBrush(rounded, brush) =>
append_visual_item(
visual_vertices,
items,
rounded.rect,
radius=rounded.radius,
brush~,
mode=visual_mode_fill,
stroke_width=0.0,
blur_radius=0.0,
state~,
size~,
scale_factor~,
)
@render_common.PlanOp::StrokeRoundedRectBrush(rounded, brush, width) =>
append_visual_item(
visual_vertices,
items,
rounded.rect,
radius=rounded.radius,
brush~,
mode=visual_mode_stroke,
stroke_width=width,
blur_radius=0.0,
state~,
size~,
scale_factor~,
)
@render_common.PlanOp::DrawShadow(shadow) =>
append_shadow_item(
visual_vertices, items, shadow, state, size, scale_factor,
)
@render_common.PlanOp::DrawText(run) =>
if native_paint_state_visible(state) &&
native_scissor_visible(
native_scissor_for_state(state, size, scale_factor),
) {
let start = text_vertices.length()
let result = build_native_text_run(
cache, atlas, run, state, size, scale_factor,
)
for vertex in result.vertices {
text_vertices.push(vertex)
}
let count = text_vertices.length() - start
if count > 0 {
items.push(
NativeDrawItem::NativeText(
start,
count,
native_scissor_for_state(state, size, scale_factor),
),
)
}
full = result.atlas_full
}
@render_common.PlanOp::DrawImage(run) =>
append_image_item(
image_vertices, items, run, state, size, scale_factor,
)
@render_common.PlanOp::PushClip(rect) => {
state_stack.push(state)
state = push_clip_state(state, rect)
}
@render_common.PlanOp::PopClip =>
state = pop_native_paint_state(state_stack, state)
@render_common.PlanOp::PushRoundedClip(rounded) => {
state_stack.push(state)
state = push_rounded_clip_state(state, rounded)
}
@render_common.PlanOp::PopRoundedClip =>
state = pop_native_paint_state(state_stack, state)
@render_common.PlanOp::PushTransform(transform) => {
state_stack.push(state)
state = {
..state,
transform: multiply_transform(state.transform, transform),
}
}
@render_common.PlanOp::PopTransform =>
state = pop_native_paint_state(state_stack, state)
@render_common.PlanOp::PushOpacity(opacity) => {
state_stack.push(state)
state = { ..state, opacity: state.opacity * clamp_opacity(opacity) }
}
@render_common.PlanOp::PopOpacity =>
state = pop_native_paint_state(state_stack, state)
@render_common.PlanOp::PushLayer(layer) => {
let mask = native_layer_mask_for_state(state, layer.mask)
let child = build_native_draw_plan_range(
cache,
atlas,
commands,
start_index=index + 1,
stop_at_layer=true,
stop_at_filter=false,
initial_state=native_scoped_child_paint_state(state),
size,
scale_factor,
)
items.push(
NativeDrawItem::NativeLayer({
plan: child.plan,
opacity: state.opacity * layer.opacity,
blend_mode: layer.blend_mode,
mask,
filter: None,
scissor: native_layer_scissor(state, size, scale_factor, mask),
}),
)
index = child.next_index - 1
}
@render_common.PlanOp::PopLayer =>
if stop_at_layer {
return {
plan: {
visual_vertices,
text_vertices,
image_vertices,
items,
atlas_dirty: was_dirty || atlas.dirty,
},
next_index: index + 1,
}
}
@render_common.PlanOp::BeginRetainedLayer(spec) => {
let mask = native_layer_mask_for_state(
state,
@core.LayerMask::RectMask(spec.frame),
)
let child = build_native_draw_plan_range(
cache,
atlas,
commands,
start_index=index + 1,
stop_at_layer=true,
stop_at_filter=false,
initial_state=native_scoped_child_paint_state(state),
size,
scale_factor,
)
items.push(
NativeDrawItem::NativeLayer({
plan: child.plan,
opacity: state.opacity,
blend_mode: @core.BlendMode::SourceOver,
mask,
filter: None,
scissor: native_layer_scissor(state, size, scale_factor, mask),
}),
)
index = child.next_index - 1
}
@render_common.PlanOp::EndRetainedLayer(_) =>
if stop_at_layer {
return {
plan: {
visual_vertices,
text_vertices,
image_vertices,
items,
atlas_dirty: was_dirty || atlas.dirty,
},
next_index: index + 1,
}
}
@render_common.PlanOp::PushFilter(filter) => {
let child = build_native_draw_plan_range(
cache,
atlas,
commands,
start_index=index + 1,
stop_at_layer=false,
stop_at_filter=true,
initial_state=native_scoped_child_paint_state(state),
size,
scale_factor,
)
items.push(
NativeDrawItem::NativeLayer({
plan: child.plan,
opacity: state.opacity,
blend_mode: @core.BlendMode::SourceOver,
mask: None,
filter: Some(filter),
scissor: native_scissor_for_state(state, size, scale_factor),
}),
)
index = child.next_index - 1
}
@render_common.PlanOp::PopFilter =>
if stop_at_filter {
return {
plan: {
visual_vertices,
text_vertices,
image_vertices,
items,
atlas_dirty: was_dirty || atlas.dirty,
},
next_index: index + 1,
}
}
@render_common.PlanOp::DrawShaderEffect(spec) =>
items.push(
NativeDrawItem::NativeShaderEffect({
name: spec.name,
frame: spec.frame,
uniforms: spec.uniforms,
fallback: spec.fallback,
opacity: state.opacity,
transform: state.transform,
scissor: native_scissor_for_state(state, size, scale_factor),
}),
)
@render_common.PlanOp::DrawPath(path) =>
append_path_item(
visual_vertices, items, path, state, size, scale_factor,
)
}
}
index += 1
}
if !full {
return {
plan: {
visual_vertices,
text_vertices,
image_vertices,
items,
atlas_dirty: was_dirty || atlas.dirty,
},
next_index: index,
}
}
atlas.clear()
if attempt == 1 {
return {
plan: {
visual_vertices: [],
text_vertices: [],
image_vertices: [],
items: [],
atlas_dirty: true,
},
next_index: commands.length(),
}
}
}
{
plan: {
visual_vertices: [],
text_vertices: [],
image_vertices: [],
items: [],
atlas_dirty: atlas.dirty,
},
next_index: commands.length(),
}
}