Skip to main content

bevy_core_pipeline/upscaling/
mod.rs

1use crate::blit::{BlitPipeline, BlitPipelineKey};
2use bevy_app::prelude::*;
3use bevy_camera::CameraOutputMode;
4use bevy_ecs::prelude::*;
5use bevy_render::{
6    camera::ExtractedCamera,
7    render_resource::*,
8    view::{ResolvedCompositingSpace, ViewTarget},
9    Render, RenderApp, RenderStartup, RenderSystems,
10};
11
12mod node;
13
14pub use node::upscaling;
15
16pub struct UpscalingPlugin;
17
18impl Plugin for UpscalingPlugin {
19    fn build(&self, app: &mut App) {
20        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
21            render_app.add_systems(
22                Render,
23                // This system should probably technically be run *after* all of the other systems
24                // that might modify `PipelineCache` via interior mutability, but for now,
25                // we've chosen to simply ignore the ambiguities out of a desire for a better refactor
26                // and aversion to extensive and intrusive system ordering.
27                // See https://github.com/bevyengine/bevy/issues/14770 for more context.
28                prepare_view_upscaling_pipelines
29                    .in_set(RenderSystems::Prepare)
30                    .ambiguous_with_all(),
31            );
32            render_app.add_systems(RenderStartup, clear_view_upscaling_pipelines);
33        }
34    }
35}
36
37#[derive(Component)]
38pub struct ViewUpscalingPipeline(CachedRenderPipelineId, BlitPipelineKey);
39
40/// This is not required on first startup but is required during render recovery
41fn clear_view_upscaling_pipelines(
42    mut commands: Commands,
43    views: Query<Entity, With<ViewUpscalingPipeline>>,
44) {
45    for entity in &views {
46        commands.entity(entity).remove::<ViewUpscalingPipeline>();
47    }
48}
49
50fn prepare_view_upscaling_pipelines(
51    mut commands: Commands,
52    mut pipeline_cache: ResMut<PipelineCache>,
53    mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
54    blit_pipeline: Res<BlitPipeline>,
55    view_targets: Query<(
56        Entity,
57        &ViewTarget,
58        Option<&ExtractedCamera>,
59        Option<&ViewUpscalingPipeline>,
60        Option<&ResolvedCompositingSpace>,
61    )>,
62) {
63    for (entity, view_target, camera, maybe_pipeline, resolved_space) in view_targets.iter() {
64        let blend_state = if let Some(extracted_camera) = camera {
65            match extracted_camera.output_mode {
66                CameraOutputMode::Skip => None,
67                CameraOutputMode::Write { blend_state, .. } => {
68                    match blend_state {
69                        None => {
70                            // Auto-detect: the first camera to render to this output
71                            // (sorted_camera_index_for_target == 0) uses replace mode;
72                            // subsequent cameras default to alpha blending so they don't
73                            // accidentally overwrite earlier cameras' output.
74                            if extracted_camera.sorted_camera_index_for_target > 0 {
75                                Some(BlendState::ALPHA_BLENDING)
76                            } else {
77                                None
78                            }
79                        }
80                        _ => blend_state,
81                    }
82                }
83            }
84        } else {
85            None
86        };
87
88        let Some(target_format) = view_target.out_texture_view_format() else {
89            continue;
90        };
91
92        let key = BlitPipelineKey {
93            target_format,
94            blend_state,
95            samples: 1,
96            source_space: ResolvedCompositingSpace::space(resolved_space),
97        };
98
99        if maybe_pipeline.is_none_or(|ViewUpscalingPipeline(_, cached_key)| *cached_key != key) {
100            let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key);
101
102            // Ensure the pipeline is loaded before continuing the frame to prevent frames without
103            // any GPU work submitted
104            pipeline_cache.block_on_render_pipeline(pipeline);
105
106            commands
107                .entity(entity)
108                .insert(ViewUpscalingPipeline(pipeline, key));
109        }
110    }
111}