bevy/crates/bevy_ui/src/render/gradient.rs
robtfm b641aa0ecf
separate border colors (#18682)
# Objective

allow specifying the left/top/right/bottom border colors separately for
ui elements

fixes #14773

## Solution

- change `BorderColor` to 
```rs
pub struct BorderColor {
    pub left: Color,
    pub top: Color,
    pub right: Color,
    pub bottom: Color,
}
```
- generate one ui node per distinct border color, set flags for the
active borders
- render only the active borders

i chose to do this rather than adding multiple colors to the
ExtractedUiNode in order to minimize the impact for the common case
where all border colors are the same.

## Testing

modified the `borders` example to use separate colors:


![image](https://github.com/user-attachments/assets/5d9a4492-429a-4ee1-9656-215511886164)

the behaviour is a bit weird but it mirrors html/css border behaviour.

---

## Migration:

To keep the existing behaviour, just change `BorderColor(color)` into
`BorderColor::all(color)`.

---------

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
2025-05-26 16:57:13 +00:00

919 lines
34 KiB
Rust

use core::{
f32::consts::{FRAC_PI_2, TAU},
hash::Hash,
ops::Range,
};
use crate::*;
use bevy_asset::*;
use bevy_color::{ColorToComponents, LinearRgba};
use bevy_ecs::{
prelude::Component,
system::{
lifetimeless::{Read, SRes},
*,
},
};
use bevy_image::prelude::*;
use bevy_math::{
ops::{cos, sin},
FloatOrd, Mat4, Rect, Vec2, Vec3Swizzles, Vec4Swizzles,
};
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_phase::*,
render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderDevice, RenderQueue},
sync_world::TemporaryRenderEntity,
view::*,
Extract, ExtractSchedule, Render, RenderSystems,
};
use bevy_sprite::BorderRect;
use bevy_transform::prelude::GlobalTransform;
use bytemuck::{Pod, Zeroable};
use super::shader_flags::BORDER_ALL;
pub const UI_GRADIENT_SHADER_HANDLE: Handle<Shader> =
weak_handle!("10116113-aac4-47fa-91c8-35cbe80dddcb");
pub struct GradientPlugin;
impl Plugin for GradientPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
UI_GRADIENT_SHADER_HANDLE,
"gradient.wgsl",
Shader::from_wgsl
);
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_render_command::<TransparentUi, DrawGradientFns>()
.init_resource::<ExtractedGradients>()
.init_resource::<ExtractedColorStops>()
.init_resource::<GradientMeta>()
.init_resource::<SpecializedRenderPipelines<GradientPipeline>>()
.add_systems(
ExtractSchedule,
extract_gradients
.in_set(RenderUiSystems::ExtractGradient)
.after(extract_uinode_background_colors),
)
.add_systems(
Render,
(
queue_gradient.in_set(RenderSystems::Queue),
prepare_gradient.in_set(RenderSystems::PrepareBindGroups),
),
);
}
}
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<GradientPipeline>();
}
}
}
#[derive(Component)]
pub struct GradientBatch {
pub range: Range<u32>,
}
#[derive(Resource)]
pub struct GradientMeta {
vertices: RawBufferVec<UiGradientVertex>,
indices: RawBufferVec<u32>,
view_bind_group: Option<BindGroup>,
}
impl Default for GradientMeta {
fn default() -> Self {
Self {
vertices: RawBufferVec::new(BufferUsages::VERTEX),
indices: RawBufferVec::new(BufferUsages::INDEX),
view_bind_group: None,
}
}
}
#[derive(Resource)]
pub struct GradientPipeline {
pub view_layout: BindGroupLayout,
}
impl FromWorld for GradientPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let view_layout = render_device.create_bind_group_layout(
"ui_gradient_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
GradientPipeline { view_layout }
}
}
pub fn compute_gradient_line_length(angle: f32, size: Vec2) -> f32 {
let center = 0.5 * size;
let v = Vec2::new(sin(angle), -cos(angle));
let (pos_corner, neg_corner) = if v.x >= 0.0 && v.y <= 0.0 {
(size.with_y(0.), size.with_x(0.))
} else if v.x >= 0.0 && v.y > 0.0 {
(size, Vec2::ZERO)
} else if v.x < 0.0 && v.y <= 0.0 {
(Vec2::ZERO, size)
} else {
(size.with_x(0.), size.with_y(0.))
};
let t_pos = (pos_corner - center).dot(v);
let t_neg = (neg_corner - center).dot(v);
(t_pos - t_neg).abs()
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct UiGradientPipelineKey {
anti_alias: bool,
pub hdr: bool,
}
impl SpecializedRenderPipeline for GradientPipeline {
type Key = UiGradientPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let vertex_layout = VertexBufferLayout::from_vertex_formats(
VertexStepMode::Vertex,
vec![
// position
VertexFormat::Float32x3,
// uv
VertexFormat::Float32x2,
// flags
VertexFormat::Uint32,
// radius
VertexFormat::Float32x4,
// border
VertexFormat::Float32x4,
// size
VertexFormat::Float32x2,
// point
VertexFormat::Float32x2,
// start_point
VertexFormat::Float32x2,
// dir
VertexFormat::Float32x2,
// start_color
VertexFormat::Float32x4,
// start_len
VertexFormat::Float32,
// end_len
VertexFormat::Float32,
// end color
VertexFormat::Float32x4,
// hint
VertexFormat::Float32,
],
);
let shader_defs = if key.anti_alias {
vec!["ANTI_ALIAS".into()]
} else {
Vec::new()
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: UI_GRADIENT_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: vec![vertex_layout],
},
fragment: Some(FragmentState {
shader: UI_GRADIENT_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: if key.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout: vec![self.view_layout.clone()],
push_constant_ranges: Vec::new(),
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
},
depth_stencil: None,
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("ui_gradient_pipeline".into()),
zero_initialize_workgroup_memory: false,
}
}
}
pub enum ResolvedGradient {
Linear { angle: f32 },
Conic { center: Vec2, start: f32 },
Radial { center: Vec2, size: Vec2 },
}
pub struct ExtractedGradient {
pub stack_index: u32,
pub transform: Mat4,
pub rect: Rect,
pub clip: Option<Rect>,
pub extracted_camera_entity: Entity,
/// range into `ExtractedColorStops`
pub stops_range: Range<usize>,
pub node_type: NodeType,
pub main_entity: MainEntity,
pub render_entity: Entity,
/// Border radius of the UI node.
/// Ordering: top left, top right, bottom right, bottom left.
pub border_radius: ResolvedBorderRadius,
/// Border thickness of the UI node.
/// Ordering: left, top, right, bottom.
pub border: BorderRect,
pub resolved_gradient: ResolvedGradient,
}
#[derive(Resource, Default)]
pub struct ExtractedGradients {
pub items: Vec<ExtractedGradient>,
}
#[derive(Resource, Default)]
pub struct ExtractedColorStops(pub Vec<(LinearRgba, f32, f32)>);
// Interpolate implicit stops (where position is `f32::NAN`)
// If the first and last stops are implicit set them to the `min` and `max` values
// so that we always have explicit start and end points to interpolate between.
fn interpolate_color_stops(stops: &mut [(LinearRgba, f32, f32)], min: f32, max: f32) {
if stops[0].1.is_nan() {
stops[0].1 = min;
}
if stops.last().unwrap().1.is_nan() {
stops.last_mut().unwrap().1 = max;
}
let mut i = 1;
while i < stops.len() - 1 {
let point = stops[i].1;
if point.is_nan() {
let start = i;
let mut end = i + 1;
while end < stops.len() - 1 && stops[end].1.is_nan() {
end += 1;
}
let start_point = stops[start - 1].1;
let end_point = stops[end].1;
let steps = end - start;
let step = (end_point - start_point) / (steps + 1) as f32;
for j in 0..steps {
stops[i + j].1 = start_point + step * (j + 1) as f32;
}
i = end;
}
i += 1;
}
}
fn compute_color_stops(
stops: &[ColorStop],
scale_factor: f32,
length: f32,
target_size: Vec2,
scratch: &mut Vec<(LinearRgba, f32, f32)>,
extracted_color_stops: &mut Vec<(LinearRgba, f32, f32)>,
) {
// resolve the physical distances of explicit stops and sort them
scratch.extend(stops.iter().filter_map(|stop| {
stop.point
.resolve(scale_factor, length, target_size)
.ok()
.map(|physical_point| (stop.color.to_linear(), physical_point, stop.hint))
}));
scratch.sort_by_key(|(_, point, _)| FloatOrd(*point));
let min = scratch
.first()
.map(|(_, min, _)| *min)
.unwrap_or(0.)
.min(0.);
// get the position of the last explicit stop and use the full length of the gradient if no explicit stops
let max = scratch
.last()
.map(|(_, max, _)| *max)
.unwrap_or(length)
.max(length);
let mut sorted_stops_drain = scratch.drain(..);
let range_start = extracted_color_stops.len();
// Fill the extracted color stops buffer
extracted_color_stops.extend(stops.iter().map(|stop| {
if stop.point == Val::Auto {
(stop.color.to_linear(), f32::NAN, stop.hint)
} else {
sorted_stops_drain.next().unwrap()
}
}));
interpolate_color_stops(&mut extracted_color_stops[range_start..], min, max);
}
pub fn extract_gradients(
mut commands: Commands,
mut extracted_gradients: ResMut<ExtractedGradients>,
mut extracted_color_stops: ResMut<ExtractedColorStops>,
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
gradients_query: Extract<
Query<(
Entity,
&ComputedNode,
&ComputedNodeTarget,
&GlobalTransform,
&InheritedVisibility,
Option<&CalculatedClip>,
AnyOf<(&BackgroundGradient, &BorderGradient)>,
)>,
>,
camera_map: Extract<UiCameraMap>,
) {
let mut camera_mapper = camera_map.get_mapper();
let mut sorted_stops = vec![];
for (
entity,
uinode,
target,
transform,
inherited_visibility,
clip,
(gradient, gradient_border),
) in &gradients_query
{
// Skip invisible images
if !inherited_visibility.get() {
continue;
}
let Some(extracted_camera_entity) = camera_mapper.map(target) else {
continue;
};
for (gradients, node_type) in [
(gradient.map(|g| &g.0), NodeType::Rect),
(gradient_border.map(|g| &g.0), NodeType::Border(BORDER_ALL)),
]
.iter()
.filter_map(|(g, n)| g.map(|g| (g, *n)))
{
for gradient in gradients.iter() {
if gradient.is_empty() {
continue;
}
if let Some(color) = gradient.get_single() {
// With a single color stop there's no gradient, fill the node with the color
extracted_uinodes.uinodes.push(ExtractedUiNode {
stack_index: uinode.stack_index,
color: color.into(),
rect: Rect {
min: Vec2::ZERO,
max: uinode.size,
},
image: AssetId::default(),
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
flip_x: false,
flip_y: false,
border_radius: uinode.border_radius,
border: uinode.border,
node_type,
transform: transform.compute_matrix(),
},
main_entity: entity.into(),
render_entity: commands.spawn(TemporaryRenderEntity).id(),
});
continue;
}
match gradient {
Gradient::Linear(LinearGradient { angle, stops }) => {
let length = compute_gradient_line_length(*angle, uinode.size);
let range_start = extracted_color_stops.0.len();
compute_color_stops(
stops,
target.scale_factor,
length,
target.physical_size.as_vec2(),
&mut sorted_stops,
&mut extracted_color_stops.0,
);
extracted_gradients.items.push(ExtractedGradient {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
stack_index: uinode.stack_index,
transform: transform.compute_matrix(),
stops_range: range_start..extracted_color_stops.0.len(),
rect: Rect {
min: Vec2::ZERO,
max: uinode.size,
},
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
main_entity: entity.into(),
node_type,
border_radius: uinode.border_radius,
border: uinode.border,
resolved_gradient: ResolvedGradient::Linear { angle: *angle },
});
}
Gradient::Radial(RadialGradient {
position: center,
shape,
stops,
}) => {
let c = center.resolve(
target.scale_factor,
uinode.size,
target.physical_size.as_vec2(),
);
let size = shape.resolve(
c,
target.scale_factor,
uinode.size,
target.physical_size.as_vec2(),
);
let length = size.x;
let range_start = extracted_color_stops.0.len();
compute_color_stops(
stops,
target.scale_factor,
length,
target.physical_size.as_vec2(),
&mut sorted_stops,
&mut extracted_color_stops.0,
);
extracted_gradients.items.push(ExtractedGradient {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
stack_index: uinode.stack_index,
transform: transform.compute_matrix(),
stops_range: range_start..extracted_color_stops.0.len(),
rect: Rect {
min: Vec2::ZERO,
max: uinode.size,
},
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
main_entity: entity.into(),
node_type,
border_radius: uinode.border_radius,
border: uinode.border,
resolved_gradient: ResolvedGradient::Radial { center: c, size },
});
}
Gradient::Conic(ConicGradient {
start,
position: center,
stops,
}) => {
let g_start = center.resolve(
target.scale_factor(),
uinode.size,
target.physical_size().as_vec2(),
);
let range_start = extracted_color_stops.0.len();
// sort the explicit stops
sorted_stops.extend(stops.iter().filter_map(|stop| {
stop.angle.map(|angle| {
(stop.color.to_linear(), angle.clamp(0., TAU), stop.hint)
})
}));
sorted_stops.sort_by_key(|(_, angle, _)| FloatOrd(*angle));
let mut sorted_stops_drain = sorted_stops.drain(..);
// fill the extracted stops buffer
extracted_color_stops.0.extend(stops.iter().map(|stop| {
if stop.angle.is_none() {
(stop.color.to_linear(), f32::NAN, stop.hint)
} else {
sorted_stops_drain.next().unwrap()
}
}));
interpolate_color_stops(
&mut extracted_color_stops.0[range_start..],
0.,
TAU,
);
extracted_gradients.items.push(ExtractedGradient {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
stack_index: uinode.stack_index,
transform: transform.compute_matrix(),
stops_range: range_start..extracted_color_stops.0.len(),
rect: Rect {
min: Vec2::ZERO,
max: uinode.size,
},
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
main_entity: entity.into(),
node_type,
border_radius: uinode.border_radius,
border: uinode.border,
resolved_gradient: ResolvedGradient::Conic {
start: *start,
center: g_start,
},
});
}
}
}
}
}
}
#[expect(
clippy::too_many_arguments,
reason = "it's a system that needs a lot of them"
)]
pub fn queue_gradient(
extracted_gradients: ResMut<ExtractedGradients>,
gradients_pipeline: Res<GradientPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<GradientPipeline>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
mut render_views: Query<(&UiCameraView, Option<&UiAntiAlias>), With<ExtractedView>>,
camera_views: Query<&ExtractedView>,
pipeline_cache: Res<PipelineCache>,
draw_functions: Res<DrawFunctions<TransparentUi>>,
) {
let draw_function = draw_functions.read().id::<DrawGradientFns>();
for (index, gradient) in extracted_gradients.items.iter().enumerate() {
let Ok((default_camera_view, ui_anti_alias)) =
render_views.get_mut(gradient.extracted_camera_entity)
else {
continue;
};
let Ok(view) = camera_views.get(default_camera_view.0) else {
continue;
};
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let pipeline = pipelines.specialize(
&pipeline_cache,
&gradients_pipeline,
UiGradientPipelineKey {
anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)),
hdr: view.hdr,
},
);
transparent_phase.add(TransparentUi {
draw_function,
pipeline,
entity: (gradient.render_entity, gradient.main_entity),
sort_key: FloatOrd(gradient.stack_index as f32 + stack_z_offsets::GRADIENT),
batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None,
index,
indexed: true,
});
}
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct UiGradientVertex {
position: [f32; 3],
uv: [f32; 2],
flags: u32,
radius: [f32; 4],
border: [f32; 4],
size: [f32; 2],
point: [f32; 2],
g_start: [f32; 2],
g_dir: [f32; 2],
start_color: [f32; 4],
start_len: f32,
end_len: f32,
end_color: [f32; 4],
hint: f32,
}
pub fn prepare_gradient(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut ui_meta: ResMut<GradientMeta>,
mut extracted_gradients: ResMut<ExtractedGradients>,
mut extracted_color_stops: ResMut<ExtractedColorStops>,
view_uniforms: Res<ViewUniforms>,
gradients_pipeline: Res<GradientPipeline>,
mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
mut previous_len: Local<usize>,
) {
if let Some(view_binding) = view_uniforms.uniforms.binding() {
let mut batches: Vec<(Entity, GradientBatch)> = Vec::with_capacity(*previous_len);
ui_meta.vertices.clear();
ui_meta.indices.clear();
ui_meta.view_bind_group = Some(render_device.create_bind_group(
"gradient_view_bind_group",
&gradients_pipeline.view_layout,
&BindGroupEntries::single(view_binding),
));
// Buffer indexes
let mut vertices_index = 0;
let mut indices_index = 0;
for ui_phase in phases.values_mut() {
for item_index in 0..ui_phase.items.len() {
let item = &mut ui_phase.items[item_index];
if let Some(gradient) = extracted_gradients
.items
.get(item.index)
.filter(|n| item.entity() == n.render_entity)
{
*item.batch_range_mut() = item_index as u32..item_index as u32 + 1;
let uinode_rect = gradient.rect;
let rect_size = uinode_rect.size().extend(1.0);
// Specify the corners of the node
let positions = QUAD_VERTEX_POSITIONS
.map(|pos| (gradient.transform * (pos * rect_size).extend(1.)).xyz());
let corner_points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy());
// Calculate the effect of clipping
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
let positions_diff = if let Some(clip) = gradient.clip {
[
Vec2::new(
f32::max(clip.min.x - positions[0].x, 0.),
f32::max(clip.min.y - positions[0].y, 0.),
),
Vec2::new(
f32::min(clip.max.x - positions[1].x, 0.),
f32::max(clip.min.y - positions[1].y, 0.),
),
Vec2::new(
f32::min(clip.max.x - positions[2].x, 0.),
f32::min(clip.max.y - positions[2].y, 0.),
),
Vec2::new(
f32::max(clip.min.x - positions[3].x, 0.),
f32::min(clip.max.y - positions[3].y, 0.),
),
]
} else {
[Vec2::ZERO; 4]
};
let positions_clipped = [
positions[0] + positions_diff[0].extend(0.),
positions[1] + positions_diff[1].extend(0.),
positions[2] + positions_diff[2].extend(0.),
positions[3] + positions_diff[3].extend(0.),
];
let points = [
corner_points[0] + positions_diff[0],
corner_points[1] + positions_diff[1],
corner_points[2] + positions_diff[2],
corner_points[3] + positions_diff[3],
];
let transformed_rect_size = gradient.transform.transform_vector3(rect_size);
// Don't try to cull nodes that have a rotation
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
// In those two cases, the culling check can proceed normally as corners will be on
// horizontal / vertical lines
// For all other angles, bypass the culling check
// This does not properly handles all rotations on all axis
if gradient.transform.x_axis[1] == 0.0 {
// Cull nodes that are completely clipped
if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
|| positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
{
continue;
}
}
let uvs = { [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y] };
let mut flags = if let NodeType::Border(borders) = gradient.node_type {
borders
} else {
0
};
let (g_start, g_dir, g_flags) = match gradient.resolved_gradient {
ResolvedGradient::Linear { angle } => {
let corner_index = (angle - FRAC_PI_2).rem_euclid(TAU) / FRAC_PI_2;
(
corner_points[corner_index as usize].into(),
// CSS angles increase in a clockwise direction
[sin(angle), -cos(angle)],
0,
)
}
ResolvedGradient::Conic { center, start } => {
(center.into(), [start, 0.], shader_flags::CONIC)
}
ResolvedGradient::Radial { center, size } => (
center.into(),
Vec2::splat(if size.y != 0. { size.x / size.y } else { 1. }).into(),
shader_flags::RADIAL,
),
};
flags |= g_flags;
let range = gradient.stops_range.start..gradient.stops_range.end - 1;
let mut segment_count = 0;
for stop_index in range {
let mut start_stop = extracted_color_stops.0[stop_index];
let end_stop = extracted_color_stops.0[stop_index + 1];
if start_stop.1 == end_stop.1 {
if stop_index == gradient.stops_range.end - 2 {
if 0 < segment_count {
start_stop.0 = LinearRgba::NONE;
}
} else {
continue;
}
}
let start_color = start_stop.0.to_f32_array();
let end_color = end_stop.0.to_f32_array();
let mut stop_flags = flags;
if 0. < start_stop.1
&& (stop_index == gradient.stops_range.start || segment_count == 0)
{
stop_flags |= shader_flags::FILL_START;
}
if stop_index == gradient.stops_range.end - 2 {
stop_flags |= shader_flags::FILL_END;
}
for i in 0..4 {
ui_meta.vertices.push(UiGradientVertex {
position: positions_clipped[i].into(),
uv: uvs[i].into(),
flags: stop_flags | shader_flags::CORNERS[i],
radius: [
gradient.border_radius.top_left,
gradient.border_radius.top_right,
gradient.border_radius.bottom_right,
gradient.border_radius.bottom_left,
],
border: [
gradient.border.left,
gradient.border.top,
gradient.border.right,
gradient.border.bottom,
],
size: rect_size.xy().into(),
g_start,
g_dir,
point: points[i].into(),
start_color,
start_len: start_stop.1,
end_len: end_stop.1,
end_color,
hint: start_stop.2,
});
}
for &i in &QUAD_INDICES {
ui_meta.indices.push(indices_index + i as u32);
}
indices_index += 4;
segment_count += 1;
}
if 0 < segment_count {
let vertices_count = 6 * segment_count;
batches.push((
item.entity(),
GradientBatch {
range: vertices_index..(vertices_index + vertices_count),
},
));
vertices_index += vertices_count;
}
}
}
}
ui_meta.vertices.write_buffer(&render_device, &render_queue);
ui_meta.indices.write_buffer(&render_device, &render_queue);
*previous_len = batches.len();
commands.try_insert_batch(batches);
}
extracted_gradients.items.clear();
extracted_color_stops.0.clear();
}
pub type DrawGradientFns = (SetItemPipeline, SetGradientViewBindGroup<0>, DrawGradient);
pub struct SetGradientViewBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetGradientViewBindGroup<I> {
type Param = SRes<GradientMeta>;
type ViewQuery = Read<ViewUniformOffset>;
type ItemQuery = ();
fn render<'w>(
_item: &P,
view_uniform: &'w ViewUniformOffset,
_entity: Option<()>,
ui_meta: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(view_bind_group) = ui_meta.into_inner().view_bind_group.as_ref() else {
return RenderCommandResult::Failure("view_bind_group not available");
};
pass.set_bind_group(I, view_bind_group, &[view_uniform.offset]);
RenderCommandResult::Success
}
}
pub struct DrawGradient;
impl<P: PhaseItem> RenderCommand<P> for DrawGradient {
type Param = SRes<GradientMeta>;
type ViewQuery = ();
type ItemQuery = Read<GradientBatch>;
#[inline]
fn render<'w>(
_item: &P,
_view: (),
batch: Option<&'w GradientBatch>,
ui_meta: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(batch) = batch else {
return RenderCommandResult::Skip;
};
let ui_meta = ui_meta.into_inner();
let Some(vertices) = ui_meta.vertices.buffer() else {
return RenderCommandResult::Failure("missing vertices to draw ui");
};
let Some(indices) = ui_meta.indices.buffer() else {
return RenderCommandResult::Failure("missing indices to draw ui");
};
// Store the vertices
pass.set_vertex_buffer(0, vertices.slice(..));
// Define how to "connect" the vertices
pass.set_index_buffer(indices.slice(..), 0, IndexFormat::Uint32);
// Draw the vertices
pass.draw_indexed(batch.range.clone(), 0, 0..1);
RenderCommandResult::Success
}
}