-
John Mitchell authoredc2aefd31
pipeline.rs 41.84 KiB
use std::marker::PhantomData;
use bevy::asset::HandleId;
use bevy::ecs::query::ROQueryItem;
use bevy::ecs::system::{SystemParam, SystemParamItem};
use bevy::prelude::{Commands, Mesh, Rect, Resource, Vec3, With};
use bevy::render::globals::{GlobalsBuffer, GlobalsUniform};
use bevy::render::mesh::VertexAttributeValues;
use bevy::render::render_phase::{
BatchedPhaseItem, DrawFunctionId, PhaseItem, RenderCommand, RenderCommandResult,
SetItemPipeline,
};
use bevy::render::render_resource::{
CachedRenderPipelineId, DynamicUniformBuffer, ShaderType, SpecializedRenderPipeline,
SpecializedRenderPipelines,
};
use bevy::render::view::ViewTarget;
use bevy::utils::FloatOrd;
use bevy::{
ecs::system::lifetimeless::{Read, SRes},
math::{Mat4, Quat, Vec2, Vec4},
prelude::{Component, Entity, FromWorld, Handle, Query, Res, ResMut, World},
render::{
color::Color,
render_asset::RenderAssets,
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType,
BlendState, BufferBindingType, BufferSize, BufferUsages, BufferVec, ColorTargetState,
ColorWrites, Extent3d, FragmentState, FrontFace, MultisampleState, PipelineCache,
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor,
SamplerBindingType, SamplerDescriptor, Shader, ShaderStages, TextureDescriptor,
TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
TextureViewDescriptor, TextureViewDimension, VertexAttribute, VertexBufferLayout,
VertexFormat, VertexState, VertexStepMode,
},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, GpuImage, Image},
},
utils::HashMap,
};
use bevy_svg::prelude::Svg;
use bytemuck::{Pod, Zeroable};
use kayak_font::{bevy::FontTextureCache, KayakFont};
use super::UNIFIED_SHADER_HANDLE;
use crate::prelude::Corner;
use crate::render::extract::{UIExtractedView, UIViewUniform, UIViewUniformOffset, UIViewUniforms};
use crate::render::opacity_layer::OpacityLayerManager;
use crate::render::svg::RenderSvgs;
use crate::render::ui_pass::{TransparentOpacityUI, TransparentUI, TransparentUIGeneric};
#[derive(Resource, Clone)]
pub struct UnifiedPipeline {
pub view_layout: BindGroupLayout,
pub types_layout: BindGroupLayout,
pub image_layout: BindGroupLayout,
empty_font_texture: GpuImage,
default_image: (GpuImage, BindGroup),
}
// const QUAD_VERTEX_POSITIONS: &[Vec3] = &[
// Vec3::from_array([0.0, 1.0, 0.0]),
// Vec3::from_array([1.0, 0.0, 0.0]),
// Vec3::from_array([0.0, 0.0, 0.0]),
// Vec3::from_array([0.0, 1.0, 0.0]),
// Vec3::from_array([1.0, 1.0, 0.0]),
// Vec3::from_array([1.0, 0.0, 0.0]),
// ];
const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
const QUAD_VERTEX_POSITIONS: [Vec2; 4] = [
Vec2::new(0.0, 0.0),
Vec2::new(1.0, 0.0),
Vec2::new(1.0, 1.0),
Vec2::new(0.0, 1.0),
];
#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UnifiedPipelineKey {
pub msaa: u32,
pub hdr: bool,
}
impl FromWorld for UnifiedPipeline {
fn from_world(world: &mut World) -> Self {
let world = world.cell();
let render_device = world.get_resource::<RenderDevice>().unwrap();
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(UIViewUniform::min_size()),
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::VERTEX_FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: Some(GlobalsUniform::min_size()),
},
count: None,
},
],
label: Some("ui_view_layout"),
});
let types_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(16),
},
count: None,
}],
label: Some("ui_types_layout"),
});
let image_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2Array,
},
count: None,
},
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
label: Some("image_layout"),
});
let empty_font_texture = FontTextureCache::get_empty(&render_device);
let texture_descriptor = TextureDescriptor {
label: Some("empty_texture"),
size: Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
view_formats: &[TextureFormat::Rgba8UnormSrgb],
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
};
let sampler_descriptor = SamplerDescriptor::default();
let texture = render_device.create_texture(&texture_descriptor);
let sampler = render_device.create_sampler(&sampler_descriptor);
let texture_view = texture.create_view(&TextureViewDescriptor {
label: Some("empty_texture_view"),
format: Some(TextureFormat::Rgba8UnormSrgb),
dimension: Some(TextureViewDimension::D2),
aspect: bevy::render::render_resource::TextureAspect::All,
base_mip_level: 0,
base_array_layer: 0,
mip_level_count: None,
array_layer_count: None,
});
let image = GpuImage {
texture,
sampler,
texture_view,
mip_level_count: 1,
size: Vec2::new(1.0, 1.0),
texture_format: TextureFormat::Rgba8UnormSrgb,
};
let binding = render_device.create_bind_group(&BindGroupDescriptor {
label: Some("default_image_bind_group"),
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&image.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&image.sampler),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(&empty_font_texture.texture_view),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&empty_font_texture.sampler),
},
],
layout: &image_layout,
});
UnifiedPipeline {
view_layout,
empty_font_texture,
types_layout,
image_layout,
default_image: (image, binding),
}
}
}
impl SpecializedRenderPipeline for UnifiedPipeline {
type Key = UnifiedPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let vertex_buffer_layout = VertexBufferLayout {
array_stride: 60,
step_mode: VertexStepMode::Vertex,
attributes: vec![
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 12,
shader_location: 1,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 28,
shader_location: 2,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 44,
shader_location: 3,
},
],
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: UNIFIED_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: UNIFIED_SHADER_HANDLE.typed::<Shader>(),
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: if key.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
blend: Some(BlendState::ALPHA_BLENDING),
// Some(BlendState {
// color: BlendComponent {
// src_factor: BlendFactor::SrcAlpha,
// dst_factor: BlendFactor::OneMinusSrcAlpha,
// operation: BlendOperation::Add,
// },
// alpha: BlendComponent {
// src_factor: BlendFactor::OneMinusDstAlpha,
// dst_factor: BlendFactor::One,
// operation: BlendOperation::Add,
// },
// }),
write_mask: ColorWrites::ALL,
})],
}),
layout: vec![
self.view_layout.clone(),
self.image_layout.clone(),
self.types_layout.clone(),
],
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: None,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
unclipped_depth: false,
},
depth_stencil: None,
multisample: MultisampleState {
count: key.msaa,
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("unified_pipeline".into()),
push_constant_ranges: vec![],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum UIQuadType {
Quad,
BoxShadow,
Text,
TextSubpixel,
Image,
Clip,
OpacityLayer,
DrawOpacityLayer,
None,
}
#[derive(Debug, Component, Clone)]
pub struct ExtractedQuad {
pub camera_entity: Entity,
pub rect: Rect,
pub color: Color,
pub char_id: u32,
pub z_index: f32,
pub font_handle: Option<Handle<KayakFont>>,
pub quad_type: UIQuadType,
pub type_index: u32,
pub border_radius: Corner<f32>,
pub image: Option<Handle<Image>>,
pub uv_min: Option<Vec2>,
pub uv_max: Option<Vec2>,
pub svg_handle: (Option<Handle<Svg>>, Option<Color>),
pub opacity_layer: u32,
}
impl Default for ExtractedQuad {
fn default() -> Self {
Self {
camera_entity: Entity::from_raw(0),
rect: Default::default(),
color: Default::default(),
char_id: Default::default(),
z_index: Default::default(),
font_handle: Default::default(),
quad_type: UIQuadType::Quad,
type_index: Default::default(),
border_radius: Default::default(),
image: Default::default(),
uv_min: Default::default(),
uv_max: Default::default(),
svg_handle: Default::default(),
opacity_layer: 0,
}
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct QuadVertex {
pub position: [f32; 3],
pub color: [f32; 4],
pub uv: [f32; 4],
pub pos_size: [f32; 4],
}
unsafe impl Zeroable for QuadVertex {}
unsafe impl Pod for QuadVertex {}
#[repr(C)]
#[derive(Copy, Clone, ShaderType)]
struct QuadType {
pub t: i32,
pub _padding_1: i32,
pub _padding_2: i32,
pub _padding_3: i32,
}
#[derive(Resource)]
pub struct QuadMeta {
pub vertices: BufferVec<QuadVertex>,
types_buffer: DynamicUniformBuffer<QuadType>,
types_bind_group: Option<BindGroup>,
}
impl Default for QuadMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
types_buffer: DynamicUniformBuffer::default(),
types_bind_group: None,
}
}
}
#[derive(Resource, Default)]
pub struct ExtractedQuads {
pub quads: Vec<ExtractedQuad>,
}
#[derive(Debug, Component, PartialEq, Copy, Clone)]
pub struct QuadBatch {
pub image_handle_id: Option<HandleId>,
pub font_handle_id: Option<HandleId>,
pub quad_type: UIQuadType,
pub type_id: u32,
pub z_index: f32,
}
#[derive(Default, Resource)]
pub struct ImageBindGroups {
values: HashMap<Handle<Image>, BindGroup>,
font_values: HashMap<Handle<KayakFont>, BindGroup>,
previous_sizes: HashMap<Handle<Image>, Vec2>,
}
#[derive(Component, Debug)]
pub struct UIViewBindGroup {
pub value: BindGroup,
}
pub fn queue_ui_view_bind_groups(
mut commands: Commands,
render_device: Res<RenderDevice>,
unified_pipeline: Res<UnifiedPipeline>,
view_uniforms: Res<UIViewUniforms>,
views: Query<Entity, With<UIExtractedView>>,
globals_buffer: Res<GlobalsBuffer>,
) {
if let (Some(view_binding), Some(globals)) = (
view_uniforms.uniforms.binding(),
globals_buffer.buffer.binding(),
) {
for entity in &views {
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: view_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: globals.clone(),
},
],
label: Some("ui_view_bind_group"),
layout: &unified_pipeline.view_layout,
});
commands.entity(entity).insert(UIViewBindGroup {
value: view_bind_group,
});
}
}
}
#[derive(Resource, Default, Debug, Clone, Copy)]
pub struct QuadTypeOffsets {
pub quad_type_offset: u32,
pub text_sub_pixel_type_offset: u32,
pub text_type_offset: u32,
pub image_type_offset: u32,
pub box_shadow_type_offset: u32,
}
pub fn queue_quad_types(
mut commands: Commands,
quad_pipeline: Res<UnifiedPipeline>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut quad_meta: ResMut<QuadMeta>,
) {
quad_meta.types_buffer.clear();
// sprite_meta.types_buffer.reserve(2, &render_device);
let quad_type_offset = quad_meta.types_buffer.push(QuadType {
t: 0,
_padding_1: 0,
_padding_2: 0,
_padding_3: 0,
});
let text_sub_pixel_type_offset = quad_meta.types_buffer.push(QuadType {
t: 1,
_padding_1: 0,
_padding_2: 0,
_padding_3: 0,
});
let text_type_offset = quad_meta.types_buffer.push(QuadType {
t: 2,
_padding_1: 0,
_padding_2: 0,
_padding_3: 0,
});
let image_type_offset = quad_meta.types_buffer.push(QuadType {
t: 3,
_padding_1: 0,
_padding_2: 0,
_padding_3: 0,
});
let box_shadow_type_offset = quad_meta.types_buffer.push(QuadType {
t: 4,
_padding_1: 0,
_padding_2: 0,
_padding_3: 0,
});
let quad_type_offsets = QuadTypeOffsets {
quad_type_offset,
text_sub_pixel_type_offset,
text_type_offset,
image_type_offset,
box_shadow_type_offset,
};
commands.insert_resource(quad_type_offsets);
quad_meta
.types_buffer
.write_buffer(&render_device, &render_queue);
if let Some(type_binding) = quad_meta.types_buffer.binding() {
quad_meta.types_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: type_binding,
}],
label: Some("quad_type_bind_group"),
layout: &quad_pipeline.types_layout,
}));
}
}
#[derive(Resource, Default)]
pub struct PreviousClip {
pub rect: Rect,
}
#[derive(Resource, Default)]
pub struct PreviousIndex {
pub index: u32,
}
#[derive(SystemParam)]
pub struct QueueQuads<'w, 's> {
render_svgs: Res<'w, RenderSvgs>,
opacity_layers: Res<'w, OpacityLayerManager>,
commands: Commands<'w, 's>,
draw_functions: Res<'w, DrawFunctions<TransparentUI>>,
draw_functions_opacity: Res<'w, DrawFunctions<TransparentOpacityUI>>,
render_device: Res<'w, RenderDevice>,
quad_meta: ResMut<'w, QuadMeta>,
quad_pipeline: Res<'w, UnifiedPipeline>,
pipelines: ResMut<'w, SpecializedRenderPipelines<UnifiedPipeline>>,
pipeline_cache: Res<'w, PipelineCache>,
extracted_quads: ResMut<'w, ExtractedQuads>,
views: Query<
'w,
's,
(
Entity,
&'static mut RenderPhase<TransparentUI>,
&'static mut RenderPhase<TransparentOpacityUI>,
&'static UIExtractedView,
),
>,
image_bind_groups: ResMut<'w, ImageBindGroups>,
unified_pipeline: Res<'w, UnifiedPipeline>,
gpu_images: Res<'w, RenderAssets<Image>>,
font_texture_cache: Res<'w, FontTextureCache>,
quad_type_offsets: Res<'w, QuadTypeOffsets>,
prev_clip: ResMut<'w, PreviousClip>,
prev_index: ResMut<'w, PreviousIndex>,
}
pub fn queue_quads(queue_quads: QueueQuads) {
let QueueQuads {
render_svgs,
opacity_layers,
mut commands,
draw_functions,
draw_functions_opacity,
render_device,
mut quad_meta,
quad_pipeline,
mut pipelines,
pipeline_cache,
mut extracted_quads,
mut views,
mut image_bind_groups,
unified_pipeline,
gpu_images,
font_texture_cache,
quad_type_offsets,
mut prev_clip,
mut prev_index,
} = queue_quads;
let extracted_sprite_len = extracted_quads.quads.len();
// don't create buffers when there are no quads
if extracted_sprite_len == 0 {
return;
}
quad_meta.vertices.clear();
quad_meta.vertices.reserve(
extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(),
&render_device,
);
// Sort sprites by z for correct transparency and then by handle to improve batching
// NOTE: This can be done independent of views by reasonably assuming that all 2D views look along the negative-z axis in world space
let extracted_quads = &mut extracted_quads.quads;
extracted_quads.sort_unstable_by(|a, b| a.z_index.partial_cmp(&b.z_index).unwrap());
let mut current_batch = QuadBatch {
image_handle_id: None,
font_handle_id: None,
quad_type: UIQuadType::None,
type_id: quad_type_offsets.quad_type_offset,
z_index: -999.0,
};
let mut current_batch_entity = Entity::PLACEHOLDER;
// Vertex buffer indices
let mut index = 0;
let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap();
let draw_opacity_quad = draw_functions_opacity
.read()
.get_id::<DrawUITransparent>()
.unwrap();
for (camera_entity, mut transparent_phase, mut opacity_transparent_phase, view) in
views.iter_mut()
{
let key = UnifiedPipelineKey {
msaa: 1,
hdr: view.hdr,
};
let spec_pipeline = pipelines.specialize(&pipeline_cache, &quad_pipeline, key);
for quad in extracted_quads.iter_mut() {
if quad.quad_type == UIQuadType::Clip {
prev_clip.rect = quad.rect;
}
if prev_clip.rect.width() < 1.0 || prev_clip.rect.height() < 1.0 {
continue;
}
queue_quads_inner(
&mut commands,
&render_device,
&font_texture_cache,
&opacity_layers,
&mut image_bind_groups,
&gpu_images,
&unified_pipeline,
&render_svgs,
&mut transparent_phase,
&mut opacity_transparent_phase,
draw_opacity_quad,
draw_quad,
spec_pipeline,
&mut quad_meta,
quad,
camera_entity,
*quad_type_offsets,
&mut current_batch,
&mut current_batch_entity,
&mut index,
)
}
}
prev_index.index = index;
}
pub fn queue_quads_inner(
commands: &mut Commands,
render_device: &RenderDevice,
font_texture_cache: &FontTextureCache,
opacity_layers: &OpacityLayerManager,
image_bind_groups: &mut ImageBindGroups,
gpu_images: &RenderAssets<Image>,
unified_pipeline: &UnifiedPipeline,
render_svgs: &RenderSvgs,
transparent_phase: &mut RenderPhase<TransparentUI>,
opacity_transparent_phase: &mut RenderPhase<TransparentOpacityUI>,
draw_opacity_quad: DrawFunctionId,
draw_quad: DrawFunctionId,
spec_pipeline: CachedRenderPipelineId,
quad_meta: &mut QuadMeta,
quad: &mut ExtractedQuad,
camera_entity: Entity,
quad_type_offsets: QuadTypeOffsets,
current_batch: &mut QuadBatch,
current_batch_entity: &mut Entity,
index: &mut u32,
) {
if camera_entity != quad.camera_entity {
return;
}
match quad.quad_type {
UIQuadType::Quad => quad.type_index = quad_type_offsets.quad_type_offset,
UIQuadType::Text => quad.type_index = quad_type_offsets.text_type_offset,
UIQuadType::TextSubpixel => quad.type_index = quad_type_offsets.text_sub_pixel_type_offset,
UIQuadType::Image => quad.type_index = quad_type_offsets.image_type_offset,
UIQuadType::BoxShadow => quad.type_index = quad_type_offsets.box_shadow_type_offset,
UIQuadType::Clip => quad.type_index = 100000,
UIQuadType::None => quad.type_index = 100001,
UIQuadType::OpacityLayer => quad.type_index = 100002,
UIQuadType::DrawOpacityLayer => quad.type_index = quad_type_offsets.image_type_offset,
};
// Ignore opacity layers
if quad.quad_type == UIQuadType::OpacityLayer || quad.quad_type == UIQuadType::None {
return;
}
let mut new_batch = QuadBatch {
image_handle_id: quad.image.clone().map(HandleId::from),
font_handle_id: quad.font_handle.clone().map(HandleId::from),
quad_type: quad.quad_type,
type_id: quad.type_index,
z_index: 0.0, // z_index: quad.z_index,
};
if new_batch != *current_batch
|| matches!(quad.quad_type, UIQuadType::Clip)
|| matches!(quad.quad_type, UIQuadType::DrawOpacityLayer)
{
if let Some(image_handle) = quad.image.as_ref() {
if let Some(gpu_image) = gpu_images.get(image_handle) {
image_bind_groups
.values
.entry(image_handle.clone_weak())
.or_insert_with(|| {
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),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(
&unified_pipeline.empty_font_texture.texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(
&unified_pipeline.empty_font_texture.sampler,
),
},
],
label: Some("ui_image_bind_group"),
layout: &unified_pipeline.image_layout,
})
});
} else {
// Skip unloaded texture.
return;
}
}
if let Some(font_handle) = quad.font_handle.as_ref() {
if let Some(gpu_image) = font_texture_cache.get_gpu_image(font_handle, gpu_images) {
new_batch.image_handle_id = Some(font_handle.id());
image_bind_groups
.font_values
.entry(font_handle.clone_weak())
.or_insert_with(|| {
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(
&unified_pipeline.default_image.0.texture_view,
),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(
&unified_pipeline.default_image.0.sampler,
),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(&gpu_image.texture_view),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
label: Some("ui_text_bind_group"),
layout: &unified_pipeline.image_layout,
})
});
}
}
// Start new batch
*current_batch = new_batch;
if quad.quad_type == UIQuadType::DrawOpacityLayer {
if let Some(layer) = opacity_layers.camera_layers.get(&camera_entity) {
let image_handle = layer.get_image_handle(quad.opacity_layer);
if let Some(gpu_image) = gpu_images.get(&image_handle) {
let new_image = if let Some(prev_size) =
image_bind_groups.previous_sizes.get(&image_handle)
{
if gpu_image.size != *prev_size {
image_bind_groups
.previous_sizes
.insert(image_handle.clone_weak(), gpu_image.size);
true
} else {
false
}
} else {
image_bind_groups
.previous_sizes
.insert(image_handle.clone_weak(), gpu_image.size);
true
};
if new_image {
image_bind_groups.values.insert(
image_handle.clone_weak(),
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),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(
&unified_pipeline.empty_font_texture.texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(
&unified_pipeline.empty_font_texture.sampler,
),
},
],
label: Some("draw_opacity_layer_bind_group"),
layout: &unified_pipeline.image_layout,
}),
);
}
current_batch.image_handle_id = Some(image_handle).map(HandleId::from);
// bevy::prelude::info!("Attaching opacity layer with index: {} with view: {:?}", quad.opacity_layer, gpu_image.texture_view);
} else {
return;
}
}
quad.opacity_layer = 0;
}
*current_batch_entity = commands.spawn(*current_batch).id();
// dbg!((current_batch_entity, current_batch, quad.rect));
}
let sprite_rect = quad.rect;
let item_start = *index;
let mut item_end = *index;
if let (Some(svg_handle), color) = (quad.svg_handle.0.as_ref(), quad.svg_handle.1.as_ref()) {
if let Some((svg, mesh)) = render_svgs.get(svg_handle) {
let new_height = (svg.view_box.h as f32 / svg.view_box.w as f32) * sprite_rect.size().x;
let svg_scale_x = sprite_rect.size().x / svg.view_box.w as f32;
let svg_scale_y = new_height / svg.view_box.h as f32;
let positions = mesh
.attribute(Mesh::ATTRIBUTE_POSITION)
.unwrap()
.as_float3()
.unwrap();
let colors = match mesh.attribute(Mesh::ATTRIBUTE_COLOR).unwrap() {
VertexAttributeValues::Float32x4(d) => Some(d),
_ => None,
}
.unwrap();
let indices = mesh.indices().unwrap();
for index in indices.iter() {
let position = positions[index];
let color = if let Some(color) = color {
[color.r(), color.g(), color.b(), color.a()]
} else {
colors[index]
};
let world = Mat4::from_scale_rotation_translation(
Vec3::new(svg_scale_x, svg_scale_y, 1.0), //sprite_rect.size().extend(1.0),
Quat::default(),
sprite_rect.min.extend(0.0),
);
let final_position = (world
* Vec4::new(
position[0], // - 34.5,
-position[1], // - 95.0,
position[2],
1.0,
))
.truncate();
quad_meta.vertices.push(QuadVertex {
position: final_position.into(),
color,
uv: [0.0; 4],
pos_size: [
0.0,
0.0,
sprite_rect.size().x,
sprite_rect.size().y,
],
});
}
*index += indices.len() as u32;
item_end = *index;
}
} else {
let color = quad.color.as_linear_rgba_f32();
let uv_min = quad.uv_min.unwrap_or(Vec2::ZERO);
let uv_max = quad.uv_max.unwrap_or(Vec2::ONE);
let bottom_left = Vec4::new(
uv_min.x,
uv_min.y,
quad.char_id as f32,
quad.border_radius.bottom_left,
);
let top_left = Vec4::new(
uv_min.x,
uv_max.y,
quad.char_id as f32,
quad.border_radius.top_left,
);
let top_right = Vec4::new(
uv_max.x,
uv_max.y,
quad.char_id as f32,
quad.border_radius.top_right,
);
let bottom_right = Vec4::new(
uv_max.x,
uv_min.y,
quad.char_id as f32,
quad.border_radius.bottom_right,
);
let uvs: [[f32; 4]; 6] = [
top_left.into(),
bottom_right.into(),
bottom_left.into(),
top_left.into(),
top_right.into(),
bottom_right.into(),
];
const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
const QUAD_VERTEX_POSITIONS: [Vec2; 4] = [
Vec2::new(0.0, 0.0),
Vec2::new(1.0, 0.0),
Vec2::new(1.0, 1.0),
Vec2::new(0.0, 1.0),
];
if !matches!(quad.quad_type, UIQuadType::Clip) {
for (index, vertex_index) in QUAD_INDICES.iter().enumerate() {
let vertex_position = QUAD_VERTEX_POSITIONS[*vertex_index];
let world = Mat4::from_scale_rotation_translation(
sprite_rect.size().extend(1.0),
Quat::default(),
sprite_rect.min.extend(0.0),
);
let final_position = (world * vertex_position.extend(0.0).extend(1.0)).truncate();
quad_meta.vertices.push(QuadVertex {
position: final_position.into(),
color,
uv: uvs[index],
pos_size: [
sprite_rect.min.x,
sprite_rect.min.y,
sprite_rect.size().x,
sprite_rect.size().y,
],
});
}
*index += QUAD_INDICES.len() as u32;
item_end = *index;
}
}
if quad.opacity_layer > 0 {
opacity_transparent_phase.add(TransparentOpacityUI {
draw_function: draw_opacity_quad,
pipeline: spec_pipeline,
entity: *current_batch_entity,
sort_key: FloatOrd(quad.z_index),
quad_type: quad.quad_type,
type_index: quad.type_index,
rect: sprite_rect,
batch_range: Some(item_start..item_end),
opacity_layer: quad.opacity_layer,
});
} else {
transparent_phase.add(TransparentUI {
draw_function: draw_quad,
pipeline: spec_pipeline,
entity: *current_batch_entity,
sort_key: FloatOrd(quad.z_index),
quad_type: quad.quad_type,
type_index: quad.type_index,
rect: sprite_rect,
batch_range: Some(item_start..item_end),
});
}
}
pub type DrawUI = (
SetItemPipeline,
SetUIViewBindGroup<TransparentUI, 0>,
DrawUIDraw<TransparentUI>,
);
pub type DrawUITransparent = (
SetItemPipeline,
SetUIViewBindGroup<TransparentOpacityUI, 0>,
DrawUIDraw<TransparentOpacityUI>,
);
pub struct SetUIViewBindGroup<T, const I: usize> {
phantom: PhantomData<T>,
}
impl<T: PhaseItem, const I: usize> RenderCommand<T> for SetUIViewBindGroup<T, I> {
type Param = ();
type ViewWorldQuery = (Read<UIViewUniformOffset>, Read<UIViewBindGroup>);
type ItemWorldQuery = ();
#[inline]
fn render<'w>(
_item: &T,
(view_uniform, ui_view_bind_group): ROQueryItem<'w, Self::ViewWorldQuery>,
_: (),
_: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
pass.set_bind_group(I, &ui_view_bind_group.value, &[view_uniform.offset]);
RenderCommandResult::Success
}
}
#[derive(Default)]
pub struct DrawUIDraw<T> {
phantom: PhantomData<T>,
}
impl<T: PhaseItem + TransparentUIGeneric + BatchedPhaseItem> RenderCommand<T> for DrawUIDraw<T> {
type Param = (SRes<QuadMeta>, SRes<UnifiedPipeline>, SRes<ImageBindGroups>);
type ViewWorldQuery = Read<UIExtractedView>;
type ItemWorldQuery = Read<QuadBatch>;
fn render<'w>(
item: &T,
view: bevy::ecs::query::ROQueryItem<'w, Self::ViewWorldQuery>,
batch: bevy::ecs::query::ROQueryItem<'w, Self::ItemWorldQuery>,
param: bevy::ecs::system::SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let (quad_meta, unified_pipeline, image_bind_groups) = param;
let quad_meta = quad_meta.into_inner();
if item.get_quad_type() == UIQuadType::Clip {
let window_size = (view.viewport.z as f32, view.viewport.w as f32);
let rect = item.get_rect();
let x = rect.min.x as u32;
let y = rect.min.y as u32;
let mut width = rect.width() as u32;
let mut height = rect.height() as u32;
width = width.min(window_size.0 as u32);
height = height.min(window_size.1 as u32);
if width == 0 || height == 0 || x > window_size.0 as u32 || y > window_size.1 as u32 {
return RenderCommandResult::Success;
}
if x + width >= window_size.0 as u32 {
width = window_size.0 as u32 - x;
}
if y + height >= window_size.1 as u32 {
height = window_size.1 as u32 - y;
}
pass.set_scissor_rect(x, y, width, height);
return RenderCommandResult::Success;
}
pass.set_vertex_buffer(0, quad_meta.vertices.buffer().unwrap().slice(..));
pass.set_bind_group(
2,
quad_meta.types_bind_group.as_ref().unwrap(),
&[batch.type_id],
);
let unified_pipeline = unified_pipeline.into_inner();
// if let Some(font_handle) = batch.font_handle_id.as_ref() {
// if let Some(image_bindings) = font_texture_cache
// .into_inner()
// .get_binding(&Handle::weak(*font_handle))
// {
// pass.set_bind_group(1, image_bindings, &[]);
// } else {
// pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]);
// }
// } else {
// pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]);
// }
if let Some(image_handle) = batch.image_handle_id.as_ref() {
let image_bind_groups = image_bind_groups.into_inner();
if let Some(bind_group) = image_bind_groups.values.get(&Handle::weak(*image_handle)) {
pass.set_bind_group(1, bind_group, &[]);
} else if let Some(bind_group) = image_bind_groups
.font_values
.get(&Handle::weak(*image_handle))
{
pass.set_bind_group(1, bind_group, &[]);
} else {
pass.set_bind_group(1, &unified_pipeline.default_image.1, &[]);
}
} else {
pass.set_bind_group(1, &unified_pipeline.default_image.1, &[]);
}
pass.draw(item.batch_range().as_ref().unwrap().clone(), 0..1);
RenderCommandResult::Success
}
}