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}