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