use bevy::{
core_pipeline::core_3d::{Opaque3d, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},
ecs::{
query::ROQueryItem,
system::{lifetimeless::SRes, SystemParamItem},
},
math::{vec3, Vec3A},
prelude::*,
render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
primitives::Aabb,
render_phase::{
AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, PhaseItem, RenderCommand,
RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases,
},
render_resource::{
BufferUsages, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState,
FragmentState, IndexFormat, MultisampleState, PipelineCache, PrimitiveState,
RawBufferVec, RenderPipelineDescriptor, SpecializedRenderPipeline,
SpecializedRenderPipelines, TextureFormat, VertexAttribute, VertexBufferLayout,
VertexFormat, VertexState, VertexStepMode,
},
renderer::{RenderDevice, RenderQueue},
view::{self, ExtractedView, RenderVisibleEntities, VisibilitySystems},
Render, RenderApp, RenderSet,
},
};
use bytemuck::{Pod, Zeroable};
#[derive(Clone, Component, ExtractComponent)]
struct CustomRenderedEntity;
#[derive(Resource)]
struct CustomPhasePipeline {
shader: Handle<Shader>,
}
struct DrawCustomPhaseItem;
impl<P> RenderCommand<P> for DrawCustomPhaseItem
where
P: PhaseItem,
{
type Param = SRes<CustomPhaseItemBuffers>;
type ViewQuery = ();
type ItemQuery = ();
fn render<'w>(
_: &P,
_: ROQueryItem<'w, Self::ViewQuery>,
_: Option<ROQueryItem<'w, Self::ItemQuery>>,
custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();
pass.set_vertex_buffer(
0,
custom_phase_item_buffers
.vertices
.buffer()
.unwrap()
.slice(..),
);
pass.set_index_buffer(
custom_phase_item_buffers
.indices
.buffer()
.unwrap()
.slice(..),
0,
IndexFormat::Uint32,
);
pass.draw_indexed(0..3, 0, 0..1);
RenderCommandResult::Success
}
}
#[derive(Resource)]
struct CustomPhaseItemBuffers {
vertices: RawBufferVec<Vertex>,
indices: RawBufferVec<u32>,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
struct Vertex {
position: Vec3,
pad0: u32,
color: Vec3,
pad1: u32,
}
impl Vertex {
const fn new(position: Vec3, color: Vec3) -> Vertex {
Vertex {
position,
color,
pad0: 0,
pad1: 0,
}
}
}
type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);
type WithCustomRenderedEntity = With<CustomRenderedEntity>;
static VERTICES: [Vertex; 3] = [
Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),
Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),
Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),
];
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins)
.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())
.add_systems(Startup, setup)
.add_systems(
PostUpdate,
view::check_visibility::<WithCustomRenderedEntity>
.in_set(VisibilitySystems::CheckVisibility),
);
app.get_sub_app_mut(RenderApp)
.unwrap()
.init_resource::<CustomPhasePipeline>()
.init_resource::<SpecializedRenderPipelines<CustomPhasePipeline>>()
.add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()
.add_systems(
Render,
prepare_custom_phase_item_buffers.in_set(RenderSet::Prepare),
)
.add_systems(Render, queue_custom_phase_item.in_set(RenderSet::Queue));
app.run();
}
fn setup(mut commands: Commands) {
commands.spawn((
Visibility::default(),
Transform::default(),
Aabb {
center: Vec3A::ZERO,
half_extents: Vec3A::splat(0.5),
},
CustomRenderedEntity,
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}
fn prepare_custom_phase_item_buffers(mut commands: Commands) {
commands.init_resource::<CustomPhaseItemBuffers>();
}
fn queue_custom_phase_item(
pipeline_cache: Res<PipelineCache>,
custom_phase_pipeline: Res<CustomPhasePipeline>,
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
mut specialized_render_pipelines: ResMut<SpecializedRenderPipelines<CustomPhasePipeline>>,
views: Query<(Entity, &RenderVisibleEntities, &Msaa), With<ExtractedView>>,
) {
let draw_custom_phase_item = opaque_draw_functions
.read()
.id::<DrawCustomPhaseItemCommands>();
for (view_entity, view_visible_entities, msaa) in views.iter() {
let Some(opaque_phase) = opaque_render_phases.get_mut(&view_entity) else {
continue;
};
for &entity in view_visible_entities
.get::<WithCustomRenderedEntity>()
.iter()
{
let pipeline_id = specialized_render_pipelines.specialize(
&pipeline_cache,
&custom_phase_pipeline,
*msaa,
);
opaque_phase.add(
Opaque3dBinKey {
draw_function: draw_custom_phase_item,
pipeline: pipeline_id,
asset_id: AssetId::<Mesh>::invalid().untyped(),
material_bind_group_id: None,
lightmap_image: None,
},
entity,
BinnedRenderPhaseType::NonMesh,
);
}
}
}
impl SpecializedRenderPipeline for CustomPhasePipeline {
type Key = Msaa;
fn specialize(&self, msaa: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
label: Some("custom render pipeline".into()),
layout: vec![],
push_constant_ranges: vec![],
vertex: VertexState {
shader: self.shader.clone(),
shader_defs: vec![],
entry_point: "vertex".into(),
buffers: vec![VertexBufferLayout {
array_stride: size_of::<Vertex>() as u64,
step_mode: VertexStepMode::Vertex,
attributes: vec![
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 16,
shader_location: 1,
},
],
}],
},
fragment: Some(FragmentState {
shader: self.shader.clone(),
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: TextureFormat::bevy_default(),
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::Always,
stencil: default(),
bias: default(),
}),
multisample: MultisampleState {
count: msaa.samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
zero_initialize_workgroup_memory: false,
}
}
}
impl FromWorld for CustomPhaseItemBuffers {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let render_queue = world.resource::<RenderQueue>();
let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);
let mut ibo = RawBufferVec::new(BufferUsages::INDEX);
for vertex in &VERTICES {
vbo.push(*vertex);
}
for index in 0..3 {
ibo.push(index);
}
vbo.write_buffer(render_device, render_queue);
ibo.write_buffer(render_device, render_queue);
CustomPhaseItemBuffers {
vertices: vbo,
indices: ibo,
}
}
}
impl FromWorld for CustomPhasePipeline {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
CustomPhasePipeline {
shader: asset_server.load("shaders/custom_phase_item.wgsl"),
}
}
}