Resolution override (#19817)

Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com>
Co-authored-by: atlv <email@atlasdostal.com>
This commit is contained in:
charlotte 🌸 2025-06-27 09:30:54 -07:00 committed by GitHub
parent 410ca48023
commit a0b90cd618
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 67 additions and 24 deletions

View File

@ -4,7 +4,7 @@ use crate::{
};
use bevy_ecs::{prelude::World, query::QueryItem};
use bevy_render::{
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
diagnostic::RecordDiagnostics,
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::{TrackedRenderPass, ViewBinnedRenderPhases},
@ -31,6 +31,7 @@ impl ViewNode for MainOpaquePass3dNode {
Option<&'static SkyboxPipelineId>,
Option<&'static SkyboxBindGroup>,
&'static ViewUniformOffset,
Option<&'static MainPassResolutionOverride>,
);
fn run<'w>(
@ -45,6 +46,7 @@ impl ViewNode for MainOpaquePass3dNode {
skybox_pipeline,
skybox_bind_group,
view_uniform_offset,
resolution_override,
): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
@ -90,7 +92,7 @@ impl ViewNode for MainOpaquePass3dNode {
let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_3d");
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
// Opaque draws

View File

@ -3,7 +3,7 @@ use crate::core_3d::Transmissive3d;
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_image::ToExtents;
use bevy_render::{
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::ViewSortedRenderPhases,
render_resource::{RenderPassDescriptor, StoreOp},
@ -28,13 +28,16 @@ impl ViewNode for MainTransmissivePass3dNode {
&'static ViewTarget,
Option<&'static ViewTransmissionTexture>,
&'static ViewDepthTexture,
Option<&'static MainPassResolutionOverride>,
);
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
(camera, view, camera_3d, target, transmission, depth): QueryItem<Self::ViewQuery>,
(camera, view, camera_3d, target, transmission, depth, resolution_override): QueryItem<
Self::ViewQuery,
>,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
@ -108,7 +111,7 @@ impl ViewNode for MainTransmissivePass3dNode {
render_context.begin_tracked_render_pass(render_pass_descriptor);
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
if let Err(err) = transmissive_phase.render(&mut render_pass, world, view_entity) {

View File

@ -1,7 +1,7 @@
use crate::core_3d::Transparent3d;
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
diagnostic::RecordDiagnostics,
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::ViewSortedRenderPhases,
@ -24,12 +24,13 @@ impl ViewNode for MainTransparentPass3dNode {
&'static ExtractedView,
&'static ViewTarget,
&'static ViewDepthTexture,
Option<&'static MainPassResolutionOverride>,
);
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
(camera, view, target, depth): QueryItem<Self::ViewQuery>,
(camera, view, target, depth, resolution_override): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
@ -69,7 +70,7 @@ impl ViewNode for MainTransparentPass3dNode {
let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_3d");
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) {

View File

@ -1,4 +1,5 @@
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::camera::MainPassResolutionOverride;
use bevy_render::experimental::occlusion_culling::OcclusionCulling;
use bevy_render::render_graph::ViewNode;
@ -66,6 +67,7 @@ impl ViewNode for LateDeferredGBufferPrepassNode {
&'static ExtractedView,
&'static ViewDepthTexture,
&'static ViewPrepassTextures,
Option<&'static MainPassResolutionOverride>,
Has<OcclusionCulling>,
Has<NoIndirectDrawing>,
);
@ -77,7 +79,7 @@ impl ViewNode for LateDeferredGBufferPrepassNode {
view_query: QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let (_, _, _, _, occlusion_culling, no_indirect_drawing) = view_query;
let (.., occlusion_culling, no_indirect_drawing) = view_query;
if !occlusion_culling || no_indirect_drawing {
return Ok(());
}
@ -105,7 +107,7 @@ impl ViewNode for LateDeferredGBufferPrepassNode {
fn run_deferred_prepass<'w>(
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(camera, extracted_view, view_depth_texture, view_prepass_textures, _, _): QueryItem<
(camera, extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _): QueryItem<
'w,
'_,
<LateDeferredGBufferPrepassNode as ViewNode>::ViewQuery,
@ -220,7 +222,7 @@ fn run_deferred_prepass<'w>(
});
let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
// Opaque draws

View File

@ -1,6 +1,6 @@
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode},
render_resource::{BindGroupEntries, PipelineCache, RenderPassDescriptor},
renderer::RenderContext,
@ -23,13 +23,14 @@ impl ViewNode for OitResolveNode {
&'static ViewUniformOffset,
&'static OitResolvePipelineId,
&'static ViewDepthTexture,
Option<&'static MainPassResolutionOverride>,
);
fn run(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
(camera, view_target, view_uniform, oit_resolve_pipeline_id, depth): QueryItem<
(camera, view_target, view_uniform, oit_resolve_pipeline_id, depth, resolution_override): QueryItem<
Self::ViewQuery,
>,
world: &World,
@ -63,7 +64,7 @@ impl ViewNode for OitResolveNode {
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
render_pass.set_render_pipeline(pipeline);

View File

@ -1,6 +1,6 @@
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
diagnostic::RecordDiagnostics,
experimental::occlusion_culling::OcclusionCulling,
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
@ -64,6 +64,7 @@ impl ViewNode for LatePrepassNode {
Option<&'static RenderSkyboxPrepassPipeline>,
Option<&'static SkyboxPrepassBindGroup>,
Option<&'static PreviousViewUniformOffset>,
Option<&'static MainPassResolutionOverride>,
Has<OcclusionCulling>,
Has<NoIndirectDrawing>,
Has<DeferredPrepass>,
@ -78,7 +79,7 @@ impl ViewNode for LatePrepassNode {
) -> Result<(), NodeRunError> {
// We only need a late prepass if we have occlusion culling and indirect
// drawing.
let (_, _, _, _, _, _, _, _, _, occlusion_culling, no_indirect_drawing, _) = query;
let (.., occlusion_culling, no_indirect_drawing, _) = query;
if !occlusion_culling || no_indirect_drawing {
return Ok(());
}
@ -109,6 +110,7 @@ fn run_prepass<'w>(
skybox_prepass_pipeline,
skybox_prepass_bind_group,
view_prev_uniform_offset,
resolution_override,
_,
_,
has_deferred,
@ -183,7 +185,7 @@ fn run_prepass<'w>(
let pass_span = diagnostics.pass_span(&mut render_pass, label);
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
// Opaque draws

View File

@ -18,7 +18,7 @@ use bevy_ecs::{
world::World,
};
use bevy_render::{
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_resource::{
LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor,
@ -42,6 +42,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode {
&'static ViewLightProbesUniformOffset,
&'static ViewScreenSpaceReflectionsUniformOffset,
&'static ViewEnvironmentMapUniformOffset,
Option<&'static MainPassResolutionOverride>,
&'static MeshletViewMaterialsMainOpaquePass,
&'static MeshletViewBindGroups,
&'static MeshletViewResources,
@ -61,6 +62,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode {
view_light_probes_offset,
view_ssr_offset,
view_environment_map_offset,
resolution_override,
meshlet_view_materials,
meshlet_view_bind_groups,
meshlet_view_resources,
@ -101,7 +103,7 @@ impl ViewNode for MeshletMainOpaquePass3dNode {
occlusion_query_set: None,
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
render_pass.set_bind_group(
@ -147,6 +149,7 @@ impl ViewNode for MeshletPrepassNode {
&'static ViewPrepassTextures,
&'static ViewUniformOffset,
&'static PreviousViewUniformOffset,
Option<&'static MainPassResolutionOverride>,
Has<MotionVectorPrepass>,
&'static MeshletViewMaterialsPrepass,
&'static MeshletViewBindGroups,
@ -162,6 +165,7 @@ impl ViewNode for MeshletPrepassNode {
view_prepass_textures,
view_uniform_offset,
previous_view_uniform_offset,
resolution_override,
view_has_motion_vector_prepass,
meshlet_view_materials,
meshlet_view_bind_groups,
@ -219,7 +223,7 @@ impl ViewNode for MeshletPrepassNode {
occlusion_query_set: None,
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
if view_has_motion_vector_prepass {
@ -270,6 +274,7 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode {
&'static ViewPrepassTextures,
&'static ViewUniformOffset,
&'static PreviousViewUniformOffset,
Option<&'static MainPassResolutionOverride>,
Has<MotionVectorPrepass>,
&'static MeshletViewMaterialsDeferredGBufferPrepass,
&'static MeshletViewBindGroups,
@ -285,6 +290,7 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode {
view_prepass_textures,
view_uniform_offset,
previous_view_uniform_offset,
resolution_override,
view_has_motion_vector_prepass,
meshlet_view_materials,
meshlet_view_bind_groups,
@ -347,7 +353,7 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode {
occlusion_query_set: None,
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
if view_has_motion_vector_prepass {

View File

@ -111,6 +111,17 @@ impl Viewport {
}
}
}
pub fn with_override(
&self,
main_pass_resolution_override: Option<&MainPassResolutionOverride>,
) -> Self {
let mut viewport = self.clone();
if let Some(override_size) = main_pass_resolution_override {
viewport.physical_size = **override_size;
}
viewport
}
}
/// Settings to define a camera sub view.
@ -1366,6 +1377,19 @@ impl TemporalJitter {
#[reflect(Default, Component)]
pub struct MipBias(pub f32);
/// Override the resolution a 3d camera's main pass is rendered at.
///
/// Does not affect post processing.
///
/// ## Usage
///
/// * Insert this component on a 3d camera entity in the render world.
/// * The resolution override must be smaller than the camera's viewport size.
/// * The resolution override is specified in physical pixels.
#[derive(Component, Reflect, Deref)]
#[reflect(Component)]
pub struct MainPassResolutionOverride(pub UVec2);
impl Default for MipBias {
fn default() -> Self {
Self(-1.0)

View File

@ -29,6 +29,7 @@ impl Plugin for CameraPlugin {
.register_type::<Exposure>()
.register_type::<TemporalJitter>()
.register_type::<MipBias>()
.register_type::<MainPassResolutionOverride>()
.init_resource::<ManualTextureViews>()
.init_resource::<ClearColor>()
.add_plugins((

View File

@ -34,7 +34,7 @@ use bevy::{
},
GetBatchData, GetFullBatchData,
},
camera::ExtractedCamera,
camera::{ExtractedCamera, MainPassResolutionOverride},
extract_component::{ExtractComponent, ExtractComponentPlugin},
mesh::{allocator::MeshAllocator, MeshVertexBufferLayoutRef, RenderMesh},
render_asset::RenderAssets,
@ -589,13 +589,14 @@ impl ViewNode for CustomDrawNode {
&'static ExtractedCamera,
&'static ExtractedView,
&'static ViewTarget,
Option<&'static MainPassResolutionOverride>,
);
fn run<'w>(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(camera, view, target): QueryItem<'w, '_, Self::ViewQuery>,
(camera, view, target, resolution_override): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
// First, we need to get our phases resource
@ -625,7 +626,7 @@ impl ViewNode for CustomDrawNode {
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
render_pass.set_camera_viewport(&viewport.with_override(resolution_override));
}
// Render the phase