
This commit allows Bevy to use `multi_draw_indirect_count` for drawing meshes. The `multi_draw_indirect_count` feature works just like `multi_draw_indirect`, but it takes the number of indirect parameters from a GPU buffer rather than specifying it on the CPU. Currently, the CPU constructs the list of indirect draw parameters with the instance count for each batch set to zero, uploads the resulting buffer to the GPU, and dispatches a compute shader that bumps the instance count for each mesh that survives culling. Unfortunately, this is inefficient when we support `multi_draw_indirect_count`. Draw commands corresponding to meshes for which all instances were culled will remain present in the list when calling `multi_draw_indirect_count`, causing overhead. Proper use of `multi_draw_indirect_count` requires eliminating these empty draw commands. To address this inefficiency, this PR makes Bevy fully construct the indirect draw commands on the GPU instead of on the CPU. Instead of writing instance counts to the draw command buffer, the mesh preprocessing shader now writes them to a separate *indirect metadata buffer*. A second compute dispatch known as the *build indirect parameters* shader runs after mesh preprocessing and converts the indirect draw metadata into actual indirect draw commands for the GPU. The build indirect parameters shader operates on a batch at a time, rather than an instance at a time, and as such each thread writes only 0 or 1 indirect draw parameters, simplifying the current logic in `mesh_preprocessing`, which currently has to have special cases for the first mesh in each batch. The build indirect parameters shader emits draw commands in a tightly packed manner, enabling maximally efficient use of `multi_draw_indirect_count`. Along the way, this patch switches mesh preprocessing to dispatch one compute invocation per render phase per view, instead of dispatching one compute invocation per view. This is preparation for two-phase occlusion culling, in which we will have two mesh preprocessing stages. In that scenario, the first mesh preprocessing stage must only process opaque and alpha tested objects, so the work items must be separated into those that are opaque or alpha tested and those that aren't. Thus this PR splits out the work items into a separate buffer for each phase. As this patch rewrites so much of the mesh preprocessing infrastructure, it was simpler to just fold the change into this patch instead of deferring it to the forthcoming occlusion culling PR. Finally, this patch changes mesh preprocessing so that it runs separately for indexed and non-indexed meshes. This is because draw commands for indexed and non-indexed meshes have different sizes and layouts. *The existing code is actually broken for non-indexed meshes*, as it attempts to overlay the indirect parameters for non-indexed meshes on top of those for indexed meshes. Consequently, right now the parameters will be read incorrectly when multiple non-indexed meshes are multi-drawn together. *This is a bug fix* and, as with the change to dispatch phases separately noted above, was easiest to include in this patch as opposed to separately. ## Migration Guide * Systems that add custom phase items now need to populate the indirect drawing-related buffers. See the `specialized_mesh_pipeline` example for an example of how this is done.
846 lines
31 KiB
Rust
846 lines
31 KiB
Rust
use bevy_app::Plugin;
|
|
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
|
|
|
use crate::Material2dBindGroupId;
|
|
use bevy_core_pipeline::{
|
|
core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT},
|
|
tonemapping::{
|
|
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
|
|
},
|
|
};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::{
|
|
prelude::*,
|
|
query::ROQueryItem,
|
|
system::{lifetimeless::*, SystemParamItem, SystemState},
|
|
};
|
|
use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
|
|
use bevy_math::{Affine3, Vec4};
|
|
use bevy_render::{
|
|
batching::{
|
|
gpu_preprocessing::IndirectParametersMetadata,
|
|
no_gpu_preprocessing::{
|
|
self, batch_and_prepare_binned_render_phase, batch_and_prepare_sorted_render_phase,
|
|
write_batched_instance_buffer, BatchedInstanceBuffer,
|
|
},
|
|
GetBatchData, GetFullBatchData, NoAutomaticBatching,
|
|
},
|
|
globals::{GlobalsBuffer, GlobalsUniform},
|
|
mesh::{
|
|
allocator::MeshAllocator, Mesh, Mesh2d, MeshVertexBufferLayoutRef, RenderMesh,
|
|
RenderMeshBufferInfo,
|
|
},
|
|
render_asset::RenderAssets,
|
|
render_phase::{
|
|
PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, TrackedRenderPass,
|
|
},
|
|
render_resource::{binding_types::uniform_buffer, *},
|
|
renderer::{RenderDevice, RenderQueue},
|
|
sync_world::{MainEntity, MainEntityHashMap},
|
|
texture::{DefaultImageSampler, FallbackImage, GpuImage},
|
|
view::{
|
|
ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility,
|
|
},
|
|
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
|
};
|
|
use bevy_transform::components::GlobalTransform;
|
|
use nonmax::NonMaxU32;
|
|
use tracing::error;
|
|
|
|
#[derive(Default)]
|
|
pub struct Mesh2dRenderPlugin;
|
|
|
|
pub const MESH2D_VERTEX_OUTPUT: Handle<Shader> = Handle::weak_from_u128(7646632476603252194);
|
|
pub const MESH2D_VIEW_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(12677582416765805110);
|
|
pub const MESH2D_VIEW_BINDINGS_HANDLE: Handle<Shader> = Handle::weak_from_u128(6901431444735842434);
|
|
pub const MESH2D_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(8994673400261890424);
|
|
pub const MESH2D_BINDINGS_HANDLE: Handle<Shader> = Handle::weak_from_u128(8983617858458862856);
|
|
pub const MESH2D_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(4976379308250389413);
|
|
pub const MESH2D_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(2971387252468633715);
|
|
|
|
impl Plugin for Mesh2dRenderPlugin {
|
|
fn build(&self, app: &mut bevy_app::App) {
|
|
load_internal_asset!(
|
|
app,
|
|
MESH2D_VERTEX_OUTPUT,
|
|
"mesh2d_vertex_output.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESH2D_VIEW_TYPES_HANDLE,
|
|
"mesh2d_view_types.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESH2D_VIEW_BINDINGS_HANDLE,
|
|
"mesh2d_view_bindings.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESH2D_TYPES_HANDLE,
|
|
"mesh2d_types.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(
|
|
app,
|
|
MESH2D_FUNCTIONS_HANDLE,
|
|
"mesh2d_functions.wgsl",
|
|
Shader::from_wgsl
|
|
);
|
|
load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl);
|
|
|
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
render_app
|
|
.init_resource::<RenderMesh2dInstances>()
|
|
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
|
|
.add_systems(ExtractSchedule, extract_mesh2d)
|
|
.add_systems(
|
|
Render,
|
|
(
|
|
batch_and_prepare_binned_render_phase::<Opaque2d, Mesh2dPipeline>
|
|
.in_set(RenderSet::PrepareResources),
|
|
batch_and_prepare_binned_render_phase::<AlphaMask2d, Mesh2dPipeline>
|
|
.in_set(RenderSet::PrepareResources),
|
|
batch_and_prepare_sorted_render_phase::<Transparent2d, Mesh2dPipeline>
|
|
.in_set(RenderSet::PrepareResources),
|
|
write_batched_instance_buffer::<Mesh2dPipeline>
|
|
.in_set(RenderSet::PrepareResourcesFlush),
|
|
prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups),
|
|
prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
|
no_gpu_preprocessing::clear_batched_cpu_instance_buffers::<Mesh2dPipeline>
|
|
.in_set(RenderSet::Cleanup)
|
|
.after(RenderSet::Render),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn finish(&self, app: &mut bevy_app::App) {
|
|
let mut mesh_bindings_shader_defs = Vec::with_capacity(1);
|
|
|
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
let render_device = render_app.world().resource::<RenderDevice>();
|
|
let batched_instance_buffer =
|
|
BatchedInstanceBuffer::<Mesh2dUniform>::new(render_device);
|
|
|
|
if let Some(per_object_buffer_batch_size) =
|
|
GpuArrayBuffer::<Mesh2dUniform>::batch_size(render_device)
|
|
{
|
|
mesh_bindings_shader_defs.push(ShaderDefVal::UInt(
|
|
"PER_OBJECT_BUFFER_BATCH_SIZE".into(),
|
|
per_object_buffer_batch_size,
|
|
));
|
|
}
|
|
|
|
render_app
|
|
.insert_resource(batched_instance_buffer)
|
|
.init_resource::<Mesh2dPipeline>();
|
|
}
|
|
|
|
// Load the mesh_bindings shader module here as it depends on runtime information about
|
|
// whether storage buffers are supported, or the maximum uniform buffer binding size.
|
|
load_internal_asset!(
|
|
app,
|
|
MESH2D_BINDINGS_HANDLE,
|
|
"mesh2d_bindings.wgsl",
|
|
Shader::from_wgsl_with_defs,
|
|
mesh_bindings_shader_defs
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
pub struct Mesh2dTransforms {
|
|
pub world_from_local: Affine3,
|
|
pub flags: u32,
|
|
}
|
|
|
|
#[derive(ShaderType, Clone, Copy)]
|
|
pub struct Mesh2dUniform {
|
|
// Affine 4x3 matrix transposed to 3x4
|
|
pub world_from_local: [Vec4; 3],
|
|
// 3x3 matrix packed in mat2x4 and f32 as:
|
|
// [0].xyz, [1].x,
|
|
// [1].yz, [2].xy
|
|
// [2].z
|
|
pub local_from_world_transpose_a: [Vec4; 2],
|
|
pub local_from_world_transpose_b: f32,
|
|
pub flags: u32,
|
|
}
|
|
|
|
impl From<&Mesh2dTransforms> for Mesh2dUniform {
|
|
fn from(mesh_transforms: &Mesh2dTransforms) -> Self {
|
|
let (local_from_world_transpose_a, local_from_world_transpose_b) =
|
|
mesh_transforms.world_from_local.inverse_transpose_3x3();
|
|
Self {
|
|
world_from_local: mesh_transforms.world_from_local.to_transpose(),
|
|
local_from_world_transpose_a,
|
|
local_from_world_transpose_b,
|
|
flags: mesh_transforms.flags,
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: These must match the bit flags in bevy_sprite/src/mesh2d/mesh2d.wgsl!
|
|
bitflags::bitflags! {
|
|
#[repr(transparent)]
|
|
pub struct MeshFlags: u32 {
|
|
const NONE = 0;
|
|
const UNINITIALIZED = 0xFFFF;
|
|
}
|
|
}
|
|
|
|
pub struct RenderMesh2dInstance {
|
|
pub transforms: Mesh2dTransforms,
|
|
pub mesh_asset_id: AssetId<Mesh>,
|
|
pub material_bind_group_id: Material2dBindGroupId,
|
|
pub automatic_batching: bool,
|
|
}
|
|
|
|
#[derive(Default, Resource, Deref, DerefMut)]
|
|
pub struct RenderMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
|
|
|
|
#[derive(Component, Default)]
|
|
pub struct Mesh2dMarker;
|
|
|
|
pub fn extract_mesh2d(
|
|
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
|
|
query: Extract<
|
|
Query<(
|
|
Entity,
|
|
&ViewVisibility,
|
|
&GlobalTransform,
|
|
&Mesh2d,
|
|
Has<NoAutomaticBatching>,
|
|
)>,
|
|
>,
|
|
) {
|
|
render_mesh_instances.clear();
|
|
|
|
for (entity, view_visibility, transform, handle, no_automatic_batching) in &query {
|
|
if !view_visibility.get() {
|
|
continue;
|
|
}
|
|
render_mesh_instances.insert(
|
|
entity.into(),
|
|
RenderMesh2dInstance {
|
|
transforms: Mesh2dTransforms {
|
|
world_from_local: (&transform.affine()).into(),
|
|
flags: MeshFlags::empty().bits(),
|
|
},
|
|
mesh_asset_id: handle.0.id(),
|
|
material_bind_group_id: Material2dBindGroupId::default(),
|
|
automatic_batching: !no_automatic_batching,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Resource, Clone)]
|
|
pub struct Mesh2dPipeline {
|
|
pub view_layout: BindGroupLayout,
|
|
pub mesh_layout: BindGroupLayout,
|
|
// This dummy white texture is to be used in place of optional textures
|
|
pub dummy_white_gpu_image: GpuImage,
|
|
pub per_object_buffer_batch_size: Option<u32>,
|
|
}
|
|
|
|
impl FromWorld for Mesh2dPipeline {
|
|
fn from_world(world: &mut World) -> Self {
|
|
let mut system_state: SystemState<(
|
|
Res<RenderDevice>,
|
|
Res<RenderQueue>,
|
|
Res<DefaultImageSampler>,
|
|
)> = SystemState::new(world);
|
|
let (render_device, render_queue, default_sampler) = system_state.get_mut(world);
|
|
let render_device = render_device.into_inner();
|
|
let tonemapping_lut_entries = get_lut_bind_group_layout_entries();
|
|
let view_layout = render_device.create_bind_group_layout(
|
|
"mesh2d_view_layout",
|
|
&BindGroupLayoutEntries::with_indices(
|
|
ShaderStages::VERTEX_FRAGMENT,
|
|
(
|
|
(0, uniform_buffer::<ViewUniform>(true)),
|
|
(1, uniform_buffer::<GlobalsUniform>(false)),
|
|
(
|
|
2,
|
|
tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT),
|
|
),
|
|
(
|
|
3,
|
|
tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
let mesh_layout = render_device.create_bind_group_layout(
|
|
"mesh2d_layout",
|
|
&BindGroupLayoutEntries::single(
|
|
ShaderStages::VERTEX_FRAGMENT,
|
|
GpuArrayBuffer::<Mesh2dUniform>::binding_layout(render_device),
|
|
),
|
|
);
|
|
// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures
|
|
let dummy_white_gpu_image = {
|
|
let image = Image::default();
|
|
let texture = render_device.create_texture(&image.texture_descriptor);
|
|
let sampler = match image.sampler {
|
|
ImageSampler::Default => (**default_sampler).clone(),
|
|
ImageSampler::Descriptor(ref descriptor) => {
|
|
render_device.create_sampler(&descriptor.as_wgpu())
|
|
}
|
|
};
|
|
|
|
let format_size = image.texture_descriptor.format.pixel_size();
|
|
render_queue.write_texture(
|
|
texture.as_image_copy(),
|
|
&image.data,
|
|
ImageDataLayout {
|
|
offset: 0,
|
|
bytes_per_row: Some(image.width() * format_size as u32),
|
|
rows_per_image: None,
|
|
},
|
|
image.texture_descriptor.size,
|
|
);
|
|
|
|
let texture_view = texture.create_view(&TextureViewDescriptor::default());
|
|
GpuImage {
|
|
texture,
|
|
texture_view,
|
|
texture_format: image.texture_descriptor.format,
|
|
sampler,
|
|
size: image.texture_descriptor.size,
|
|
mip_level_count: image.texture_descriptor.mip_level_count,
|
|
}
|
|
};
|
|
Mesh2dPipeline {
|
|
view_layout,
|
|
mesh_layout,
|
|
dummy_white_gpu_image,
|
|
per_object_buffer_batch_size: GpuArrayBuffer::<Mesh2dUniform>::batch_size(
|
|
render_device,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Mesh2dPipeline {
|
|
pub fn get_image_texture<'a>(
|
|
&'a self,
|
|
gpu_images: &'a RenderAssets<GpuImage>,
|
|
handle_option: &Option<Handle<Image>>,
|
|
) -> Option<(&'a TextureView, &'a Sampler)> {
|
|
if let Some(handle) = handle_option {
|
|
let gpu_image = gpu_images.get(handle)?;
|
|
Some((&gpu_image.texture_view, &gpu_image.sampler))
|
|
} else {
|
|
Some((
|
|
&self.dummy_white_gpu_image.texture_view,
|
|
&self.dummy_white_gpu_image.sampler,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GetBatchData for Mesh2dPipeline {
|
|
type Param = (
|
|
SRes<RenderMesh2dInstances>,
|
|
SRes<RenderAssets<RenderMesh>>,
|
|
SRes<MeshAllocator>,
|
|
);
|
|
type CompareData = (Material2dBindGroupId, AssetId<Mesh>);
|
|
type BufferData = Mesh2dUniform;
|
|
|
|
fn get_batch_data(
|
|
(mesh_instances, _, _): &SystemParamItem<Self::Param>,
|
|
(_entity, main_entity): (Entity, MainEntity),
|
|
) -> Option<(Self::BufferData, Option<Self::CompareData>)> {
|
|
let mesh_instance = mesh_instances.get(&main_entity)?;
|
|
Some((
|
|
(&mesh_instance.transforms).into(),
|
|
mesh_instance.automatic_batching.then_some((
|
|
mesh_instance.material_bind_group_id,
|
|
mesh_instance.mesh_asset_id,
|
|
)),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl GetFullBatchData for Mesh2dPipeline {
|
|
type BufferInputData = ();
|
|
|
|
fn get_binned_batch_data(
|
|
(mesh_instances, _, _): &SystemParamItem<Self::Param>,
|
|
main_entity: MainEntity,
|
|
) -> Option<Self::BufferData> {
|
|
let mesh_instance = mesh_instances.get(&main_entity)?;
|
|
Some((&mesh_instance.transforms).into())
|
|
}
|
|
|
|
fn get_index_and_compare_data(
|
|
_: &SystemParamItem<Self::Param>,
|
|
_query_item: MainEntity,
|
|
) -> Option<(NonMaxU32, Option<Self::CompareData>)> {
|
|
error!(
|
|
"`get_index_and_compare_data` is only intended for GPU mesh uniform building, \
|
|
but this is not yet implemented for 2d meshes"
|
|
);
|
|
None
|
|
}
|
|
|
|
fn get_binned_index(
|
|
_: &SystemParamItem<Self::Param>,
|
|
_query_item: MainEntity,
|
|
) -> Option<NonMaxU32> {
|
|
error!(
|
|
"`get_binned_index` is only intended for GPU mesh uniform building, \
|
|
but this is not yet implemented for 2d meshes"
|
|
);
|
|
None
|
|
}
|
|
|
|
fn write_batch_indirect_parameters_metadata(
|
|
input_index: u32,
|
|
indexed: bool,
|
|
base_output_index: u32,
|
|
batch_set_index: Option<NonMaxU32>,
|
|
indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::IndirectParametersBuffers,
|
|
indirect_parameters_offset: u32,
|
|
) {
|
|
// Note that `IndirectParameters` covers both of these structures, even
|
|
// though they actually have distinct layouts. See the comment above that
|
|
// type for more information.
|
|
let indirect_parameters = IndirectParametersMetadata {
|
|
mesh_index: input_index,
|
|
base_output_index,
|
|
batch_set_index: match batch_set_index {
|
|
None => !0,
|
|
Some(batch_set_index) => u32::from(batch_set_index),
|
|
},
|
|
instance_count: 0,
|
|
};
|
|
|
|
if indexed {
|
|
indirect_parameters_buffer.set_indexed(indirect_parameters_offset, indirect_parameters);
|
|
} else {
|
|
indirect_parameters_buffer
|
|
.set_non_indexed(indirect_parameters_offset, indirect_parameters);
|
|
}
|
|
}
|
|
}
|
|
|
|
bitflags::bitflags! {
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
#[repr(transparent)]
|
|
// NOTE: Apparently quadro drivers support up to 64x MSAA.
|
|
// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.
|
|
// FIXME: make normals optional?
|
|
pub struct Mesh2dPipelineKey: u32 {
|
|
const NONE = 0;
|
|
const HDR = 1 << 0;
|
|
const TONEMAP_IN_SHADER = 1 << 1;
|
|
const DEBAND_DITHER = 1 << 2;
|
|
const BLEND_ALPHA = 1 << 3;
|
|
const MAY_DISCARD = 1 << 4;
|
|
const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
|
|
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
|
|
const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
|
}
|
|
}
|
|
|
|
impl Mesh2dPipelineKey {
|
|
const MSAA_MASK_BITS: u32 = 0b111;
|
|
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
|
const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;
|
|
const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3;
|
|
const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;
|
|
const TONEMAP_METHOD_SHIFT_BITS: u32 =
|
|
Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();
|
|
|
|
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
|
|
let msaa_bits =
|
|
(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
|
|
Self::from_bits_retain(msaa_bits)
|
|
}
|
|
|
|
pub fn from_hdr(hdr: bool) -> Self {
|
|
if hdr {
|
|
Mesh2dPipelineKey::HDR
|
|
} else {
|
|
Mesh2dPipelineKey::NONE
|
|
}
|
|
}
|
|
|
|
pub fn msaa_samples(&self) -> u32 {
|
|
1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)
|
|
}
|
|
|
|
pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self {
|
|
let primitive_topology_bits = ((primitive_topology as u32)
|
|
& Self::PRIMITIVE_TOPOLOGY_MASK_BITS)
|
|
<< Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
|
|
Self::from_bits_retain(primitive_topology_bits)
|
|
}
|
|
|
|
pub fn primitive_topology(&self) -> PrimitiveTopology {
|
|
let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS)
|
|
& Self::PRIMITIVE_TOPOLOGY_MASK_BITS;
|
|
match primitive_topology_bits {
|
|
x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList,
|
|
x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList,
|
|
x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip,
|
|
x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList,
|
|
x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip,
|
|
_ => PrimitiveTopology::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SpecializedMeshPipeline for Mesh2dPipeline {
|
|
type Key = Mesh2dPipelineKey;
|
|
|
|
fn specialize(
|
|
&self,
|
|
key: Self::Key,
|
|
layout: &MeshVertexBufferLayoutRef,
|
|
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
|
let mut shader_defs = Vec::new();
|
|
let mut vertex_attributes = Vec::new();
|
|
|
|
if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
|
|
shader_defs.push("VERTEX_POSITIONS".into());
|
|
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
|
|
}
|
|
|
|
if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) {
|
|
shader_defs.push("VERTEX_NORMALS".into());
|
|
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1));
|
|
}
|
|
|
|
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
|
|
shader_defs.push("VERTEX_UVS".into());
|
|
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2));
|
|
}
|
|
|
|
if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) {
|
|
shader_defs.push("VERTEX_TANGENTS".into());
|
|
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
|
|
}
|
|
|
|
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
|
|
shader_defs.push("VERTEX_COLORS".into());
|
|
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4));
|
|
}
|
|
|
|
if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) {
|
|
shader_defs.push("TONEMAP_IN_SHADER".into());
|
|
shader_defs.push(ShaderDefVal::UInt(
|
|
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
|
|
2,
|
|
));
|
|
shader_defs.push(ShaderDefVal::UInt(
|
|
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
|
|
3,
|
|
));
|
|
|
|
let method = key.intersection(Mesh2dPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
|
|
|
match method {
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_NONE => {
|
|
shader_defs.push("TONEMAP_METHOD_NONE".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD => {
|
|
shader_defs.push("TONEMAP_METHOD_REINHARD".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE => {
|
|
shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED => {
|
|
shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_AGX => {
|
|
shader_defs.push("TONEMAP_METHOD_AGX".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM => {
|
|
shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC => {
|
|
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
|
|
}
|
|
Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE => {
|
|
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
|
|
}
|
|
_ => {}
|
|
}
|
|
// Debanding is tied to tonemapping in the shader, cannot run without it.
|
|
if key.contains(Mesh2dPipelineKey::DEBAND_DITHER) {
|
|
shader_defs.push("DEBAND_DITHER".into());
|
|
}
|
|
}
|
|
|
|
if key.contains(Mesh2dPipelineKey::MAY_DISCARD) {
|
|
shader_defs.push("MAY_DISCARD".into());
|
|
}
|
|
|
|
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
|
|
|
|
let format = match key.contains(Mesh2dPipelineKey::HDR) {
|
|
true => ViewTarget::TEXTURE_FORMAT_HDR,
|
|
false => TextureFormat::bevy_default(),
|
|
};
|
|
|
|
let (depth_write_enabled, label, blend);
|
|
if key.contains(Mesh2dPipelineKey::BLEND_ALPHA) {
|
|
label = "transparent_mesh2d_pipeline";
|
|
blend = Some(BlendState::ALPHA_BLENDING);
|
|
depth_write_enabled = false;
|
|
} else {
|
|
label = "opaque_mesh2d_pipeline";
|
|
blend = None;
|
|
depth_write_enabled = true;
|
|
}
|
|
|
|
Ok(RenderPipelineDescriptor {
|
|
vertex: VertexState {
|
|
shader: MESH2D_SHADER_HANDLE,
|
|
entry_point: "vertex".into(),
|
|
shader_defs: shader_defs.clone(),
|
|
buffers: vec![vertex_buffer_layout],
|
|
},
|
|
fragment: Some(FragmentState {
|
|
shader: MESH2D_SHADER_HANDLE,
|
|
shader_defs,
|
|
entry_point: "fragment".into(),
|
|
targets: vec![Some(ColorTargetState {
|
|
format,
|
|
blend,
|
|
write_mask: ColorWrites::ALL,
|
|
})],
|
|
}),
|
|
layout: vec![self.view_layout.clone(), self.mesh_layout.clone()],
|
|
push_constant_ranges: vec![],
|
|
primitive: PrimitiveState {
|
|
front_face: FrontFace::Ccw,
|
|
cull_mode: None,
|
|
unclipped_depth: false,
|
|
polygon_mode: PolygonMode::Fill,
|
|
conservative: false,
|
|
topology: key.primitive_topology(),
|
|
strip_index_format: None,
|
|
},
|
|
depth_stencil: Some(DepthStencilState {
|
|
format: CORE_2D_DEPTH_FORMAT,
|
|
depth_write_enabled,
|
|
depth_compare: CompareFunction::GreaterEqual,
|
|
stencil: StencilState {
|
|
front: StencilFaceState::IGNORE,
|
|
back: StencilFaceState::IGNORE,
|
|
read_mask: 0,
|
|
write_mask: 0,
|
|
},
|
|
bias: DepthBiasState {
|
|
constant: 0,
|
|
slope_scale: 0.0,
|
|
clamp: 0.0,
|
|
},
|
|
}),
|
|
multisample: MultisampleState {
|
|
count: key.msaa_samples(),
|
|
mask: !0,
|
|
alpha_to_coverage_enabled: false,
|
|
},
|
|
label: Some(label.into()),
|
|
zero_initialize_workgroup_memory: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
pub struct Mesh2dBindGroup {
|
|
pub value: BindGroup,
|
|
}
|
|
|
|
pub fn prepare_mesh2d_bind_group(
|
|
mut commands: Commands,
|
|
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
|
render_device: Res<RenderDevice>,
|
|
mesh2d_uniforms: Res<BatchedInstanceBuffer<Mesh2dUniform>>,
|
|
) {
|
|
if let Some(binding) = mesh2d_uniforms.instance_data_binding() {
|
|
commands.insert_resource(Mesh2dBindGroup {
|
|
value: render_device.create_bind_group(
|
|
"mesh2d_bind_group",
|
|
&mesh2d_pipeline.mesh_layout,
|
|
&BindGroupEntries::single(binding),
|
|
),
|
|
});
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
pub struct Mesh2dViewBindGroup {
|
|
pub value: BindGroup,
|
|
}
|
|
|
|
pub fn prepare_mesh2d_view_bind_groups(
|
|
mut commands: Commands,
|
|
render_device: Res<RenderDevice>,
|
|
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
|
view_uniforms: Res<ViewUniforms>,
|
|
views: Query<(Entity, &Tonemapping), (With<ExtractedView>, With<Camera2d>)>,
|
|
globals_buffer: Res<GlobalsBuffer>,
|
|
tonemapping_luts: Res<TonemappingLuts>,
|
|
images: Res<RenderAssets<GpuImage>>,
|
|
fallback_image: Res<FallbackImage>,
|
|
) {
|
|
let (Some(view_binding), Some(globals)) = (
|
|
view_uniforms.uniforms.binding(),
|
|
globals_buffer.buffer.binding(),
|
|
) else {
|
|
return;
|
|
};
|
|
|
|
for (entity, tonemapping) in &views {
|
|
let lut_bindings =
|
|
get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);
|
|
let view_bind_group = render_device.create_bind_group(
|
|
"mesh2d_view_bind_group",
|
|
&mesh2d_pipeline.view_layout,
|
|
&BindGroupEntries::with_indices((
|
|
(0, view_binding.clone()),
|
|
(1, globals.clone()),
|
|
(2, lut_bindings.0),
|
|
(3, lut_bindings.1),
|
|
)),
|
|
);
|
|
|
|
commands.entity(entity).insert(Mesh2dViewBindGroup {
|
|
value: view_bind_group,
|
|
});
|
|
}
|
|
}
|
|
|
|
pub struct SetMesh2dViewBindGroup<const I: usize>;
|
|
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMesh2dViewBindGroup<I> {
|
|
type Param = ();
|
|
type ViewQuery = (Read<ViewUniformOffset>, Read<Mesh2dViewBindGroup>);
|
|
type ItemQuery = ();
|
|
|
|
#[inline]
|
|
fn render<'w>(
|
|
_item: &P,
|
|
(view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, Self::ViewQuery>,
|
|
_view: Option<()>,
|
|
_param: SystemParamItem<'w, '_, Self::Param>,
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
) -> RenderCommandResult {
|
|
pass.set_bind_group(I, &mesh2d_view_bind_group.value, &[view_uniform.offset]);
|
|
|
|
RenderCommandResult::Success
|
|
}
|
|
}
|
|
|
|
pub struct SetMesh2dBindGroup<const I: usize>;
|
|
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMesh2dBindGroup<I> {
|
|
type Param = SRes<Mesh2dBindGroup>;
|
|
type ViewQuery = ();
|
|
type ItemQuery = ();
|
|
|
|
#[inline]
|
|
fn render<'w>(
|
|
item: &P,
|
|
_view: (),
|
|
_item_query: Option<()>,
|
|
mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>,
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
) -> RenderCommandResult {
|
|
let mut dynamic_offsets: [u32; 1] = Default::default();
|
|
let mut offset_count = 0;
|
|
if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() {
|
|
dynamic_offsets[offset_count] = dynamic_offset;
|
|
offset_count += 1;
|
|
}
|
|
pass.set_bind_group(
|
|
I,
|
|
&mesh2d_bind_group.into_inner().value,
|
|
&dynamic_offsets[..offset_count],
|
|
);
|
|
RenderCommandResult::Success
|
|
}
|
|
}
|
|
|
|
pub struct DrawMesh2d;
|
|
impl<P: PhaseItem> RenderCommand<P> for DrawMesh2d {
|
|
type Param = (
|
|
SRes<RenderAssets<RenderMesh>>,
|
|
SRes<RenderMesh2dInstances>,
|
|
SRes<MeshAllocator>,
|
|
);
|
|
type ViewQuery = ();
|
|
type ItemQuery = ();
|
|
|
|
#[inline]
|
|
fn render<'w>(
|
|
item: &P,
|
|
_view: (),
|
|
_item_query: Option<()>,
|
|
(meshes, render_mesh2d_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>,
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
) -> RenderCommandResult {
|
|
let meshes = meshes.into_inner();
|
|
let render_mesh2d_instances = render_mesh2d_instances.into_inner();
|
|
let mesh_allocator = mesh_allocator.into_inner();
|
|
|
|
let Some(RenderMesh2dInstance { mesh_asset_id, .. }) =
|
|
render_mesh2d_instances.get(&item.main_entity())
|
|
else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Some(gpu_mesh) = meshes.get(*mesh_asset_id) else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(mesh_asset_id) else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
|
|
pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..));
|
|
|
|
let batch_range = item.batch_range();
|
|
match &gpu_mesh.buffer_info {
|
|
RenderMeshBufferInfo::Indexed {
|
|
index_format,
|
|
count,
|
|
} => {
|
|
let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(mesh_asset_id)
|
|
else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
|
|
pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format);
|
|
|
|
pass.draw_indexed(
|
|
index_buffer_slice.range.start..(index_buffer_slice.range.start + count),
|
|
vertex_buffer_slice.range.start as i32,
|
|
batch_range.clone(),
|
|
);
|
|
}
|
|
RenderMeshBufferInfo::NonIndexed => {
|
|
pass.draw(vertex_buffer_slice.range, batch_range.clone());
|
|
}
|
|
}
|
|
RenderCommandResult::Success
|
|
}
|
|
}
|