tirbofish/dropbear · diff
feature: skybox on redback-runtime works
todo: animations now and user defined skyboxes and reflections.
Signature present but could not be verified.
Unverified
@@ -81,6 +81,9 @@ thiserror = "2.0" tempfile = "3.24" combine = "4.6" glyphon = { git = "https://github.com/grovesNL/glyphon", rev = "9dd9376" } +puffin = "0.19" +bitflags = "2.10" +features = "0.10" [workspace.dependencies.image] version = "0.24" @@ -45,6 +45,9 @@ typetag.workspace = true postcard.workspace = true pollster.workspace = true image.workspace = true +puffin.workspace = true +bitflags.workspace = true + #yakui-wgpu.workspace = true #yakui.workspace = true @@ -0,0 +1,301 @@ +// Copyright ⓒ 2017 Fletcher Nichol and/or applicable contributors. +// +// Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE or +// <http://www.apache.org/licenses/LICENSE-2.0>) or the MIT license (see<LICENSE-MIT or +// <http://opensource.org/licenses/MIT>, at your option. This file may not be copied, modified, or +// distributed except according to those terms. + +//! `features` is a small library that implements runtime [feature toggles][fowler_toggles] for +//! your library or program allowing behavior to be changed on boot or dynamically at runtime using +//! the same compiled binary artifact. This is different from cargo's [feature][cargo_feature] +//! support which uses conditional compilation. +//! +//! At its core, it is a macro (`features!`) that takes a collection of feature flag names which it +//! uses to generate a module containing a function to enable a feature toggle (`::enable()`), a +//! function to disable a feature toggle (`::disable()`) and a function to check if a feature +//! toggle is enabled (`::is_enabled()`). +//! +//! ## Example +//! +//! Basic example: +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! #[macro_use] +//! extern crate features; +//! +//! features! { +//! pub mod feature { +//! const Alpha = 0b00000001, +//! const Beta = 0b00000010 +//! } +//! } +//! +//! fn main() { +//! assert_eq!(false, feature::is_enabled(feature::Alpha)); +//! assert_eq!(false, feature::is_enabled(feature::Beta)); +//! +//! feature::enable(feature::Beta); +//! assert_eq!(false, feature::is_enabled(feature::Alpha)); +//! assert_eq!(true, feature::is_enabled(feature::Beta)); +//! } +//! ``` +//! +//! Multiple feature sets: +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! #[macro_use] +//! extern crate features; +//! +//! features! { +//! pub mod ux { +//! const JsonOutput = 0b10000000, +//! const VerboseOutput = 0b01000000 +//! } +//! } +//! +//! features! { +//! pub mod srv { +//! const Http2Downloading = 0b10000000, +//! const BitTorrentDownloading = 0b01000000 +//! } +//! } +//! +//! fn main() { +//! // Parse CLI args, environment, read config file etc... +//! srv::enable(srv::BitTorrentDownloading); +//! ux::enable(ux::JsonOutput); +//! +//! if srv::is_enabled(srv::Http2Downloading) { +//! println!("Downloading via http2..."); +//! } else if srv::is_enabled(srv::BitTorrentDownloading) { +//! println!("Downloading via bit torrent..."); +//! } else { +//! println!("Downloading the old fashioned way..."); +//! } +//! +//! if ux::is_enabled(ux::VerboseOutput) { +//! println!("COOL"); +//! } +//! } +//! ``` +//! +//! ## Feature Toggle References +//! +//! Here are some articles and projects which insipred the implementation of `features`: +//! +//! * [Feature Toggles](https://martinfowler.com/articles/feature-toggles.html) (Martin Fowler's +//! blog) +//! * [Using Feature Flags to Ship Changes with +//! Confidence](https://blog.travis-ci.com/2014-03-04-use-feature-flags-to-ship-changes-with-confidence/) +//! (TravisCI's blog) +//! * [Feature Toggles are one of the worst kinds of Technical +//! Debt](http://swreflections.blogspot.ca/2014/08/feature-toggles-are-one-of-worst-kinds.html) +//! (excellent cautionary tale and warning) +//! * [Feature toggle](https://en.wikipedia.org/wiki/Feature_toggle) (Wikipedia article) +//! * [Ruby feature gem](https://github.com/mgsnova/feature) (nice prior art) +//! +//! [fowler_toggles]: https://martinfowler.com/articles/feature-toggles.html +//! [cargo_feature]: http://doc.crates.io/manifest.html#the-features-section + +/// The `features!` macro generates a module to contain all feature toggling logic. +/// +/// # Examples +/// +/// Basic example: +/// +/// ``` +/// #[macro_use] +/// extern crate bitflags; +/// #[macro_use] +/// extern crate features; +/// +/// features! { +/// pub mod feature { +/// const Alpha = 0b00000001, +/// const Beta = 0b00000010 +/// } +/// } +/// +/// fn main() { +/// assert_eq!(false, feature::is_enabled(feature::Alpha)); +/// assert_eq!(false, feature::is_enabled(feature::Beta)); +/// +/// feature::enable(feature::Beta); +/// assert_eq!(false, feature::is_enabled(feature::Alpha)); +/// assert_eq!(true, feature::is_enabled(feature::Beta)); +/// } +/// ``` +/// +/// Multiple feature sets: +/// +/// ``` +/// #[macro_use] +/// extern crate bitflags; +/// #[macro_use] +/// extern crate features; +/// +/// features! { +/// pub mod ux { +/// const JsonOutput = 0b10000000, +/// const VerboseOutput = 0b01000000 +/// } +/// } +/// +/// features! { +/// pub mod srv { +/// const Http2Downloading = 0b10000000, +/// const BitTorrentDownloading = 0b01000000 +/// } +/// } +/// +/// fn main() { +/// // Parse CLI args, environment, read config file etc... +/// srv::enable(srv::BitTorrentDownloading); +/// ux::enable(ux::JsonOutput); +/// +/// if srv::is_enabled(srv::Http2Downloading) { +/// println!("Downloading via http2..."); +/// } else if srv::is_enabled(srv::BitTorrentDownloading) { +/// println!("Downloading via bit torrent..."); +/// } else { +/// println!("Downloading the old fashioned way..."); +/// } +/// +/// if ux::is_enabled(ux::VerboseOutput) { +/// println!("COOL"); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! features { + (mod $mod_name:ident { + $($(#[$flag_attr:meta])* const $flag:ident = $value:expr),+ + }) => { + #[allow(non_upper_case_globals)] + mod $mod_name { + use $crate::features; + features! { + @_impl mod $mod_name { + $($(#[$flag_attr])* const $flag = $value),+ + } + } + } + }; + (pub mod $mod_name:ident { + $($(#[$flag_attr:meta])* const $flag:ident = $value:expr),+ + }) => { + #[allow(non_upper_case_globals)] + pub mod $mod_name { + use $crate::features; + features! { + @_impl mod $mod_name { + $($(#[$flag_attr])* const $flag = $value),+ + } + } + } + }; + (@_impl mod $mod_name:ident { + $($(#[$flag_attr:meta])* const $flag:ident = $value:expr),+ + }) => { + use std::sync::atomic; + use bitflags::bitflags; + + bitflags! { + pub struct Flags: usize { + $( + $(#[$flag_attr])* const $flag = $value; + )+ + } + } + $( + pub const $flag: Flags = Flags::$flag; + )+ + + static FEATURES: atomic::AtomicUsize = atomic::AtomicUsize::new(0); + + #[allow(dead_code)] + pub fn enable(flag: Flags) { + let mut flags = Flags::from_bits_truncate(FEATURES.load(atomic::Ordering::Relaxed)); + flags.insert(flag); + FEATURES.store(flags.bits(), atomic::Ordering::Relaxed); + } + + #[allow(dead_code)] + pub fn disable(flag: Flags) { + let mut flags = Flags::from_bits_truncate(FEATURES.load(atomic::Ordering::Relaxed)); + flags.remove(flag); + FEATURES.store(flags.bits(), atomic::Ordering::Relaxed); + } + + #[allow(dead_code)] + pub fn is_enabled(flag: Flags) -> bool { + flags().contains(flag) + } + + #[allow(dead_code)] + pub fn flags() -> Flags { + Flags::from_bits_truncate(FEATURES.load(atomic::Ordering::Relaxed)) + } + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn enabling() { + features! { + pub mod f { + const Alpha = 0b00000001, + const Beta = 0b00000010 + } + } + + assert_eq!(false, f::is_enabled(f::Alpha)); + assert_eq!(false, f::is_enabled(f::Beta)); + + f::enable(f::Alpha); + assert_eq!(true, f::is_enabled(f::Alpha)); + assert_eq!(false, f::is_enabled(f::Beta)); + + // Enable again + f::enable(f::Alpha); + assert_eq!(true, f::is_enabled(f::Alpha)); + assert_eq!(false, f::is_enabled(f::Beta)); + + f::enable(f::Beta); + assert_eq!(true, f::is_enabled(f::Alpha)); + assert_eq!(true, f::is_enabled(f::Beta)); + } + + #[test] + fn disabling() { + features! { + pub mod f { + const Cool = 0b01000000, + const Beans = 0b10000000 + } + } + + f::enable(f::Cool); + f::enable(f::Beans); + assert_eq!(true, f::is_enabled(f::Cool)); + assert_eq!(true, f::is_enabled(f::Beans)); + + f::disable(f::Cool); + assert_eq!(false, f::is_enabled(f::Cool)); + assert_eq!(true, f::is_enabled(f::Beans)); + + // Disable again + f::disable(f::Cool); + assert_eq!(false, f::is_enabled(f::Cool)); + assert_eq!(true, f::is_enabled(f::Beans)); + + f::disable(f::Beans); + assert_eq!(false, f::is_enabled(f::Cool)); + assert_eq!(false, f::is_enabled(f::Beans)); + } +} @@ -19,6 +19,14 @@ pub mod texture; pub mod pipelines; pub mod mipmap; pub mod sky; +pub mod features; + +features! { + pub mod build { + const Debug = 0b00000001, + const Release = 0b00000000 + } +} pub static WGPU_BACKEND: OnceLock<String> = OnceLock::new(); pub const PHYSICS_STEP_RATE: u32 = 120; @@ -234,7 +242,7 @@ Hardware: present_mode: surface_caps.present_modes[0], // alpha_mode: surface_caps.alpha_modes[0], alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], + view_formats: vec![surface_format.add_srgb_suffix()], desired_maximum_frame_latency: 2, }; @@ -343,7 +351,7 @@ Hardware: let hdr = Arc::new(RwLock::new(HdrPipeline::new( &device, &config, - Texture::TEXTURE_FORMAT, + config.format.add_srgb_suffix(), ))); // let yakui_renderer = Arc::new(Mutex::new(yakui_wgpu::YakuiWgpu::new( @@ -496,7 +504,11 @@ Hardware: let view = output .texture - .create_view(&wgpu::TextureViewDescriptor::default()); + .create_view(&wgpu::TextureViewDescriptor { + label: Some("surface texture view descriptor"), + format: Some(self.config.read().format.add_srgb_suffix()), + ..Default::default() + }); { // ensures clearing of the encoder is done correctly. let mut encoder = CommandEncoder::new(graphics.clone(), Some("surface clear render encoder")); @@ -902,6 +914,10 @@ impl App { /// Creates a new instance of the application. It only sets the default for the struct + the /// window config. fn new(app_data: AppInfo, future_queue: Option<Arc<FutureQueue>>) -> Self { + if build::is_enabled(build::Debug) { + puffin::set_scopes_on(true); + } + let instance = Arc::new(Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::PRIMARY, ..Default::default() @@ -1073,6 +1089,8 @@ impl ApplicationHandler for App { WindowEvent::RedrawRequested => { self.future_queue.poll(); + puffin::GlobalProfiler::lock().new_frame(); + let frame_start = Instant::now(); let active_handlers = state.scene_manager.get_active_input_handlers(); @@ -19,7 +19,7 @@ impl Default for Globals { fn default() -> Self { Self { num_lights: 0, - ambient_strength: 0.1, + ambient_strength: 0.8, _padding: [0; 2], } } @@ -5,6 +5,8 @@ use image::codecs::hdr::HdrDecoder; use crate::graphics::SharedGraphicsContext; use crate::pipelines::{create_render_pipeline_ex}; +pub const DEFAULT_SKY_TEXTURE: &[u8] = include_bytes!("../../../resources/textures/kloofendal_48d_partly_cloudy_puresky_4k.hdr"); + pub struct CubeTexture { texture: wgpu::Texture, sampler: wgpu::Sampler, @@ -207,7 +207,7 @@ impl Texture { mip_level_count: 1, // leave me alone sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: Texture::TEXTURE_FORMAT, + format: config.format.add_srgb_suffix(), usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }; @@ -215,21 +215,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> { match tab { EditorTab::Viewport => { log_once::debug_once!("Viewport focused"); - // ------------------- Template for querying active camera ----------------- - // if let Some(active_camera) = self.active_camera { - // if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent, Option<&CameraFollowTarget>)>(*active_camera) { - // if let Some((camera, component, follow_target)) = q.get() { - - // } else { - // log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera) - // } - // } else { - // log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera); - // } - // } else { - // log_once::warn_once!("No active camera found"); - // } - // ------------------------------------------------------------------------- let available_rect = ui.available_rect_before_wrap(); let available_size = available_rect.size(); @@ -62,7 +62,7 @@ use dropbear_engine::mipmap::MipMapper; use dropbear_engine::pipelines::{create_render_pipeline, DropbearShaderPipeline}; use dropbear_engine::pipelines::shader::MainRenderPipeline; use dropbear_engine::pipelines::GlobalsUniform; -use dropbear_engine::sky::{HdrLoader, SkyPipeline}; +use dropbear_engine::sky::{HdrLoader, SkyPipeline, DEFAULT_SKY_TEXTURE}; use eucalyptus_core::physics::collider::{ColliderShapeKey, WireframeGeometry}; use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw; use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline; @@ -1353,18 +1353,16 @@ impl Editor { self.light_cube_pipeline = Some(LightCubePipeline::new(graphics.clone())); self.shader_globals = Some(GlobalsUniform::new(graphics.clone(), Some("editor shader globals"))); self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone())); - // Mipmaps are generated by the engine during texture creation; keep this optional field unused for now. self.mipmapper = None; self.texture_id = Some((*graphics.texture_id).clone()); self.window = Some(graphics.window.clone()); self.is_world_loaded.mark_rendering_loaded(); - let sky_bytes = include_bytes!("../../../../resources/textures/kloofendal_48d_partly_cloudy_puresky_4k.hdr"); let sky_texture = HdrLoader::from_equirectangular_bytes( &graphics.device, &graphics.queue, - sky_bytes, + DEFAULT_SKY_TEXTURE, 1080, Some("sky texture") ); @@ -303,7 +303,7 @@ impl KinoState { device, queue, self.renderer.texture_bind_group_layout(), - self.renderer.format, + self.renderer.texture_format(), ); return self.assets.add_texture_with_label(label, fallback); } @@ -323,7 +323,7 @@ impl KinoState { raw.as_ref(), width, height, - self.renderer.format, + self.renderer.texture_format(), ); self.assets.add_texture_with_label(label, texture) } @@ -15,6 +15,7 @@ pub struct KinoWGPURenderer { pipeline: KinoRendererPipeline, default_texture: texture::Texture, pub format: wgpu::TextureFormat, + texture_format: wgpu::TextureFormat, pub size: Vec2, pub text: KinoTextRenderer, @@ -31,6 +32,7 @@ impl KinoWGPURenderer { ) -> Self { log::debug!("Creating KinoWGPURenderer"); let pipeline = KinoRendererPipeline::new(device, surface_format); + let texture_format = wgpu::TextureFormat::Rgba8UnormSrgb; let camera_buffer = device.create_buffer_init(&BufferInitDescriptor { label: None, @@ -54,7 +56,12 @@ impl KinoWGPURenderer { }], }); - let default_texture = texture::Texture::create_default(&device, &queue, &pipeline.texture_bind_group_layout, surface_format); + let default_texture = texture::Texture::create_default( + &device, + &queue, + &pipeline.texture_bind_group_layout, + texture_format, + ); let text = KinoTextRenderer::new(&device, &queue, surface_format); log::debug!("Created KinoWGPURenderer"); @@ -62,6 +69,7 @@ impl KinoWGPURenderer { pipeline, default_texture, format: surface_format, + texture_format, size: Vec2::from_array(size), text, camera: CameraRendering { @@ -71,6 +79,10 @@ impl KinoWGPURenderer { } } + pub fn texture_format(&self) -> wgpu::TextureFormat { + self.texture_format + } + pub fn upload_camera_matrix(&mut self, queue: &wgpu::Queue, view_proj: [[f32; 4]; 4]) { queue.write_buffer( &self.camera.buffer, @@ -15,6 +15,24 @@ pub struct Texture { } impl Texture { + fn bytes_per_pixel(format: TextureFormat) -> Option<u32> { + match format { + TextureFormat::Rgba8Unorm + | TextureFormat::Rgba8UnormSrgb + | TextureFormat::Bgra8Unorm + | TextureFormat::Bgra8UnormSrgb => Some(4), + TextureFormat::Rgba16Float + | TextureFormat::Rgba16Unorm + | TextureFormat::Rgba16Snorm + | TextureFormat::Rgba16Uint + | TextureFormat::Rgba16Sint => Some(8), + TextureFormat::Rgba32Float + | TextureFormat::Rgba32Uint + | TextureFormat::Rgba32Sint => Some(16), + _ => None, + } + } + /// Creates a new texture from raw RGBA image data, /// uploads the data, & builds the bind group using the layout /// @@ -30,6 +48,19 @@ impl Texture { texture_format: TextureFormat, ) -> Self { log::debug!("Creating new texture"); + + let bytes_per_pixel = Self::bytes_per_pixel(texture_format).unwrap_or(4); + let expected_len = (width as usize) + .saturating_mul(height as usize) + .saturating_mul(bytes_per_pixel as usize); + if data.len() != expected_len { + log::error!( + "Texture data length {} does not match expected {} for {:?}", + data.len(), + expected_len, + texture_format + ); + } let texture = device.create_texture(&TextureDescriptor { label: None, @@ -56,7 +87,7 @@ impl Texture { data, TexelCopyBufferLayout { offset: 0, - bytes_per_row: Some(4 * width), + bytes_per_row: Some(bytes_per_pixel * width), rows_per_image: Some(height), }, Extent3d { @@ -25,8 +25,10 @@ use eucalyptus_core::command::COMMAND_BUFFER; use eucalyptus_core::scene::loading::IsSceneLoaded; use std::collections::HashMap; use std::path::PathBuf; +use log::error; use wgpu::SurfaceConfiguration; use winit::window::Fullscreen; +use dropbear_engine::sky::{HdrLoader, SkyPipeline, DEFAULT_SKY_TEXTURE}; // use yakui_winit::YakuiWinit; use dropbear_engine::texture::Texture; use eucalyptus_core::physics::PhysicsState; @@ -123,6 +125,7 @@ pub struct PlayMode { main_pipeline: Option<MainRenderPipeline>, shader_globals: Option<GlobalsUniform>, collider_wireframe_pipeline: Option<ColliderWireframePipeline>, + sky_pipeline: Option<SkyPipeline>, initial_scene: Option<String>, current_scene: Option<String>, @@ -207,6 +210,7 @@ impl PlayMode { last_size: (0, 0), }, kino: None, + sky_pipeline: None, }; log::debug!("Created new play mode instance"); @@ -220,19 +224,35 @@ impl PlayMode { self.shader_globals = Some(GlobalsUniform::new(graphics.clone(), Some("runtime shader globals"))); self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone())); - let hdr_format = graphics.hdr.read().format(); self.kino = Some(KinoState::new( KinoWGPURenderer::new( &graphics.device, &graphics.queue, - hdr_format, + graphics.hdr.read().format(), [ graphics.viewport_texture.size.width as f32, graphics.viewport_texture.size.height as f32, ], ), KinoWinitWindowing::new(graphics.window.clone()), - )) + )); + + let sky_texture = HdrLoader::from_equirectangular_bytes( + &graphics.device, + &graphics.queue, + DEFAULT_SKY_TEXTURE, + 1080, + Some("sky texture") + ); + + match sky_texture { + Ok(sky_texture) => { + self.sky_pipeline = Some(SkyPipeline::new(graphics.clone(), sky_texture)); + } + Err(e) => { + error!("Failed to load sky texture: {}", e); + } + } } fn reload_scripts_for_current_world(&mut self) { @@ -611,13 +611,6 @@ impl Scene for PlayMode { } fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>) { - let clear_color = Color { - r: 100.0 / 255.0, - g: 149.0 / 255.0, - b: 237.0 / 255.0, - a: 1.0, - }; - let hdr = graphics.hdr.read(); let mut encoder = CommandEncoder::new(graphics.clone(), Some("runtime viewport encoder")); @@ -641,37 +634,34 @@ impl Scene for PlayMode { }; log_once::debug_once!("Pipeline ready"); - { // ensures clearing of the encoder is done correctly. - let mut encoder = dropbear_engine::graphics::CommandEncoder::new(graphics.clone(), Some("viewport clear render encoder")); - - { - let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("viewport clear pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.view(), - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 100.0 / 255.0, - g: 149.0 / 255.0, - b: 237.0 / 255.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - } - - hdr.process(&mut encoder, &graphics.viewport_texture.view); - - if let Err(e) = encoder.submit() { - log_once::error_once!("{}", e); - } + { + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("viewport clear pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.view(), + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 100.0 / 255.0, + g: 149.0 / 255.0, + b: 237.0 / 255.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + occlusion_query_set: None, + timestamp_writes: None, + }); } let lights = { @@ -763,14 +753,14 @@ impl Scene for PlayMode { depth_slice: None, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(clear_color), + load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &graphics.depth_texture.view, depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0.0), + load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }), stencil_ops: None, @@ -974,13 +964,6 @@ impl Scene for PlayMode { } } - hdr.process(&mut encoder, &graphics.viewport_texture.view); - - if let Err(e) = encoder.submit() { - log_once::error_once!("{}", e); - } - - // UI_CONTEXT.with(|v| { // let commands = graphics.yakui_renderer.lock().paint( // &mut v.borrow().yakui_state.lock(), @@ -998,11 +981,46 @@ impl Scene for PlayMode { // }); } + if let Some(sky) = &self.sky_pipeline { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("sky render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.view(), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_pipeline(&sky.pipeline); + render_pass.set_bind_group(0, &camera.bind_group, &[]); + render_pass.set_bind_group(1, &sky.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + + hdr.process(&mut encoder, &graphics.viewport_texture.view); + + if let Err(e) = encoder.submit() { + log_once::error_once!("{}", e); + } + if let Some(kino) = &mut self.kino { let mut encoder = CommandEncoder::new(graphics.clone(), Some("kino encoder")); kino.render(&graphics.device, &graphics.queue, &mut encoder, hdr.view()); - hdr.process(&mut encoder, &graphics.viewport_texture.view); if let Err(e) = encoder.submit() { log_once::error_once!("Unable to submit kino: {}", e); }