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