Files
benilla-octo/crates/benilla-app/src/opaque2d.rs
T
SamandClaude Fable 5.1 58164abde7 right-click works on a nameplate, and the honor tab names your rank
- right-clicking a nameplate acts again: both buttons read the press pick, as the reference picks once on the down edge and never re-picks on release (2230)
- the honor tab's Highest Rank and title name the rank: a rank title's team digit is the race, not the faction group, so a GM-flagged character keeps its rank (2227)
- a raid's party feed watches only its members' descriptors, and the empty 2D opaque pass is skipped: ~0.7 ms a frame in a crowd (2225)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 16:57:38 +07:00

129 lines
5.8 KiB
Rust

//! **The 2D opaque pass, skipped when it has nothing to draw** (decision 2225).
//!
//! bevy's `MainOpaquePass2dNode` opens a command encoder and a render pass whether or not its two
//! phases hold an item, and on every camera of ours they never do: each player-UI quad is in the
//! transparent phase (`AlphaMode2d::Blend`, decision 0254), Bevy UI paints through its own node,
//! egui through its own. 2197 parked the pass as "price it on an immediate-mode GPU"; 2205 priced
//! its GPU at zero; the crowd profile of 2225 priced its CPU at ~0.2 ms a frame, parked, traced —
//! an encoder and a pass that wgpu sizes and clears its usage trackers for, empty or not (the
//! residency term `perf::gpu`'s census names).
//!
//! The pass exists in bevy for the clear, and the clear does not need it: whichever pass touches
//! the view target first clears it (`ColorAttachment`'s first-call semantics, the same rule
//! `static_gx` and the FFX combine already lean on), and bevy's transparent 2D node opens its pass
//! unconditionally for exactly that reason. So an empty opaque pass has nothing to do that the
//! pass after it does not.
//!
//! This is bevy's node with one early return, registered under bevy's own label:
//! `RenderGraph::add_node` is a map insert, so the later registration replaces the node and the
//! graph's edges — keyed by label — carry over untouched.
use bevy::core_pipeline::core_2d::graph::{Core2d, Node2d};
use bevy::core_pipeline::core_2d::{AlphaMask2d, Opaque2d};
use bevy::ecs::query::QueryItem;
use bevy::prelude::*;
use bevy::render::camera::ExtractedCamera;
use bevy::render::diagnostic::RecordDiagnostics;
use bevy::render::render_graph::{
NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner,
};
use bevy::render::render_phase::{TrackedRenderPass, ViewBinnedRenderPhases};
use bevy::render::render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp};
use bevy::render::renderer::RenderContext;
use bevy::render::view::{ExtractedView, ViewDepthTexture, ViewTarget};
use bevy::render::RenderApp;
#[derive(Default)]
struct SkipEmptyOpaque2dNode;
impl ViewNode for SkipEmptyOpaque2dNode {
type ViewQuery = (
&'static ExtractedCamera,
&'static ExtractedView,
&'static ViewTarget,
&'static ViewDepthTexture,
);
fn run<'w>(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let (Some(opaque_phases), Some(alpha_mask_phases)) = (
world.get_resource::<ViewBinnedRenderPhases<Opaque2d>>(),
world.get_resource::<ViewBinnedRenderPhases<AlphaMask2d>>(),
) else {
return Ok(());
};
let view_entity = graph.view_entity();
let (Some(opaque_phase), Some(alpha_mask_phase)) = (
opaque_phases.get(&view.retained_view_entity),
alpha_mask_phases.get(&view.retained_view_entity),
) else {
return Ok(());
};
// The one line bevy's node does not have. Nothing to draw means nothing to encode: the
// transparent pass that follows opens its own pass unconditionally and takes the target's
// first-call clear.
if opaque_phase.is_empty() && alpha_mask_phase.is_empty() {
return Ok(());
}
let diagnostics = render_context.diagnostic_recorder();
let color_attachments = [Some(target.get_color_attachment())];
let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store));
render_context.add_command_buffer_generation_task(move |render_device| {
let mut command_encoder =
render_device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("main_opaque_pass_2d_command_encoder"),
});
let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("main_opaque_pass_2d"),
color_attachments: &color_attachments,
depth_stencil_attachment,
timestamp_writes: None,
occlusion_query_set: None,
});
let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_2d");
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
}
if !opaque_phase.is_empty() {
if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) {
error!("Error encountered while rendering the 2d opaque phase {err:?}");
}
}
if !alpha_mask_phase.is_empty() {
if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) {
error!("Error encountered while rendering the 2d alpha mask phase {err:?}");
}
}
pass_span.end(&mut render_pass);
drop(render_pass);
command_encoder.finish()
});
Ok(())
}
}
/// Replaces bevy's 2D opaque node under its own label. Added by [`crate::ui_pass::PlayerUiPlugin`],
/// which owns every camera that runs the `Core2d` graph, after `DefaultPlugins` has registered the
/// node it replaces.
pub(crate) struct SkipEmptyOpaque2dPlugin;
impl Plugin for SkipEmptyOpaque2dPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.add_render_graph_node::<ViewNodeRunner<SkipEmptyOpaque2dNode>>(
Core2d,
Node2d::MainOpaquePass,
);
}
}