Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs
6596 views
1
use core::{hash::Hash, ops::Range};
2
3
use crate::*;
4
use bevy_asset::*;
5
use bevy_color::{ColorToComponents, LinearRgba};
6
use bevy_ecs::{
7
prelude::Component,
8
system::{
9
lifetimeless::{Read, SRes},
10
*,
11
},
12
};
13
use bevy_image::prelude::*;
14
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
15
use bevy_mesh::VertexBufferLayout;
16
use bevy_platform::collections::HashMap;
17
use bevy_render::{
18
render_asset::RenderAssets,
19
render_phase::*,
20
render_resource::{binding_types::uniform_buffer, *},
21
renderer::{RenderDevice, RenderQueue},
22
texture::GpuImage,
23
view::*,
24
Extract, ExtractSchedule, Render, RenderSystems,
25
};
26
use bevy_render::{sync_world::MainEntity, RenderStartup};
27
use bevy_shader::Shader;
28
use bevy_sprite::{SliceScaleMode, SpriteImageMode, TextureSlicer};
29
use bevy_sprite_render::SpriteAssetEvents;
30
use bevy_ui::widget;
31
use bevy_utils::default;
32
use binding_types::{sampler, texture_2d};
33
use bytemuck::{Pod, Zeroable};
34
35
pub struct UiTextureSlicerPlugin;
36
37
impl Plugin for UiTextureSlicerPlugin {
38
fn build(&self, app: &mut App) {
39
embedded_asset!(app, "ui_texture_slice.wgsl");
40
41
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
42
render_app
43
.add_render_command::<TransparentUi, DrawUiTextureSlices>()
44
.init_resource::<ExtractedUiTextureSlices>()
45
.init_resource::<UiTextureSliceMeta>()
46
.init_resource::<UiTextureSliceImageBindGroups>()
47
.init_resource::<SpecializedRenderPipelines<UiTextureSlicePipeline>>()
48
.add_systems(RenderStartup, init_ui_texture_slice_pipeline)
49
.add_systems(
50
ExtractSchedule,
51
extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice),
52
)
53
.add_systems(
54
Render,
55
(
56
queue_ui_slices.in_set(RenderSystems::Queue),
57
prepare_ui_slices.in_set(RenderSystems::PrepareBindGroups),
58
),
59
);
60
}
61
}
62
}
63
64
#[repr(C)]
65
#[derive(Copy, Clone, Pod, Zeroable)]
66
struct UiTextureSliceVertex {
67
pub position: [f32; 3],
68
pub uv: [f32; 2],
69
pub color: [f32; 4],
70
pub slices: [f32; 4],
71
pub border: [f32; 4],
72
pub repeat: [f32; 4],
73
pub atlas: [f32; 4],
74
}
75
76
#[derive(Component)]
77
pub struct UiTextureSlicerBatch {
78
pub range: Range<u32>,
79
pub image: AssetId<Image>,
80
}
81
82
#[derive(Resource)]
83
pub struct UiTextureSliceMeta {
84
vertices: RawBufferVec<UiTextureSliceVertex>,
85
indices: RawBufferVec<u32>,
86
view_bind_group: Option<BindGroup>,
87
}
88
89
impl Default for UiTextureSliceMeta {
90
fn default() -> Self {
91
Self {
92
vertices: RawBufferVec::new(BufferUsages::VERTEX),
93
indices: RawBufferVec::new(BufferUsages::INDEX),
94
view_bind_group: None,
95
}
96
}
97
}
98
99
#[derive(Resource, Default)]
100
pub struct UiTextureSliceImageBindGroups {
101
pub values: HashMap<AssetId<Image>, BindGroup>,
102
}
103
104
#[derive(Resource)]
105
pub struct UiTextureSlicePipeline {
106
pub view_layout: BindGroupLayout,
107
pub image_layout: BindGroupLayout,
108
pub shader: Handle<Shader>,
109
}
110
111
pub fn init_ui_texture_slice_pipeline(
112
mut commands: Commands,
113
render_device: Res<RenderDevice>,
114
asset_server: Res<AssetServer>,
115
) {
116
let view_layout = render_device.create_bind_group_layout(
117
"ui_texture_slice_view_layout",
118
&BindGroupLayoutEntries::single(
119
ShaderStages::VERTEX_FRAGMENT,
120
uniform_buffer::<ViewUniform>(true),
121
),
122
);
123
124
let image_layout = render_device.create_bind_group_layout(
125
"ui_texture_slice_image_layout",
126
&BindGroupLayoutEntries::sequential(
127
ShaderStages::FRAGMENT,
128
(
129
texture_2d(TextureSampleType::Float { filterable: true }),
130
sampler(SamplerBindingType::Filtering),
131
),
132
),
133
);
134
135
commands.insert_resource(UiTextureSlicePipeline {
136
view_layout,
137
image_layout,
138
shader: load_embedded_asset!(asset_server.as_ref(), "ui_texture_slice.wgsl"),
139
});
140
}
141
142
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
143
pub struct UiTextureSlicePipelineKey {
144
pub hdr: bool,
145
}
146
147
impl SpecializedRenderPipeline for UiTextureSlicePipeline {
148
type Key = UiTextureSlicePipelineKey;
149
150
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
151
let vertex_layout = VertexBufferLayout::from_vertex_formats(
152
VertexStepMode::Vertex,
153
vec![
154
// position
155
VertexFormat::Float32x3,
156
// uv
157
VertexFormat::Float32x2,
158
// color
159
VertexFormat::Float32x4,
160
// normalized texture slicing lines (left, top, right, bottom)
161
VertexFormat::Float32x4,
162
// normalized target slicing lines (left, top, right, bottom)
163
VertexFormat::Float32x4,
164
// repeat values (horizontal side, vertical side, horizontal center, vertical center)
165
VertexFormat::Float32x4,
166
// normalized texture atlas rect (left, top, right, bottom)
167
VertexFormat::Float32x4,
168
],
169
);
170
let shader_defs = Vec::new();
171
172
RenderPipelineDescriptor {
173
vertex: VertexState {
174
shader: self.shader.clone(),
175
shader_defs: shader_defs.clone(),
176
buffers: vec![vertex_layout],
177
..default()
178
},
179
fragment: Some(FragmentState {
180
shader: self.shader.clone(),
181
shader_defs,
182
targets: vec![Some(ColorTargetState {
183
format: if key.hdr {
184
ViewTarget::TEXTURE_FORMAT_HDR
185
} else {
186
TextureFormat::bevy_default()
187
},
188
blend: Some(BlendState::ALPHA_BLENDING),
189
write_mask: ColorWrites::ALL,
190
})],
191
..default()
192
}),
193
layout: vec![self.view_layout.clone(), self.image_layout.clone()],
194
label: Some("ui_texture_slice_pipeline".into()),
195
..default()
196
}
197
}
198
}
199
200
pub struct ExtractedUiTextureSlice {
201
pub stack_index: u32,
202
pub transform: Affine2,
203
pub rect: Rect,
204
pub atlas_rect: Option<Rect>,
205
pub image: AssetId<Image>,
206
pub clip: Option<Rect>,
207
pub extracted_camera_entity: Entity,
208
pub color: LinearRgba,
209
pub image_scale_mode: SpriteImageMode,
210
pub flip_x: bool,
211
pub flip_y: bool,
212
pub inverse_scale_factor: f32,
213
pub main_entity: MainEntity,
214
pub render_entity: Entity,
215
}
216
217
#[derive(Resource, Default)]
218
pub struct ExtractedUiTextureSlices {
219
pub slices: Vec<ExtractedUiTextureSlice>,
220
}
221
222
pub fn extract_ui_texture_slices(
223
mut commands: Commands,
224
mut extracted_ui_slicers: ResMut<ExtractedUiTextureSlices>,
225
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
226
slicers_query: Extract<
227
Query<(
228
Entity,
229
&ComputedNode,
230
&UiGlobalTransform,
231
&InheritedVisibility,
232
Option<&CalculatedClip>,
233
&ComputedUiTargetCamera,
234
&ImageNode,
235
)>,
236
>,
237
camera_map: Extract<UiCameraMap>,
238
) {
239
let mut camera_mapper = camera_map.get_mapper();
240
241
for (entity, uinode, transform, inherited_visibility, clip, camera, image) in &slicers_query {
242
// Skip invisible images
243
if !inherited_visibility.get()
244
|| image.color.is_fully_transparent()
245
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
246
{
247
continue;
248
}
249
250
let image_scale_mode = match image.image_mode.clone() {
251
widget::NodeImageMode::Sliced(texture_slicer) => {
252
SpriteImageMode::Sliced(texture_slicer)
253
}
254
widget::NodeImageMode::Tiled {
255
tile_x,
256
tile_y,
257
stretch_value,
258
} => SpriteImageMode::Tiled {
259
tile_x,
260
tile_y,
261
stretch_value,
262
},
263
_ => continue,
264
};
265
266
let Some(extracted_camera_entity) = camera_mapper.map(camera) else {
267
continue;
268
};
269
270
let atlas_rect = image
271
.texture_atlas
272
.as_ref()
273
.and_then(|s| s.texture_rect(&texture_atlases))
274
.map(|r| r.as_rect());
275
276
let atlas_rect = match (atlas_rect, image.rect) {
277
(None, None) => None,
278
(None, Some(image_rect)) => Some(image_rect),
279
(Some(atlas_rect), None) => Some(atlas_rect),
280
(Some(atlas_rect), Some(mut image_rect)) => {
281
image_rect.min += atlas_rect.min;
282
image_rect.max += atlas_rect.min;
283
Some(image_rect)
284
}
285
};
286
287
extracted_ui_slicers.slices.push(ExtractedUiTextureSlice {
288
render_entity: commands.spawn(TemporaryRenderEntity).id(),
289
stack_index: uinode.stack_index,
290
transform: transform.into(),
291
color: image.color.into(),
292
rect: Rect {
293
min: Vec2::ZERO,
294
max: uinode.size,
295
},
296
clip: clip.map(|clip| clip.clip),
297
image: image.image.id(),
298
extracted_camera_entity,
299
image_scale_mode,
300
atlas_rect,
301
flip_x: image.flip_x,
302
flip_y: image.flip_y,
303
inverse_scale_factor: uinode.inverse_scale_factor,
304
main_entity: entity.into(),
305
});
306
}
307
}
308
309
#[expect(
310
clippy::too_many_arguments,
311
reason = "it's a system that needs a lot of them"
312
)]
313
pub fn queue_ui_slices(
314
extracted_ui_slicers: ResMut<ExtractedUiTextureSlices>,
315
ui_slicer_pipeline: Res<UiTextureSlicePipeline>,
316
mut pipelines: ResMut<SpecializedRenderPipelines<UiTextureSlicePipeline>>,
317
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
318
mut render_views: Query<&UiCameraView, With<ExtractedView>>,
319
camera_views: Query<&ExtractedView>,
320
pipeline_cache: Res<PipelineCache>,
321
draw_functions: Res<DrawFunctions<TransparentUi>>,
322
) {
323
let draw_function = draw_functions.read().id::<DrawUiTextureSlices>();
324
for (index, extracted_slicer) in extracted_ui_slicers.slices.iter().enumerate() {
325
let Ok(default_camera_view) =
326
render_views.get_mut(extracted_slicer.extracted_camera_entity)
327
else {
328
continue;
329
};
330
331
let Ok(view) = camera_views.get(default_camera_view.0) else {
332
continue;
333
};
334
335
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
336
else {
337
continue;
338
};
339
340
let pipeline = pipelines.specialize(
341
&pipeline_cache,
342
&ui_slicer_pipeline,
343
UiTextureSlicePipelineKey { hdr: view.hdr },
344
);
345
346
transparent_phase.add(TransparentUi {
347
draw_function,
348
pipeline,
349
entity: (extracted_slicer.render_entity, extracted_slicer.main_entity),
350
sort_key: FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::IMAGE),
351
batch_range: 0..0,
352
extra_index: PhaseItemExtraIndex::None,
353
index,
354
indexed: true,
355
});
356
}
357
}
358
359
pub fn prepare_ui_slices(
360
mut commands: Commands,
361
render_device: Res<RenderDevice>,
362
render_queue: Res<RenderQueue>,
363
mut ui_meta: ResMut<UiTextureSliceMeta>,
364
mut extracted_slices: ResMut<ExtractedUiTextureSlices>,
365
view_uniforms: Res<ViewUniforms>,
366
texture_slicer_pipeline: Res<UiTextureSlicePipeline>,
367
mut image_bind_groups: ResMut<UiTextureSliceImageBindGroups>,
368
gpu_images: Res<RenderAssets<GpuImage>>,
369
mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
370
events: Res<SpriteAssetEvents>,
371
mut previous_len: Local<usize>,
372
) {
373
// If an image has changed, the GpuImage has (probably) changed
374
for event in &events.images {
375
match event {
376
AssetEvent::Added { .. } |
377
AssetEvent::Unused { .. } |
378
// Images don't have dependencies
379
AssetEvent::LoadedWithDependencies { .. } => {}
380
AssetEvent::Modified { id } | AssetEvent::Removed { id } => {
381
image_bind_groups.values.remove(id);
382
}
383
};
384
}
385
386
if let Some(view_binding) = view_uniforms.uniforms.binding() {
387
let mut batches: Vec<(Entity, UiTextureSlicerBatch)> = Vec::with_capacity(*previous_len);
388
389
ui_meta.vertices.clear();
390
ui_meta.indices.clear();
391
ui_meta.view_bind_group = Some(render_device.create_bind_group(
392
"ui_texture_slice_view_bind_group",
393
&texture_slicer_pipeline.view_layout,
394
&BindGroupEntries::single(view_binding),
395
));
396
397
// Buffer indexes
398
let mut vertices_index = 0;
399
let mut indices_index = 0;
400
401
for ui_phase in phases.values_mut() {
402
let mut batch_item_index = 0;
403
let mut batch_image_handle = AssetId::invalid();
404
let mut batch_image_size = Vec2::ZERO;
405
406
for item_index in 0..ui_phase.items.len() {
407
let item = &mut ui_phase.items[item_index];
408
if let Some(texture_slices) = extracted_slices
409
.slices
410
.get(item.index)
411
.filter(|n| item.entity() == n.render_entity)
412
{
413
let mut existing_batch = batches.last_mut();
414
415
if batch_image_handle == AssetId::invalid()
416
|| existing_batch.is_none()
417
|| (batch_image_handle != AssetId::default()
418
&& texture_slices.image != AssetId::default()
419
&& batch_image_handle != texture_slices.image)
420
{
421
if let Some(gpu_image) = gpu_images.get(texture_slices.image) {
422
batch_item_index = item_index;
423
batch_image_handle = texture_slices.image;
424
batch_image_size = gpu_image.size_2d().as_vec2();
425
426
let new_batch = UiTextureSlicerBatch {
427
range: vertices_index..vertices_index,
428
image: texture_slices.image,
429
};
430
431
batches.push((item.entity(), new_batch));
432
433
image_bind_groups
434
.values
435
.entry(batch_image_handle)
436
.or_insert_with(|| {
437
render_device.create_bind_group(
438
"ui_texture_slice_image_layout",
439
&texture_slicer_pipeline.image_layout,
440
&BindGroupEntries::sequential((
441
&gpu_image.texture_view,
442
&gpu_image.sampler,
443
)),
444
)
445
});
446
447
existing_batch = batches.last_mut();
448
} else {
449
continue;
450
}
451
} else if let Some(ref mut existing_batch) = existing_batch
452
&& batch_image_handle == AssetId::default()
453
&& texture_slices.image != AssetId::default()
454
{
455
if let Some(gpu_image) = gpu_images.get(texture_slices.image) {
456
batch_image_handle = texture_slices.image;
457
batch_image_size = gpu_image.size_2d().as_vec2();
458
existing_batch.1.image = texture_slices.image;
459
460
image_bind_groups
461
.values
462
.entry(batch_image_handle)
463
.or_insert_with(|| {
464
render_device.create_bind_group(
465
"ui_texture_slice_image_layout",
466
&texture_slicer_pipeline.image_layout,
467
&BindGroupEntries::sequential((
468
&gpu_image.texture_view,
469
&gpu_image.sampler,
470
)),
471
)
472
});
473
} else {
474
continue;
475
}
476
}
477
478
let uinode_rect = texture_slices.rect;
479
480
let rect_size = uinode_rect.size();
481
482
// Specify the corners of the node
483
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
484
(texture_slices.transform.transform_point2(pos * rect_size)).extend(0.)
485
});
486
487
// Calculate the effect of clipping
488
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
489
let positions_diff = if let Some(clip) = texture_slices.clip {
490
[
491
Vec2::new(
492
f32::max(clip.min.x - positions[0].x, 0.),
493
f32::max(clip.min.y - positions[0].y, 0.),
494
),
495
Vec2::new(
496
f32::min(clip.max.x - positions[1].x, 0.),
497
f32::max(clip.min.y - positions[1].y, 0.),
498
),
499
Vec2::new(
500
f32::min(clip.max.x - positions[2].x, 0.),
501
f32::min(clip.max.y - positions[2].y, 0.),
502
),
503
Vec2::new(
504
f32::max(clip.min.x - positions[3].x, 0.),
505
f32::min(clip.max.y - positions[3].y, 0.),
506
),
507
]
508
} else {
509
[Vec2::ZERO; 4]
510
};
511
512
let positions_clipped = [
513
positions[0] + positions_diff[0].extend(0.),
514
positions[1] + positions_diff[1].extend(0.),
515
positions[2] + positions_diff[2].extend(0.),
516
positions[3] + positions_diff[3].extend(0.),
517
];
518
519
let transformed_rect_size =
520
texture_slices.transform.transform_vector2(rect_size);
521
522
// Don't try to cull nodes that have a rotation
523
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
524
// In those two cases, the culling check can proceed normally as corners will be on
525
// horizontal / vertical lines
526
// For all other angles, bypass the culling check
527
// This does not properly handles all rotations on all axis
528
if texture_slices.transform.x_axis[1] == 0.0 {
529
// Cull nodes that are completely clipped
530
if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
531
|| positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
532
{
533
continue;
534
}
535
}
536
let flags = if texture_slices.image != AssetId::default() {
537
shader_flags::TEXTURED
538
} else {
539
shader_flags::UNTEXTURED
540
};
541
542
let uvs = if flags == shader_flags::UNTEXTURED {
543
[Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
544
} else {
545
let atlas_extent = uinode_rect.max;
546
[
547
Vec2::new(
548
uinode_rect.min.x + positions_diff[0].x,
549
uinode_rect.min.y + positions_diff[0].y,
550
),
551
Vec2::new(
552
uinode_rect.max.x + positions_diff[1].x,
553
uinode_rect.min.y + positions_diff[1].y,
554
),
555
Vec2::new(
556
uinode_rect.max.x + positions_diff[2].x,
557
uinode_rect.max.y + positions_diff[2].y,
558
),
559
Vec2::new(
560
uinode_rect.min.x + positions_diff[3].x,
561
uinode_rect.max.y + positions_diff[3].y,
562
),
563
]
564
.map(|pos| pos / atlas_extent)
565
};
566
567
let color = texture_slices.color.to_f32_array();
568
569
let (image_size, mut atlas) = if let Some(atlas) = texture_slices.atlas_rect {
570
(
571
atlas.size(),
572
[
573
atlas.min.x / batch_image_size.x,
574
atlas.min.y / batch_image_size.y,
575
atlas.max.x / batch_image_size.x,
576
atlas.max.y / batch_image_size.y,
577
],
578
)
579
} else {
580
(batch_image_size, [0., 0., 1., 1.])
581
};
582
583
if texture_slices.flip_x {
584
atlas.swap(0, 2);
585
}
586
587
if texture_slices.flip_y {
588
atlas.swap(1, 3);
589
}
590
591
let [slices, border, repeat] = compute_texture_slices(
592
image_size,
593
uinode_rect.size() * texture_slices.inverse_scale_factor,
594
&texture_slices.image_scale_mode,
595
);
596
597
for i in 0..4 {
598
ui_meta.vertices.push(UiTextureSliceVertex {
599
position: positions_clipped[i].into(),
600
uv: uvs[i].into(),
601
color,
602
slices,
603
border,
604
repeat,
605
atlas,
606
});
607
}
608
609
for &i in &QUAD_INDICES {
610
ui_meta.indices.push(indices_index + i as u32);
611
}
612
613
vertices_index += 6;
614
indices_index += 4;
615
616
existing_batch.unwrap().1.range.end = vertices_index;
617
ui_phase.items[batch_item_index].batch_range_mut().end += 1;
618
} else {
619
batch_image_handle = AssetId::invalid();
620
}
621
}
622
}
623
ui_meta.vertices.write_buffer(&render_device, &render_queue);
624
ui_meta.indices.write_buffer(&render_device, &render_queue);
625
*previous_len = batches.len();
626
commands.try_insert_batch(batches);
627
}
628
extracted_slices.slices.clear();
629
}
630
631
pub type DrawUiTextureSlices = (
632
SetItemPipeline,
633
SetSlicerViewBindGroup<0>,
634
SetSlicerTextureBindGroup<1>,
635
DrawSlicer,
636
);
637
638
pub struct SetSlicerViewBindGroup<const I: usize>;
639
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSlicerViewBindGroup<I> {
640
type Param = SRes<UiTextureSliceMeta>;
641
type ViewQuery = Read<ViewUniformOffset>;
642
type ItemQuery = ();
643
644
fn render<'w>(
645
_item: &P,
646
view_uniform: &'w ViewUniformOffset,
647
_entity: Option<()>,
648
ui_meta: SystemParamItem<'w, '_, Self::Param>,
649
pass: &mut TrackedRenderPass<'w>,
650
) -> RenderCommandResult {
651
let Some(view_bind_group) = ui_meta.into_inner().view_bind_group.as_ref() else {
652
return RenderCommandResult::Failure("view_bind_group not available");
653
};
654
pass.set_bind_group(I, view_bind_group, &[view_uniform.offset]);
655
RenderCommandResult::Success
656
}
657
}
658
pub struct SetSlicerTextureBindGroup<const I: usize>;
659
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSlicerTextureBindGroup<I> {
660
type Param = SRes<UiTextureSliceImageBindGroups>;
661
type ViewQuery = ();
662
type ItemQuery = Read<UiTextureSlicerBatch>;
663
664
#[inline]
665
fn render<'w>(
666
_item: &P,
667
_view: (),
668
batch: Option<&'w UiTextureSlicerBatch>,
669
image_bind_groups: SystemParamItem<'w, '_, Self::Param>,
670
pass: &mut TrackedRenderPass<'w>,
671
) -> RenderCommandResult {
672
let image_bind_groups = image_bind_groups.into_inner();
673
let Some(batch) = batch else {
674
return RenderCommandResult::Skip;
675
};
676
677
pass.set_bind_group(I, image_bind_groups.values.get(&batch.image).unwrap(), &[]);
678
RenderCommandResult::Success
679
}
680
}
681
pub struct DrawSlicer;
682
impl<P: PhaseItem> RenderCommand<P> for DrawSlicer {
683
type Param = SRes<UiTextureSliceMeta>;
684
type ViewQuery = ();
685
type ItemQuery = Read<UiTextureSlicerBatch>;
686
687
#[inline]
688
fn render<'w>(
689
_item: &P,
690
_view: (),
691
batch: Option<&'w UiTextureSlicerBatch>,
692
ui_meta: SystemParamItem<'w, '_, Self::Param>,
693
pass: &mut TrackedRenderPass<'w>,
694
) -> RenderCommandResult {
695
let Some(batch) = batch else {
696
return RenderCommandResult::Skip;
697
};
698
let ui_meta = ui_meta.into_inner();
699
let Some(vertices) = ui_meta.vertices.buffer() else {
700
return RenderCommandResult::Failure("missing vertices to draw ui");
701
};
702
let Some(indices) = ui_meta.indices.buffer() else {
703
return RenderCommandResult::Failure("missing indices to draw ui");
704
};
705
706
// Store the vertices
707
pass.set_vertex_buffer(0, vertices.slice(..));
708
// Define how to "connect" the vertices
709
pass.set_index_buffer(indices.slice(..), 0, IndexFormat::Uint32);
710
// Draw the vertices
711
pass.draw_indexed(batch.range.clone(), 0, 0..1);
712
RenderCommandResult::Success
713
}
714
}
715
716
fn compute_texture_slices(
717
image_size: Vec2,
718
target_size: Vec2,
719
image_scale_mode: &SpriteImageMode,
720
) -> [[f32; 4]; 3] {
721
match image_scale_mode {
722
SpriteImageMode::Sliced(TextureSlicer {
723
border: border_rect,
724
center_scale_mode,
725
sides_scale_mode,
726
max_corner_scale,
727
}) => {
728
let min_coeff = (target_size / image_size)
729
.min_element()
730
.min(*max_corner_scale);
731
732
// calculate the normalized extents of the nine-patched image slices
733
let slices = [
734
border_rect.left / image_size.x,
735
border_rect.top / image_size.y,
736
1. - border_rect.right / image_size.x,
737
1. - border_rect.bottom / image_size.y,
738
];
739
740
// calculate the normalized extents of the target slices
741
let border = [
742
(border_rect.left / target_size.x) * min_coeff,
743
(border_rect.top / target_size.y) * min_coeff,
744
1. - (border_rect.right / target_size.x) * min_coeff,
745
1. - (border_rect.bottom / target_size.y) * min_coeff,
746
];
747
748
let image_side_width = image_size.x * (slices[2] - slices[0]);
749
let image_side_height = image_size.y * (slices[3] - slices[1]);
750
let target_side_width = target_size.x * (border[2] - border[0]);
751
let target_side_height = target_size.y * (border[3] - border[1]);
752
753
// compute the number of times to repeat the side and center slices when tiling along each axis
754
// if the returned value is `1.` the slice will be stretched to fill the axis.
755
let repeat_side_x =
756
compute_tiled_subaxis(image_side_width, target_side_width, sides_scale_mode);
757
let repeat_side_y =
758
compute_tiled_subaxis(image_side_height, target_side_height, sides_scale_mode);
759
let repeat_center_x =
760
compute_tiled_subaxis(image_side_width, target_side_width, center_scale_mode);
761
let repeat_center_y =
762
compute_tiled_subaxis(image_side_height, target_side_height, center_scale_mode);
763
764
[
765
slices,
766
border,
767
[
768
repeat_side_x,
769
repeat_side_y,
770
repeat_center_x,
771
repeat_center_y,
772
],
773
]
774
}
775
SpriteImageMode::Tiled {
776
tile_x,
777
tile_y,
778
stretch_value,
779
} => {
780
let rx = compute_tiled_axis(*tile_x, image_size.x, target_size.x, *stretch_value);
781
let ry = compute_tiled_axis(*tile_y, image_size.y, target_size.y, *stretch_value);
782
[[0., 0., 1., 1.], [0., 0., 1., 1.], [1., 1., rx, ry]]
783
}
784
SpriteImageMode::Auto => {
785
unreachable!("Slices can not be computed for SpriteImageMode::Stretch")
786
}
787
SpriteImageMode::Scale(_) => {
788
unreachable!("Slices can not be computed for SpriteImageMode::Scale")
789
}
790
}
791
}
792
793
fn compute_tiled_axis(tile: bool, image_extent: f32, target_extent: f32, stretch: f32) -> f32 {
794
if tile {
795
let s = image_extent * stretch;
796
target_extent / s
797
} else {
798
1.
799
}
800
}
801
802
fn compute_tiled_subaxis(image_extent: f32, target_extent: f32, mode: &SliceScaleMode) -> f32 {
803
match mode {
804
SliceScaleMode::Stretch => 1.,
805
SliceScaleMode::Tile { stretch_value } => {
806
let s = image_extent * *stretch_value;
807
target_extent / s
808
}
809
}
810
}
811
812