Skip to main content

bevy_ui_render/
ui_texture_slice_pipeline.rs

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                // position
153                VertexFormat::Float32x3,
154                // uv
155                VertexFormat::Float32x2,
156                // color
157                VertexFormat::Float32x4,
158                // normalized texture slicing lines (left, top, right, bottom)
159                VertexFormat::Float32x4,
160                // normalized target slicing lines (left, top, right, bottom)
161                VertexFormat::Float32x4,
162                // repeat values (horizontal side, vertical side, horizontal center, vertical center)
163                VertexFormat::Float32x4,
164                // normalized texture atlas rect (left, top, right, bottom)
165                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/// A render-world resource that stores all texture slices in the scene.
209#[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    /// The list of texture slices grouped by their main-world entity, along with
212    /// each group's target camera entity.
213    ///
214    /// This is a two-level data structure so that we can quickly remove all
215    /// texture slices associated with a main-world entity when it changes.
216    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                // The `bevy_ui::widget::update_image_content_size_system` marks
244                // `ImageNodeSize` as changed to indicate that the image metrics
245                // and/or texture atlas layout changed, so we need to watch for
246                // changes to that component, even though we don't read it.
247                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        // If there were any previous UI slices for this entity, despawn them.
296        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        // Skip invisible images
312        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    // Only remove the render-world data if we didn't handle the node above.
388    // It's possible that a relevant component was removed and added in the same
389    // frame.
390    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    // If an image has changed, the GpuImage has (probably) changed
491    for event in &events.images {
492        match event {
493            AssetEvent::Added { .. } |
494            AssetEvent::Unused { .. } |
495            // Images don't have dependencies
496            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        // Buffer indexes
515        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                    // Initialize the batch range to be zero-length initially.
531                    // We'll extend it as we accumulate items into this batch.
532                    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                    // Specify the corners of the node
608                    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        // Store the vertices
768        pass.set_vertex_buffer(0, vertices.slice(..));
769        // Define how to "connect" the vertices
770        pass.set_index_buffer(indices.slice(..), IndexFormat::Uint32);
771        // Draw the vertices
772        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            // calculate the normalized extents of the nine-patched image slices
794            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            // calculate the normalized extents of the target slices
802            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            // compute the number of times to repeat the side and center slices when tiling along each axis
815            // if the returned value is `1.` the slice will be stretched to fill the axis.
816            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}