Path: blob/main/examples/shader_advanced/custom_phase_item.rs
9345 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::{16change_detection::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(..),95IndexFormat::Uint32,96);9798// Draw one triangle (3 vertices).99pass.draw_indexed(0..3, 0, 0..1);100101RenderCommandResult::Success102}103}104105/// The GPU vertex and index buffers for our custom phase item.106///107/// As the custom phase item is a single triangle, these are uploaded once and108/// then left alone.109#[derive(Resource)]110struct CustomPhaseItemBuffers {111/// The vertices for the single triangle.112///113/// This is a [`RawBufferVec`] because that's the simplest and fastest type114/// of GPU buffer, and [`Vertex`] objects are simple.115vertices: RawBufferVec<Vertex>,116117/// The indices of the single triangle.118///119/// As above, this is a [`RawBufferVec`] because `u32` values have trivial120/// size and alignment.121indices: RawBufferVec<u32>,122}123124/// The CPU-side structure that describes a single vertex of the triangle.125#[derive(Clone, Copy, Pod, Zeroable)]126#[repr(C)]127struct Vertex {128/// The 3D position of the triangle vertex.129position: Vec3,130/// Padding.131pad0: u32,132/// The color of the triangle vertex.133color: Vec3,134/// Padding.135pad1: u32,136}137138impl Vertex {139/// Creates a new vertex structure.140const fn new(position: Vec3, color: Vec3) -> Vertex {141Vertex {142position,143color,144pad0: 0,145pad1: 0,146}147}148}149150/// The custom draw commands that Bevy executes for each entity we enqueue into151/// the render phase.152type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);153154/// A single triangle's worth of vertices, for demonstration purposes.155static VERTICES: [Vertex; 3] = [156Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),157Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),158Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),159];160161/// The entry point.162fn main() {163let mut app = App::new();164app.add_plugins(DefaultPlugins)165.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())166.add_systems(Startup, setup);167168// We make sure to add these to the render app, not the main app.169app.sub_app_mut(RenderApp)170.init_resource::<CustomPhasePipeline>()171.add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()172.add_systems(173Render,174prepare_custom_phase_item_buffers.in_set(RenderSystems::Prepare),175)176.add_systems(Render, queue_custom_phase_item.in_set(RenderSystems::Queue));177178app.run();179}180181/// Spawns the objects in the scene.182fn setup(mut commands: Commands) {183// Spawn a single entity that has custom rendering. It'll be extracted into184// the render world via [`ExtractComponent`].185commands.spawn((186Visibility::default(),187Transform::default(),188// This `Aabb` is necessary for the visibility checks to work.189Aabb {190center: Vec3A::ZERO,191half_extents: Vec3A::splat(0.5),192},193CustomRenderedEntity,194));195196// Spawn the camera.197commands.spawn((198Camera3d::default(),199Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),200));201}202203/// Creates the [`CustomPhaseItemBuffers`] resource.204///205/// This must be done in a startup system because it needs the [`RenderDevice`]206/// and [`RenderQueue`] to exist, and they don't until [`App::run`] is called.207fn prepare_custom_phase_item_buffers(mut commands: Commands) {208commands.init_resource::<CustomPhaseItemBuffers>();209}210211/// A render-world system that enqueues the entity with custom rendering into212/// the opaque render phases of each view.213fn queue_custom_phase_item(214pipeline_cache: Res<PipelineCache>,215mut pipeline: ResMut<CustomPhasePipeline>,216mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,217opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,218views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>,219mut next_tick: Local<Tick>,220) {221let draw_custom_phase_item = opaque_draw_functions222.read()223.id::<DrawCustomPhaseItemCommands>();224225// Render phases are per-view, so we need to iterate over all views so that226// the entity appears in them. (In this example, we have only one view, but227// it's good practice to loop over all views anyway.)228for (view, view_visible_entities, msaa) in views.iter() {229let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {230continue;231};232233// Find all the custom rendered entities that are visible from this234// view.235for &entity in view_visible_entities.get::<CustomRenderedEntity>().iter() {236// Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain237// some per-view settings, such as whether the view is HDR, but for238// simplicity's sake we simply hard-code the view's characteristics,239// with the exception of number of MSAA samples.240let Ok(pipeline_id) = pipeline241.variants242.specialize(&pipeline_cache, CustomPhaseKey(*msaa))243else {244continue;245};246247// Bump the change tick in order to force Bevy to rebuild the bin.248let this_tick = next_tick.get() + 1;249next_tick.set(this_tick);250251// Add the custom render item. We use the252// [`BinnedRenderPhaseType::NonMesh`] type to skip the special253// handling that Bevy has for meshes (preprocessing, indirect254// draws, etc.)255//256// The asset ID is arbitrary; we simply use [`AssetId::invalid`],257// but you can use anything you like. Note that the asset ID need258// not be the ID of a [`Mesh`].259opaque_phase.add(260Opaque3dBatchSetKey {261draw_function: draw_custom_phase_item,262pipeline: pipeline_id,263material_bind_group_index: None,264lightmap_slab: None,265vertex_slab: default(),266index_slab: None,267},268Opaque3dBinKey {269asset_id: AssetId::<Mesh>::invalid().untyped(),270},271entity,272InputUniformIndex::default(),273BinnedRenderPhaseType::NonMesh,274*next_tick,275);276}277}278}279280struct CustomPhaseSpecializer;281282#[derive(Resource)]283struct CustomPhasePipeline {284/// the `variants` collection holds onto the shader handle through the base descriptor285variants: Variants<RenderPipeline, CustomPhaseSpecializer>,286}287288impl FromWorld for CustomPhasePipeline {289fn from_world(world: &mut World) -> Self {290let asset_server = world.resource::<AssetServer>();291let shader = asset_server.load("shaders/custom_phase_item.wgsl");292293let base_descriptor = RenderPipelineDescriptor {294label: Some("custom render pipeline".into()),295vertex: VertexState {296shader: shader.clone(),297buffers: vec![VertexBufferLayout {298array_stride: size_of::<Vertex>() as u64,299step_mode: VertexStepMode::Vertex,300// This needs to match the layout of [`Vertex`].301attributes: vec![302VertexAttribute {303format: VertexFormat::Float32x3,304offset: 0,305shader_location: 0,306},307VertexAttribute {308format: VertexFormat::Float32x3,309offset: 16,310shader_location: 1,311},312],313}],314..default()315},316fragment: Some(FragmentState {317shader: shader.clone(),318targets: vec![Some(ColorTargetState {319// Ordinarily, you'd want to check whether the view has the320// HDR format and substitute the appropriate texture format321// here, but we omit that for simplicity.322format: TextureFormat::bevy_default(),323blend: None,324write_mask: ColorWrites::ALL,325})],326..default()327}),328// Note that if your view has no depth buffer this will need to be329// changed.330depth_stencil: Some(DepthStencilState {331format: CORE_3D_DEPTH_FORMAT,332depth_write_enabled: false,333depth_compare: CompareFunction::Always,334stencil: default(),335bias: default(),336}),337..default()338};339340let variants = Variants::new(CustomPhaseSpecializer, base_descriptor);341342Self { variants }343}344}345346#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]347struct CustomPhaseKey(Msaa);348349impl Specializer<RenderPipeline> for CustomPhaseSpecializer {350type Key = CustomPhaseKey;351352fn specialize(353&self,354key: Self::Key,355descriptor: &mut RenderPipelineDescriptor,356) -> Result<Canonical<Self::Key>, BevyError> {357descriptor.multisample.count = key.0.samples();358Ok(key)359}360}361362impl FromWorld for CustomPhaseItemBuffers {363fn from_world(world: &mut World) -> Self {364let render_device = world.resource::<RenderDevice>();365let render_queue = world.resource::<RenderQueue>();366367// Create the vertex and index buffers.368let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);369let mut ibo = RawBufferVec::new(BufferUsages::INDEX);370371for vertex in &VERTICES {372vbo.push(*vertex);373}374for index in 0..3 {375ibo.push(index);376}377378// These two lines are required in order to trigger the upload to GPU.379vbo.write_buffer(render_device, render_queue);380ibo.write_buffer(render_device, render_queue);381382CustomPhaseItemBuffers {383vertices: vbo,384indices: ibo,385}386}387}388389390