Skip to main content

bevy_render/view/
mod.rs

1pub mod composition;
2pub mod visibility;
3pub mod window;
4
5use bevy_camera::{
6    primitives::Frustum, Camera, CameraMainTextureUsages, ClearColor, ClearColorConfig,
7    CompositingSpace, Exposure, MainPassResolutionOverride, NormalizedRenderTarget,
8};
9use bevy_diagnostic::FrameCount;
10pub use composition::*;
11pub use visibility::*;
12pub use window::*;
13
14use crate::{
15    camera::{ExtractedCamera, MipBias, NormalizedRenderTargetExt as _, TemporalJitter},
16    extract_component::ExtractComponentPlugin,
17    occlusion_culling::OcclusionCulling,
18    render_asset::RenderAssets,
19    render_phase::ViewRangefinder3d,
20    render_resource::{
21        BindGroup, DynamicUniformBuffer, ShaderType, Texture, TextureView, TextureViewId,
22    },
23    renderer::{RenderDevice, RenderQueue},
24    sync_world::MainEntity,
25    texture::{
26        CachedTexture, ColorAttachment, DepthStencilAttachment, GpuImage, ManualTextureViews,
27        OutputColorAttachment, TextureCache,
28    },
29    GpuResourceAppExt, Render, RenderApp, RenderSystems,
30};
31use alloc::sync::{Arc, Weak};
32use bevy_app::{App, Plugin};
33use bevy_color::{LinearRgba, Oklaba, Srgba};
34use bevy_derive::{Deref, DerefMut};
35use bevy_ecs::prelude::*;
36use bevy_extract_macros::ExtractComponent;
37use bevy_image::ToExtents;
38use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
39use bevy_platform::collections::{hash_map::Entry, HashMap};
40use bevy_reflect::{std_traits::ReflectDefault, Reflect};
41use bevy_transform::components::GlobalTransform;
42use core::{
43    ops::Range,
44    sync::atomic::{AtomicUsize, Ordering},
45};
46use wgpu::{
47    BufferUsages, Color as WgpuColor, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
48    StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
49};
50
51/// The matrix that converts from the RGB to the LMS color space.
52///
53/// To derive this, first we convert from RGB to [CIE 1931 XYZ]:
54///
55/// ```text
56/// ⎡ X ⎤   ⎡ 0.490  0.310  0.200 ⎤ ⎡ R ⎤
57/// ⎢ Y ⎥ = ⎢ 0.177  0.812  0.011 ⎥ ⎢ G ⎥
58/// ⎣ Z ⎦   ⎣ 0.000  0.010  0.990 ⎦ ⎣ B ⎦
59/// ```
60///
61/// Then we convert to LMS according to the [CAM16 standard matrix]:
62///
63/// ```text
64/// ⎡ L ⎤   ⎡  0.401   0.650  -0.051 ⎤ ⎡ X ⎤
65/// ⎢ M ⎥ = ⎢ -0.250   1.204   0.046 ⎥ ⎢ Y ⎥
66/// ⎣ S ⎦   ⎣ -0.002   0.049   0.953 ⎦ ⎣ Z ⎦
67/// ```
68///
69/// The resulting matrix is just the concatenation of these two matrices, to do
70/// the conversion in one step.
71///
72/// [CIE 1931 XYZ]: https://en.wikipedia.org/wiki/CIE_1931_color_space
73/// [CAM16 standard matrix]: https://en.wikipedia.org/wiki/LMS_color_space
74static RGB_TO_LMS: Mat3 = mat3(
75    vec3(0.311692, 0.0905138, 0.00764433),
76    vec3(0.652085, 0.901341, 0.0486554),
77    vec3(0.0362225, 0.00814478, 0.943700),
78);
79
80/// The inverse of the [`RGB_TO_LMS`] matrix, converting from the LMS color
81/// space back to RGB.
82static LMS_TO_RGB: Mat3 = mat3(
83    vec3(4.06305, -0.40791, -0.0118812),
84    vec3(-2.93241, 1.40437, -0.0486532),
85    vec3(-0.130646, 0.00353630, 1.0605344),
86);
87
88/// The [CIE 1931] *xy* chromaticity coordinates of the [D65 white point].
89///
90/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space
91/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
92static D65_XY: Vec2 = vec2(0.31272, 0.32903);
93
94/// The [D65 white point] in [LMS color space].
95///
96/// [LMS color space]: https://en.wikipedia.org/wiki/LMS_color_space
97/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
98static D65_LMS: Vec3 = vec3(0.975538, 1.01648, 1.08475);
99
100/// Mask bits (5-bit) for use in pipeline key bitfields.
101pub const COLOR_TARGET_FORMAT_MASK_BITS: u32 = 0b11111;
102
103/// Encode a [`TextureFormat`] as a 5-bit code for use in pipeline key bitfields.
104///
105/// Covers all WebGPU renderable and blendable texture formats. Some of them need optional features.
106/// See <https://gpuweb.github.io/gpuweb/#plain-color-formats>.
107#[inline]
108pub fn texture_format_to_code(format: TextureFormat) -> Option<u8> {
109    Some(match format {
110        TextureFormat::R8Unorm => 0,
111        TextureFormat::R8Snorm => 1,
112        TextureFormat::Rg8Unorm => 2,
113        TextureFormat::Rg8Snorm => 3,
114        TextureFormat::Rgba8Unorm => 4,
115        TextureFormat::Rgba8UnormSrgb => 5,
116        TextureFormat::Rgba8Snorm => 6,
117        TextureFormat::Bgra8Unorm => 7,
118        TextureFormat::Bgra8UnormSrgb => 8,
119        TextureFormat::R16Float => 11,
120        TextureFormat::R16Unorm => 9,
121        TextureFormat::R16Snorm => 10,
122        TextureFormat::Rg16Float => 12,
123        TextureFormat::Rg16Unorm => 13,
124        TextureFormat::Rg16Snorm => 14,
125        TextureFormat::Rgba16Float => 15,
126        TextureFormat::Rgba16Unorm => 16,
127        TextureFormat::Rgba16Snorm => 17,
128        TextureFormat::R32Float => 18,
129        TextureFormat::Rg32Float => 19,
130        TextureFormat::Rgba32Float => 20,
131        TextureFormat::Rgb10a2Unorm => 21,
132        TextureFormat::Rg11b10Ufloat => 22,
133        _ => return None,
134    })
135}
136
137/// Decode a 5-bit code back into a [`TextureFormat`].
138///
139/// Inverse of [`texture_format_to_code`].
140#[inline]
141pub fn texture_format_from_code(code: u8) -> Option<TextureFormat> {
142    Some(match code {
143        0 => TextureFormat::R8Unorm,
144        1 => TextureFormat::R8Snorm,
145        2 => TextureFormat::Rg8Unorm,
146        3 => TextureFormat::Rg8Snorm,
147        4 => TextureFormat::Rgba8Unorm,
148        5 => TextureFormat::Rgba8UnormSrgb,
149        6 => TextureFormat::Rgba8Snorm,
150        7 => TextureFormat::Bgra8Unorm,
151        8 => TextureFormat::Bgra8UnormSrgb,
152        11 => TextureFormat::R16Float,
153        9 => TextureFormat::R16Unorm,
154        10 => TextureFormat::R16Snorm,
155        12 => TextureFormat::Rg16Float,
156        13 => TextureFormat::Rg16Unorm,
157        14 => TextureFormat::Rg16Snorm,
158        15 => TextureFormat::Rgba16Float,
159        16 => TextureFormat::Rgba16Unorm,
160        17 => TextureFormat::Rgba16Snorm,
161        18 => TextureFormat::R32Float,
162        19 => TextureFormat::Rg32Float,
163        20 => TextureFormat::Rgba32Float,
164        21 => TextureFormat::Rgb10a2Unorm,
165        22 => TextureFormat::Rg11b10Ufloat,
166        _ => return None,
167    })
168}
169
170pub struct ViewPlugin;
171
172impl Plugin for ViewPlugin {
173    fn build(&self, app: &mut App) {
174        app
175            // NOTE: windows.is_changed() handles cases where a window was resized
176            .add_plugins((
177                ExtractComponentPlugin::<Msaa>::default(),
178                ExtractComponentPlugin::<OcclusionCulling>::default(),
179                RenderVisibilityRangePlugin,
180            ));
181
182        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
183            render_app.configure_sets(
184                Render,
185                ResolveCompositingSpaces
186                    .in_set(RenderSystems::CreateViews)
187                    .after(crate::camera::sort_cameras),
188            );
189            render_app.add_systems(
190                Render,
191                (
192                    resolve_composition_spaces.in_set(ResolveCompositingSpaces),
193                    // `TextureView`s need to be dropped before reconfiguring window surfaces.
194                    clear_view_attachments
195                        .in_set(RenderSystems::PrepareViews)
196                        .before(create_surfaces),
197                    cleanup_view_targets_for_resize
198                        .in_set(RenderSystems::PrepareViews)
199                        .before(create_surfaces),
200                    prepare_view_attachments
201                        .in_set(RenderSystems::PrepareViews)
202                        .before(prepare_view_targets)
203                        .after(prepare_windows),
204                    prepare_view_targets
205                        .in_set(RenderSystems::PrepareViews)
206                        .after(prepare_windows)
207                        .after(crate::render_asset::prepare_assets::<GpuImage>)
208                        .ambiguous_with(crate::camera::sort_cameras), // doesn't use `sorted_camera_index_for_target`
209                    prepare_view_uniforms.in_set(RenderSystems::PrepareResources),
210                    collect_visible_cpu_culled_entities.in_set(RenderSystems::PrepareAssets),
211                ),
212            );
213        }
214    }
215
216    fn finish(&self, app: &mut App) {
217        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
218            render_app
219                .init_gpu_resource::<ViewUniforms>()
220                .init_gpu_resource::<ViewTargetAttachments>();
221        }
222    }
223}
224
225/// Component for configuring the number of samples for [Multi-Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing)
226/// for a [`Camera`].
227///
228/// Defaults to 4 samples. A higher number of samples results in smoother edges.
229///
230/// Some advanced rendering features may require that MSAA is disabled.
231///
232/// Note that the web currently only supports 1 or 4 samples.
233#[derive(
234    Component,
235    Default,
236    Clone,
237    Copy,
238    ExtractComponent,
239    Reflect,
240    PartialEq,
241    PartialOrd,
242    Eq,
243    Hash,
244    Debug,
245)]
246#[reflect(Component, Default, PartialEq, Hash, Debug)]
247#[extract_app(RenderApp)]
248pub enum Msaa {
249    Off = 1,
250    Sample2 = 2,
251    #[default]
252    Sample4 = 4,
253    Sample8 = 8,
254}
255
256impl Msaa {
257    #[inline]
258    pub fn samples(&self) -> u32 {
259        *self as u32
260    }
261
262    pub fn from_samples(samples: u32) -> Self {
263        match samples {
264            1 => Msaa::Off,
265            2 => Msaa::Sample2,
266            4 => Msaa::Sample4,
267            8 => Msaa::Sample8,
268            _ => panic!("Unsupported MSAA sample count: {samples}"),
269        }
270    }
271}
272
273/// Optionally enables a tonemapping shader that attempts to map linear input stimulus into a perceptually uniform image for a given [`Camera`] entity.
274///
275/// The tonemapping pass lives in `bevy_core_pipeline`. The type is defined in
276/// `bevy_render` so render-world code can read it.
277#[derive(
278    Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq,
279)]
280#[extract_component_filter(With<Camera>)]
281#[reflect(Component, Debug, Hash, Default, PartialEq)]
282#[extract_app(RenderApp)]
283pub enum Tonemapping {
284    /// Bypass tonemapping. No color grading, exposure, or dither applies.
285    None,
286    /// Identity tone curve. [`ColorGrading`], exposure, and [`DebandDither`]
287    /// still apply. The output is unbounded display-linear.
288    Linear,
289    /// Suffers from lots hue shifting, brights don't desaturate naturally.
290    /// Bright primaries and secondaries don't desaturate at all.
291    Reinhard,
292    /// Suffers from hue shifting. Brights don't desaturate much at all across the spectrum.
293    ReinhardLuminance,
294    /// Same base implementation that Godot 4.0 uses for Tonemap ACES.
295    /// <https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl>
296    /// Not neutral, has a very specific aesthetic, intentional and dramatic hue shifting.
297    /// Bright greens and reds turn orange. Bright blues turn magenta.
298    /// Significantly increased contrast. Brights desaturate across the spectrum.
299    AcesFitted,
300    /// By Troy Sobotka
301    /// <https://github.com/sobotka/AgX>
302    /// Very neutral. Image is somewhat desaturated when compared to other tonemappers.
303    /// Little to no hue shifting. Subtle [Abney shifting](https://en.wikipedia.org/wiki/Abney_effect).
304    /// NOTE: Requires the `tonemapping_luts` cargo feature.
305    AgX,
306    /// By Tomasz Stachowiak
307    /// Has little hue shifting in the darks and mids, but lots in the brights. Brights desaturate across the spectrum.
308    /// Is sort of between Reinhard and `ReinhardLuminance`. Conceptually similar to reinhard-jodie.
309    /// Designed as a compromise if you want e.g. decent skin tones in low light, but can't afford to re-do your
310    /// VFX to look good without hue shifting.
311    SomewhatBoringDisplayTransform,
312    /// Current Bevy default.
313    /// By Tomasz Stachowiak
314    /// <https://github.com/h3r2tic/tony-mc-mapface>
315    /// Very neutral. Subtle but intentional hue shifting. Brights desaturate across the spectrum.
316    /// Comment from author:
317    /// Tony is a display transform intended for real-time applications such as games.
318    /// It is intentionally boring, does not increase contrast or saturation, and stays close to the
319    /// input stimulus where compression isn't necessary.
320    /// Brightness-equivalent luminance of the input stimulus is compressed. The non-linearity resembles Reinhard.
321    /// Color hues are preserved during compression, except for a deliberate [Bezold–Brücke shift](https://en.wikipedia.org/wiki/Bezold%E2%80%93Br%C3%BCcke_shift).
322    /// To avoid posterization, selective desaturation is employed, with care to avoid the [Abney effect](https://en.wikipedia.org/wiki/Abney_effect).
323    /// NOTE: Requires the `tonemapping_luts` cargo feature.
324    #[default]
325    TonyMcMapface,
326    /// Default Filmic Display Transform from blender.
327    /// Somewhat neutral. Suffers from hue shifting. Brights desaturate across the spectrum.
328    /// NOTE: Requires the `tonemapping_luts` cargo feature.
329    BlenderFilmic,
330    /// Despite its name, it is not considered to be neutral.
331    /// Highly saturated colors and tends to produce a very high contrast image.
332    /// Suffers from significant [Abney shifting](https://en.wikipedia.org/wiki/Abney_effect), and tends to crush grays and desaturated colors.
333    /// Designed for e-commerce to faithfully reproduce the colors of brand's logos when used with low brightness grayscale lighting.
334    /// See [the KhronosGroup spec](https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral) for more information.
335    KhronosPbrNeutral,
336}
337
338impl Tonemapping {
339    pub fn is_enabled(&self) -> bool {
340        *self != Tonemapping::None
341    }
342}
343
344/// Enables a debanding shader that applies dithering to mitigate color banding in the final image for a given [`Camera`] entity.
345#[derive(
346    Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq,
347)]
348#[extract_component_filter(With<Camera>)]
349#[reflect(Component, Debug, Hash, Default, PartialEq)]
350#[extract_app(RenderApp)]
351pub enum DebandDither {
352    #[default]
353    Disabled,
354    Enabled,
355}
356
357/// An identifier for a view that is stable across frames.
358///
359/// We can't use [`Entity`] for this because render world entities aren't
360/// stable, and we can't use just [`MainEntity`] because some main world views
361/// extract to multiple render world views. For example, a directional light
362/// extracts to one render world view per cascade, and a point light extracts to
363/// one render world view per cubemap face. So we pair the main entity with an
364/// *auxiliary entity* and a *subview index*, which *together* uniquely identify
365/// a view in the render world in a way that's stable from frame to frame.
366#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
367pub struct RetainedViewEntity {
368    /// The main entity that this view corresponds to.
369    pub main_entity: MainEntity,
370
371    /// Another entity associated with the view entity.
372    ///
373    /// This is currently used for shadow cascades. If there are multiple
374    /// cameras, each camera needs to have its own set of shadow cascades. Thus
375    /// the light and subview index aren't themselves enough to uniquely
376    /// identify a shadow cascade: we need the camera that the cascade is
377    /// associated with as well. This entity stores that camera.
378    pub auxiliary_entity: Option<MainEntity>,
379
380    /// The index of the view corresponding to the entity.
381    ///
382    /// For example, for point lights that cast shadows, this is the index of
383    /// the cubemap face (0 through 5 inclusive). For directional lights, this
384    /// is the index of the cascade.
385    pub subview_index: u32,
386}
387
388impl RetainedViewEntity {
389    /// Creates a new [`RetainedViewEntity`] from the given main world entity,
390    /// auxiliary main world entity, and subview index.
391    ///
392    /// See [`RetainedViewEntity::subview_index`] for an explanation of what
393    /// `auxiliary_entity` and `subview_index` are.
394    pub fn new(
395        main_entity: MainEntity,
396        auxiliary_entity: Option<MainEntity>,
397        subview_index: u32,
398    ) -> Self {
399        Self {
400            main_entity,
401            auxiliary_entity,
402            subview_index,
403        }
404    }
405}
406
407/// Describes a view in the render world.
408///
409/// Each entity in the main world can potentially extract to multiple views,
410/// each of which have a [`RetainedViewEntity::subview_index`].
411/// For instance, point lights with shadows extract to 6 subviews,
412/// one for each side of the shadow cubemap.
413/// [`Camera3d`](bevy_camera::Camera3d) extracts into a [`ExtractedView`]
414/// and [`ExtractedCamera`] component.
415#[derive(Component)]
416pub struct ExtractedView {
417    /// The entity in the main world corresponding to this render world view.
418    pub retained_view_entity: RetainedViewEntity,
419    /// Typically a column-major right-handed projection matrix, one of either:
420    ///
421    /// Perspective (infinite reverse z)
422    /// ```text
423    /// f = 1 / tan(fov_y_radians / 2)
424    ///
425    /// ⎡ f / aspect  0   0     0 ⎤
426    /// ⎢          0  f   0     0 ⎥
427    /// ⎢          0  0   0  near ⎥
428    /// ⎣          0  0  -1     0 ⎦
429    /// ```
430    ///
431    /// Orthographic
432    /// ```text
433    /// w = right - left
434    /// h = top - bottom
435    /// d = far - near
436    /// cw = -right - left
437    /// ch = -top - bottom
438    ///
439    /// ⎡ 2 / w      0      0   cw / w ⎤
440    /// ⎢     0  2 / h      0   ch / h ⎥
441    /// ⎢     0      0  1 / d  far / d ⎥
442    /// ⎣     0      0      0        1 ⎦
443    /// ```
444    ///
445    /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic
446    ///
447    /// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]`
448    ///
449    /// Custom projections are also possible however.
450    pub clip_from_view: Mat4,
451    pub world_from_view: GlobalTransform,
452    // The view-projection matrix. When provided it is used instead of deriving it from
453    // `projection` and `transform` fields, which can be helpful in cases where numerical
454    // stability matters and there is a more direct way to derive the view-projection matrix.
455    pub clip_from_world: Option<Mat4>,
456    /// The [`TextureFormat`] this view will render to. Note that this may diverge from
457    /// the [`RenderTarget`](bevy_camera::RenderTarget)'s texture format. Among other
458    /// reasons, [`Hdr`](bevy_camera::Hdr) sets an the internal render target format
459    /// override to ensure sufficient precision is present for lighting calculations.
460    pub target_format: TextureFormat,
461    // uvec4(origin.x, origin.y, width, height)
462    pub viewport: UVec4,
463    pub color_grading: ColorGrading,
464
465    /// Whether to switch culling mode so that materials that request backface
466    /// culling cull front faces, and vice versa.
467    ///
468    /// This is typically used for cameras that mirror the world that they
469    /// render across a plane, because doing that flips the winding of each
470    /// polygon.
471    ///
472    /// This setting doesn't affect materials that disable backface culling.
473    pub invert_culling: bool,
474}
475
476impl ExtractedView {
477    /// Creates a 3D rangefinder for a view
478    pub fn rangefinder3d(&self) -> ViewRangefinder3d {
479        ViewRangefinder3d::from_world_from_view(&self.world_from_view.affine())
480    }
481}
482
483/// Configures filmic color grading parameters to adjust the image appearance.
484///
485/// Color grading is applied just before tonemapping for a given [`Camera`]
486/// entity, with the sole exception of the `post_saturation` value in
487/// [`ColorGradingGlobal`], which is applied after tonemapping.
488#[derive(Component, Reflect, Debug, Default, Clone, PartialEq)]
489#[reflect(Component, Default, Debug, Clone, PartialEq)]
490pub struct ColorGrading {
491    /// Filmic color grading values applied to the image as a whole (as opposed
492    /// to individual sections, like shadows and highlights).
493    pub global: ColorGradingGlobal,
494
495    /// Color grading values that are applied to the darker parts of the image.
496    ///
497    /// The cutoff points can be customized with the
498    /// [`ColorGradingGlobal::midtones_range`] field.
499    pub shadows: ColorGradingSection,
500
501    /// Color grading values that are applied to the parts of the image with
502    /// intermediate brightness.
503    ///
504    /// The cutoff points can be customized with the
505    /// [`ColorGradingGlobal::midtones_range`] field.
506    pub midtones: ColorGradingSection,
507
508    /// Color grading values that are applied to the lighter parts of the image.
509    ///
510    /// The cutoff points can be customized with the
511    /// [`ColorGradingGlobal::midtones_range`] field.
512    pub highlights: ColorGradingSection,
513}
514
515/// Filmic color grading values applied to the image as a whole (as opposed to
516/// individual sections, like shadows and highlights).
517#[derive(Clone, Debug, Reflect, PartialEq)]
518#[reflect(Default, Clone, PartialEq)]
519pub struct ColorGradingGlobal {
520    /// Exposure value (EV) offset, measured in stops.
521    pub exposure: f32,
522
523    /// An adjustment made to the [CIE 1931] chromaticity *x* value.
524    ///
525    /// Positive values make the colors redder. Negative values make the colors
526    /// bluer. This has no effect on luminance (brightness).
527    ///
528    /// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
529    pub temperature: f32,
530
531    /// An adjustment made to the [CIE 1931] chromaticity *y* value.
532    ///
533    /// Positive values make the colors more magenta. Negative values make the
534    /// colors greener. This has no effect on luminance (brightness).
535    ///
536    /// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
537    pub tint: f32,
538
539    /// An adjustment to the [hue], in radians.
540    ///
541    /// Adjusting this value changes the perceived colors in the image: red to
542    /// yellow to green to blue, etc. It has no effect on the saturation or
543    /// brightness of the colors.
544    ///
545    /// [hue]: https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
546    pub hue: f32,
547
548    /// Saturation adjustment applied after tonemapping.
549    /// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
550    /// with luminance defined by ITU-R BT.709
551    /// Values above 1.0 increase saturation.
552    pub post_saturation: f32,
553
554    /// The luminance (brightness) ranges that are considered part of the
555    /// "midtones" of the image.
556    ///
557    /// This affects which [`ColorGradingSection`]s apply to which colors. Note
558    /// that the sections smoothly blend into one another, to avoid abrupt
559    /// transitions.
560    ///
561    /// The default value is 0.2 to 0.7.
562    pub midtones_range: Range<f32>,
563}
564
565/// The [`ColorGrading`] structure, packed into the most efficient form for the
566/// GPU.
567#[derive(Clone, Copy, Debug, ShaderType)]
568pub struct ColorGradingUniform {
569    pub balance: Mat3,
570    pub saturation: Vec3,
571    pub contrast: Vec3,
572    pub gamma: Vec3,
573    pub gain: Vec3,
574    pub lift: Vec3,
575    pub midtone_range: Vec2,
576    pub exposure: f32,
577    pub hue: f32,
578    pub post_saturation: f32,
579}
580
581/// A section of color grading values that can be selectively applied to
582/// shadows, midtones, and highlights.
583#[derive(Reflect, Debug, Copy, Clone, PartialEq)]
584#[reflect(Clone, PartialEq)]
585pub struct ColorGradingSection {
586    /// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
587    /// with luminance defined by ITU-R BT.709.
588    /// Values above 1.0 increase saturation.
589    pub saturation: f32,
590
591    /// Adjusts the range of colors.
592    ///
593    /// A value of 1.0 applies no changes. Values below 1.0 move the colors more
594    /// toward a neutral gray. Values above 1.0 spread the colors out away from
595    /// the neutral gray.
596    pub contrast: f32,
597
598    /// A nonlinear luminance adjustment, mainly affecting the high end of the
599    /// range.
600    ///
601    /// This is the *n* exponent in the standard [ASC CDL] formula for color
602    /// correction:
603    ///
604    /// ```text
605    /// out = (i × s + o)ⁿ
606    /// ```
607    ///
608    /// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
609    pub gamma: f32,
610
611    /// A linear luminance adjustment, mainly affecting the middle part of the
612    /// range.
613    ///
614    /// This is the *s* factor in the standard [ASC CDL] formula for color
615    /// correction:
616    ///
617    /// ```text
618    /// out = (i × s + o)ⁿ
619    /// ```
620    ///
621    /// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
622    pub gain: f32,
623
624    /// A fixed luminance adjustment, mainly affecting the lower part of the
625    /// range.
626    ///
627    /// This is the *o* term in the standard [ASC CDL] formula for color
628    /// correction:
629    ///
630    /// ```text
631    /// out = (i × s + o)ⁿ
632    /// ```
633    ///
634    /// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
635    pub lift: f32,
636}
637
638impl Default for ColorGradingGlobal {
639    fn default() -> Self {
640        Self {
641            exposure: 0.0,
642            temperature: 0.0,
643            tint: 0.0,
644            hue: 0.0,
645            post_saturation: 1.0,
646            midtones_range: 0.2..0.7,
647        }
648    }
649}
650
651impl Default for ColorGradingSection {
652    fn default() -> Self {
653        Self {
654            saturation: 1.0,
655            contrast: 1.0,
656            gamma: 1.0,
657            gain: 1.0,
658            lift: 0.0,
659        }
660    }
661}
662
663impl ColorGrading {
664    /// Creates a new [`ColorGrading`] instance in which shadows, midtones, and
665    /// highlights all have the same set of color grading values.
666    pub fn with_identical_sections(
667        global: ColorGradingGlobal,
668        section: ColorGradingSection,
669    ) -> ColorGrading {
670        ColorGrading {
671            global,
672            highlights: section,
673            midtones: section,
674            shadows: section,
675        }
676    }
677
678    /// Returns an iterator that visits the shadows, midtones, and highlights
679    /// sections, in that order.
680    pub fn all_sections(&self) -> impl Iterator<Item = &ColorGradingSection> {
681        [&self.shadows, &self.midtones, &self.highlights].into_iter()
682    }
683
684    /// Applies the given mutating function to the shadows, midtones, and
685    /// highlights sections, in that order.
686    ///
687    /// Returns an array composed of the results of such evaluation, in that
688    /// order.
689    pub fn all_sections_mut(&mut self) -> impl Iterator<Item = &mut ColorGradingSection> {
690        [&mut self.shadows, &mut self.midtones, &mut self.highlights].into_iter()
691    }
692}
693
694/// A resource, part of the render world, that stores the resolved origin for
695/// LOD selection for shadow maps of point and spot lights.
696#[derive(Default, Resource, Debug)]
697pub struct RenderShadowLodOrigin(pub Vec3);
698
699#[derive(Clone, ShaderType)]
700pub struct ViewUniform {
701    pub clip_from_world: Mat4,
702    pub unjittered_clip_from_world: Mat4,
703    pub world_from_clip: Mat4,
704    pub world_from_view: Mat4,
705    pub view_from_world: Mat4,
706    /// Typically a column-major right-handed projection matrix, one of either:
707    ///
708    /// Perspective (infinite reverse z)
709    /// ```text
710    /// f = 1 / tan(fov_y_radians / 2)
711    ///
712    /// ⎡ f / aspect  0   0     0 ⎤
713    /// ⎢          0  f   0     0 ⎥
714    /// ⎢          0  0   0  near ⎥
715    /// ⎣          0  0  -1     0 ⎦
716    /// ```
717    ///
718    /// Orthographic
719    /// ```text
720    /// w = right - left
721    /// h = top - bottom
722    /// d = far - near
723    /// cw = -right - left
724    /// ch = -top - bottom
725    ///
726    /// ⎡ 2 / w      0      0   cw / w ⎤
727    /// ⎢     0  2 / h      0   ch / h ⎥
728    /// ⎢     0      0  1 / d  far / d ⎥
729    /// ⎣     0      0      0        1 ⎦
730    /// ```
731    ///
732    /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic
733    ///
734    /// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]`
735    ///
736    /// Custom projections are also possible however.
737    pub clip_from_view: Mat4,
738    pub view_from_clip: Mat4,
739    pub world_position: Vec3,
740    pub exposure: f32,
741    // viewport(x_origin, y_origin, width, height)
742    pub viewport: Vec4,
743    pub main_pass_viewport: Vec4,
744    /// 6 world-space half spaces (normal: vec3, distance: f32) ordered left, right, top, bottom, near, far.
745    /// The normal vectors point towards the interior of the frustum.
746    /// A half space contains `p` if `normal.dot(p) + distance > 0.`
747    pub frustum: [Vec4; 6],
748    /// The world-space position of the camera used to resolve visibility
749    /// ranges.
750    ///
751    /// This is the position of the camera itself, unless this view isn't
752    /// associated with a camera, in which case it's the position of the primary
753    /// camera.
754    pub lod_view_world_position: Vec3,
755    pub color_grading: ColorGradingUniform,
756    pub mip_bias: f32,
757    pub frame_count: u32,
758}
759
760#[derive(Resource)]
761pub struct ViewUniforms {
762    pub uniforms: DynamicUniformBuffer<ViewUniform>,
763}
764
765impl FromWorld for ViewUniforms {
766    fn from_world(world: &mut World) -> Self {
767        let mut uniforms = DynamicUniformBuffer::default();
768        uniforms.set_label(Some("view_uniforms_buffer"));
769
770        let render_device = world.resource::<RenderDevice>();
771        if render_device.limits().max_storage_buffers_per_shader_stage > 0 {
772            uniforms.add_usages(BufferUsages::STORAGE);
773        }
774
775        Self { uniforms }
776    }
777}
778
779#[derive(Component)]
780pub struct ViewUniformOffset {
781    pub offset: u32,
782}
783
784#[derive(Component, Clone)]
785pub struct ViewTarget {
786    main_textures: MainTargetTextures,
787    main_texture_format: TextureFormat,
788    /// 0 represents `main_textures.a`, 1 represents `main_textures.b`
789    /// This is shared across view targets with the same render target
790    main_texture: Arc<AtomicUsize>,
791    /// The final output attachment this view will present to, if available.
792    out_texture: Option<OutputColorAttachment>,
793}
794
795/// Contains [`OutputColorAttachment`] used for each target present on any view in the current
796/// frame, after being prepared by [`prepare_view_attachments`]. Users that want to override
797/// the default output color attachment for a specific target can do so by adding a
798/// [`OutputColorAttachment`] to this resource before [`prepare_view_targets`] is called.
799#[derive(Resource, Default, Deref, DerefMut)]
800pub struct ViewTargetAttachments(HashMap<NormalizedRenderTarget, OutputColorAttachment>);
801
802pub struct PostProcessWrite<'a> {
803    pub source: &'a TextureView,
804    pub source_texture: &'a Texture,
805    pub destination: &'a TextureView,
806    pub destination_texture: &'a Texture,
807}
808
809impl From<ColorGrading> for ColorGradingUniform {
810    fn from(component: ColorGrading) -> Self {
811        // Compute the balance matrix that will be used to apply the white
812        // balance adjustment to an RGB color. Our general approach will be to
813        // convert both the color and the developer-supplied white point to the
814        // LMS color space, apply the conversion, and then convert back.
815        //
816        // First, we start with the CIE 1931 *xy* values of the standard D65
817        // illuminant:
818        // <https://en.wikipedia.org/wiki/Standard_illuminant#D65_values>
819        //
820        // We then adjust them based on the developer's requested white balance.
821        let white_point_xy = D65_XY + vec2(-component.global.temperature, component.global.tint);
822
823        // Convert the white point from CIE 1931 *xy* to LMS. First, we convert to XYZ:
824        //
825        //                  Y          Y
826        //     Y = 1    X = ─ x    Z = ─ (1 - x - y)
827        //                  y          y
828        //
829        // Then we convert from XYZ to LMS color space, using the CAM16 matrix
830        // from <https://en.wikipedia.org/wiki/LMS_color_space#Later_CIECAMs>:
831        //
832        //     ⎡ L ⎤   ⎡  0.401   0.650  -0.051 ⎤ ⎡ X ⎤
833        //     ⎢ M ⎥ = ⎢ -0.250   1.204   0.046 ⎥ ⎢ Y ⎥
834        //     ⎣ S ⎦   ⎣ -0.002   0.049   0.953 ⎦ ⎣ Z ⎦
835        //
836        // The following formula is just a simplification of the above.
837
838        let white_point_lms = vec3(0.701634, 1.15856, -0.904175)
839            + (vec3(-0.051461, 0.045854, 0.953127)
840                + vec3(0.452749, -0.296122, -0.955206) * white_point_xy.x)
841                / white_point_xy.y;
842
843        // Now that we're in LMS space, perform the white point scaling.
844        let white_point_adjustment = Mat3::from_diagonal(D65_LMS / white_point_lms);
845
846        // Finally, combine the RGB → LMS → corrected LMS → corrected RGB
847        // pipeline into a single 3×3 matrix.
848        let balance = LMS_TO_RGB * white_point_adjustment * RGB_TO_LMS;
849
850        Self {
851            balance,
852            saturation: vec3(
853                component.shadows.saturation,
854                component.midtones.saturation,
855                component.highlights.saturation,
856            ),
857            contrast: vec3(
858                component.shadows.contrast,
859                component.midtones.contrast,
860                component.highlights.contrast,
861            ),
862            gamma: vec3(
863                component.shadows.gamma,
864                component.midtones.gamma,
865                component.highlights.gamma,
866            ),
867            gain: vec3(
868                component.shadows.gain,
869                component.midtones.gain,
870                component.highlights.gain,
871            ),
872            lift: vec3(
873                component.shadows.lift,
874                component.midtones.lift,
875                component.highlights.lift,
876            ),
877            midtone_range: vec2(
878                component.global.midtones_range.start,
879                component.global.midtones_range.end,
880            ),
881            exposure: component.global.exposure,
882            hue: component.global.hue,
883            post_saturation: component.global.post_saturation,
884        }
885    }
886}
887
888/// Add this component to a camera to disable *indirect mode*.
889///
890/// Indirect mode, automatically enabled on supported hardware, allows Bevy to
891/// offload transform and cull operations to the GPU, reducing CPU overhead.
892/// Doing this, however, reduces the amount of control that your app has over
893/// instancing decisions. In certain circumstances, you may want to disable
894/// indirect drawing so that your app can manually instance meshes as it sees
895/// fit. See the `custom_shader_instancing` example.
896///
897/// The vast majority of applications will not need to use this component, as it
898/// generally reduces rendering performance.
899///
900/// Note: This component should only be added when initially spawning a camera. Adding
901/// or removing after spawn can result in unspecified behavior.
902#[derive(Component, Default)]
903pub struct NoIndirectDrawing;
904
905impl ViewTarget {
906    /// Retrieve this target's main texture's color attachment.
907    pub fn get_color_attachment(&self) -> RenderPassColorAttachment<'_> {
908        if self.main_texture.load(Ordering::SeqCst) == 0 {
909            self.main_textures.a.get_attachment()
910        } else {
911            self.main_textures.b.get_attachment()
912        }
913    }
914
915    /// Retrieve this target's "unsampled" main texture's color attachment.
916    pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment<'_> {
917        if self.main_texture.load(Ordering::SeqCst) == 0 {
918            self.main_textures.a.get_unsampled_attachment()
919        } else {
920            self.main_textures.b.get_unsampled_attachment()
921        }
922    }
923
924    /// The "main" unsampled texture.
925    pub fn main_texture(&self) -> &Texture {
926        if self.main_texture.load(Ordering::SeqCst) == 0 {
927            &self.main_textures.a.texture.texture
928        } else {
929            &self.main_textures.b.texture.texture
930        }
931    }
932
933    /// The _other_ "main" unsampled texture.
934    /// In most cases you should use [`Self::main_texture`] instead and never this.
935    /// The textures will naturally be swapped when [`Self::post_process_write`] is called.
936    ///
937    /// A use case for this is to be able to prepare a bind group for all main textures
938    /// ahead of time.
939    pub fn main_texture_other(&self) -> &Texture {
940        if self.main_texture.load(Ordering::SeqCst) == 0 {
941            &self.main_textures.b.texture.texture
942        } else {
943            &self.main_textures.a.texture.texture
944        }
945    }
946
947    /// The "main" unsampled texture.
948    pub fn main_texture_view(&self) -> &TextureView {
949        if self.main_texture.load(Ordering::SeqCst) == 0 {
950            &self.main_textures.a.texture.default_view
951        } else {
952            &self.main_textures.b.texture.default_view
953        }
954    }
955
956    /// The _other_ "main" unsampled texture view.
957    /// In most cases you should use [`Self::main_texture_view`] instead and never this.
958    /// The textures will naturally be swapped when [`Self::post_process_write`] is called.
959    ///
960    /// A use case for this is to be able to prepare a bind group for all main textures
961    /// ahead of time.
962    pub fn main_texture_other_view(&self) -> &TextureView {
963        if self.main_texture.load(Ordering::SeqCst) == 0 {
964            &self.main_textures.b.texture.default_view
965        } else {
966            &self.main_textures.a.texture.default_view
967        }
968    }
969
970    /// The "main" sampled texture.
971    pub fn sampled_main_texture(&self) -> Option<&Texture> {
972        self.main_textures
973            .a
974            .resolve_target
975            .as_ref()
976            .map(|sampled| &sampled.texture)
977    }
978
979    /// The "main" sampled texture view.
980    pub fn sampled_main_texture_view(&self) -> Option<&TextureView> {
981        self.main_textures
982            .a
983            .resolve_target
984            .as_ref()
985            .map(|sampled| &sampled.default_view)
986    }
987
988    /// Currently bevy's main texture format can be:
989    /// - If rendering to screen:
990    ///   For HDR, it's `Rgba16Float`.
991    ///   For LDR, it's `Rgba8Unorm` when [`CompositingSpace::Srgb`], otherwise `Rgba8UnormSrgb`.
992    /// - If rendering to texture: the format is the same as texture view's format.
993    #[inline]
994    pub fn main_texture_format(&self) -> TextureFormat {
995        self.main_texture_format
996    }
997
998    /// The final texture this view will render to.
999    #[inline]
1000    pub fn out_texture(&self) -> Option<&TextureView> {
1001        self.out_texture.as_ref().map(|t| &t.view)
1002    }
1003
1004    /// The final texture this view will render to, as a color attachment.
1005    pub fn out_texture_color_attachment(
1006        &self,
1007        clear_color: Option<LinearRgba>,
1008    ) -> Option<RenderPassColorAttachment<'_>> {
1009        self.out_texture
1010            .as_ref()
1011            .map(|t| t.get_attachment(clear_color))
1012    }
1013
1014    /// Whether the final texture this view will render to needs to be presented.
1015    pub fn needs_present(&self) -> bool {
1016        self.out_texture
1017            .as_ref()
1018            .is_some_and(OutputColorAttachment::needs_present)
1019    }
1020
1021    /// The format of the final texture this view will render to, if any.
1022    #[inline]
1023    pub fn out_texture_view_format(&self) -> Option<TextureFormat> {
1024        self.out_texture.as_ref().map(|t| t.view_format)
1025    }
1026
1027    /// This will start a new "post process write", which assumes that the caller
1028    /// will write the [`PostProcessWrite`]'s `source` to the `destination`.
1029    ///
1030    /// `source` is the "current" main texture. This will internally flip this
1031    /// [`ViewTarget`]'s main texture to the `destination` texture, so the caller
1032    /// _must_ ensure `source` is copied to `destination`, with or without modifications.
1033    /// Failing to do so will cause the current main texture information to be lost.
1034    pub fn post_process_write(&self) -> PostProcessWrite<'_> {
1035        let old_is_a_main_texture = self.main_texture.fetch_xor(1, Ordering::SeqCst);
1036        // if the old main texture is a, then the post processing must write from a to b
1037        if old_is_a_main_texture == 0 {
1038            self.main_textures.b.mark_as_cleared();
1039            PostProcessWrite {
1040                source: &self.main_textures.a.texture.default_view,
1041                source_texture: &self.main_textures.a.texture.texture,
1042                destination: &self.main_textures.b.texture.default_view,
1043                destination_texture: &self.main_textures.b.texture.texture,
1044            }
1045        } else {
1046            self.main_textures.a.mark_as_cleared();
1047            PostProcessWrite {
1048                source: &self.main_textures.b.texture.default_view,
1049                source_texture: &self.main_textures.b.texture.texture,
1050                destination: &self.main_textures.a.texture.default_view,
1051                destination_texture: &self.main_textures.a.texture.texture,
1052            }
1053        }
1054    }
1055}
1056
1057pub trait CreatePostProcessBindGroup:
1058    Fn(&TextureView) -> (TextureViewId, BindGroup) + Copy
1059{
1060}
1061
1062impl<T: Fn(&TextureView) -> (TextureViewId, BindGroup) + Copy> CreatePostProcessBindGroup for T {}
1063
1064/// Used to create a [`PostProcessBindGroupCache`]
1065pub struct PostProcessBindGroupCacheBuilder<F>
1066where
1067    F: CreatePostProcessBindGroup,
1068{
1069    create_bind_group: F,
1070}
1071
1072impl<F> PostProcessBindGroupCacheBuilder<F>
1073where
1074    F: CreatePostProcessBindGroup,
1075{
1076    /// Initializes the [`PostProcessBindGroupCacheBuilder`]
1077    ///
1078    /// The closure should create the bind group that contains the source texture of a post
1079    /// processing effect
1080    pub fn new(create_bind_group: F) -> Self {
1081        Self { create_bind_group }
1082    }
1083
1084    /// Generates the bind group cache based on the `Self::create_bind_group` closure
1085    pub fn generate_bind_groups(&self, view_target: &ViewTarget) -> PostProcessBindGroupCache {
1086        PostProcessBindGroupCache::new(view_target, self.create_bind_group)
1087    }
1088}
1089
1090/// Caches the bind groups for each main textures used when making a post processing effect
1091pub struct PostProcessBindGroupCache {
1092    a: (TextureViewId, BindGroup),
1093    b: (TextureViewId, BindGroup),
1094}
1095
1096impl PostProcessBindGroupCache {
1097    /// Creates the bind group for both main textures used in post processing effects
1098    pub fn new<F: CreatePostProcessBindGroup>(
1099        view_target: &ViewTarget,
1100        create_bind_group: F,
1101    ) -> Self {
1102        Self {
1103            a: create_bind_group(view_target.main_texture_view()),
1104            b: create_bind_group(view_target.main_texture_other_view()),
1105        }
1106    }
1107
1108    /// Determines if the bind groups need to be updated based on the main textures id
1109    ///
1110    /// If you have anything else that determines if the bind groups need to be
1111    /// updated you need to do it manually
1112    pub fn should_update(&self, view_target: &ViewTarget) -> bool {
1113        let (texture_a, _) = &self.a;
1114        if *texture_a != view_target.main_texture_view().id() {
1115            return true;
1116        }
1117        let (texture_b, _) = &self.b;
1118        if *texture_b != view_target.main_texture_other_view().id() {
1119            return true;
1120        }
1121
1122        false
1123    }
1124
1125    /// Updates the bind group associated with each main textures
1126    ///
1127    /// Use the builder to get the callback used when creating the bind group
1128    pub fn update<F: CreatePostProcessBindGroup>(
1129        &mut self,
1130        view_target: &ViewTarget,
1131        builder: PostProcessBindGroupCacheBuilder<F>,
1132    ) {
1133        self.a = (builder.create_bind_group)(view_target.main_texture_view());
1134        self.b = (builder.create_bind_group)(view_target.main_texture_other_view());
1135    }
1136
1137    /// Gets the bind group associated with the current main texture
1138    ///
1139    /// Generally, this will be the bind group associated with the source texture
1140    pub fn get_current_bind_group(&self, texture: &TextureView) -> &BindGroup {
1141        let (_, bind_group) = if self.a.0 == texture.id() {
1142            &self.a
1143        } else {
1144            &self.b
1145        };
1146        bind_group
1147    }
1148}
1149
1150#[derive(Component)]
1151pub struct ViewDepthStencilTexture {
1152    pub attachment: DepthStencilAttachment,
1153}
1154
1155impl ViewDepthStencilTexture {
1156    pub fn new(
1157        texture: CachedTexture,
1158        depth_clear_value: Option<f32>,
1159        stencil_clear_value: Option<u32>,
1160    ) -> Self {
1161        let attachment =
1162            DepthStencilAttachment::new(texture, None, depth_clear_value, stencil_clear_value);
1163        Self { attachment }
1164    }
1165
1166    pub fn texture(&self) -> &Texture {
1167        &self.attachment.texture.texture
1168    }
1169
1170    pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> {
1171        self.attachment.get_attachment(store)
1172    }
1173}
1174
1175pub fn prepare_view_uniforms(
1176    mut commands: Commands,
1177    render_device: Res<RenderDevice>,
1178    render_queue: Res<RenderQueue>,
1179    mut view_uniforms: ResMut<ViewUniforms>,
1180    views: Query<(
1181        Entity,
1182        Option<&ExtractedCamera>,
1183        &ExtractedView,
1184        Option<&Frustum>,
1185        Option<&TemporalJitter>,
1186        Option<&MipBias>,
1187        Option<&MainPassResolutionOverride>,
1188    )>,
1189    frame_count: Res<FrameCount>,
1190    shadow_lod_origin: Option<Res<RenderShadowLodOrigin>>,
1191) {
1192    let view_iter = views.iter();
1193    let view_count = view_iter.len();
1194    let Some(mut writer) =
1195        view_uniforms
1196            .uniforms
1197            .get_writer(view_count, &render_device, &render_queue)
1198    else {
1199        return;
1200    };
1201    for (
1202        entity,
1203        extracted_camera,
1204        extracted_view,
1205        frustum,
1206        temporal_jitter,
1207        mip_bias,
1208        resolution_override,
1209    ) in &views
1210    {
1211        let viewport = extracted_view.viewport.as_vec4();
1212        let mut main_pass_viewport = viewport;
1213        if let Some(resolution_override) = resolution_override {
1214            main_pass_viewport.z = resolution_override.0.x as f32;
1215            main_pass_viewport.w = resolution_override.0.y as f32;
1216        }
1217
1218        let unjittered_projection = extracted_view.clip_from_view;
1219        let mut clip_from_view = unjittered_projection;
1220
1221        if let Some(temporal_jitter) = temporal_jitter {
1222            temporal_jitter.jitter_projection(&mut clip_from_view, main_pass_viewport.zw());
1223        }
1224
1225        let view_from_clip = clip_from_view.inverse();
1226        let world_from_view = extracted_view.world_from_view.to_matrix();
1227        let view_from_world = world_from_view.inverse();
1228
1229        let clip_from_world = if temporal_jitter.is_some() {
1230            clip_from_view * view_from_world
1231        } else {
1232            extracted_view
1233                .clip_from_world
1234                .unwrap_or_else(|| clip_from_view * view_from_world)
1235        };
1236
1237        // Map Frustum type to shader array<vec4<f32>, 6>
1238        let frustum = frustum
1239            .map(|frustum| frustum.half_spaces.map(|h| h.normal_d()))
1240            .unwrap_or([Vec4::ZERO; 6]);
1241
1242        // Determine the position of the camera used for resolving visibility
1243        // ranges (LODs).
1244        let lod_view_world_position = match (&extracted_camera, &shadow_lod_origin) {
1245            (Some(_), _) | (None, None) => {
1246                // If we're rendering a camera directly (i.e. we're not
1247                // rendering a shadow map), we use this camera's position as the
1248                // LOD view position.
1249                extracted_view.world_from_view.translation()
1250            }
1251            (None, Some(shadow_lod_origin))
1252                if extracted_view
1253                    .retained_view_entity
1254                    .auxiliary_entity
1255                    .is_none() =>
1256            {
1257                // If this is a shadow map not associated with a camera (a point
1258                // light or spot light shadow map), use the shadow LOD origin.
1259                shadow_lod_origin.0
1260            }
1261            (None, Some(shadow_lod_origin)) => {
1262                // Otherwise, if we're rendering a shadow map that is associated
1263                // with a camera (i.e. a directional light shadow map, at
1264                // present), we use the position of that camera as the LOD view
1265                // position. This ensures that each rendered object has a shadow
1266                // and that no invisible objects have shadows.
1267                match extracted_view
1268                    .retained_view_entity
1269                    .auxiliary_entity
1270                    .and_then(|entity| views.get(*entity).ok())
1271                {
1272                    Some((_, _, camera_view, _, _, _, _)) => {
1273                        camera_view.world_from_view.translation()
1274                    }
1275                    None => shadow_lod_origin.0,
1276                }
1277            }
1278        };
1279
1280        let view_uniforms = ViewUniformOffset {
1281            offset: writer.write(&ViewUniform {
1282                clip_from_world,
1283                unjittered_clip_from_world: unjittered_projection * view_from_world,
1284                world_from_clip: world_from_view * view_from_clip,
1285                world_from_view,
1286                view_from_world,
1287                clip_from_view,
1288                view_from_clip,
1289                world_position: extracted_view.world_from_view.translation(),
1290                exposure: extracted_camera
1291                    .map(|c| c.exposure)
1292                    .unwrap_or_else(|| Exposure::default().exposure()),
1293                viewport,
1294                main_pass_viewport,
1295                frustum,
1296                lod_view_world_position,
1297                color_grading: extracted_view.color_grading.clone().into(),
1298                mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0,
1299                frame_count: frame_count.0,
1300            }),
1301        };
1302
1303        commands.entity(entity).insert(view_uniforms);
1304    }
1305}
1306
1307#[derive(Clone)]
1308struct MainTargetTextures {
1309    a: ColorAttachment,
1310    b: ColorAttachment,
1311    /// 0 represents `main_textures.a`, 1 represents `main_textures.b`
1312    /// This is shared across view targets with the same render target
1313    main_texture: Arc<AtomicUsize>,
1314}
1315
1316/// Prepares the view target [`OutputColorAttachment`] for each view in the current frame.
1317pub fn prepare_view_attachments(
1318    images: Res<RenderAssets<GpuImage>>,
1319    manual_texture_views: Res<ManualTextureViews>,
1320    cameras: Query<&ExtractedCamera>,
1321    mut view_target_attachments: ResMut<ViewTargetAttachments>,
1322    windows: Query<(MainEntity, &ExtractedWindow)>,
1323) {
1324    for camera in cameras.iter() {
1325        let Some(target) = &camera.target else {
1326            continue;
1327        };
1328
1329        if matches!(camera.output_mode, bevy_camera::CameraOutputMode::Skip) {
1330            continue;
1331        }
1332
1333        match view_target_attachments.entry(target.clone()) {
1334            Entry::Occupied(_) => {}
1335            Entry::Vacant(entry) => {
1336                let Some(attachment) = target
1337                    .get_texture_view(&windows, &images, &manual_texture_views)
1338                    .cloned()
1339                    .zip(target.get_texture_view_format(&windows, &images, &manual_texture_views))
1340                    .map(|(view, format)| OutputColorAttachment::new(view.clone(), format))
1341                else {
1342                    continue;
1343                };
1344                entry.insert(attachment);
1345            }
1346        };
1347    }
1348}
1349
1350/// Clears the view target [`OutputColorAttachment`]s.
1351pub fn clear_view_attachments(mut view_target_attachments: ResMut<ViewTargetAttachments>) {
1352    view_target_attachments.clear();
1353}
1354
1355pub fn cleanup_view_targets_for_resize(
1356    mut commands: Commands,
1357    windows: Query<(MainEntity, &ExtractedWindow)>,
1358    cameras: Query<(Entity, &ExtractedCamera), With<ViewTarget>>,
1359) {
1360    for (entity, camera) in &cameras {
1361        if let Some(NormalizedRenderTarget::Window(window_ref)) = &camera.target
1362            && let Some((_, window)) = windows.iter().find(|(e, _)| *e == window_ref.entity())
1363            && (window.size_changed || window.present_mode_changed)
1364        {
1365            commands.entity(entity).remove::<ViewTarget>();
1366        }
1367    }
1368}
1369
1370/// The settings that decide which cameras share main textures. Cameras with
1371/// equal keys share one allocation in [`prepare_view_targets`], and
1372/// [`resolve_composition_spaces`] groups them the same way.
1373type MainTextureKey = (
1374    Option<NormalizedRenderTarget>,
1375    TextureUsages,
1376    TextureFormat,
1377    Msaa,
1378);
1379
1380fn main_texture_key(
1381    camera: &ExtractedCamera,
1382    view: &ExtractedView,
1383    texture_usage: &CameraMainTextureUsages,
1384    msaa: Msaa,
1385) -> MainTextureKey {
1386    (
1387        camera.target.clone(),
1388        texture_usage.0,
1389        view.target_format,
1390        msaa,
1391    )
1392}
1393
1394pub fn prepare_view_targets(
1395    mut commands: Commands,
1396    clear_color_global: Res<ClearColor>,
1397    render_device: Res<RenderDevice>,
1398    mut texture_cache: ResMut<TextureCache>,
1399    cameras: Query<(
1400        Entity,
1401        &ExtractedCamera,
1402        &ExtractedView,
1403        &CameraMainTextureUsages,
1404        &Msaa,
1405        Option<&ResolvedCompositingSpace>,
1406    )>,
1407    view_target_attachments: Res<ViewTargetAttachments>,
1408    mut main_texture_atomics: Local<HashMap<MainTextureKey, Weak<AtomicUsize>>>,
1409) {
1410    main_texture_atomics.retain(|_, weak| weak.strong_count() > 0);
1411
1412    let mut textures = <HashMap<_, _>>::default();
1413    for (entity, camera, view, texture_usage, msaa, resolved_space) in cameras.iter() {
1414        let Some(target_size) = camera.physical_target_size else {
1415            // If we don't have a target size, we can't create the main texture and have to bail
1416            commands.entity(entity).try_remove::<ViewTarget>();
1417            continue;
1418        };
1419
1420        let out_attachment = camera
1421            .target
1422            .as_ref()
1423            .and_then(|target| view_target_attachments.get(target));
1424
1425        // If we have no output and the camera is set to clear, we can skip rendering
1426        // entirely.
1427        if out_attachment.is_none() && !matches!(camera.clear_color, ClearColorConfig::None) {
1428            commands.entity(entity).try_remove::<ViewTarget>();
1429            continue;
1430        }
1431
1432        let main_texture_format = view.target_format;
1433
1434        let clear_color = match camera.clear_color {
1435            ClearColorConfig::Custom(color) => Some(color),
1436            ClearColorConfig::None => None,
1437            _ => Some(clear_color_global.0),
1438        };
1439
1440        // Convert clear color to the format expected by the main texture
1441        let resolved_space = ResolvedCompositingSpace::space(resolved_space);
1442        let converted_clear_color: Option<WgpuColor> =
1443            clear_color.map(|color| match resolved_space {
1444                // If main texture stores Oklab or Srgb, convert Color to it for correct clear.
1445                Some(CompositingSpace::Oklab) => Oklaba::from(color).into(),
1446                Some(CompositingSpace::Srgb) => Srgba::from(color).into(),
1447                Some(CompositingSpace::Linear) | None => LinearRgba::from(color).into(),
1448            });
1449
1450        let key = main_texture_key(camera, view, texture_usage, *msaa);
1451        let (a, b, sampled, main_texture) = textures.entry(key.clone()).or_insert_with(|| {
1452            let descriptor = TextureDescriptor {
1453                label: None,
1454                size: target_size.to_extents(),
1455                mip_level_count: 1,
1456                sample_count: 1,
1457                dimension: TextureDimension::D2,
1458                format: main_texture_format,
1459                usage: texture_usage.0,
1460                view_formats: match main_texture_format {
1461                    TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
1462                    TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
1463                    _ => &[],
1464                },
1465            };
1466            let a = texture_cache.get(
1467                &render_device,
1468                TextureDescriptor {
1469                    label: Some("main_texture_a"),
1470                    ..descriptor
1471                },
1472            );
1473            let b = texture_cache.get(
1474                &render_device,
1475                TextureDescriptor {
1476                    label: Some("main_texture_b"),
1477                    ..descriptor
1478                },
1479            );
1480            let sampled = if msaa.samples() > 1 {
1481                let sampled = texture_cache.get(
1482                    &render_device,
1483                    TextureDescriptor {
1484                        label: Some("main_texture_sampled"),
1485                        size: target_size.to_extents(),
1486                        mip_level_count: 1,
1487                        sample_count: msaa.samples(),
1488                        dimension: TextureDimension::D2,
1489                        format: main_texture_format,
1490                        usage: TextureUsages::RENDER_ATTACHMENT,
1491                        view_formats: descriptor.view_formats,
1492                    },
1493                );
1494                Some(sampled)
1495            } else {
1496                None
1497            };
1498            // re-use the same atomics frame to frame for views with the same main texture
1499            // to ensure post process writes persist through msaa writeback
1500            let main_texture = match main_texture_atomics.entry(key) {
1501                Entry::Occupied(e) => e
1502                    .get()
1503                    .upgrade()
1504                    .expect("dead weaks were pruned at top of system"),
1505                Entry::Vacant(e) => {
1506                    let arc = Arc::new(AtomicUsize::new(0));
1507                    e.insert(Arc::downgrade(&arc));
1508                    arc
1509                }
1510            };
1511            (a, b, sampled, main_texture)
1512        });
1513
1514        let main_textures = MainTargetTextures {
1515            a: ColorAttachment::new(a.clone(), sampled.clone(), None, converted_clear_color),
1516            b: ColorAttachment::new(b.clone(), sampled.clone(), None, converted_clear_color),
1517            main_texture: main_texture.clone(),
1518        };
1519
1520        commands.entity(entity).insert(ViewTarget {
1521            main_texture: main_textures.main_texture.clone(),
1522            main_textures,
1523            main_texture_format,
1524            out_texture: out_attachment.cloned(),
1525        });
1526    }
1527}