Merge pull request #416 from wlx-team/staging
wgui: perf: Scissor render culling
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
<div gap="4">
|
||||
<Button id="button_red" text="Red button" width="150" height="32" color="#FF0000" tooltip_str="I'm at the top" tooltip_side="top" />
|
||||
<Button id="button_aqua" text="Aqua button" width="150" height="32" color="#00FFFF" tooltip_str="I'm at the bottom" tooltip_side="bottom" />
|
||||
<Button id="button_yellow" text="Yellow button" width="150" height="32" color="#FFFF00" tooltip="TESTBED.HELLO_WORLD" tooltip_side="right" />
|
||||
<Button id="button_yellow" text="Yellow button" width="150" height="32" color="#FFFF00" tooltip="TESTBED.HELLO_WORLD" tooltip_side="left" />
|
||||
</div>
|
||||
<div gap="4">
|
||||
<Button id="button_click_me" text="Click me" width="128" height="24" color="#FFFFFF" />
|
||||
|
||||
@@ -359,7 +359,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.unwrap();
|
||||
|
||||
if debug_draw_enabled {
|
||||
log::debug!("pass count: {}", draw_result.pass_count);
|
||||
log::debug!(
|
||||
"pass count: {}, primitive commands count: {}",
|
||||
draw_result.pass_count,
|
||||
draw_result.primitive_commands_count
|
||||
);
|
||||
}
|
||||
|
||||
cmd_buf.end_rendering().unwrap();
|
||||
|
||||
@@ -129,27 +129,34 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
|
||||
let transform = Mat4::from_translation(Vec3::new(-0.5, 0.0, 0.0));
|
||||
|
||||
// this value needs to be bigger than rectangle padding sizes due to the
|
||||
// transform stack & scissoring design. Needs investigation, zero-size objects
|
||||
// would result in PushScissorStackResult::OutOfBounds otherwise preventing us
|
||||
// to render the label. Didn't find the best solution for this edge-case yet,
|
||||
// so here it is.
|
||||
let pin_size = 32.0;
|
||||
|
||||
let (pin_left, pin_top, pin_align_items, pin_justify_content) = match params.info.side {
|
||||
TooltipSide::Left => (
|
||||
absolute_boundary.left() - spacing,
|
||||
absolute_boundary.top() + absolute_boundary.size.y / 2.0,
|
||||
absolute_boundary.left() - spacing - pin_size,
|
||||
absolute_boundary.top() + absolute_boundary.size.y / 2.0 - pin_size / 2.0,
|
||||
taffy::AlignItems::Center,
|
||||
taffy::JustifyContent::End,
|
||||
),
|
||||
TooltipSide::Right => (
|
||||
absolute_boundary.left() + absolute_boundary.size.x + spacing,
|
||||
absolute_boundary.top() + absolute_boundary.size.y / 2.0,
|
||||
absolute_boundary.top() + absolute_boundary.size.y / 2.0 - pin_size / 2.0,
|
||||
taffy::AlignItems::Center,
|
||||
taffy::JustifyContent::Start,
|
||||
),
|
||||
TooltipSide::Top => (
|
||||
absolute_boundary.left() + absolute_boundary.size.x / 2.0,
|
||||
absolute_boundary.top() - spacing,
|
||||
absolute_boundary.left() + absolute_boundary.size.x / 2.0 - pin_size / 2.0,
|
||||
absolute_boundary.top() - spacing - pin_size,
|
||||
taffy::AlignItems::End,
|
||||
taffy::JustifyContent::Center,
|
||||
),
|
||||
TooltipSide::Bottom => (
|
||||
absolute_boundary.left() + absolute_boundary.size.x / 2.0,
|
||||
absolute_boundary.left() + absolute_boundary.size.x / 2.0 - pin_size / 2.0,
|
||||
absolute_boundary.top() + absolute_boundary.size.y + spacing,
|
||||
taffy::AlignItems::Baseline,
|
||||
taffy::JustifyContent::Center,
|
||||
@@ -173,8 +180,8 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
},
|
||||
/* important, to make it centered! */
|
||||
size: taffy::Size {
|
||||
width: length(0.0),
|
||||
height: length(0.0),
|
||||
width: length(pin_size),
|
||||
height: length(pin_size),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -288,24 +288,48 @@ pub fn push_transform_stack(
|
||||
});
|
||||
}
|
||||
|
||||
/// returns true if scissor has been pushed
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum PushScissorStackResult {
|
||||
VisibleDontClip, // scissor calculated, but don't clip anything
|
||||
VisibleAndClip, // scissor should be applied at this stage (ScissorSet primitive needs to be called)
|
||||
OutOfBounds, // scissor rectangle is out of bounds (negative boundary dimensions)
|
||||
}
|
||||
|
||||
impl PushScissorStackResult {
|
||||
pub fn should_display(&self) -> bool {
|
||||
*self != Self::OutOfBounds
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if scissor has been pushed.
|
||||
pub fn push_scissor_stack(
|
||||
transform_stack: &mut TransformStack,
|
||||
scissor_stack: &mut ScissorStack,
|
||||
scroll_shift: Vec2,
|
||||
info: &Option<ScrollbarInfo>,
|
||||
style: &taffy::Style,
|
||||
) -> bool {
|
||||
let scissor_pushed = info.is_some() && has_overflow_clip(style);
|
||||
if !scissor_pushed {
|
||||
return false;
|
||||
}
|
||||
|
||||
) -> PushScissorStackResult {
|
||||
let mut boundary_absolute = drawing::Boundary::construct_absolute(transform_stack);
|
||||
boundary_absolute.pos += scroll_shift;
|
||||
|
||||
let do_clip = info.is_some() && has_overflow_clip(style);
|
||||
|
||||
scissor_stack.push(ScissorBoundary(boundary_absolute));
|
||||
|
||||
true
|
||||
if scissor_stack.is_out_of_bounds() {
|
||||
return PushScissorStackResult::OutOfBounds;
|
||||
}
|
||||
|
||||
if do_clip {
|
||||
PushScissorStackResult::VisibleAndClip
|
||||
} else {
|
||||
PushScissorStackResult::VisibleDontClip
|
||||
}
|
||||
}
|
||||
|
||||
struct DrawWidgetInternal {
|
||||
// how many times ScissorSet render primitives has been called?
|
||||
scissor_set_count: u32,
|
||||
}
|
||||
|
||||
fn draw_widget(
|
||||
@@ -313,6 +337,7 @@ fn draw_widget(
|
||||
state: &mut DrawState,
|
||||
node_id: taffy::NodeId,
|
||||
style: &taffy::Style,
|
||||
internal: &mut DrawWidgetInternal,
|
||||
widget: &Widget,
|
||||
) {
|
||||
let Ok(l) = params.layout.state.tree.layout(node_id) else {
|
||||
@@ -346,9 +371,11 @@ fn draw_widget(
|
||||
));
|
||||
}
|
||||
|
||||
let scissor_pushed = push_scissor_stack(state.transform_stack, state.scissor_stack, scroll_shift, &info, style);
|
||||
let starting_scissor_set_count = internal.scissor_set_count;
|
||||
|
||||
if scissor_pushed {
|
||||
let scissor_result = push_scissor_stack(state.transform_stack, state.scissor_stack, scroll_shift, &info, style);
|
||||
|
||||
if scissor_result == PushScissorStackResult::VisibleAndClip {
|
||||
if params.debug_draw {
|
||||
let mut boundary_relative = drawing::Boundary::construct_relative(state.transform_stack);
|
||||
boundary_relative.pos += scroll_shift;
|
||||
@@ -358,10 +385,10 @@ fn draw_widget(
|
||||
Color::new(1.0, 0.0, 1.0, 1.0),
|
||||
));
|
||||
}
|
||||
|
||||
state
|
||||
.primitives
|
||||
.push(drawing::RenderPrimitive::ScissorSet(*state.scissor_stack.get()));
|
||||
internal.scissor_set_count += 1;
|
||||
}
|
||||
|
||||
let draw_params = widget::DrawParams {
|
||||
@@ -370,12 +397,16 @@ fn draw_widget(
|
||||
style,
|
||||
};
|
||||
|
||||
widget_state.draw_all(state, &draw_params);
|
||||
if scissor_result.should_display() {
|
||||
widget_state.draw_all(state, &draw_params);
|
||||
draw_children(params, state, node_id, internal, false);
|
||||
}
|
||||
|
||||
draw_children(params, state, node_id, false);
|
||||
state.scissor_stack.pop();
|
||||
|
||||
if scissor_pushed {
|
||||
state.scissor_stack.pop();
|
||||
let current_scissor_set_count = internal.scissor_set_count;
|
||||
|
||||
if current_scissor_set_count > starting_scissor_set_count {
|
||||
state
|
||||
.primitives
|
||||
.push(drawing::RenderPrimitive::ScissorSet(*state.scissor_stack.get()));
|
||||
@@ -392,7 +423,13 @@ fn draw_widget(
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taffy::NodeId, is_topmost: bool) {
|
||||
fn draw_children(
|
||||
params: &DrawParams,
|
||||
state: &mut DrawState,
|
||||
parent_node_id: taffy::NodeId,
|
||||
internal: &mut DrawWidgetInternal,
|
||||
is_topmost: bool,
|
||||
) {
|
||||
let layout = ¶ms.layout;
|
||||
|
||||
for node_id in layout.state.tree.child_ids(parent_node_id) {
|
||||
@@ -415,7 +452,7 @@ fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taf
|
||||
continue;
|
||||
};
|
||||
|
||||
draw_widget(params, state, node_id, style, widget);
|
||||
draw_widget(params, state, node_id, style, internal, widget);
|
||||
|
||||
if is_topmost {
|
||||
state.primitives.push(RenderPrimitive::NewPass);
|
||||
@@ -439,7 +476,9 @@ pub fn draw(params: &mut DrawParams) -> anyhow::Result<Vec<RenderPrimitive>> {
|
||||
alterables: &mut alterables,
|
||||
};
|
||||
|
||||
draw_children(params, &mut state, params.layout.tree_root_node, true);
|
||||
let mut internal = DrawWidgetInternal { scissor_set_count: 0 };
|
||||
|
||||
draw_children(params, &mut state, params.layout.tree_root_node, &mut internal, true);
|
||||
|
||||
params.layout.process_alterables(alterables)?;
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ impl Layout {
|
||||
|
||||
widget.data.cached_absolute_boundary = drawing::Boundary::construct_absolute(&alterables.transform_stack);
|
||||
|
||||
let scissor_pushed = push_scissor_stack(
|
||||
let scissor_result = push_scissor_stack(
|
||||
&mut alterables.transform_stack,
|
||||
&mut alterables.scissor_stack,
|
||||
scroll_shift,
|
||||
@@ -450,24 +450,24 @@ impl Layout {
|
||||
style,
|
||||
);
|
||||
|
||||
// check children first
|
||||
self.push_event_children(node_id, event, event_result, alterables, user_data)?;
|
||||
if scissor_result.should_display() {
|
||||
// check children first
|
||||
self.push_event_children(node_id, event, event_result, alterables, user_data)?;
|
||||
|
||||
if event_result.can_propagate() {
|
||||
let mut params = EventParams {
|
||||
state: &self.state,
|
||||
layout: l,
|
||||
alterables,
|
||||
node_id,
|
||||
style,
|
||||
};
|
||||
if event_result.can_propagate() {
|
||||
let mut params = EventParams {
|
||||
state: &self.state,
|
||||
layout: l,
|
||||
alterables,
|
||||
node_id,
|
||||
style,
|
||||
};
|
||||
|
||||
widget.process_event(widget_id, node_id, event, event_result, user_data, &mut params)?;
|
||||
widget.process_event(widget_id, node_id, event, event_result, user_data, &mut params)?;
|
||||
}
|
||||
}
|
||||
|
||||
if scissor_pushed {
|
||||
alterables.scissor_stack.pop();
|
||||
}
|
||||
alterables.scissor_stack.pop();
|
||||
alterables.transform_stack.pop();
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -173,6 +173,7 @@ pub struct Context {
|
||||
|
||||
pub struct ContextDrawResult {
|
||||
pub pass_count: u32,
|
||||
pub primitive_commands_count: u32,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
@@ -239,7 +240,7 @@ impl Context {
|
||||
|
||||
let mut passes = Vec::<RendererPass>::new();
|
||||
let mut needs_new_pass = true;
|
||||
let mut next_scissor: Option<drawing::Boundary> = None;
|
||||
let mut cur_scissor: Option<drawing::Boundary> = None;
|
||||
|
||||
for primitive in primitives {
|
||||
if needs_new_pass {
|
||||
@@ -247,7 +248,7 @@ impl Context {
|
||||
&mut atlas.text_atlas,
|
||||
shared.rect_pipeline.clone(),
|
||||
shared.image_pipeline.clone(),
|
||||
next_scissor,
|
||||
cur_scissor,
|
||||
self.pixel_scale,
|
||||
)?);
|
||||
needs_new_pass = false;
|
||||
@@ -309,14 +310,26 @@ impl Context {
|
||||
.add_image(extent.boundary, image.clone(), &extent.transform);
|
||||
}
|
||||
drawing::RenderPrimitive::ScissorSet(boundary) => {
|
||||
next_scissor = Some(boundary.0);
|
||||
needs_new_pass = true;
|
||||
let skip = if let Some(cur_scissor) = cur_scissor {
|
||||
// do not create a new pass if it's not needed (same scissor values)
|
||||
cur_scissor == boundary.0
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
cur_scissor = Some(boundary.0);
|
||||
if skip {
|
||||
//log::debug!("same scissor boundary, re-using the same pass");
|
||||
} else {
|
||||
needs_new_pass = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = ContextDrawResult {
|
||||
pass_count: passes.len() as u32,
|
||||
primitive_commands_count: primitives.len() as u32,
|
||||
};
|
||||
|
||||
for mut pass in passes {
|
||||
|
||||
@@ -6,6 +6,7 @@ pub trait Pushable<T> {
|
||||
fn push(&mut self, item: &T);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenericStack<T, const STACK_MAX: usize> {
|
||||
pub stack: [T; STACK_MAX],
|
||||
top: u8,
|
||||
@@ -50,7 +51,7 @@ impl<T: StackItem<T>, const STACK_MAX: usize> Default for GenericStack<T, STACK_
|
||||
// Transform stack
|
||||
// ########################################
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Transform {
|
||||
pub rel_pos: Vec2,
|
||||
pub visual_dim: Vec2, // for convenience
|
||||
@@ -95,7 +96,7 @@ pub type TransformStack = GenericStack<Transform, 64>;
|
||||
// Scissor stack
|
||||
// ########################################
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct ScissorBoundary(pub drawing::Boundary);
|
||||
|
||||
impl Default for ScissorBoundary {
|
||||
@@ -144,3 +145,10 @@ impl Pushable<ScissorBoundary> for ScissorBoundary {
|
||||
}
|
||||
|
||||
pub type ScissorStack = GenericStack<ScissorBoundary, 64>;
|
||||
|
||||
impl ScissorStack {
|
||||
pub const fn is_out_of_bounds(&self) -> bool {
|
||||
let boundary = &self.get().0;
|
||||
boundary.width() < 0.0 || boundary.height() < 0.0
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user