mirror of
https://github.com/bevyengine/bevy
synced 2025-01-08 19:29:04 +00:00
7b2cf98896
# Objective - Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource. - However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource. - This meant that effectively only one extract which wrote to resources could run at a time. - We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that. ## Solution - Move the extract stage to run on the render world. - Add the main world as a `MainWorld` resource. - Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`. ## Future work It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on. We need to determine how this interacts with https://github.com/bevyengine/bevy/pull/3519 It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too. ## Todo I still need to add doc comments to `Extract`. --- ## Changelog ### Changed - The `Extract` `RenderStage` now runs on the render world (instead of the main world as before). You must use the `Extract` `SystemParam` to access the main world during the extract phase. Resources on the render world can now be accessed using `ResMut` during extract. ### Removed - `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead ## Migration Guide The `Extract` `RenderStage` now runs on the render world (instead of the main world as before). You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it. For example, if previously your extract system looked like: ```rust fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) { for cloud in clouds.iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` the new version would be: ```rust fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) { for cloud in clouds.value().iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` The diff is: ```diff --- a/src/clouds.rs +++ b/src/clouds.rs @@ -1,5 +1,5 @@ -fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) { - for cloud in clouds.iter() { +fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) { + for cloud in clouds.value().iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` You can now also access resources from the render world using the normal system parameters during `Extract`: ```rust fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) { *render_assets = source_assets.clone(); } ``` Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
563 lines
19 KiB
Rust
563 lines
19 KiB
Rust
mod pipeline;
|
|
mod render_pass;
|
|
|
|
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
|
pub use pipeline::*;
|
|
pub use render_pass::*;
|
|
|
|
use crate::{prelude::UiCameraConfig, CalculatedClip, Node, UiColor, UiImage};
|
|
use bevy_app::prelude::*;
|
|
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
|
|
use bevy_ecs::prelude::*;
|
|
use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles};
|
|
use bevy_reflect::TypeUuid;
|
|
use bevy_render::{
|
|
camera::{Camera, CameraProjection, DepthCalculation, OrthographicProjection, WindowOrigin},
|
|
color::Color,
|
|
render_asset::RenderAssets,
|
|
render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType},
|
|
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase},
|
|
render_resource::*,
|
|
renderer::{RenderDevice, RenderQueue},
|
|
texture::Image,
|
|
view::{ExtractedView, ViewUniforms, Visibility},
|
|
Extract, RenderApp, RenderStage,
|
|
};
|
|
use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas};
|
|
use bevy_text::{DefaultTextPipeline, Text};
|
|
use bevy_transform::components::GlobalTransform;
|
|
use bevy_utils::FloatOrd;
|
|
use bevy_utils::HashMap;
|
|
use bevy_window::{WindowId, Windows};
|
|
use bytemuck::{Pod, Zeroable};
|
|
use std::ops::Range;
|
|
|
|
pub mod node {
|
|
pub const UI_PASS_DRIVER: &str = "ui_pass_driver";
|
|
}
|
|
|
|
pub mod draw_ui_graph {
|
|
pub const NAME: &str = "draw_ui";
|
|
pub mod input {
|
|
pub const VIEW_ENTITY: &str = "view_entity";
|
|
}
|
|
pub mod node {
|
|
pub const UI_PASS: &str = "ui_pass";
|
|
}
|
|
}
|
|
|
|
pub const UI_SHADER_HANDLE: HandleUntyped =
|
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 13012847047162779583);
|
|
|
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
|
|
pub enum RenderUiSystem {
|
|
ExtractNode,
|
|
}
|
|
|
|
pub fn build_ui_render(app: &mut App) {
|
|
load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl);
|
|
|
|
let render_app = match app.get_sub_app_mut(RenderApp) {
|
|
Ok(render_app) => render_app,
|
|
Err(_) => return,
|
|
};
|
|
|
|
render_app
|
|
.init_resource::<UiPipeline>()
|
|
.init_resource::<SpecializedRenderPipelines<UiPipeline>>()
|
|
.init_resource::<UiImageBindGroups>()
|
|
.init_resource::<UiMeta>()
|
|
.init_resource::<ExtractedUiNodes>()
|
|
.init_resource::<DrawFunctions<TransparentUi>>()
|
|
.add_render_command::<TransparentUi, DrawUi>()
|
|
.add_system_to_stage(
|
|
RenderStage::Extract,
|
|
extract_default_ui_camera_view::<Camera2d>,
|
|
)
|
|
.add_system_to_stage(
|
|
RenderStage::Extract,
|
|
extract_default_ui_camera_view::<Camera3d>,
|
|
)
|
|
.add_system_to_stage(
|
|
RenderStage::Extract,
|
|
extract_uinodes.label(RenderUiSystem::ExtractNode),
|
|
)
|
|
.add_system_to_stage(
|
|
RenderStage::Extract,
|
|
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
|
|
)
|
|
.add_system_to_stage(RenderStage::Prepare, prepare_uinodes)
|
|
.add_system_to_stage(RenderStage::Queue, queue_uinodes)
|
|
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<TransparentUi>);
|
|
|
|
// Render graph
|
|
let ui_graph_2d = get_ui_graph(render_app);
|
|
let ui_graph_3d = get_ui_graph(render_app);
|
|
let mut graph = render_app.world.resource_mut::<RenderGraph>();
|
|
|
|
if let Some(graph_2d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_2d::graph::NAME) {
|
|
graph_2d.add_sub_graph(draw_ui_graph::NAME, ui_graph_2d);
|
|
graph_2d.add_node(
|
|
draw_ui_graph::node::UI_PASS,
|
|
RunGraphOnViewNode::new(draw_ui_graph::NAME),
|
|
);
|
|
graph_2d
|
|
.add_node_edge(
|
|
bevy_core_pipeline::core_2d::graph::node::MAIN_PASS,
|
|
draw_ui_graph::node::UI_PASS,
|
|
)
|
|
.unwrap();
|
|
graph_2d
|
|
.add_slot_edge(
|
|
graph_2d.input_node().unwrap().id,
|
|
bevy_core_pipeline::core_2d::graph::input::VIEW_ENTITY,
|
|
draw_ui_graph::node::UI_PASS,
|
|
RunGraphOnViewNode::IN_VIEW,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
if let Some(graph_3d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) {
|
|
graph_3d.add_sub_graph(draw_ui_graph::NAME, ui_graph_3d);
|
|
graph_3d.add_node(
|
|
draw_ui_graph::node::UI_PASS,
|
|
RunGraphOnViewNode::new(draw_ui_graph::NAME),
|
|
);
|
|
graph_3d
|
|
.add_node_edge(
|
|
bevy_core_pipeline::core_3d::graph::node::MAIN_PASS,
|
|
draw_ui_graph::node::UI_PASS,
|
|
)
|
|
.unwrap();
|
|
graph_3d
|
|
.add_slot_edge(
|
|
graph_3d.input_node().unwrap().id,
|
|
bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY,
|
|
draw_ui_graph::node::UI_PASS,
|
|
RunGraphOnViewNode::IN_VIEW,
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
fn get_ui_graph(render_app: &mut App) -> RenderGraph {
|
|
let ui_pass_node = UiPassNode::new(&mut render_app.world);
|
|
let mut ui_graph = RenderGraph::default();
|
|
ui_graph.add_node(draw_ui_graph::node::UI_PASS, ui_pass_node);
|
|
let input_node_id = ui_graph.set_input(vec![SlotInfo::new(
|
|
draw_ui_graph::input::VIEW_ENTITY,
|
|
SlotType::Entity,
|
|
)]);
|
|
ui_graph
|
|
.add_slot_edge(
|
|
input_node_id,
|
|
draw_ui_graph::input::VIEW_ENTITY,
|
|
draw_ui_graph::node::UI_PASS,
|
|
UiPassNode::IN_VIEW,
|
|
)
|
|
.unwrap();
|
|
ui_graph
|
|
}
|
|
|
|
pub struct ExtractedUiNode {
|
|
pub transform: Mat4,
|
|
pub color: Color,
|
|
pub rect: Rect,
|
|
pub image: Handle<Image>,
|
|
pub atlas_size: Option<Vec2>,
|
|
pub clip: Option<Rect>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ExtractedUiNodes {
|
|
pub uinodes: Vec<ExtractedUiNode>,
|
|
}
|
|
|
|
pub fn extract_uinodes(
|
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
|
images: Extract<Res<Assets<Image>>>,
|
|
uinode_query: Extract<
|
|
Query<(
|
|
&Node,
|
|
&GlobalTransform,
|
|
&UiColor,
|
|
&UiImage,
|
|
&Visibility,
|
|
Option<&CalculatedClip>,
|
|
)>,
|
|
>,
|
|
) {
|
|
extracted_uinodes.uinodes.clear();
|
|
for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
|
|
if !visibility.is_visible {
|
|
continue;
|
|
}
|
|
let image = image.0.clone_weak();
|
|
// Skip loading images
|
|
if !images.contains(&image) {
|
|
continue;
|
|
}
|
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
|
transform: transform.compute_matrix(),
|
|
color: color.0,
|
|
rect: bevy_sprite::Rect {
|
|
min: Vec2::ZERO,
|
|
max: uinode.size,
|
|
},
|
|
image,
|
|
atlas_size: None,
|
|
clip: clip.map(|clip| clip.clip),
|
|
});
|
|
}
|
|
}
|
|
|
|
/// The UI camera is "moved back" by this many units (plus the [`UI_CAMERA_TRANSFORM_OFFSET`]) and also has a view
|
|
/// distance of this many units. This ensures that with a left-handed projection,
|
|
/// as ui elements are "stacked on top of each other", they are within the camera's view
|
|
/// and have room to grow.
|
|
// TODO: Consider computing this value at runtime based on the maximum z-value.
|
|
const UI_CAMERA_FAR: f32 = 1000.0;
|
|
|
|
// This value is subtracted from the far distance for the camera's z-position to ensure nodes at z == 0.0 are rendered
|
|
// TODO: Evaluate if we still need this.
|
|
const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1;
|
|
|
|
#[derive(Component)]
|
|
pub struct DefaultCameraView(pub Entity);
|
|
|
|
pub fn extract_default_ui_camera_view<T: Component>(
|
|
mut commands: Commands,
|
|
query: Extract<Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>>,
|
|
) {
|
|
for (entity, camera, camera_ui) in query.iter() {
|
|
// ignore cameras with disabled ui
|
|
if matches!(camera_ui, Some(&UiCameraConfig { show_ui: false, .. })) {
|
|
continue;
|
|
}
|
|
if let (Some(logical_size), Some(physical_size)) = (
|
|
camera.logical_viewport_size(),
|
|
camera.physical_viewport_size(),
|
|
) {
|
|
let mut projection = OrthographicProjection {
|
|
far: UI_CAMERA_FAR,
|
|
window_origin: WindowOrigin::BottomLeft,
|
|
depth_calculation: DepthCalculation::ZDifference,
|
|
..Default::default()
|
|
};
|
|
projection.update(logical_size.x, logical_size.y);
|
|
let default_camera_view = commands
|
|
.spawn()
|
|
.insert(ExtractedView {
|
|
projection: projection.get_projection_matrix(),
|
|
transform: GlobalTransform::from_xyz(
|
|
0.0,
|
|
0.0,
|
|
UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
|
|
),
|
|
width: physical_size.x,
|
|
height: physical_size.y,
|
|
})
|
|
.id();
|
|
commands.get_or_spawn(entity).insert_bundle((
|
|
DefaultCameraView(default_camera_view),
|
|
RenderPhase::<TransparentUi>::default(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn extract_text_uinodes(
|
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
|
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
|
text_pipeline: Extract<Res<DefaultTextPipeline>>,
|
|
windows: Extract<Res<Windows>>,
|
|
uinode_query: Extract<
|
|
Query<(
|
|
Entity,
|
|
&Node,
|
|
&GlobalTransform,
|
|
&Text,
|
|
&Visibility,
|
|
Option<&CalculatedClip>,
|
|
)>,
|
|
>,
|
|
) {
|
|
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
|
|
for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() {
|
|
if !visibility.is_visible {
|
|
continue;
|
|
}
|
|
// Skip if size is set to zero (e.g. when a parent is set to `Display::None`)
|
|
if uinode.size == Vec2::ZERO {
|
|
continue;
|
|
}
|
|
if let Some(text_layout) = text_pipeline.get_glyphs(&entity) {
|
|
let text_glyphs = &text_layout.glyphs;
|
|
let alignment_offset = (uinode.size / -2.0).extend(0.0);
|
|
|
|
for text_glyph in text_glyphs {
|
|
let color = text.sections[text_glyph.section_index].style.color;
|
|
let atlas = texture_atlases
|
|
.get(&text_glyph.atlas_info.texture_atlas)
|
|
.unwrap();
|
|
let texture = atlas.texture.clone_weak();
|
|
let index = text_glyph.atlas_info.glyph_index as usize;
|
|
let rect = atlas.textures[index];
|
|
let atlas_size = Some(atlas.size);
|
|
|
|
let transform =
|
|
Mat4::from_rotation_translation(transform.rotation, transform.translation)
|
|
* Mat4::from_scale(transform.scale / scale_factor)
|
|
* Mat4::from_translation(
|
|
alignment_offset * scale_factor + text_glyph.position.extend(0.),
|
|
);
|
|
|
|
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
|
transform,
|
|
color,
|
|
rect,
|
|
image: texture,
|
|
atlas_size,
|
|
clip: clip.map(|clip| clip.clip),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Pod, Zeroable)]
|
|
struct UiVertex {
|
|
pub position: [f32; 3],
|
|
pub uv: [f32; 2],
|
|
pub color: [f32; 4],
|
|
}
|
|
|
|
pub struct UiMeta {
|
|
vertices: BufferVec<UiVertex>,
|
|
view_bind_group: Option<BindGroup>,
|
|
}
|
|
|
|
impl Default for UiMeta {
|
|
fn default() -> Self {
|
|
Self {
|
|
vertices: BufferVec::new(BufferUsages::VERTEX),
|
|
view_bind_group: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [
|
|
Vec3::new(-0.5, -0.5, 0.0),
|
|
Vec3::new(0.5, -0.5, 0.0),
|
|
Vec3::new(0.5, 0.5, 0.0),
|
|
Vec3::new(-0.5, 0.5, 0.0),
|
|
];
|
|
|
|
const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
|
|
|
|
#[derive(Component)]
|
|
pub struct UiBatch {
|
|
pub range: Range<u32>,
|
|
pub image: Handle<Image>,
|
|
pub z: f32,
|
|
}
|
|
|
|
pub fn prepare_uinodes(
|
|
mut commands: Commands,
|
|
render_device: Res<RenderDevice>,
|
|
render_queue: Res<RenderQueue>,
|
|
mut ui_meta: ResMut<UiMeta>,
|
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
|
) {
|
|
ui_meta.vertices.clear();
|
|
|
|
// sort by increasing z for correct transparency
|
|
extracted_uinodes
|
|
.uinodes
|
|
.sort_by(|a, b| FloatOrd(a.transform.w_axis[2]).cmp(&FloatOrd(b.transform.w_axis[2])));
|
|
|
|
let mut start = 0;
|
|
let mut end = 0;
|
|
let mut current_batch_handle = Default::default();
|
|
let mut last_z = 0.0;
|
|
for extracted_uinode in &extracted_uinodes.uinodes {
|
|
if current_batch_handle != extracted_uinode.image {
|
|
if start != end {
|
|
commands.spawn_bundle((UiBatch {
|
|
range: start..end,
|
|
image: current_batch_handle,
|
|
z: last_z,
|
|
},));
|
|
start = end;
|
|
}
|
|
current_batch_handle = extracted_uinode.image.clone_weak();
|
|
}
|
|
|
|
let uinode_rect = extracted_uinode.rect;
|
|
let rect_size = uinode_rect.size().extend(1.0);
|
|
|
|
// Specify the corners of the node
|
|
let positions = QUAD_VERTEX_POSITIONS
|
|
.map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz());
|
|
|
|
// Calculate the effect of clipping
|
|
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
|
|
let positions_diff = if let Some(clip) = extracted_uinode.clip {
|
|
[
|
|
Vec2::new(
|
|
f32::max(clip.min.x - positions[0].x, 0.),
|
|
f32::max(clip.min.y - positions[0].y, 0.),
|
|
),
|
|
Vec2::new(
|
|
f32::min(clip.max.x - positions[1].x, 0.),
|
|
f32::max(clip.min.y - positions[1].y, 0.),
|
|
),
|
|
Vec2::new(
|
|
f32::min(clip.max.x - positions[2].x, 0.),
|
|
f32::min(clip.max.y - positions[2].y, 0.),
|
|
),
|
|
Vec2::new(
|
|
f32::max(clip.min.x - positions[3].x, 0.),
|
|
f32::min(clip.max.y - positions[3].y, 0.),
|
|
),
|
|
]
|
|
} else {
|
|
[Vec2::ZERO; 4]
|
|
};
|
|
|
|
let positions_clipped = [
|
|
positions[0] + positions_diff[0].extend(0.),
|
|
positions[1] + positions_diff[1].extend(0.),
|
|
positions[2] + positions_diff[2].extend(0.),
|
|
positions[3] + positions_diff[3].extend(0.),
|
|
];
|
|
|
|
let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size);
|
|
|
|
// Cull nodes that are completely clipped
|
|
if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
|
|
|| positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Clip UVs (Note: y is reversed in UV space)
|
|
let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max);
|
|
let uvs = [
|
|
Vec2::new(
|
|
uinode_rect.min.x + positions_diff[0].x,
|
|
uinode_rect.max.y - positions_diff[0].y,
|
|
),
|
|
Vec2::new(
|
|
uinode_rect.max.x + positions_diff[1].x,
|
|
uinode_rect.max.y - positions_diff[1].y,
|
|
),
|
|
Vec2::new(
|
|
uinode_rect.max.x + positions_diff[2].x,
|
|
uinode_rect.min.y - positions_diff[2].y,
|
|
),
|
|
Vec2::new(
|
|
uinode_rect.min.x + positions_diff[3].x,
|
|
uinode_rect.min.y - positions_diff[3].y,
|
|
),
|
|
]
|
|
.map(|pos| pos / atlas_extent);
|
|
|
|
for i in QUAD_INDICES {
|
|
ui_meta.vertices.push(UiVertex {
|
|
position: positions_clipped[i].into(),
|
|
uv: uvs[i].into(),
|
|
color: extracted_uinode.color.as_linear_rgba_f32(),
|
|
});
|
|
}
|
|
|
|
last_z = extracted_uinode.transform.w_axis[2];
|
|
end += QUAD_INDICES.len() as u32;
|
|
}
|
|
|
|
// if start != end, there is one last batch to process
|
|
if start != end {
|
|
commands.spawn_bundle((UiBatch {
|
|
range: start..end,
|
|
image: current_batch_handle,
|
|
z: last_z,
|
|
},));
|
|
}
|
|
|
|
ui_meta.vertices.write_buffer(&render_device, &render_queue);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct UiImageBindGroups {
|
|
pub values: HashMap<Handle<Image>, BindGroup>,
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn queue_uinodes(
|
|
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
|
render_device: Res<RenderDevice>,
|
|
mut ui_meta: ResMut<UiMeta>,
|
|
view_uniforms: Res<ViewUniforms>,
|
|
ui_pipeline: Res<UiPipeline>,
|
|
mut pipelines: ResMut<SpecializedRenderPipelines<UiPipeline>>,
|
|
mut pipeline_cache: ResMut<PipelineCache>,
|
|
mut image_bind_groups: ResMut<UiImageBindGroups>,
|
|
gpu_images: Res<RenderAssets<Image>>,
|
|
ui_batches: Query<(Entity, &UiBatch)>,
|
|
mut views: Query<&mut RenderPhase<TransparentUi>>,
|
|
events: Res<SpriteAssetEvents>,
|
|
) {
|
|
// If an image has changed, the GpuImage has (probably) changed
|
|
for event in &events.images {
|
|
match event {
|
|
AssetEvent::Created { .. } => None,
|
|
AssetEvent::Modified { handle } | AssetEvent::Removed { handle } => {
|
|
image_bind_groups.values.remove(handle)
|
|
}
|
|
};
|
|
}
|
|
|
|
if let Some(view_binding) = view_uniforms.uniforms.binding() {
|
|
ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
|
|
entries: &[BindGroupEntry {
|
|
binding: 0,
|
|
resource: view_binding,
|
|
}],
|
|
label: Some("ui_view_bind_group"),
|
|
layout: &ui_pipeline.view_layout,
|
|
}));
|
|
let draw_ui_function = draw_functions.read().get_id::<DrawUi>().unwrap();
|
|
let pipeline = pipelines.specialize(&mut pipeline_cache, &ui_pipeline, UiPipelineKey {});
|
|
for mut transparent_phase in views.iter_mut() {
|
|
for (entity, batch) in ui_batches.iter() {
|
|
image_bind_groups
|
|
.values
|
|
.entry(batch.image.clone_weak())
|
|
.or_insert_with(|| {
|
|
let gpu_image = gpu_images.get(&batch.image).unwrap();
|
|
render_device.create_bind_group(&BindGroupDescriptor {
|
|
entries: &[
|
|
BindGroupEntry {
|
|
binding: 0,
|
|
resource: BindingResource::TextureView(&gpu_image.texture_view),
|
|
},
|
|
BindGroupEntry {
|
|
binding: 1,
|
|
resource: BindingResource::Sampler(&gpu_image.sampler),
|
|
},
|
|
],
|
|
label: Some("ui_material_bind_group"),
|
|
layout: &ui_pipeline.image_layout,
|
|
})
|
|
});
|
|
transparent_phase.add(TransparentUi {
|
|
draw_function: draw_ui_function,
|
|
pipeline,
|
|
entity,
|
|
sort_key: FloatOrd(batch.z),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|