Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
cea52ad
Expose multiview_mask field on RenderPipelineDescriptor
bigmark222 May 21, 2026
a907dae
Add DynamicArrayUniformBuffer for packed runtime-sized arrays
bigmark222 May 21, 2026
27148a6
Add Multiview component for single-pass stereo cameras
bigmark222 May 21, 2026
ec45f7c
Pack ViewUniforms via DynamicArrayUniformBuffer
bigmark222 May 21, 2026
fe17922
Tighten DynamicArrayUniformBuffer test and binding() doc
bigmark222 May 22, 2026
d24715c
Cap Multiview at MAX_VIEW_COUNT (32) and skip empty views
bigmark222 May 22, 2026
3ada6e9
Correct build_view_uniform comment about clip_from_world override
bigmark222 May 22, 2026
7de2e6d
Allocate ViewTarget main texture as array for Multiview cameras
bigmark222 May 22, 2026
04ed678
Reshape mesh view binding as a runtime-sized View array
bigmark222 May 22, 2026
4e30054
Plumb MAX_VIEW_COUNT through mesh + prepass pipelines
bigmark222 May 22, 2026
49a33cf
Update remaining view.X consumers to view()
bigmark222 May 22, 2026
64bd95b
Plumb @builtin(view_index) into pbr_prepass + wireframe entries
bigmark222 May 22, 2026
d2df899
Add input_texture.wgsl helper for fullscreen post-fx multiview
bigmark222 May 22, 2026
ebab8b7
Drop offset variants from input_texture.wgsl helper family
bigmark222 May 22, 2026
d23224f
Convert blit pipeline to multiview
bigmark222 May 22, 2026
7d3ef04
Convert bloom first-downsample to multiview
bigmark222 May 22, 2026
81207d0
Convert FXAA pipeline to multiview
bigmark222 May 22, 2026
ec31300
Convert SSAO read to multiview
bigmark222 May 22, 2026
50a5185
Convert prepass-texture reads to multiview
bigmark222 May 22, 2026
4a02b84
Convert transmission texture read to multiview
bigmark222 May 22, 2026
5a346ce
Push MULTIVIEW shader-def in deferred lighting + SSR pipelines
bigmark222 May 22, 2026
80f52dd
Allocate SSAO textures per-eye and dispatch per-eye
bigmark222 May 24, 2026
daeb2c4
Convert skybox view binding to MAX_VIEW_COUNT array
bigmark222 May 24, 2026
0cd0370
Gate per-layer prepass view creation on prepass texture layer count
bigmark222 May 24, 2026
48d6c68
Convert tonemapping view binding to MAX_VIEW_COUNT array
bigmark222 May 24, 2026
d4a61b9
Convert OIT resolve view binding to MAX_VIEW_COUNT array
bigmark222 May 24, 2026
01809fd
Convert background motion vectors view binding to MAX_VIEW_COUNT array
bigmark222 May 24, 2026
43fe133
Convert GPU clustering view bindings to MAX_VIEW_COUNT array
bigmark222 May 24, 2026
c315f71
Grow prepass + depth textures per-eye and add per-layer attachment API
bigmark222 May 24, 2026
ac409d6
Dispatch prepass + deferred render-graph nodes per eye
bigmark222 May 24, 2026
e186c03
F1 Track first-call latch per layer in attachment per-layer accessors
bigmark222 May 24, 2026
dc391d3
Convert volumetric fog depth binding to per-eye D2Array under MULTIVIEW
bigmark222 May 24, 2026
a82e6b8
Convert atmosphere render_sky depth binding to per-eye D2Array under …
bigmark222 May 24, 2026
32e7770
Convert depth of field depth binding to per-eye D2Array under MULTIVIEW
bigmark222 May 24, 2026
d92e550
F2 Seed per-layer first-call slots from current global latch
bigmark222 May 24, 2026
544cdeb
Convert transmission texture + main pass to per-eye D2Array under MUL…
bigmark222 May 24, 2026
572af15
Broadcast background motion vectors under MULTIVIEW (L7d)
bigmark222 May 24, 2026
4392068
F1 Use shift-safe formulation for L7d multiview_mask
bigmark222 May 24, 2026
a7fd04a
Broadcast FXAA under MULTIVIEW (L7d)
bigmark222 May 24, 2026
d799b04
Broadcast tonemapping under MULTIVIEW (L7d)
bigmark222 May 24, 2026
1490228
Extract skybox into broadcast pass under MULTIVIEW (L7d)
bigmark222 May 24, 2026
109bde0
Broadcast main_opaque_pass_3d Opaque3d / AlphaMask3d under MULTIVIEW …
bigmark222 May 24, 2026
90bb176
Broadcast prepass items under MULTIVIEW (L7d Shape D)
bigmark222 May 24, 2026
41ef9ff
Broadcast deferred prepass under MULTIVIEW (L7d Shape D)
bigmark222 May 24, 2026
8421161
Strip session-internal phase labels from in-tree comments
bigmark222 May 24, 2026
8652f9e
Ignore .scratch/ working-notes directory
bigmark222 May 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ compressed_texture_cache

# Generated by "examples/dev_tools/schedule_data.rs"
**/app_data.ron

# AI assistant scratch notes (architecture reverse-engineering, never committed)
.scratch/
66 changes: 46 additions & 20 deletions crates/bevy_anti_alias/src/fxaa/fxaa.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// Tweaks by mrDIMAS - https://github.com/FyroxEngine/Fyrox/blob/master/src/renderer/shaders/fxaa_fs.glsl

#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
#import bevy_core_pipeline::input_texture::{input_texture, sample_input_level, current_view_index}

@group(0) @binding(0) var screenTexture: texture_2d<f32>;
@group(0) @binding(1) var samp: sampler;

// Trims the algorithm from processing darks.
Expand Down Expand Up @@ -73,22 +73,40 @@ fn rgb2luma(rgb: vec3<f32>) -> f32 {

// Performs FXAA post-process anti-aliasing as described in the Nvidia FXAA white paper and the associated shader code.
@fragment
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let resolution = vec2<f32>(textureDimensions(screenTexture));
fn fragment(
in: FullscreenVertexOutput,
#ifdef MULTIVIEW
@builtin(view_index) view_index: i32,
#endif
) -> @location(0) vec4<f32> {
#ifdef MULTIVIEW
current_view_index = view_index;
#endif
let resolution = vec2<f32>(textureDimensions(input_texture));
let inverseScreenSize = 1.0 / resolution.xy;
let texCoord = in.position.xy * inverseScreenSize;

let centerSample = textureSampleLevel(screenTexture, samp, texCoord, 0.0);
let centerSample = sample_input_level(samp, texCoord, 0.0);
let colorCenter = centerSample.rgb;

// Luma at the current fragment
let lumaCenter = rgb2luma(colorCenter);

// Luma at the four direct neighbors of the current fragment.
let lumaDown = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(0, -1)).rgb);
let lumaUp = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(0, 1)).rgb);
let lumaLeft = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(-1, 0)).rgb);
let lumaRight = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(1, 0)).rgb);
// WGSL requires the const-offset operand of textureSampleLevel to be a
// const-expression, so it can't pass through a helper — branch the
// multiview/non-multiview cases at the callsite.
#ifdef MULTIVIEW
let lumaDown = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(0, -1)).rgb);
let lumaUp = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(0, 1)).rgb);
let lumaLeft = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(-1, 0)).rgb);
let lumaRight = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(1, 0)).rgb);
#else
let lumaDown = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(0, -1)).rgb);
let lumaUp = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(0, 1)).rgb);
let lumaLeft = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(-1, 0)).rgb);
let lumaRight = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(1, 0)).rgb);
#endif

// Find the maximum and minimum luma around the current fragment.
let lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
Expand All @@ -102,11 +120,19 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
return centerSample;
}

// Query the 4 remaining corners lumas.
let lumaDownLeft = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(-1, -1)).rgb);
let lumaUpRight = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(1, 1)).rgb);
let lumaUpLeft = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(-1, 1)).rgb);
let lumaDownRight = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(1, -1)).rgb);
// Query the 4 remaining corners lumas. Same const-offset constraint as
// above — duplicate the call set under MULTIVIEW.
#ifdef MULTIVIEW
let lumaDownLeft = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(-1, -1)).rgb);
let lumaUpRight = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(1, 1)).rgb);
let lumaUpLeft = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(-1, 1)).rgb);
let lumaDownRight = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, current_view_index, 0.0, vec2<i32>(1, -1)).rgb);
#else
let lumaDownLeft = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(-1, -1)).rgb);
let lumaUpRight = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(1, 1)).rgb);
let lumaUpLeft = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(-1, 1)).rgb);
let lumaDownRight = rgb2luma(textureSampleLevel(input_texture, samp, texCoord, 0.0, vec2<i32>(1, -1)).rgb);
#endif

// Combine the four edges lumas (using intermediary variables for future computations with the same values).
let lumaDownUp = lumaDown + lumaUp;
Expand Down Expand Up @@ -174,8 +200,8 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
var uv2 = currentUv + offset; // * QUALITY(0); // (quality 0 is 1.0)

// Read the lumas at both current extremities of the exploration segment, and compute the delta wrt to the local average luma.
var lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture, samp, uv1, 0.0).rgb);
var lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture, samp, uv2, 0.0).rgb);
var lumaEnd1 = rgb2luma(sample_input_level(samp, uv1, 0.0).rgb);
var lumaEnd2 = rgb2luma(sample_input_level(samp, uv2, 0.0).rgb);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
lumaEnd2 = lumaEnd2 - lumaLocalAverage;

Expand All @@ -192,13 +218,13 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
if (!reachedBoth) {
for (var i: i32 = 2; i < ITERATIONS; i = i + 1) {
// If needed, read luma in 1st direction, compute delta.
if (!reached1) {
lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture, samp, uv1, 0.0).rgb);
if (!reached1) {
lumaEnd1 = rgb2luma(sample_input_level(samp, uv1, 0.0).rgb);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
}
// If needed, read luma in opposite direction, compute delta.
if (!reached2) {
lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture, samp, uv2, 0.0).rgb);
if (!reached2) {
lumaEnd2 = rgb2luma(sample_input_level(samp, uv2, 0.0).rgb);
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
}
// If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge.
Expand Down Expand Up @@ -269,6 +295,6 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
}

// Read the color at the new UV coordinates, and use it.
var finalColor = textureSampleLevel(screenTexture, samp, finalUv, 0.0).rgb;
var finalColor = sample_input_level(samp, finalUv, 0.0).rgb;
return vec4<f32>(finalColor, centerSample.a);
}
71 changes: 60 additions & 11 deletions crates/bevy_anti_alias/src/fxaa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ use bevy_render::{
camera::ExtractedCamera,
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_resource::{
binding_types::{sampler, texture_2d},
binding_types::{sampler, texture_2d, texture_2d_array},
*,
},
renderer::RenderDevice,
view::ExtractedView,
view::{ExtractedMultiview, ExtractedView},
GpuResourceAppExt, Render, RenderApp, RenderStartup, RenderSystems,
};
use bevy_shader::Shader;
use bevy_shader::{Shader, ShaderDefVal};
use bevy_utils::default;
use core::num::NonZeroU32;

mod node;

Expand Down Expand Up @@ -112,7 +113,10 @@ impl Plugin for FxaaPlugin {

#[derive(Resource)]
pub struct FxaaPipeline {
texture_bind_group: BindGroupLayoutDescriptor,
pub texture_bind_group: BindGroupLayoutDescriptor,
/// Multiview bind-group layout — the texture binding is a
/// `texture_2d_array` whose layer is picked from `@builtin(view_index)`.
pub texture_bind_group_multiview: BindGroupLayoutDescriptor,
sampler: Sampler,
fullscreen_shader: FullscreenShader,
fragment_shader: Handle<Shader>,
Expand All @@ -135,6 +139,17 @@ pub fn init_fxaa_pipeline(
),
);

let texture_bind_group_multiview = BindGroupLayoutDescriptor::new(
"fxaa_texture_bind_group_layout_multiview",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d_array(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);

let sampler = render_device.create_sampler(&SamplerDescriptor {
mipmap_filter: MipmapFilterMode::Linear,
mag_filter: FilterMode::Linear,
Expand All @@ -144,6 +159,7 @@ pub fn init_fxaa_pipeline(

commands.insert_resource(FxaaPipeline {
texture_bind_group,
texture_bind_group_multiview,
sampler,
fullscreen_shader: fullscreen_shader.clone(),
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "fxaa.wgsl"),
Expand All @@ -160,22 +176,50 @@ pub struct FxaaPipelineKey {
edge_threshold: Sensitivity,
edge_threshold_min: Sensitivity,
target_format: TextureFormat,
/// Source texture layer count. `> 1` picks the `texture_2d_array`
/// layout and emits `MULTIVIEW` + `MAX_VIEW_COUNT` shader-defs.
multiview_view_count: u32,
}

impl SpecializedRenderPipeline for FxaaPipeline {
type Key = FxaaPipelineKey;

fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut shader_defs = vec![
format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(),
format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(),
];

let layout = if key.multiview_view_count > 1 {
shader_defs.push("MULTIVIEW".into());
shader_defs.push(ShaderDefVal::UInt(
"MAX_VIEW_COUNT".into(),
key.multiview_view_count,
));
self.texture_bind_group_multiview.clone()
} else {
self.texture_bind_group.clone()
};

// Broadcast across every eye layer in a single pass. The matching
// render-pass descriptor in `node.rs` sets the same mask. The mask
// is `(1 << view_count) - 1` (one bit per eye); computed via
// `u32::MAX >> (32 - view_count)` to avoid the shift overflow that
// `1 << 32` would hit at the `MAX_VIEW_COUNT` cap.
let multiview_mask = if key.multiview_view_count > 1 {
NonZeroU32::new(u32::MAX >> (32 - key.multiview_view_count))
} else {
None
};

RenderPipelineDescriptor {
label: Some("fxaa".into()),
layout: vec![self.texture_bind_group.clone()],
multiview_mask,
layout: vec![layout],
vertex: self.fullscreen_shader.to_vertex_state(),
fragment: Some(FragmentState {
shader: self.fragment_shader.clone(),
shader_defs: vec![
format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(),
format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(),
],
shader_defs,
targets: vec![Some(ColorTargetState {
format: key.target_format,
blend: None,
Expand All @@ -193,19 +237,24 @@ pub fn prepare_fxaa_pipelines(
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<FxaaPipeline>>,
fxaa_pipeline: Res<FxaaPipeline>,
cameras: Query<(Entity, &ExtractedView, &Fxaa), With<ExtractedCamera>>,
cameras: Query<
(Entity, &ExtractedView, &Fxaa, Option<&ExtractedMultiview>),
With<ExtractedCamera>,
>,
) {
for (entity, view, fxaa) in &cameras {
for (entity, view, fxaa, multiview) in &cameras {
if !fxaa.enabled {
continue;
}
let multiview_view_count = multiview.map_or(1, |m| m.subviews.len() as u32);
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&fxaa_pipeline,
FxaaPipelineKey {
edge_threshold: fxaa.edge_threshold,
edge_threshold_min: fxaa.edge_threshold_min,
target_format: view.target_format,
multiview_view_count,
},
);

Expand Down
22 changes: 20 additions & 2 deletions crates/bevy_anti_alias/src/fxaa/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy_render::{
renderer::{RenderContext, ViewQuery},
view::ViewTarget,
};
use core::num::NonZeroU32;

pub fn fxaa(
view: ViewQuery<(&ViewTarget, &CameraFxaaPipeline, &Fxaa)>,
Expand All @@ -30,12 +31,17 @@ pub fn fxaa(
let post_process = target.post_process_write();
let source = post_process.source;
let destination = post_process.destination;
let layout = if target.multiview_count().is_some() {
&fxaa_pipeline.texture_bind_group_multiview
} else {
&fxaa_pipeline.texture_bind_group
};
let bind_group = match &mut *cached_bind_group {
Some((id, bind_group)) if source.id() == *id => bind_group,
cached => {
let bind_group = ctx.render_device().create_bind_group(
None,
&pipeline_cache.get_bind_group_layout(&fxaa_pipeline.texture_bind_group),
&pipeline_cache.get_bind_group_layout(layout),
&BindGroupEntries::sequential((source, &fxaa_pipeline.sampler)),
);

Expand All @@ -44,6 +50,18 @@ pub fn fxaa(
}
};

// Broadcast across every eye layer in a single pass. The matching
// pipeline descriptor in `mod.rs` sets the same mask. The mask is
// `(1 << view_count) - 1` (one bit per eye); computed via
// `u32::MAX >> (32 - view_count)` to avoid the shift overflow that
// `1 << 32` would hit at the `MAX_VIEW_COUNT` cap.
let view_count = target.multiview_count().map_or(1, |n| n.get());
let multiview_mask = if view_count > 1 {
NonZeroU32::new(u32::MAX >> (32 - view_count))
} else {
None
};

let pass_descriptor = RenderPassDescriptor {
label: Some("fxaa"),
color_attachments: &[Some(RenderPassColorAttachment {
Expand All @@ -55,7 +73,7 @@ pub fn fxaa(
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
multiview_mask,
};

let diagnostics = ctx.diagnostic_recorder();
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_camera/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod camera;
mod clear_color;
mod components;
mod multiview;
pub mod primitives;
mod projection;
pub mod visibility;
Expand All @@ -10,6 +11,7 @@ use bevy_ecs::schedule::SystemSet;
pub use camera::*;
pub use clear_color::*;
pub use components::*;
pub use multiview::*;
pub use projection::*;

use bevy_app::{App, Plugin};
Expand Down
Loading
Loading