Skip to main content

myth_scene/
background.rs

1//! Background Mode & Settings
2//!
3//! Defines the background rendering mode for a scene and the settings wrapper
4//! that owns the GPU uniform buffer for skybox parameters.
5//!
6//! # Architecture
7//!
8//! [`BackgroundMode`] is the lightweight enum that describes *what* to draw
9//! (solid color, gradient, or texture). [`BackgroundSettings`] wraps the mode
10//! together with a `CpuBuffer<SkyboxParamsUniforms>` whose version is
11//! automatically bumped only when setter methods actually change a value.
12//! The render pass calls `ensure_buffer_id()` in its `prepare()` step and
13//! never writes to the buffer — GPU sync happens only when needed.
14//!
15//! # Supported Modes
16//!
17//! - [`BackgroundMode::Color`]: Solid color clear (most efficient - uses hardware clear)
18//! - [`BackgroundMode::Gradient`]: Vertical gradient (top → bottom)
19//! - [`BackgroundMode::Texture`]: Texture-based background (cubemap, equirectangular, planar)
20
21use glam::Vec4;
22
23use myth_resources::WgslType;
24use myth_resources::buffer::CpuBuffer;
25use myth_resources::define_gpu_data_struct;
26use myth_resources::texture::TextureSource;
27
28// ============================================================================
29// GPU Uniform Struct
30// ============================================================================
31
32define_gpu_data_struct!(
33    /// Skybox per-draw parameters uploaded to the GPU.
34    ///
35    /// Camera data (view_projection_inverse, camera_position) is obtained from
36    /// the global bind group's `RenderStateUniforms`, so only skybox-specific
37    /// values live here.
38    struct SkyboxParamsUniforms {
39        pub color_top: Vec4,
40        pub color_bottom: Vec4,
41        pub rotation: f32 = 0.0,
42        pub intensity: f32 = 1.0,
43        pub(crate) __pad0: f32,
44        pub(crate) __pad1: f32,
45    }
46);
47
48// ============================================================================
49// BackgroundMode (lightweight enum — describes *what* to draw)
50// ============================================================================
51
52/// Background rendering mode.
53///
54/// Determines how the scene background is rendered. Each variant maps
55/// to a different shader pipeline variant for optimal performance.
56///
57/// # Performance
58///
59/// - `Color`: Uses GPU hardware clear — zero draw calls, maximum throughput.
60/// - `Gradient`: Renders a fullscreen triangle with per-vertex interpolation.
61/// - `Texture`: Renders a fullscreen triangle with texture sampling + ray reconstruction.
62#[derive(Clone, Debug)]
63pub enum BackgroundMode {
64    /// Solid color clear (most efficient).
65    ///
66    /// Uses the GPU's hardware clear operation — no draw calls needed.
67    /// Alpha channel can be used for post-composition (typically 1.0).
68    Color(Vec4),
69
70    /// Vertical gradient (sky color → ground color).
71    ///
72    /// Renders a fullscreen triangle with smooth interpolation based
73    /// on view direction's Y component.
74    Gradient {
75        /// Color at the top of the sky (Y = +1)
76        top: Vec4,
77        /// Color at the bottom/ground (Y = -1)
78        bottom: Vec4,
79    },
80
81    /// Texture-based background (skybox / panorama / planar).
82    ///
83    /// Renders a fullscreen triangle with ray reconstruction from the
84    /// depth buffer, sampling the background texture along the view direction.
85    Texture {
86        /// Texture source (asset handle or attachment)
87        source: TextureSource,
88        /// Y-axis rotation in radians
89        rotation: f32,
90        /// Brightness/exposure multiplier
91        intensity: f32,
92        /// Texture mapping method
93        mapping: BackgroundMapping,
94    },
95}
96
97/// Texture mapping method for background rendering.
98///
99/// Each variant produces a different shader pipeline variant
100/// (via `ShaderDefines`) to avoid dynamic branching.
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102pub enum BackgroundMapping {
103    /// Standard cubemap sampling.
104    ///
105    /// The view direction is used directly as a cubemap lookup vector.
106    /// Best for pre-processed environment cubemaps.
107    Cube,
108
109    /// Equirectangular (latitude-longitude) projection.
110    ///
111    /// Maps the view direction to UV coordinates using `atan2` / `asin`.
112    /// Best for 360° panoramic HDR images.
113    Equirectangular,
114
115    /// Planar screen-space mapping.
116    ///
117    /// The texture is mapped directly to screen space (UV = NDC).
118    /// Not affected by camera rotation or zoom — acts as a fixed backdrop.
119    Planar,
120}
121
122impl Default for BackgroundMode {
123    fn default() -> Self {
124        // Default: dark grey, matching common 3D editor conventions
125        Self::Color(Vec4::new(0.0, 0.0, 0.0, 1.0))
126    }
127}
128
129impl BackgroundMode {
130    /// Creates a solid color background.
131    #[inline]
132    #[must_use]
133    pub fn color(r: f32, g: f32, b: f32) -> Self {
134        Self::Color(Vec4::new(r, g, b, 1.0))
135    }
136
137    /// Creates a solid color background with alpha.
138    #[inline]
139    #[must_use]
140    pub fn color_with_alpha(r: f32, g: f32, b: f32, a: f32) -> Self {
141        Self::Color(Vec4::new(r, g, b, a))
142    }
143
144    /// Creates a vertical gradient background.
145    #[inline]
146    #[must_use]
147    pub fn gradient(top: Vec4, bottom: Vec4) -> Self {
148        Self::Gradient { top, bottom }
149    }
150
151    /// Creates a cubemap skybox background.
152    #[inline]
153    #[must_use]
154    pub fn cubemap(source: impl Into<TextureSource>, intensity: f32) -> Self {
155        Self::Texture {
156            source: source.into(),
157            rotation: 0.0,
158            intensity,
159            mapping: BackgroundMapping::Cube,
160        }
161    }
162
163    /// Creates an equirectangular panorama background.
164    #[inline]
165    #[must_use]
166    pub fn equirectangular(source: impl Into<TextureSource>, intensity: f32) -> Self {
167        Self::Texture {
168            source: source.into(),
169            rotation: 0.0,
170            intensity,
171            mapping: BackgroundMapping::Equirectangular,
172        }
173    }
174
175    /// Creates a planar (screen-space) background.
176    #[inline]
177    #[must_use]
178    pub fn planar(source: impl Into<TextureSource>, intensity: f32) -> Self {
179        Self::Texture {
180            source: source.into(),
181            rotation: 0.0,
182            intensity,
183            mapping: BackgroundMapping::Planar,
184        }
185    }
186
187    /// Returns the clear color for the RenderPass.
188    ///
189    /// - `Color` mode: returns the specified color (hardware clear).
190    /// - Other modes: returns black (skybox pass will overdraw).
191    #[must_use]
192    pub fn clear_color(&self) -> wgpu::Color {
193        match self {
194            Self::Color(c) => wgpu::Color {
195                r: f64::from(c.x),
196                g: f64::from(c.y),
197                b: f64::from(c.z),
198                a: f64::from(c.w),
199            },
200            // For gradient/texture modes, clear to black.
201            // The SkyboxPass will fill uncovered pixels.
202            Self::Gradient { .. } | Self::Texture { .. } => wgpu::Color::BLACK,
203        }
204    }
205
206    /// Returns `true` if this mode requires a skybox draw call.
207    ///
208    /// `Color` mode uses hardware clear and needs no draw call.
209    #[inline]
210    #[must_use]
211    pub fn needs_skybox_pass(&self) -> bool {
212        !matches!(self, Self::Color(_))
213    }
214}
215
216// ============================================================================
217// BackgroundSettings (wraps mode + CpuBuffer — follows ToneMappingSettings pattern)
218// ============================================================================
219
220/// Background rendering configuration (mode + automatic uniform version control).
221///
222/// Wraps [`BackgroundMode`] together with a `CpuBuffer<SkyboxParamsUniforms>`
223/// whose version is automatically bumped only when setter methods write new
224/// values. The render pass only calls `ensure_buffer_id()` — no per-frame
225/// buffer writes occur in the render pipeline.
226///
227/// # Usage
228///
229/// ```rust,ignore
230/// // Set mode (automatically syncs uniform values)
231/// scene.background.set_mode(BackgroundMode::equirectangular(tex, 1.0));
232///
233/// // Fine-tune individual parameters
234/// scene.background.set_rotation(0.5);
235/// scene.background.set_intensity(2.0);
236/// ```
237#[derive(Debug, Clone)]
238pub struct BackgroundSettings {
239    /// The current background rendering mode.
240    #[doc(hidden)]
241    pub mode: BackgroundMode,
242
243    /// Skybox parameters uniform buffer (version-tracked).
244    /// Updated via setter methods; render pass only reads.
245    #[doc(hidden)]
246    pub uniforms: CpuBuffer<SkyboxParamsUniforms>,
247}
248
249impl Default for BackgroundSettings {
250    fn default() -> Self {
251        let mode = BackgroundMode::default();
252        let uniforms = CpuBuffer::new(
253            SkyboxParamsUniforms::default(),
254            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
255            Some("Skybox Params Uniforms"),
256        );
257        let mut settings = Self { mode, uniforms };
258        settings.sync_uniforms_from_mode();
259        settings
260    }
261}
262
263impl BackgroundSettings {
264    /// Returns a reference to the current background mode.
265    #[inline]
266    #[must_use]
267    pub fn mode(&self) -> &BackgroundMode {
268        &self.mode
269    }
270
271    /// Sets the background mode and syncs uniform values accordingly.
272    ///
273    /// The `CpuBuffer` version is bumped only if the derived uniform values
274    /// actually differ from the current state.
275    pub fn set_mode(&mut self, mode: BackgroundMode) {
276        self.mode = mode;
277        self.sync_uniforms_from_mode();
278    }
279
280    /// Sets the Y-axis rotation (radians) for texture-based backgrounds.
281    ///
282    /// Also updates the `rotation` field inside `BackgroundMode::Texture`
283    /// to keep the enum and buffer in sync.
284    pub fn set_rotation(&mut self, rotation: f32) {
285        if let BackgroundMode::Texture { rotation: r, .. } = &mut self.mode {
286            *r = rotation;
287        }
288        self.uniforms.write().rotation = rotation;
289    }
290
291    /// Sets the brightness/exposure multiplier for texture-based backgrounds.
292    ///
293    /// Also updates the `intensity` field inside `BackgroundMode::Texture`
294    /// to keep the enum and buffer in sync.
295    pub fn set_intensity(&mut self, intensity: f32) {
296        if let BackgroundMode::Texture { intensity: i, .. } = &mut self.mode {
297            *i = intensity;
298        }
299        self.uniforms.write().intensity = intensity;
300    }
301
302    /// Sets gradient colors (top and bottom).
303    ///
304    /// Switches the mode to `Gradient` if it isn't already.
305    pub fn set_gradient_colors(&mut self, top: Vec4, bottom: Vec4) {
306        self.mode = BackgroundMode::Gradient { top, bottom };
307        let mut p = self.uniforms.write();
308        p.color_top = top;
309        p.color_bottom = bottom;
310        p.rotation = 0.0;
311        p.intensity = 1.0;
312    }
313
314    // === Delegate methods from BackgroundMode ===
315
316    /// Returns the clear color for the RenderPass.
317    #[inline]
318    #[must_use]
319    pub fn clear_color(&self) -> wgpu::Color {
320        self.mode.clear_color()
321    }
322
323    /// Returns `true` if the current mode requires a skybox draw call.
324    #[inline]
325    #[must_use]
326    pub fn needs_skybox_pass(&self) -> bool {
327        self.mode.needs_skybox_pass()
328    }
329
330    // === Internal ===
331
332    /// Derives uniform values from the current `BackgroundMode` and writes
333    /// them into the `CpuBuffer`.
334    fn sync_uniforms_from_mode(&mut self) {
335        let mut p = self.uniforms.write();
336        match &self.mode {
337            BackgroundMode::Color(c) => {
338                p.color_top = *c;
339                p.color_bottom = *c;
340                p.rotation = 0.0;
341                p.intensity = 1.0;
342            }
343            BackgroundMode::Gradient { top, bottom } => {
344                p.color_top = *top;
345                p.color_bottom = *bottom;
346                p.rotation = 0.0;
347                p.intensity = 1.0;
348            }
349            BackgroundMode::Texture {
350                rotation,
351                intensity,
352                ..
353            } => {
354                p.color_top = Vec4::ZERO;
355                p.color_bottom = Vec4::ZERO;
356                p.rotation = *rotation;
357                p.intensity = *intensity;
358            }
359        }
360    }
361}