1use core::{hash::Hash, ops::Range};
4
5use bevy_app::prelude::*;
6use bevy_asset::*;
7use bevy_camera::visibility::InheritedVisibility;
8use bevy_color::{Alpha, ColorToComponents, LinearRgba};
9use bevy_ecs::prelude::*;
10use bevy_ecs::{
11 prelude::Component,
12 system::{
13 lifetimeless::{Read, SRes},
14 *,
15 },
16};
17use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2};
18use bevy_mesh::VertexBufferLayout;
19use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
20use bevy_render::{
21 render_phase::*,
22 render_resource::{binding_types::uniform_buffer, *},
23 renderer::{RenderDevice, RenderQueue},
24 view::*,
25 Extract, ExtractSchedule, Render, RenderSystems,
26};
27use bevy_render::{GpuResourceAppExt, RenderApp, RenderStartup};
28use bevy_shader::{Shader, ShaderDefVal};
29use bevy_ui::{
30 BoxShadow, CalculatedClip, ComputedNode, ComputedStackIndex, ComputedUiRenderTargetInfo,
31 ComputedUiTargetCamera, ResolvedBorderRadius, UiGlobalTransform, Val,
32};
33use bevy_utils::default;
34use bytemuck::{Pod, Zeroable};
35
36use crate::{BoxShadowSamples, RenderUiSystems, TransparentUi, UiCameraMap};
37
38use super::{stack_z_offsets, UiCameraView, QUAD_INDICES, QUAD_VERTEX_POSITIONS};
39
40pub struct BoxShadowPlugin;
42
43impl Plugin for BoxShadowPlugin {
44 fn build(&self, app: &mut App) {
45 {
{
let mut embedded =
app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
let path =
{
let crate_name =
"bevy_ui_render::box_shadow".split(':').next().unwrap();
::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
"src".as_ref(), "src/box_shadow.rs".as_ref(),
"box_shadow.wgsl".as_ref())
};
let watched_path =
::bevy_asset::io::embedded::watched_path("src/box_shadow.rs",
"box_shadow.wgsl");
embedded.insert_asset(watched_path, &path,
b"#import bevy_render::view::View;\n#import bevy_render::globals::Globals;\n\nconst PI: f32 = 3.14159265358979323846;\nconst SAMPLES: i32 = #SHADOW_SAMPLES;\n\n@group(0) @binding(0) var<uniform> view: View;\n\nstruct BoxShadowVertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) point: vec2<f32>,\n @location(1) color: vec4<f32>,\n @location(2) @interpolate(flat) size: vec2<f32>,\n @location(3) @interpolate(flat) radius: vec4<f32>, \n @location(4) @interpolate(flat) blur: f32,\n}\n\nfn gaussian(x: f32, sigma: f32) -> f32 {\n return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * PI) * sigma);\n}\n\n// Approximates the Gauss error function: https://en.wikipedia.org/wiki/Error_function\nfn erf(p: vec2<f32>) -> vec2<f32> {\n let s = sign(p);\n let a = abs(p);\n // fourth degree polynomial approximation for erf\n var result = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;\n result = result * result;\n return s - s / (result * result);\n}\n\n// returns the closest corner radius based on the signs of the components of p\nfn selectCorner(p: vec2<f32>, c: vec4<f32>) -> f32 {\n return mix(mix(c.x, c.y, step(0., p.x)), mix(c.w, c.z, step(0., p.x)), step(0., p.y));\n}\n\nfn horizontalRoundedBoxShadow(x: f32, y: f32, blur: f32, corner: f32, half_size: vec2<f32>) -> f32 {\n let d = min(half_size.y - corner - abs(y), 0.);\n let c = half_size.x - corner + sqrt(max(0., corner * corner - d * d));\n let integral = 0.5 + 0.5 * erf((x + vec2(-c, c)) * (sqrt(0.5) / blur));\n return integral.y - integral.x;\n}\n\nfn roundedBoxShadow(\n lower: vec2<f32>,\n upper: vec2<f32>,\n point: vec2<f32>,\n blur: f32,\n corners: vec4<f32>,\n) -> f32 {\n let center = (lower + upper) * 0.5;\n let half_size = (upper - lower) * 0.5;\n let p = point - center;\n let low = p.y - half_size.y;\n let high = p.y + half_size.y;\n let start = clamp(-3. * blur, low, high);\n let end = clamp(3. * blur, low, high);\n let step = (end - start) / f32(SAMPLES);\n var y = start + step * 0.5;\n var value: f32 = 0.0;\n for (var i = 0; i < SAMPLES; i++) {\n let corner = selectCorner(p, corners);\n value += horizontalRoundedBoxShadow(p.x, p.y - y, blur, corner, half_size) * gaussian(y, blur) * step;\n y += step;\n }\n return value;\n}\n\n@vertex\nfn vertex(\n @location(0) vertex_position: vec3<f32>,\n @location(1) uv: vec2<f32>,\n @location(2) vertex_color: vec4<f32>,\n @location(3) size: vec2<f32>,\n @location(4) radius: vec4<f32>,\n @location(5) blur: f32,\n @location(6) bounds: vec2<f32>,\n) -> BoxShadowVertexOutput {\n var out: BoxShadowVertexOutput;\n out.position = view.clip_from_world * vec4(vertex_position, 1.0);\n out.point = (uv.xy - 0.5) * bounds;\n out.color = vertex_color;\n out.size = size;\n out.radius = radius;\n out.blur = blur;\n return out;\n}\n\n@fragment\nfn fragment(\n in: BoxShadowVertexOutput,\n) -> @location(0) vec4<f32> {\n let g = in.color.a * roundedBoxShadow(-0.5 * in.size, 0.5 * in.size, in.point, max(in.blur, 0.01), in.radius);\n return vec4(in.color.rgb, g);\n}\n\n\n\n");
}
};embedded_asset!(app, "box_shadow.wgsl");
46
47 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
48 render_app
49 .add_render_command::<TransparentUi, DrawBoxShadows>()
50 .init_resource::<ExtractedBoxShadows>()
51 .init_gpu_resource::<BoxShadowMeta>()
52 .init_gpu_resource::<SpecializedRenderPipelines<BoxShadowPipeline>>()
53 .add_systems(RenderStartup, init_box_shadow_pipeline)
54 .add_systems(
55 ExtractSchedule,
56 extract_shadows.in_set(RenderUiSystems::ExtractBoxShadows),
57 )
58 .add_systems(
59 Render,
60 (
61 queue_shadows.in_set(RenderSystems::Queue),
62 prepare_shadows.in_set(RenderSystems::PrepareBindGroups),
63 ),
64 );
65 }
66 }
67}
68
69#[repr(C)]
70#[derive(#[automatically_derived]
impl ::core::marker::Copy for BoxShadowVertex { }Copy, #[automatically_derived]
impl ::core::clone::Clone for BoxShadowVertex {
#[inline]
fn clone(&self) -> BoxShadowVertex {
let _: ::core::clone::AssertParamIsClone<[f32; 3]>;
let _: ::core::clone::AssertParamIsClone<[f32; 2]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
let _: ::core::clone::AssertParamIsClone<[f32; 2]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
let _: ::core::clone::AssertParamIsClone<f32>;
let _: ::core::clone::AssertParamIsClone<[f32; 2]>;
*self
}
}Clone, unsafe impl ::bytemuck::Pod for BoxShadowVertex {}Pod, unsafe impl ::bytemuck::Zeroable for BoxShadowVertex {}Zeroable)]
71struct BoxShadowVertex {
72 position: [f32; 3],
73 uvs: [f32; 2],
74 vertex_color: [f32; 4],
75 size: [f32; 2],
76 radius: [f32; 4],
77 blur: f32,
78 bounds: [f32; 2],
79}
80
81#[derive(impl bevy_ecs::component::Component for UiShadowsBatch where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::Table;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}Component)]
82pub struct UiShadowsBatch {
83 pub range: Range<u32>,
84 pub camera: Entity,
85}
86
87#[derive(impl bevy_ecs::resource::Resource for BoxShadowMeta where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
89pub struct BoxShadowMeta {
90 vertices: RawBufferVec<BoxShadowVertex>,
91 indices: RawBufferVec<u32>,
92 view_bind_group: Option<BindGroup>,
93}
94
95impl Default for BoxShadowMeta {
96 fn default() -> Self {
97 Self {
98 vertices: RawBufferVec::new(BufferUsages::VERTEX),
99 indices: RawBufferVec::new(BufferUsages::INDEX),
100 view_bind_group: None,
101 }
102 }
103}
104
105#[derive(impl bevy_ecs::resource::Resource for BoxShadowPipeline where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
106pub struct BoxShadowPipeline {
107 pub view_layout: BindGroupLayoutDescriptor,
108 pub shader: Handle<Shader>,
109}
110
111pub fn init_box_shadow_pipeline(mut commands: Commands, asset_server: Res<AssetServer>) {
112 let view_layout = BindGroupLayoutDescriptor::new(
113 "box_shadow_view_layout",
114 &BindGroupLayoutEntries::single(
115 ShaderStages::VERTEX_FRAGMENT,
116 uniform_buffer::<ViewUniform>(true),
117 ),
118 );
119
120 commands.insert_resource(BoxShadowPipeline {
121 view_layout,
122 shader: {
let (path, asset_server) =
{
let path =
{
{
let crate_name =
"bevy_ui_render::box_shadow".split(':').next().unwrap();
::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
"src".as_ref(), "src/box_shadow.rs".as_ref(),
"box_shadow.wgsl".as_ref())
}
};
let path =
::bevy_asset::AssetPath::from_path_buf(path).with_source("embedded");
let asset_server =
::bevy_asset::io::embedded::GetAssetServer::get_asset_server(asset_server.as_ref());
(path, asset_server)
};
asset_server.load(path)
}load_embedded_asset!(asset_server.as_ref(), "box_shadow.wgsl"),
123 });
124}
125
126#[derive(#[automatically_derived]
impl ::core::clone::Clone for BoxShadowPipelineKey {
#[inline]
fn clone(&self) -> BoxShadowPipelineKey {
let _: ::core::clone::AssertParamIsClone<TextureFormat>;
let _: ::core::clone::AssertParamIsClone<u32>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for BoxShadowPipelineKey { }Copy, #[automatically_derived]
impl ::core::hash::Hash for BoxShadowPipelineKey {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
::core::hash::Hash::hash(&self.target_format, state);
::core::hash::Hash::hash(&self.samples, state)
}
}Hash, #[automatically_derived]
impl ::core::cmp::PartialEq for BoxShadowPipelineKey {
#[inline]
fn eq(&self, other: &BoxShadowPipelineKey) -> bool {
self.samples == other.samples &&
self.target_format == other.target_format
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for BoxShadowPipelineKey {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<TextureFormat>;
let _: ::core::cmp::AssertParamIsEq<u32>;
}
}Eq)]
127pub struct BoxShadowPipelineKey {
128 pub target_format: TextureFormat,
129 pub samples: u32,
131}
132
133impl SpecializedRenderPipeline for BoxShadowPipeline {
134 type Key = BoxShadowPipelineKey;
135
136 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
137 let vertex_layout = VertexBufferLayout::from_vertex_formats(
138 VertexStepMode::Vertex,
139 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[VertexFormat::Float32x3, VertexFormat::Float32x2,
VertexFormat::Float32x4, VertexFormat::Float32x2,
VertexFormat::Float32x4, VertexFormat::Float32,
VertexFormat::Float32x2]))vec![
140 VertexFormat::Float32x3,
142 VertexFormat::Float32x2,
144 VertexFormat::Float32x4,
146 VertexFormat::Float32x2,
148 VertexFormat::Float32x4,
150 VertexFormat::Float32,
152 VertexFormat::Float32x2,
154 ],
155 );
156 let shader_defs = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[ShaderDefVal::UInt("SHADOW_SAMPLES".to_string(), key.samples)]))vec![ShaderDefVal::UInt(
157 "SHADOW_SAMPLES".to_string(),
158 key.samples,
159 )];
160
161 RenderPipelineDescriptor {
162 vertex: VertexState {
163 shader: self.shader.clone(),
164 shader_defs: shader_defs.clone(),
165 buffers: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[vertex_layout]))vec![vertex_layout],
166 ..default()
167 },
168 fragment: Some(FragmentState {
169 shader: self.shader.clone(),
170 shader_defs,
171 targets: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[Some(ColorTargetState {
format: key.target_format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})]))vec![Some(ColorTargetState {
172 format: key.target_format,
173 blend: Some(BlendState::ALPHA_BLENDING),
174 write_mask: ColorWrites::ALL,
175 })],
176 ..default()
177 }),
178 layout: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[self.view_layout.clone()]))vec![self.view_layout.clone()],
179 label: Some("box_shadow_pipeline".into()),
180 ..default()
181 }
182 }
183}
184
185pub struct ExtractedBoxShadow {
187 pub stack_index: u32,
188 pub transform: Affine2,
189 pub bounds: Vec2,
190 pub clip: Option<Rect>,
191 pub extracted_camera_entity: Entity,
192 pub color: LinearRgba,
193 pub radius: ResolvedBorderRadius,
194 pub blur_radius: f32,
195 pub size: Vec2,
196 pub main_entity: MainEntity,
197 pub render_entity: Entity,
198}
199
200#[derive(impl bevy_ecs::resource::Resource for ExtractedBoxShadows where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource, #[automatically_derived]
impl ::core::default::Default for ExtractedBoxShadows {
#[inline]
fn default() -> ExtractedBoxShadows {
ExtractedBoxShadows {
box_shadows: ::core::default::Default::default(),
}
}
}Default)]
202pub struct ExtractedBoxShadows {
203 pub box_shadows: Vec<ExtractedBoxShadow>,
204}
205
206pub fn extract_shadows(
207 mut commands: Commands,
208 mut extracted_box_shadows: ResMut<ExtractedBoxShadows>,
209 box_shadow_query: Extract<
210 Query<(
211 Entity,
212 &ComputedNode,
213 &ComputedStackIndex,
214 &UiGlobalTransform,
215 &InheritedVisibility,
216 &BoxShadow,
217 Option<&CalculatedClip>,
218 &ComputedUiTargetCamera,
219 &ComputedUiRenderTargetInfo,
220 )>,
221 >,
222 camera_map: Extract<UiCameraMap>,
223) {
224 let mut mapping = camera_map.get_mapper();
225
226 for (entity, uinode, stack_index, transform, visibility, box_shadow, clip, camera, target) in
227 &box_shadow_query
228 {
229 if !visibility.get() || box_shadow.is_empty() || uinode.is_empty() {
231 continue;
232 }
233
234 let Some(extracted_camera_entity) = mapping.map(camera) else {
235 continue;
236 };
237
238 let ui_physical_viewport_size = target.physical_size().as_vec2();
239 let scale_factor = target.scale_factor();
240
241 for drop_shadow in box_shadow.iter() {
242 if drop_shadow.color.is_fully_transparent() {
243 continue;
244 }
245
246 let resolve_val = |val, base, scale_factor| match val {
247 Val::Auto => 0.,
248 Val::Px(px) => px * scale_factor,
249 Val::Percent(percent) => percent / 100. * base,
250 Val::Vw(percent) => percent / 100. * ui_physical_viewport_size.x,
251 Val::Vh(percent) => percent / 100. * ui_physical_viewport_size.y,
252 Val::VMin(percent) => percent / 100. * ui_physical_viewport_size.min_element(),
253 Val::VMax(percent) => percent / 100. * ui_physical_viewport_size.max_element(),
254 };
255
256 let spread_x = resolve_val(drop_shadow.spread_radius, uinode.size().x, scale_factor);
257 let spread_ratio = (spread_x + uinode.size().x) / uinode.size().x;
258
259 let spread = vec2(spread_x, uinode.size().y * spread_ratio - uinode.size().y);
260
261 let blur_radius = resolve_val(drop_shadow.blur_radius, uinode.size().x, scale_factor);
262 let offset = vec2(
263 resolve_val(drop_shadow.x_offset, uinode.size().x, scale_factor),
264 resolve_val(drop_shadow.y_offset, uinode.size().y, scale_factor),
265 );
266
267 let shadow_size = uinode.size() + spread;
268 if shadow_size.cmple(Vec2::ZERO).any() {
269 continue;
270 }
271
272 let radius = ResolvedBorderRadius {
273 top_left: uinode.border_radius.top_left * spread_ratio,
274 top_right: uinode.border_radius.top_right * spread_ratio,
275 bottom_left: uinode.border_radius.bottom_left * spread_ratio,
276 bottom_right: uinode.border_radius.bottom_right * spread_ratio,
277 };
278
279 extracted_box_shadows.box_shadows.push(ExtractedBoxShadow {
280 render_entity: commands.spawn(TemporaryRenderEntity).id(),
281 stack_index: stack_index.0,
282 transform: Affine2::from(transform) * Affine2::from_translation(offset),
283 color: drop_shadow.color.into(),
284 bounds: shadow_size + 6. * blur_radius,
285 clip: clip.map(|clip| clip.clip),
286 extracted_camera_entity,
287 radius,
288 blur_radius,
289 size: shadow_size,
290 main_entity: entity.into(),
291 });
292 }
293 }
294}
295
296#[expect(
297 clippy::too_many_arguments,
298 reason = "it's a system that needs a lot of them"
299)]
300pub fn queue_shadows(
301 extracted_box_shadows: ResMut<ExtractedBoxShadows>,
302 box_shadow_pipeline: Res<BoxShadowPipeline>,
303 mut pipelines: ResMut<SpecializedRenderPipelines<BoxShadowPipeline>>,
304 mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
305 mut render_views: Query<(&UiCameraView, Option<&BoxShadowSamples>), With<ExtractedView>>,
306 camera_views: Query<&ExtractedView>,
307 pipeline_cache: Res<PipelineCache>,
308 draw_functions: Res<DrawFunctions<TransparentUi>>,
309) {
310 let draw_function = draw_functions.read().id::<DrawBoxShadows>();
311 for (index, extracted_shadow) in extracted_box_shadows.box_shadows.iter().enumerate() {
312 let entity = extracted_shadow.render_entity;
313 let Ok((default_camera_view, shadow_samples)) =
314 render_views.get_mut(extracted_shadow.extracted_camera_entity)
315 else {
316 continue;
317 };
318
319 let Ok(view) = camera_views.get(default_camera_view.0) else {
320 continue;
321 };
322
323 let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
324 else {
325 continue;
326 };
327
328 let pipeline = pipelines.specialize(
329 &pipeline_cache,
330 &box_shadow_pipeline,
331 BoxShadowPipelineKey {
332 target_format: view.target_format,
333 samples: shadow_samples.copied().unwrap_or_default().0,
334 },
335 );
336
337 transparent_phase.add_transient(TransparentUi {
338 draw_function,
339 pipeline,
340 entity: (entity, extracted_shadow.main_entity),
341 sort_key: FloatOrd(extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW),
342
343 batch_range: 0..0,
344 extra_index: PhaseItemExtraIndex::None,
345 index,
346 indexed: true,
347 });
348 }
349}
350
351pub fn prepare_shadows(
352 mut commands: Commands,
353 render_device: Res<RenderDevice>,
354 render_queue: Res<RenderQueue>,
355 pipeline_cache: Res<PipelineCache>,
356 mut ui_meta: ResMut<BoxShadowMeta>,
357 mut extracted_shadows: ResMut<ExtractedBoxShadows>,
358 view_uniforms: Res<ViewUniforms>,
359 box_shadow_pipeline: Res<BoxShadowPipeline>,
360 mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
361 mut previous_len: Local<usize>,
362) {
363 if let Some(view_binding) = view_uniforms.uniforms.binding() {
364 let mut batches: Vec<(Entity, UiShadowsBatch)> = Vec::with_capacity(*previous_len);
365
366 ui_meta.vertices.clear();
367 ui_meta.indices.clear();
368 ui_meta.view_bind_group = Some(render_device.create_bind_group(
369 "box_shadow_view_bind_group",
370 &pipeline_cache.get_bind_group_layout(&box_shadow_pipeline.view_layout),
371 &BindGroupEntries::single(view_binding),
372 ));
373
374 let mut vertices_index = 0;
376 let mut indices_index = 0;
377
378 for ui_phase in phases.values_mut() {
379 for item_index in 0..ui_phase.items.len() {
380 let item = &mut ui_phase.items[item_index];
381 let Some(box_shadow) = extracted_shadows
382 .box_shadows
383 .get(item.index)
384 .filter(|n| item.entity() == n.render_entity)
385 else {
386 continue;
387 };
388 let rect_size = box_shadow.bounds;
389
390 let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
392 box_shadow
393 .transform
394 .transform_point2(pos * rect_size)
395 .extend(0.)
396 });
397
398 let positions_diff = if let Some(clip) = box_shadow.clip {
401 [
402 Vec2::new(
403 f32::max(clip.min.x - positions[0].x, 0.),
404 f32::max(clip.min.y - positions[0].y, 0.),
405 ),
406 Vec2::new(
407 f32::min(clip.max.x - positions[1].x, 0.),
408 f32::max(clip.min.y - positions[1].y, 0.),
409 ),
410 Vec2::new(
411 f32::min(clip.max.x - positions[2].x, 0.),
412 f32::min(clip.max.y - positions[2].y, 0.),
413 ),
414 Vec2::new(
415 f32::max(clip.min.x - positions[3].x, 0.),
416 f32::min(clip.max.y - positions[3].y, 0.),
417 ),
418 ]
419 } else {
420 [Vec2::ZERO; 4]
421 };
422
423 let positions_clipped = [
424 positions[0] + positions_diff[0].extend(0.),
425 positions[1] + positions_diff[1].extend(0.),
426 positions[2] + positions_diff[2].extend(0.),
427 positions[3] + positions_diff[3].extend(0.),
428 ];
429
430 let transformed_rect_size = box_shadow.transform.transform_vector2(rect_size).abs();
431
432 if box_shadow.transform.x_axis[1] == 0.0 {
439 if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
441 || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
442 {
443 continue;
444 }
445 }
446
447 let uvs = [
448 Vec2::new(positions_diff[0].x, positions_diff[0].y),
449 Vec2::new(
450 box_shadow.bounds.x + positions_diff[1].x,
451 positions_diff[1].y,
452 ),
453 Vec2::new(
454 box_shadow.bounds.x + positions_diff[2].x,
455 box_shadow.bounds.y + positions_diff[2].y,
456 ),
457 Vec2::new(
458 positions_diff[3].x,
459 box_shadow.bounds.y + positions_diff[3].y,
460 ),
461 ]
462 .map(|pos| pos / box_shadow.bounds);
463
464 for i in 0..4 {
465 ui_meta.vertices.push(BoxShadowVertex {
466 position: positions_clipped[i].into(),
467 uvs: uvs[i].into(),
468 vertex_color: box_shadow.color.to_f32_array(),
469 size: box_shadow.size.into(),
470 radius: box_shadow.radius.into(),
471 blur: box_shadow.blur_radius,
472 bounds: rect_size.into(),
473 });
474 }
475
476 for &i in &QUAD_INDICES {
477 ui_meta.indices.push(indices_index + i as u32);
478 }
479
480 batches.push((
481 item.entity(),
482 UiShadowsBatch {
483 range: vertices_index..vertices_index + 6,
484 camera: box_shadow.extracted_camera_entity,
485 },
486 ));
487
488 vertices_index += 6;
489 indices_index += 4;
490
491 *ui_phase.items[item_index].batch_range_mut() =
493 item_index as u32..item_index as u32 + 1;
494 }
495 }
496 ui_meta.vertices.write_buffer(&render_device, &render_queue);
497 ui_meta.indices.write_buffer(&render_device, &render_queue);
498 *previous_len = batches.len();
499 commands.try_insert_batch(batches);
500 }
501 extracted_shadows.box_shadows.clear();
502}
503
504pub type DrawBoxShadows = (SetItemPipeline, SetBoxShadowViewBindGroup<0>, DrawBoxShadow);
505
506pub struct SetBoxShadowViewBindGroup<const I: usize>;
507impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetBoxShadowViewBindGroup<I> {
508 type Param = SRes<BoxShadowMeta>;
509 type ViewQuery = Read<ViewUniformOffset>;
510 type ItemQuery = ();
511
512 fn render<'w>(
513 _item: &P,
514 view_uniform: &'w ViewUniformOffset,
515 _entity: Option<()>,
516 ui_meta: SystemParamItem<'w, '_, Self::Param>,
517 pass: &mut TrackedRenderPass<'w>,
518 ) -> RenderCommandResult {
519 let Some(view_bind_group) = ui_meta.into_inner().view_bind_group.as_ref() else {
520 return RenderCommandResult::Failure("view_bind_group not available");
521 };
522 pass.set_bind_group(I, view_bind_group, &[view_uniform.offset]);
523 RenderCommandResult::Success
524 }
525}
526
527pub struct DrawBoxShadow;
528impl<P: PhaseItem> RenderCommand<P> for DrawBoxShadow {
529 type Param = SRes<BoxShadowMeta>;
530 type ViewQuery = ();
531 type ItemQuery = Read<UiShadowsBatch>;
532
533 #[inline]
534 fn render<'w>(
535 _item: &P,
536 _view: (),
537 batch: Option<&'w UiShadowsBatch>,
538 ui_meta: SystemParamItem<'w, '_, Self::Param>,
539 pass: &mut TrackedRenderPass<'w>,
540 ) -> RenderCommandResult {
541 let Some(batch) = batch else {
542 return RenderCommandResult::Skip;
543 };
544 let ui_meta = ui_meta.into_inner();
545 let Some(vertices) = ui_meta.vertices.buffer() else {
546 return RenderCommandResult::Failure("missing vertices to draw ui");
547 };
548 let Some(indices) = ui_meta.indices.buffer() else {
549 return RenderCommandResult::Failure("missing indices to draw ui");
550 };
551
552 pass.set_vertex_buffer(0, vertices.slice(..));
554 pass.set_index_buffer(indices.slice(..), IndexFormat::Uint32);
556 pass.draw_indexed(batch.range.clone(), 0, 0..1);
558 RenderCommandResult::Success
559 }
560}