1use core::{hash::Hash, ops::Range};
2
3use crate::clipping::clip_polygon;
4use crate::*;
5use bevy_asset::*;
6use bevy_color::{ColorToComponents, LinearRgba};
7use bevy_ecs::{
8 prelude::Component,
9 system::{
10 lifetimeless::{Read, SRes},
11 *,
12 },
13};
14use bevy_image::prelude::*;
15use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
16use bevy_mesh::VertexBufferLayout;
17use bevy_platform::collections::HashMap;
18use bevy_render::{
19 render_asset::RenderAssets,
20 render_phase::*,
21 render_resource::{binding_types::uniform_buffer, *},
22 renderer::{RenderDevice, RenderQueue},
23 texture::GpuImage,
24 view::*,
25 Extract, ExtractSchedule, Render, RenderSystems,
26};
27use bevy_render::{sync_world::MainEntity, GpuResourceAppExt, RenderStartup};
28use bevy_shader::Shader;
29use bevy_sprite::{SliceScaleMode, SpriteImageMode, TextureSlicer};
30use bevy_sprite_render::SpriteAssetEvents;
31use bevy_ui::widget::NodeImageMode;
32use bevy_ui::{ComputedStackIndex, VisualBox};
33use bevy_utils::default;
34use binding_types::{sampler, texture_2d};
35use bytemuck::{Pod, Zeroable};
36
37pub struct UiTextureSlicerPlugin;
38
39impl Plugin for UiTextureSlicerPlugin {
40 fn build(&self, app: &mut App) {
41 {
{
let mut embedded =
app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
let path =
{
let crate_name =
"bevy_ui_render::ui_texture_slice_pipeline".split(':').next().unwrap();
::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
"src".as_ref(), "src/ui_texture_slice_pipeline.rs".as_ref(),
"ui_texture_slice.wesl".as_ref())
};
let watched_path =
::bevy_asset::io::embedded::watched_path("src/ui_texture_slice_pipeline.rs",
"ui_texture_slice.wesl");
embedded.insert_asset(watched_path, &path,
b"import bevy_render::view::View;\nimport bevy_render::globals::Globals;\n\n@group(0) @binding(0)\nvar<uniform> view: View;\n@group(0) @binding(1)\nvar<uniform> globals: Globals;\n\n@group(1) @binding(0) var sprite_texture: texture_2d<f32>;\n@group(1) @binding(1) var sprite_sampler: sampler;\n\nstruct UiVertexOutput {\n @location(0) uv: vec2<f32>,\n @location(1) color: vec4<f32>,\n\n /// Defines the dividing line that are used to split the texture atlas rect into corner, side and center slices\n /// The distances are normalized and from the top left corner of the texture atlas rect\n /// x = distance of the left vertical dividing line\n /// y = distance of the top horizontal dividing line\n /// z = distance of the right vertical dividing line\n /// w = distance of the bottom horizontal dividing line\n @location(2) @interpolate(flat) texture_slices: vec4<f32>,\n\n /// Defines the dividing line that are used to split the render target into corner, side and center slices\n /// The distances are normalized and from the top left corner of the render target\n /// x = distance of left vertical dividing line\n /// y = distance of top horizontal dividing line\n /// z = distance of right vertical dividing line\n /// w = distance of bottom horizontal dividing line\n @location(3) @interpolate(flat) target_slices: vec4<f32>,\n\n /// The number of times the side or center texture slices should be repeated when mapping them to the border slices\n /// x = number of times to repeat along the horizontal axis for the side textures\n /// y = number of times to repeat along the vertical axis for the side textures\n /// z = number of times to repeat along the horizontal axis for the center texture\n /// w = number of times to repeat along the vertical axis for the center texture\n @location(4) @interpolate(flat) repeat: vec4<f32>,\n\n /// normalized texture atlas rect coordinates\n /// x, y = top, left corner of the atlas rect\n /// z, w = bottom, right corner of the atlas rect\n @location(5) @interpolate(flat) atlas_rect: vec4<f32>,\n @builtin(position) position: vec4<f32>,\n}\n\n@vertex\nfn vertex(\n @location(0) vertex_position: vec3<f32>,\n @location(1) vertex_uv: vec2<f32>,\n @location(2) vertex_color: vec4<f32>,\n @location(3) texture_slices: vec4<f32>,\n @location(4) target_slices: vec4<f32>,\n @location(5) repeat: vec4<f32>,\n @location(6) atlas_rect: vec4<f32>,\n) -> UiVertexOutput {\n var out: UiVertexOutput;\n out.uv = vertex_uv;\n out.color = vertex_color;\n out.position = view.clip_from_world * vec4<f32>(vertex_position, 1.0);\n out.texture_slices = texture_slices;\n out.target_slices = target_slices;\n out.repeat = repeat;\n out.atlas_rect = atlas_rect;\n return out;\n}\n\n/// maps a point along the axis of the render target to slice coordinates\n/// Arguments:\n/// - p: normalized distance along the axis\n/// - il: target min dividing point\n/// - ih: target max dividing point\n/// - tl: slice min dividing point\n/// - th: slice max dividing point\n/// - r: number of times to repeat the slice for sides and the center\nfn map_axis_with_repeat(\n p: f32,\n il: f32,\n ih: f32,\n tl: f32,\n th: f32,\n r: f32,\n) -> f32 {\n if p < il {\n // inside one of the two left (horizontal axis) or top (vertical axis) corners\n return (p / il) * tl;\n } else if ih < p {\n // inside one of the two (horizontal axis) or top (vertical axis) corners\n return th + ((p - ih) / (1 - ih)) * (1 - th);\n } else {\n // not inside a corner, repeat the texture\n return tl + fract((r * (p - il)) / (ih - il)) * (th - tl);\n }\n}\n\nfn map_uvs_to_slice(\n uv: vec2<f32>,\n target_slices: vec4<f32>,\n texture_slices: vec4<f32>,\n repeat: vec4<f32>,\n) -> vec2<f32> {\n var r: vec2<f32>;\n if target_slices.x <= uv.x && uv.x <= target_slices.z && target_slices.y <= uv.y && uv.y <= target_slices.w {\n // use the center repeat values if the uv coords are inside the center slice of the target\n r = repeat.zw;\n } else {\n // use the side repeat values if the uv coords are outside the center slice\n r = repeat.xy;\n }\n\n // map horizontal axis\n let x = map_axis_with_repeat(uv.x, target_slices.x, target_slices.z, texture_slices.x, texture_slices.z, r.x);\n\n // map vertical axis\n let y = map_axis_with_repeat(uv.y, target_slices.y, target_slices.w, texture_slices.y, texture_slices.w, r.y);\n\n return vec2(x, y);\n}\n\n@fragment\nfn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {\n // map the target uvs to slice coords\n let uv = map_uvs_to_slice(in.uv, in.target_slices, in.texture_slices, in.repeat);\n\n // map the slice coords to texture coords\n let atlas_uv = in.atlas_rect.xy + uv * (in.atlas_rect.zw - in.atlas_rect.xy);\n\n return in.color * textureSample(sprite_texture, sprite_sampler, atlas_uv);\n}\n");
}
};embedded_asset!(app, "ui_texture_slice.wesl");
42
43 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
44 render_app
45 .add_render_command::<TransparentUi, DrawUiTextureSlices>()
46 .init_resource::<ExtractedUiTextureSlices>()
47 .init_gpu_resource::<UiTextureSliceMeta>()
48 .init_gpu_resource::<UiTextureSliceImageBindGroups>()
49 .init_gpu_resource::<SpecializedRenderPipelines<UiTextureSlicePipeline>>()
50 .add_systems(RenderStartup, init_ui_texture_slice_pipeline)
51 .add_systems(
52 ExtractSchedule,
53 extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice),
54 )
55 .add_systems(
56 Render,
57 (
58 queue_ui_slices.in_set(RenderSystems::Queue),
59 prepare_ui_slices.in_set(RenderSystems::PrepareBindGroups),
60 ),
61 );
62 }
63 }
64}
65
66#[repr(C)]
67#[derive(#[automatically_derived]
impl ::core::marker::Copy for UiTextureSliceVertex { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for UiTextureSliceVertex { }
#[automatically_derived]
impl ::core::clone::Clone for UiTextureSliceVertex {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<[f32; 3]>;
let _: ::core::clone::AssertParamIsClone<[f32; 2]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
let _: ::core::clone::AssertParamIsClone<[f32; 4]>;
*self
}
}Clone, const _: () =
{
if !(::core::mem::size_of::<UiTextureSliceVertex>() ==
(::core::mem::size_of::<[f32; 3]>() +
::core::mem::size_of::<[f32; 2]>() +
::core::mem::size_of::<[f32; 4]>() +
::core::mem::size_of::<[f32; 4]>() +
::core::mem::size_of::<[f32; 4]>() +
::core::mem::size_of::<[f32; 4]>() +
::core::mem::size_of::<[f32; 4]>())) {
{
::std::rt::begin_panic("derive(Pod) was applied to a type with padding");
}
};
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 3]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 2]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Pod>() {}
assert_impl::<[f32; 4]>();
}
};
unsafe impl ::bytemuck::Pod for UiTextureSliceVertex {}Pod, const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 3]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 2]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 4]>();
}
};
const _: fn() =
||
{
#[allow(clippy :: missing_const_for_fn)]
#[doc(hidden)]
fn check() {
fn assert_impl<T: ::bytemuck::Zeroable>() {}
assert_impl::<[f32; 4]>();
}
};
unsafe impl ::bytemuck::Zeroable for UiTextureSliceVertex {}Zeroable)]
68struct UiTextureSliceVertex {
69 pub position: [f32; 3],
70 pub uv: [f32; 2],
71 pub color: [f32; 4],
72 pub slices: [f32; 4],
73 pub border: [f32; 4],
74 pub repeat: [f32; 4],
75 pub atlas: [f32; 4],
76}
77
78#[derive(impl bevy_ecs::component::Component for UiTextureSlicerBatch 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)]
79pub struct UiTextureSlicerBatch {
80 pub range: Range<u32>,
81 pub image: AssetId<Image>,
82}
83
84#[derive(impl bevy_ecs::component::Component for UiTextureSliceMeta where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<UiTextureSliceMeta>()
{
id
} else {
required_components.components_registrator().register_component::<UiTextureSliceMeta>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
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
}
}
impl bevy_ecs::resource::Resource for UiTextureSliceMeta where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
85pub struct UiTextureSliceMeta {
86 vertices: RawBufferVec<UiTextureSliceVertex>,
87 indices: RawBufferVec<u32>,
88 view_bind_group: Option<BindGroup>,
89}
90
91impl Default for UiTextureSliceMeta {
92 fn default() -> Self {
93 Self {
94 vertices: RawBufferVec::new(BufferUsages::VERTEX),
95 indices: RawBufferVec::new(BufferUsages::INDEX),
96 view_bind_group: None,
97 }
98 }
99}
100
101#[derive(impl bevy_ecs::component::Component for UiTextureSliceImageBindGroups where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<UiTextureSliceImageBindGroups>()
{
id
} else {
required_components.components_registrator().register_component::<UiTextureSliceImageBindGroups>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
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
}
}
impl bevy_ecs::resource::Resource for UiTextureSliceImageBindGroups where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource, #[automatically_derived]
impl ::core::default::Default for UiTextureSliceImageBindGroups {
#[inline]
fn default() -> Self {
Self { values: ::core::default::Default::default() }
}
}Default)]
102pub struct UiTextureSliceImageBindGroups {
103 pub values: HashMap<AssetId<Image>, BindGroup>,
104}
105
106#[derive(impl bevy_ecs::component::Component for UiTextureSlicePipeline where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<UiTextureSlicePipeline>()
{
id
} else {
required_components.components_registrator().register_component::<UiTextureSlicePipeline>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
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
}
}
impl bevy_ecs::resource::Resource for UiTextureSlicePipeline where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
107pub struct UiTextureSlicePipeline {
108 pub view_layout: BindGroupLayoutDescriptor,
109 pub image_layout: BindGroupLayoutDescriptor,
110 pub shader: Handle<Shader>,
111}
112
113pub fn init_ui_texture_slice_pipeline(mut commands: Commands, asset_server: Res<AssetServer>) {
114 let view_layout = BindGroupLayoutDescriptor::new(
115 "ui_texture_slice_view_layout",
116 &BindGroupLayoutEntries::single(
117 ShaderStages::VERTEX_FRAGMENT,
118 uniform_buffer::<ViewUniform>(true),
119 ),
120 );
121
122 let image_layout = BindGroupLayoutDescriptor::new(
123 "ui_texture_slice_image_layout",
124 &BindGroupLayoutEntries::sequential(
125 ShaderStages::FRAGMENT,
126 (
127 texture_2d(TextureSampleType::Float { filterable: true }),
128 sampler(SamplerBindingType::Filtering),
129 ),
130 ),
131 );
132
133 commands.insert_resource(UiTextureSlicePipeline {
134 view_layout,
135 image_layout,
136 shader: {
let (path, asset_server) =
{
let path =
{
{
let crate_name =
"bevy_ui_render::ui_texture_slice_pipeline".split(':').next().unwrap();
::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
"src".as_ref(), "src/ui_texture_slice_pipeline.rs".as_ref(),
"ui_texture_slice.wesl".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(), "ui_texture_slice.wesl"),
137 });
138}
139
140#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for UiTextureSlicePipelineKey { }
#[automatically_derived]
impl ::core::clone::Clone for UiTextureSlicePipelineKey {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<TextureFormat>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UiTextureSlicePipelineKey { }Copy, #[automatically_derived]
impl ::core::hash::Hash for UiTextureSlicePipelineKey {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
::core::hash::Hash::hash(&self.target_format, state)
}
}Hash, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for UiTextureSlicePipelineKey { }
#[automatically_derived]
impl ::core::cmp::PartialEq for UiTextureSlicePipelineKey {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.target_format == other.target_format
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UiTextureSlicePipelineKey {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<TextureFormat>;
}
}Eq)]
141pub struct UiTextureSlicePipelineKey {
142 pub target_format: TextureFormat,
143}
144
145impl SpecializedRenderPipeline for UiTextureSlicePipeline {
146 type Key = UiTextureSlicePipelineKey;
147
148 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
149 let vertex_layout = VertexBufferLayout::from_vertex_formats(
150 VertexStepMode::Vertex,
151 ::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::Float32x4,
VertexFormat::Float32x4, VertexFormat::Float32x4,
VertexFormat::Float32x4]))vec![
152 VertexFormat::Float32x3,
154 VertexFormat::Float32x2,
156 VertexFormat::Float32x4,
158 VertexFormat::Float32x4,
160 VertexFormat::Float32x4,
162 VertexFormat::Float32x4,
164 VertexFormat::Float32x4,
166 ],
167 );
168 let shader_defs = Vec::new();
169
170 RenderPipelineDescriptor {
171 vertex: VertexState {
172 shader: self.shader.clone(),
173 shader_defs: shader_defs.clone(),
174 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],
175 ..default()
176 },
177 fragment: Some(FragmentState {
178 shader: self.shader.clone(),
179 shader_defs,
180 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 {
181 format: key.target_format,
182 blend: Some(BlendState::ALPHA_BLENDING),
183 write_mask: ColorWrites::ALL,
184 })],
185 ..default()
186 }),
187 layout: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[self.view_layout.clone(), self.image_layout.clone()]))vec![self.view_layout.clone(), self.image_layout.clone()],
188 label: Some("ui_texture_slice_pipeline".into()),
189 ..default()
190 }
191 }
192}
193
194pub struct ExtractedUiTextureSlice {
195 pub stack_index: u32,
196 pub transform: Affine2,
197 pub rect: Rect,
198 pub atlas_rect: Option<Rect>,
199 pub image: AssetId<Image>,
200 pub clip: Option<CalculatedClip>,
201 pub color: LinearRgba,
202 pub image_scale_mode: SpriteImageMode,
203 pub flip_x: bool,
204 pub flip_y: bool,
205 pub inverse_scale_factor: f32,
206}
207
208#[derive(impl bevy_ecs::component::Component for ExtractedUiTextureSlices where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<ExtractedUiTextureSlices>()
{
id
} else {
required_components.components_registrator().register_component::<ExtractedUiTextureSlices>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
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
}
}
impl bevy_ecs::resource::Resource for ExtractedUiTextureSlices where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource, #[automatically_derived]
impl ::core::default::Default for ExtractedUiTextureSlices {
#[inline]
fn default() -> Self {
Self { slices: ::core::default::Default::default() }
}
}Default)]
210pub struct ExtractedUiTextureSlices {
211 pub slices: MainEntityHashMap<(Entity, EntityIndexMap<ExtractedUiTextureSlice>)>,
217}
218
219pub fn extract_ui_texture_slices(
220 mut commands: Commands,
221 mut extracted_ui_slicers: ResMut<ExtractedUiTextureSlices>,
222 texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
223 slicers_query: Extract<
224 Query<
225 (
226 Entity,
227 &ComputedNode,
228 &ComputedStackIndex,
229 &UiGlobalTransform,
230 &InheritedVisibility,
231 Option<&CalculatedClip>,
232 &ComputedUiTargetCamera,
233 &ImageNode,
234 ),
235 Or<(
236 Changed<ComputedNode>,
237 Changed<ComputedStackIndex>,
238 Changed<UiGlobalTransform>,
239 Changed<InheritedVisibility>,
240 Changed<CalculatedClip>,
241 Changed<ComputedUiTargetCamera>,
242 Changed<ImageNode>,
243 Changed<ImageNodeSize>,
248 )>,
249 >,
250 >,
251 unfiltered_slicers_query: Extract<
252 Query<(
253 Entity,
254 &ComputedNode,
255 &ComputedStackIndex,
256 &UiGlobalTransform,
257 &InheritedVisibility,
258 Option<&CalculatedClip>,
259 &ComputedUiTargetCamera,
260 &ImageNode,
261 )>,
262 >,
263 camera_map: Extract<UiCameraMap>,
264 (
265 mut removed_computed_node_query,
266 mut removed_computed_stack_index_query,
267 mut removed_ui_global_transform_query,
268 mut removed_inherited_visibility_query,
269 mut removed_calculated_clip_query,
270 mut removed_computed_ui_target_camera_query,
271 mut removed_image_node_query,
272 ): (
273 Extract<RemovedComponents<ComputedNode>>,
274 Extract<RemovedComponents<ComputedStackIndex>>,
275 Extract<RemovedComponents<UiGlobalTransform>>,
276 Extract<RemovedComponents<InheritedVisibility>>,
277 Extract<RemovedComponents<CalculatedClip>>,
278 Extract<RemovedComponents<ComputedUiTargetCamera>>,
279 Extract<RemovedComponents<ImageNode>>,
280 ),
281 mut nodes_processed_this_frame: Local<MainEntityHashSet>,
282) {
283 nodes_processed_this_frame.clear();
284 let mut camera_mapper = camera_map.get_mapper();
285
286 for (entity, uinode, stack_index, transform, inherited_visibility, clip, camera, image) in
287 slicers_query.iter().chain(
288 removed_calculated_clip_query
289 .read()
290 .filter_map(|entity| unfiltered_slicers_query.get(entity).ok()),
291 )
292 {
293 let main_entity = MainEntity::from(entity);
294
295 for (render_entity, _) in extracted_ui_slicers
297 .slices
298 .get_mut(&main_entity)
299 .iter_mut()
300 .flat_map(|(_, slices)| slices.drain(..))
301 {
302 commands.entity(render_entity).despawn();
303 }
304
305 let visual_box = match image.visual_box {
306 VisualBox::ContentBox => uinode.content_box(),
307 VisualBox::PaddingBox => uinode.padding_box(),
308 VisualBox::BorderBox => uinode.border_box(),
309 };
310
311 if !inherited_visibility.get()
313 || image.color.is_fully_transparent()
314 || image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
315 || visual_box.size().cmple(Vec2::ZERO).any()
316 {
317 continue;
318 }
319
320 let image_scale_mode = match image.image_mode.clone() {
321 NodeImageMode::Sliced(texture_slicer) => SpriteImageMode::Sliced(texture_slicer),
322 NodeImageMode::Tiled {
323 tile_x,
324 tile_y,
325 stretch_value,
326 } => SpriteImageMode::Tiled {
327 tile_x,
328 tile_y,
329 stretch_value,
330 },
331 _ => continue,
332 };
333
334 let Some(extracted_camera_entity) = camera_mapper.map(camera) else {
335 continue;
336 };
337 if let Some((camera_entity, _)) = extracted_ui_slicers.slices.get_mut(&main_entity) {
338 *camera_entity = extracted_camera_entity;
339 }
340
341 nodes_processed_this_frame.insert(main_entity);
342
343 let atlas_rect = image
344 .texture_atlas
345 .as_ref()
346 .and_then(|s| s.texture_rect(&texture_atlases))
347 .map(|r| r.as_rect());
348
349 let atlas_rect = match (atlas_rect, image.rect) {
350 (None, None) => None,
351 (None, Some(image_rect)) => Some(image_rect),
352 (Some(atlas_rect), None) => Some(atlas_rect),
353 (Some(atlas_rect), Some(mut image_rect)) => {
354 image_rect.min += atlas_rect.min;
355 image_rect.max += atlas_rect.min;
356 Some(image_rect)
357 }
358 };
359
360 extracted_ui_slicers
361 .slices
362 .entry(main_entity)
363 .or_insert_with(|| (extracted_camera_entity, Default::default()))
364 .1
365 .insert(
366 commands.spawn_empty().id(),
367 ExtractedUiTextureSlice {
368 stack_index: stack_index.0,
369 transform: Affine2::from(*transform)
370 * Affine2::from_translation(visual_box.center()),
371 color: image.color.into(),
372 rect: Rect {
373 min: Vec2::ZERO,
374 max: visual_box.size(),
375 },
376 clip: clip.cloned(),
377 image: image.image.id(),
378 image_scale_mode,
379 atlas_rect,
380 flip_x: image.flip_x,
381 flip_y: image.flip_y,
382 inverse_scale_factor: uinode.inverse_scale_factor,
383 },
384 );
385 }
386
387 for main_entity in removed_computed_node_query
391 .read()
392 .chain(removed_computed_stack_index_query.read())
393 .chain(removed_ui_global_transform_query.read())
394 .chain(removed_inherited_visibility_query.read())
395 .chain(removed_computed_ui_target_camera_query.read())
396 .chain(removed_image_node_query.read())
397 {
398 let main_entity = MainEntity::from(main_entity);
399 if nodes_processed_this_frame.contains(&main_entity) {
400 continue;
401 }
402 let Some((_, mut extracted_nodes)) = extracted_ui_slicers.slices.remove(&main_entity)
403 else {
404 continue;
405 };
406 for (render_entity, _) in extracted_nodes.drain(..) {
407 commands.entity(render_entity).despawn();
408 }
409 }
410}
411
412#[expect(
413 clippy::too_many_arguments,
414 reason = "it's a system that needs a lot of them"
415)]
416pub fn queue_ui_slices(
417 extracted_ui_slicers: ResMut<ExtractedUiTextureSlices>,
418 ui_slicer_pipeline: Res<UiTextureSlicePipeline>,
419 mut pipelines: ResMut<SpecializedRenderPipelines<UiTextureSlicePipeline>>,
420 mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
421 render_views: Query<&UiCameraView, With<ExtractedView>>,
422 camera_views: Query<&ExtractedView>,
423 pipeline_cache: Res<PipelineCache>,
424 draw_functions: Res<DrawFunctions<TransparentUi>>,
425) {
426 let draw_function = draw_functions.read().id::<DrawUiTextureSlices>();
427 let mut current_camera_entity = Entity::PLACEHOLDER;
428 let mut current_phase = None;
429
430 for (main_entity, (extracted_camera_entity, subslices)) in extracted_ui_slicers.slices.iter() {
431 if current_camera_entity != *extracted_camera_entity {
432 current_phase =
433 render_views
434 .get(*extracted_camera_entity)
435 .ok()
436 .and_then(|default_camera_view| {
437 camera_views
438 .get(default_camera_view.0)
439 .ok()
440 .and_then(|view| {
441 transparent_render_phases
442 .get_mut(&view.retained_view_entity)
443 .map(|transparent_phase| {
444 let pipeline = pipelines.specialize(
445 &pipeline_cache,
446 &ui_slicer_pipeline,
447 UiTextureSlicePipelineKey {
448 target_format: view.target_format,
449 },
450 );
451 (pipeline, transparent_phase)
452 })
453 })
454 });
455 current_camera_entity = *extracted_camera_entity;
456 }
457
458 let Some((pipeline, transparent_phase)) = current_phase.as_mut() else {
459 continue;
460 };
461 for (render_entity, extracted_slicer) in subslices.iter() {
462 transparent_phase.add_transient(TransparentUi {
463 draw_function,
464 pipeline: *pipeline,
465 entity: (*render_entity, *main_entity),
466 sort_key: FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::IMAGE),
467 batch_range: 0..0,
468 extra_index: PhaseItemExtraIndex::None,
469 indexed: true,
470 });
471 }
472 }
473}
474
475pub fn prepare_ui_slices(
476 mut commands: Commands,
477 render_device: Res<RenderDevice>,
478 render_queue: Res<RenderQueue>,
479 pipeline_cache: Res<PipelineCache>,
480 mut ui_meta: ResMut<UiTextureSliceMeta>,
481 extracted_slices: Res<ExtractedUiTextureSlices>,
482 view_uniforms: Res<ViewUniforms>,
483 texture_slicer_pipeline: Res<UiTextureSlicePipeline>,
484 mut image_bind_groups: ResMut<UiTextureSliceImageBindGroups>,
485 gpu_images: Res<RenderAssets<GpuImage>>,
486 mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
487 events: Res<SpriteAssetEvents>,
488 mut previous_len: Local<usize>,
489) {
490 for event in &events.images {
492 match event {
493 AssetEvent::Added { .. } |
494 AssetEvent::Unused { .. } |
495 AssetEvent::LoadedWithDependencies { .. } => {}
497 AssetEvent::Modified { id } | AssetEvent::Removed { id } => {
498 image_bind_groups.values.remove(id);
499 }
500 };
501 }
502
503 if let Some(view_binding) = view_uniforms.uniforms.binding() {
504 let mut batches: Vec<(Entity, UiTextureSlicerBatch)> = Vec::with_capacity(*previous_len);
505
506 ui_meta.vertices.clear();
507 ui_meta.indices.clear();
508 ui_meta.view_bind_group = Some(render_device.create_bind_group(
509 "ui_texture_slice_view_bind_group",
510 &pipeline_cache.get_bind_group_layout(&texture_slicer_pipeline.view_layout),
511 &BindGroupEntries::single(view_binding),
512 ));
513
514 let mut vertices_index = 0;
516 let mut indices_index = 0;
517
518 for ui_phase in phases.values_mut() {
519 let mut batch_item_index = 0;
520 let mut batch_image_handle = None;
521 let mut batch_image_size = Vec2::ZERO;
522
523 for item_index in 0..ui_phase.items.len() {
524 let item = &mut ui_phase.items[item_index];
525 if let Some(texture_slices) = extracted_slices
526 .slices
527 .get(&item.main_entity())
528 .and_then(|(_, subslices)| subslices.get(&item.entity()))
529 {
530 item.batch_range = (item_index as u32)..(item_index as u32);
533
534 let mut existing_batch = batches.last_mut();
535
536 if batch_image_handle.is_none()
537 || existing_batch.is_none()
538 || (batch_image_handle != Some(AssetId::default())
539 && texture_slices.image != AssetId::default()
540 && batch_image_handle != Some(texture_slices.image))
541 {
542 if let Some(gpu_image) = gpu_images.get(texture_slices.image) {
543 batch_item_index = item_index;
544 batch_image_handle = Some(texture_slices.image);
545 batch_image_size = gpu_image.size_2d().as_vec2();
546
547 let new_batch = UiTextureSlicerBatch {
548 range: vertices_index..vertices_index,
549 image: texture_slices.image,
550 };
551
552 batches.push((item.entity(), new_batch));
553
554 image_bind_groups
555 .values
556 .entry(texture_slices.image)
557 .or_insert_with(|| {
558 render_device.create_bind_group(
559 "ui_texture_slice_image_layout",
560 &pipeline_cache.get_bind_group_layout(
561 &texture_slicer_pipeline.image_layout,
562 ),
563 &BindGroupEntries::sequential((
564 &gpu_image.texture_view,
565 &gpu_image.sampler,
566 )),
567 )
568 });
569
570 existing_batch = batches.last_mut();
571 } else {
572 continue;
573 }
574 } else if let Some(ref mut existing_batch) = existing_batch
575 && batch_image_handle == Some(AssetId::default())
576 && texture_slices.image != AssetId::default()
577 {
578 if let Some(gpu_image) = gpu_images.get(texture_slices.image) {
579 batch_image_handle = Some(texture_slices.image);
580 batch_image_size = gpu_image.size_2d().as_vec2();
581 existing_batch.1.image = texture_slices.image;
582
583 image_bind_groups
584 .values
585 .entry(texture_slices.image)
586 .or_insert_with(|| {
587 render_device.create_bind_group(
588 "ui_texture_slice_image_layout",
589 &pipeline_cache.get_bind_group_layout(
590 &texture_slicer_pipeline.image_layout,
591 ),
592 &BindGroupEntries::sequential((
593 &gpu_image.texture_view,
594 &gpu_image.sampler,
595 )),
596 )
597 });
598 } else {
599 continue;
600 }
601 }
602
603 let uinode_rect = texture_slices.rect;
604
605 let rect_size = uinode_rect.size();
606
607 let positions = QUAD_VERTEX_POSITIONS
609 .map(|pos| texture_slices.transform.transform_point2(pos * rect_size));
610
611 let uvs = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y];
612
613 let color = texture_slices.color.to_f32_array();
614
615 let (image_size, mut atlas) = if let Some(atlas) = texture_slices.atlas_rect {
616 (
617 atlas.size(),
618 [
619 atlas.min.x / batch_image_size.x,
620 atlas.min.y / batch_image_size.y,
621 atlas.max.x / batch_image_size.x,
622 atlas.max.y / batch_image_size.y,
623 ],
624 )
625 } else {
626 (batch_image_size, [0., 0., 1., 1.])
627 };
628
629 if texture_slices.flip_x {
630 atlas.swap(0, 2);
631 }
632
633 if texture_slices.flip_y {
634 atlas.swap(1, 3);
635 }
636
637 let [slices, border, repeat] = compute_texture_slices(
638 image_size,
639 uinode_rect.size() * texture_slices.inverse_scale_factor,
640 &texture_slices.image_scale_mode,
641 );
642
643 let vertices = clip_polygon(
644 texture_slices.clip.as_ref(),
645 &[
646 (positions[0], uvs[0]),
647 (positions[1], uvs[1]),
648 (positions[2], uvs[2]),
649 (positions[3], uvs[3]),
650 ],
651 Vec2::lerp,
652 );
653 if vertices.is_empty() {
654 continue;
655 }
656
657 for vertex in &vertices {
658 ui_meta.vertices.push(UiTextureSliceVertex {
659 position: vertex.0.extend(0.).into(),
660 uv: vertex.1.into(),
661 color,
662 slices,
663 border,
664 repeat,
665 atlas,
666 });
667 }
668
669 for i in 1..vertices.len() as u32 - 1 {
670 ui_meta.indices.push(indices_index);
671 ui_meta.indices.push(indices_index + i);
672 ui_meta.indices.push(indices_index + i + 1);
673 }
674
675 vertices_index += 3 * (vertices.len() as u32 - 2);
676 indices_index += vertices.len() as u32;
677
678 existing_batch.unwrap().1.range.end = vertices_index;
679 ui_phase.items[batch_item_index].batch_range_mut().end += 1;
680 } else {
681 batch_image_handle = None;
682 }
683 }
684 }
685 ui_meta.vertices.write_buffer(&render_device, &render_queue);
686 ui_meta.indices.write_buffer(&render_device, &render_queue);
687 *previous_len = batches.len();
688 commands.try_insert_batch(batches);
689 }
690}
691
692pub type DrawUiTextureSlices = (
693 SetItemPipeline,
694 SetSlicerViewBindGroup<0>,
695 SetSlicerTextureBindGroup<1>,
696 DrawSlicer,
697);
698
699pub struct SetSlicerViewBindGroup<const I: usize>;
700impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSlicerViewBindGroup<I> {
701 type Param = SRes<UiTextureSliceMeta>;
702 type ViewQuery = Read<ViewUniformOffset>;
703 type ItemQuery = ();
704
705 fn render<'w>(
706 _item: &P,
707 view_uniform: &'w ViewUniformOffset,
708 _entity: Option<()>,
709 ui_meta: SystemParamItem<'w, '_, Self::Param>,
710 pass: &mut TrackedRenderPass<'w>,
711 ) -> RenderCommandResult {
712 let Some(view_bind_group) = ui_meta.into_inner().view_bind_group.as_ref() else {
713 return RenderCommandResult::Failure("view_bind_group not available");
714 };
715 pass.set_bind_group(I, view_bind_group, &[view_uniform.offset]);
716 RenderCommandResult::Success
717 }
718}
719pub struct SetSlicerTextureBindGroup<const I: usize>;
720impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSlicerTextureBindGroup<I> {
721 type Param = SRes<UiTextureSliceImageBindGroups>;
722 type ViewQuery = ();
723 type ItemQuery = Read<UiTextureSlicerBatch>;
724
725 #[inline]
726 fn render<'w>(
727 _item: &P,
728 _view: (),
729 batch: Option<&'w UiTextureSlicerBatch>,
730 image_bind_groups: SystemParamItem<'w, '_, Self::Param>,
731 pass: &mut TrackedRenderPass<'w>,
732 ) -> RenderCommandResult {
733 let image_bind_groups = image_bind_groups.into_inner();
734 let Some(batch) = batch else {
735 return RenderCommandResult::Skip;
736 };
737
738 pass.set_bind_group(I, image_bind_groups.values.get(&batch.image).unwrap(), &[]);
739 RenderCommandResult::Success
740 }
741}
742pub struct DrawSlicer;
743impl<P: PhaseItem> RenderCommand<P> for DrawSlicer {
744 type Param = SRes<UiTextureSliceMeta>;
745 type ViewQuery = ();
746 type ItemQuery = Read<UiTextureSlicerBatch>;
747
748 #[inline]
749 fn render<'w>(
750 _item: &P,
751 _view: (),
752 batch: Option<&'w UiTextureSlicerBatch>,
753 ui_meta: SystemParamItem<'w, '_, Self::Param>,
754 pass: &mut TrackedRenderPass<'w>,
755 ) -> RenderCommandResult {
756 let Some(batch) = batch else {
757 return RenderCommandResult::Skip;
758 };
759 let ui_meta = ui_meta.into_inner();
760 let Some(vertices) = ui_meta.vertices.buffer() else {
761 return RenderCommandResult::Failure("missing vertices to draw ui");
762 };
763 let Some(indices) = ui_meta.indices.buffer() else {
764 return RenderCommandResult::Failure("missing indices to draw ui");
765 };
766
767 pass.set_vertex_buffer(0, vertices.slice(..));
769 pass.set_index_buffer(indices.slice(..), IndexFormat::Uint32);
771 pass.draw_indexed(batch.range.clone(), 0, 0..1);
773 RenderCommandResult::Success
774 }
775}
776
777fn compute_texture_slices(
778 image_size: Vec2,
779 target_size: Vec2,
780 image_scale_mode: &SpriteImageMode,
781) -> [[f32; 4]; 3] {
782 match image_scale_mode {
783 SpriteImageMode::Sliced(TextureSlicer {
784 border: border_rect,
785 center_scale_mode,
786 sides_scale_mode,
787 max_corner_scale,
788 }) => {
789 let min_coeff = (target_size / image_size)
790 .min_element()
791 .min(*max_corner_scale);
792
793 let slices = [
795 border_rect.min_inset.x / image_size.x,
796 border_rect.min_inset.y / image_size.y,
797 1. - border_rect.max_inset.x / image_size.x,
798 1. - border_rect.max_inset.y / image_size.y,
799 ];
800
801 let border = [
803 (border_rect.min_inset.x / target_size.x) * min_coeff,
804 (border_rect.min_inset.y / target_size.y) * min_coeff,
805 1. - (border_rect.max_inset.x / target_size.x) * min_coeff,
806 1. - (border_rect.max_inset.y / target_size.y) * min_coeff,
807 ];
808
809 let image_side_width = image_size.x * (slices[2] - slices[0]);
810 let image_side_height = image_size.y * (slices[3] - slices[1]);
811 let target_side_width = target_size.x * (border[2] - border[0]);
812 let target_side_height = target_size.y * (border[3] - border[1]);
813
814 let repeat_side_x =
817 compute_tiled_subaxis(image_side_width, target_side_width, sides_scale_mode);
818 let repeat_side_y =
819 compute_tiled_subaxis(image_side_height, target_side_height, sides_scale_mode);
820 let repeat_center_x =
821 compute_tiled_subaxis(image_side_width, target_side_width, center_scale_mode);
822 let repeat_center_y =
823 compute_tiled_subaxis(image_side_height, target_side_height, center_scale_mode);
824
825 [
826 slices,
827 border,
828 [
829 repeat_side_x,
830 repeat_side_y,
831 repeat_center_x,
832 repeat_center_y,
833 ],
834 ]
835 }
836 SpriteImageMode::Tiled {
837 tile_x,
838 tile_y,
839 stretch_value,
840 } => {
841 let rx = compute_tiled_axis(*tile_x, image_size.x, target_size.x, *stretch_value);
842 let ry = compute_tiled_axis(*tile_y, image_size.y, target_size.y, *stretch_value);
843 [[0., 0., 1., 1.], [0., 0., 1., 1.], [1., 1., rx, ry]]
844 }
845 SpriteImageMode::Auto => {
846 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("Slices can not be computed for SpriteImageMode::Stretch")));
}unreachable!("Slices can not be computed for SpriteImageMode::Stretch")
847 }
848 SpriteImageMode::Scale(_) => {
849 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("Slices can not be computed for SpriteImageMode::Scale")));
}unreachable!("Slices can not be computed for SpriteImageMode::Scale")
850 }
851 }
852}
853
854fn compute_tiled_axis(tile: bool, image_extent: f32, target_extent: f32, stretch: f32) -> f32 {
855 if tile {
856 let s = image_extent * stretch;
857 target_extent / s
858 } else {
859 1.
860 }
861}
862
863fn compute_tiled_subaxis(image_extent: f32, target_extent: f32, mode: &SliceScaleMode) -> f32 {
864 match mode {
865 SliceScaleMode::Stretch => 1.,
866 SliceScaleMode::Tile { stretch_value } => {
867 let s = image_extent * *stretch_value;
868 target_extent / s
869 }
870 }
871}