Path: blob/main/examples/shader_advanced/custom_phase_item.rs
6849 views
//! Demonstrates how to enqueue custom draw commands in a render phase.1//!2//! This example shows how to use the built-in3//! [`bevy_render::render_phase::BinnedRenderPhase`] functionality with a4//! custom [`RenderCommand`] to allow inserting arbitrary GPU drawing logic5//! into Bevy's pipeline. This is not the only way to add custom rendering code6//! into Bevy—render nodes are another, lower-level method—but it does allow7//! for better reuse of parts of Bevy's built-in mesh rendering logic.89use bevy::{10camera::{11primitives::Aabb,12visibility::{self, VisibilityClass},13},14core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},15ecs::{16component::Tick,17query::ROQueryItem,18system::{lifetimeless::SRes, SystemParamItem},19},20mesh::VertexBufferLayout,21prelude::*,22render::{23extract_component::{ExtractComponent, ExtractComponentPlugin},24render_phase::{25AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, InputUniformIndex, PhaseItem,26RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass,27ViewBinnedRenderPhases,28},29render_resource::{30BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction,31DepthStencilState, FragmentState, IndexFormat, PipelineCache, RawBufferVec,32RenderPipeline, RenderPipelineDescriptor, Specializer, SpecializerKey, TextureFormat,33Variants, VertexAttribute, VertexFormat, VertexState, VertexStepMode,34},35renderer::{RenderDevice, RenderQueue},36view::{ExtractedView, RenderVisibleEntities},37Render, RenderApp, RenderSystems,38},39};40use bytemuck::{Pod, Zeroable};4142/// A marker component that represents an entity that is to be rendered using43/// our custom phase item.44///45/// Note the [`ExtractComponent`] trait implementation: this is necessary to46/// tell Bevy that this object should be pulled into the render world. Also note47/// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system48/// that entities with this component need to be examined for visibility.49#[derive(Clone, Component, ExtractComponent)]50#[require(VisibilityClass)]51#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]52struct CustomRenderedEntity;5354/// A [`RenderCommand`] that binds the vertex and index buffers and issues the55/// draw command for our custom phase item.56struct DrawCustomPhaseItem;5758impl<P> RenderCommand<P> for DrawCustomPhaseItem59where60P: PhaseItem,61{62type Param = SRes<CustomPhaseItemBuffers>;6364type ViewQuery = ();6566type ItemQuery = ();6768fn render<'w>(69_: &P,70_: ROQueryItem<'w, '_, Self::ViewQuery>,71_: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,72custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>,73pass: &mut TrackedRenderPass<'w>,74) -> RenderCommandResult {75// Borrow check workaround.76let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();7778// Tell the GPU where the vertices are.79pass.set_vertex_buffer(800,81custom_phase_item_buffers82.vertices83.buffer()84.unwrap()85.slice(..),86);8788// Tell the GPU where the indices are.89pass.set_index_buffer(90custom_phase_item_buffers91.indices92.buffer()93.unwrap()94.slice(..),950,96IndexFormat::Uint32,97);9899// Draw one triangle (3 vertices).100pass.draw_indexed(0..3, 0, 0..1);101102RenderCommandResult::Success103}104}105106/// The GPU vertex and index buffers for our custom phase item.107///108/// As the custom phase item is a single triangle, these are uploaded once and109/// then left alone.110#[derive(Resource)]111struct CustomPhaseItemBuffers {112/// The vertices for the single triangle.113///114/// This is a [`RawBufferVec`] because that's the simplest and fastest type115/// of GPU buffer, and [`Vertex`] objects are simple.116vertices: RawBufferVec<Vertex>,117118/// The indices of the single triangle.119///120/// As above, this is a [`RawBufferVec`] because `u32` values have trivial121/// size and alignment.122indices: RawBufferVec<u32>,123}124125/// The CPU-side structure that describes a single vertex of the triangle.126#[derive(Clone, Copy, Pod, Zeroable)]127#[repr(C)]128struct Vertex {129/// The 3D position of the triangle vertex.130position: Vec3,131/// Padding.132pad0: u32,133/// The color of the triangle vertex.134color: Vec3,135/// Padding.136pad1: u32,137}138139impl Vertex {140/// Creates a new vertex structure.141const fn new(position: Vec3, color: Vec3) -> Vertex {142Vertex {143position,144color,145pad0: 0,146pad1: 0,147}148}149}150151/// The custom draw commands that Bevy executes for each entity we enqueue into152/// the render phase.153type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);154155/// A single triangle's worth of vertices, for demonstration purposes.156static VERTICES: [Vertex; 3] = [157Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),158Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),159Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),160];161162/// The entry point.163fn main() {164let mut app = App::new();165app.add_plugins(DefaultPlugins)166.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())167.add_systems(Startup, setup);168169// We make sure to add these to the render app, not the main app.170app.sub_app_mut(RenderApp)171.init_resource::<CustomPhasePipeline>()172.add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()173.add_systems(174Render,175prepare_custom_phase_item_buffers.in_set(RenderSystems::Prepare),176)177.add_systems(Render, queue_custom_phase_item.in_set(RenderSystems::Queue));178179app.run();180}181182/// Spawns the objects in the scene.183fn setup(mut commands: Commands) {184// Spawn a single entity that has custom rendering. It'll be extracted into185// the render world via [`ExtractComponent`].186commands.spawn((187Visibility::default(),188Transform::default(),189// This `Aabb` is necessary for the visibility checks to work.190Aabb {191center: Vec3A::ZERO,192half_extents: Vec3A::splat(0.5),193},194CustomRenderedEntity,195));196197// Spawn the camera.198commands.spawn((199Camera3d::default(),200Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),201));202}203204/// Creates the [`CustomPhaseItemBuffers`] resource.205///206/// This must be done in a startup system because it needs the [`RenderDevice`]207/// and [`RenderQueue`] to exist, and they don't until [`App::run`] is called.208fn prepare_custom_phase_item_buffers(mut commands: Commands) {209commands.init_resource::<CustomPhaseItemBuffers>();210}211212/// A render-world system that enqueues the entity with custom rendering into213/// the opaque render phases of each view.214fn queue_custom_phase_item(215pipeline_cache: Res<PipelineCache>,216mut pipeline: ResMut<CustomPhasePipeline>,217mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,218opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,219views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>,220mut next_tick: Local<Tick>,221) {222let draw_custom_phase_item = opaque_draw_functions223.read()224.id::<DrawCustomPhaseItemCommands>();225226// Render phases are per-view, so we need to iterate over all views so that227// the entity appears in them. (In this example, we have only one view, but228// it's good practice to loop over all views anyway.)229for (view, view_visible_entities, msaa) in views.iter() {230let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {231continue;232};233234// Find all the custom rendered entities that are visible from this235// view.236for &entity in view_visible_entities.get::<CustomRenderedEntity>().iter() {237// Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain238// some per-view settings, such as whether the view is HDR, but for239// simplicity's sake we simply hard-code the view's characteristics,240// with the exception of number of MSAA samples.241let Ok(pipeline_id) = pipeline242.variants243.specialize(&pipeline_cache, CustomPhaseKey(*msaa))244else {245continue;246};247248// Bump the change tick in order to force Bevy to rebuild the bin.249let this_tick = next_tick.get() + 1;250next_tick.set(this_tick);251252// Add the custom render item. We use the253// [`BinnedRenderPhaseType::NonMesh`] type to skip the special254// handling that Bevy has for meshes (preprocessing, indirect255// draws, etc.)256//257// The asset ID is arbitrary; we simply use [`AssetId::invalid`],258// but you can use anything you like. Note that the asset ID need259// not be the ID of a [`Mesh`].260opaque_phase.add(261Opaque3dBatchSetKey {262draw_function: draw_custom_phase_item,263pipeline: pipeline_id,264material_bind_group_index: None,265lightmap_slab: None,266vertex_slab: default(),267index_slab: None,268},269Opaque3dBinKey {270asset_id: AssetId::<Mesh>::invalid().untyped(),271},272entity,273InputUniformIndex::default(),274BinnedRenderPhaseType::NonMesh,275*next_tick,276);277}278}279}280281struct CustomPhaseSpecializer;282283#[derive(Resource)]284struct CustomPhasePipeline {285/// the `variants` collection holds onto the shader handle through the base descriptor286variants: Variants<RenderPipeline, CustomPhaseSpecializer>,287}288289impl FromWorld for CustomPhasePipeline {290fn from_world(world: &mut World) -> Self {291let asset_server = world.resource::<AssetServer>();292let shader = asset_server.load("shaders/custom_phase_item.wgsl");293294let base_descriptor = RenderPipelineDescriptor {295label: Some("custom render pipeline".into()),296vertex: VertexState {297shader: shader.clone(),298buffers: vec![VertexBufferLayout {299array_stride: size_of::<Vertex>() as u64,300step_mode: VertexStepMode::Vertex,301// This needs to match the layout of [`Vertex`].302attributes: vec![303VertexAttribute {304format: VertexFormat::Float32x3,305offset: 0,306shader_location: 0,307},308VertexAttribute {309format: VertexFormat::Float32x3,310offset: 16,311shader_location: 1,312},313],314}],315..default()316},317fragment: Some(FragmentState {318shader: shader.clone(),319targets: vec![Some(ColorTargetState {320// Ordinarily, you'd want to check whether the view has the321// HDR format and substitute the appropriate texture format322// here, but we omit that for simplicity.323format: TextureFormat::bevy_default(),324blend: None,325write_mask: ColorWrites::ALL,326})],327..default()328}),329// Note that if your view has no depth buffer this will need to be330// changed.331depth_stencil: Some(DepthStencilState {332format: CORE_3D_DEPTH_FORMAT,333depth_write_enabled: false,334depth_compare: CompareFunction::Always,335stencil: default(),336bias: default(),337}),338..default()339};340341let variants = Variants::new(CustomPhaseSpecializer, base_descriptor);342343Self { variants }344}345}346347#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]348struct CustomPhaseKey(Msaa);349350impl Specializer<RenderPipeline> for CustomPhaseSpecializer {351type Key = CustomPhaseKey;352353fn specialize(354&self,355key: Self::Key,356descriptor: &mut RenderPipelineDescriptor,357) -> Result<Canonical<Self::Key>, BevyError> {358descriptor.multisample.count = key.0.samples();359Ok(key)360}361}362363impl FromWorld for CustomPhaseItemBuffers {364fn from_world(world: &mut World) -> Self {365let render_device = world.resource::<RenderDevice>();366let render_queue = world.resource::<RenderQueue>();367368// Create the vertex and index buffers.369let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);370let mut ibo = RawBufferVec::new(BufferUsages::INDEX);371372for vertex in &VERTICES {373vbo.push(*vertex);374}375for index in 0..3 {376ibo.push(index);377}378379// These two lines are required in order to trigger the upload to GPU.380vbo.write_buffer(render_device, render_queue);381ibo.write_buffer(render_device, render_queue);382383CustomPhaseItemBuffers {384vertices: vbo,385indices: ibo,386}387}388}389390391