From ab1af88bcb1d683214b999681decfe14927912d5 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Fri, 17 Feb 2023 16:44:59 +0100 Subject: [PATCH 1/3] implement support for 3d textures --- crates/re_renderer/shader/composite.wgsl | 2 +- crates/re_renderer/shader/types.wgsl | 4 +- crates/re_renderer/src/context.rs | 6 +- .../re_renderer/src/resource_managers/mod.rs | 5 +- .../src/resource_managers/texture_manager.rs | 196 ++++++++++++++++++ 5 files changed, 208 insertions(+), 5 deletions(-) diff --git a/crates/re_renderer/shader/composite.wgsl b/crates/re_renderer/shader/composite.wgsl index 4889db97f357..96313383d665 100644 --- a/crates/re_renderer/shader/composite.wgsl +++ b/crates/re_renderer/shader/composite.wgsl @@ -17,7 +17,7 @@ fn main(in: VertexOutput) -> @location(0) Vec4 { // but are about the location of the texel in the target texture. var input = textureSample(input_texture, nearest_sampler, in.texcoord).rgb; // TODO(andreas): Do something meaningful with values above 1 - input = clamp(input, ZERO, ONE); + input = clamp(input, ZERO.xyz, ONE.xyz); // Convert to srgb - this is necessary since the final eframe output does *not* have an srgb format. // Note that the input here is assumed to be linear - if the input texture was an srgb texture it would have been converted on load. diff --git a/crates/re_renderer/shader/types.wgsl b/crates/re_renderer/shader/types.wgsl index 77bc28fc9d18..0388e30f4c75 100644 --- a/crates/re_renderer/shader/types.wgsl +++ b/crates/re_renderer/shader/types.wgsl @@ -21,5 +21,5 @@ const X = Vec3(1.0, 0.0, 0.0); const Y = Vec3(0.0, 1.0, 0.0); const Z = Vec3(0.0, 0.0, 1.0); -const ZERO = Vec3(0.0, 0.0, 0.0); -const ONE = Vec3(1.0, 1.0, 1.0); +const ZERO = Vec4(0.0, 0.0, 0.0, 0.0); +const ONE = Vec4(1.0, 1.0, 1.0, 1.0); diff --git a/crates/re_renderer/src/context.rs b/crates/re_renderer/src/context.rs index 4640fb5d0476..5637bd1002ee 100644 --- a/crates/re_renderer/src/context.rs +++ b/crates/re_renderer/src/context.rs @@ -6,7 +6,7 @@ use crate::{ config::RenderContextConfig, global_bindings::GlobalBindings, renderer::Renderer, - resource_managers::{MeshManager, TextureManager2D}, + resource_managers::{MeshManager, TextureManager2D, TextureManager3D}, wgpu_resources::WgpuResourcePools, FileResolver, FileServer, FileSystem, RecommendedFileResolver, }; @@ -28,6 +28,7 @@ pub struct RenderContext { pub gpu_resources: WgpuResourcePools, pub mesh_manager: MeshManager, pub texture_manager_2d: TextureManager2D, + pub texture_manager_3d: TextureManager3D, // TODO(andreas): Add frame/lifetime statistics, shared resources (e.g. "global" uniform buffer), ?? frame_index: u64, @@ -136,6 +137,8 @@ impl RenderContext { ); let texture_manager_2d = TextureManager2D::new(device.clone(), queue.clone(), &mut gpu_resources.textures); + let texture_manager_3d = + TextureManager3D::new(device.clone(), queue.clone(), &mut gpu_resources.textures); RenderContext { device, @@ -148,6 +151,7 @@ impl RenderContext { mesh_manager, texture_manager_2d, + texture_manager_3d, resolver, diff --git a/crates/re_renderer/src/resource_managers/mod.rs b/crates/re_renderer/src/resource_managers/mod.rs index 8dd8b29aeb7a..0bdd6be837f3 100644 --- a/crates/re_renderer/src/resource_managers/mod.rs +++ b/crates/re_renderer/src/resource_managers/mod.rs @@ -10,7 +10,10 @@ mod mesh_manager; pub use mesh_manager::{GpuMeshHandle, MeshManager}; mod texture_manager; -pub use texture_manager::{GpuTexture2DHandle, Texture2DCreationDesc, TextureManager2D}; +pub use texture_manager::{ + GpuTexture2DHandle, GpuTexture3DHandle, Texture2DCreationDesc, Texture3DCreationDesc, + TextureManager2D, TextureManager3D, +}; mod resource_manager; pub use resource_manager::{ResourceHandle, ResourceLifeTime, ResourceManagerError}; diff --git a/crates/re_renderer/src/resource_managers/texture_manager.rs b/crates/re_renderer/src/resource_managers/texture_manager.rs index 25866348b788..1c17035230fa 100644 --- a/crates/re_renderer/src/resource_managers/texture_manager.rs +++ b/crates/re_renderer/src/resource_managers/texture_manager.rs @@ -1,5 +1,7 @@ use std::{num::NonZeroU32, sync::Arc}; +use glam::UVec3; + use crate::{ wgpu_resources::{GpuTextureHandleStrong, GpuTexturePool, TextureDesc}, DebugLabel, @@ -7,6 +9,8 @@ use crate::{ use super::ResourceManagerError; +// --- 2D --- + /// Handle to a 2D resource. /// /// Currently, this is solely a more strongly typed regular gpu texture handle. @@ -198,3 +202,195 @@ impl TextureManager2D { // In the future we might add handling of background processing or introduce frame-lived textures. } } + +// --- 3D --- + +/// Handle to a 3D resource. +/// +/// Currently, this is solely a more strongly typed regular gpu texture handle. +/// Since all textures have "long lived" behavior (no temp allocation, alive until unused), +/// there is no difference as with buffer reliant data like meshes or most contents of draw-data. +#[derive(Clone)] +pub struct GpuTexture3DHandle(GpuTextureHandleStrong); + +impl GpuTexture3DHandle { + pub fn invalid() -> Self { + Self(Arc::new(crate::wgpu_resources::GpuTextureHandle::default())) + } +} + +/// Data required to create a texture 3D resource. +/// +/// It is *not* stored along side the resulting texture resource! +pub struct Texture3DCreationDesc<'a> { + pub label: DebugLabel, + /// Data for the highest mipmap level. + /// Must be padded according to wgpu rules and ready for upload. + /// TODO(andreas): This should be a kind of factory function/builder instead which gets target memory passed in. + pub data: &'a [u8], + pub format: wgpu::TextureFormat, + pub dimensions: UVec3, + //generate_mip_maps: bool, // TODO(andreas): generate mipmaps! +} + +impl<'a> Texture3DCreationDesc<'a> { + pub fn convert_rgb8_to_rgba8(rgb_pixels: &[u8]) -> Vec { + rgb_pixels + .chunks_exact(3) + .flat_map(|color| [color[0], color[1], color[2], 255]) + .collect() + } +} + +/// Texture manager for 3D textures. +/// +/// The scope is intentionally limited to particular kinds of textures that currently +/// require this kind of handle abstraction/management. +/// More complex textures types are typically handled within renderer which utilize the texture pool directly. +/// This manager in contrast, deals with user provided texture data! +/// We might revisit this later and make this texture manager more general purpose. +pub struct TextureManager3D { + // Long lived/short lived doesn't make sense for textures since we don't yet know a way to + // optimize for short lived textures as we do with buffer data. + // TODO + //manager: ResourceManager, + white_texture: GpuTexture3DHandle, + + // For convenience to reduce amount of times we need to pass them around + device: Arc, + queue: Arc, +} + +impl TextureManager3D { + pub(crate) fn new( + device: Arc, + queue: Arc, + texture_pool: &mut GpuTexturePool, + ) -> Self { + let white_texture = Self::create_and_upload_texture( + &device, + &queue, + texture_pool, + &Texture3DCreationDesc { + label: "placeholder".into(), + data: &[255, 255, 255, 255], + format: wgpu::TextureFormat::Rgba8UnormSrgb, + dimensions: UVec3::ONE, + }, + ); + + Self { + white_texture, + device, + queue, + } + } + + /// Creates a new 3D texture resource and schedules data upload to the GPU. + #[allow(clippy::unused_self)] + pub fn create( + &mut self, + texture_pool: &mut GpuTexturePool, + creation_desc: &Texture3DCreationDesc<'_>, + ) -> GpuTexture3DHandle { + // TODO(andreas): Disabled the warning as we're moving towards using this texture manager for user-logged images. + // However, it's still very much a concern especially once we add mipmapping. Something we need to keep in mind. + // + // if !resource.width.is_power_of_two() || !resource.width.is_power_of_two() { + // re_log::warn!( + // "Texture {:?} has the non-power-of-two (NPOT) resolution of {}x{}. \ + // NPOT textures are slower and on WebGL can't handle mipmapping, UV wrapping and UV tiling", + // resource.label, + // resource.width, + // resource.height + // ); + // } + + // Currently we don't store any data in the the texture manager. + // In the future we might handle (lazy?) mipmap generation in here or keep track of lazy upload processing. + + Self::create_and_upload_texture(&self.device, &self.queue, texture_pool, creation_desc) + } + + /// Returns a white unit cube. + pub fn white_texture_handle(&self) -> &GpuTexture3DHandle { + &self.white_texture + } + + fn create_and_upload_texture( + device: &wgpu::Device, + queue: &wgpu::Queue, + texture_pool: &mut GpuTexturePool, + creation_desc: &Texture3DCreationDesc<'_>, + ) -> GpuTexture3DHandle { + crate::profile_function!(); + let size = wgpu::Extent3d { + width: creation_desc.dimensions.x, + height: creation_desc.dimensions.y, + depth_or_array_layers: creation_desc.dimensions.z, + }; + let texture_handle = texture_pool.alloc( + device, + &TextureDesc { + label: creation_desc.label.clone(), + size, + mip_level_count: 1, // TODO(andreas) + sample_count: 1, + dimension: wgpu::TextureDimension::D3, + format: creation_desc.format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + }, + ); + let texture = texture_pool.get_resource(&texture_handle).unwrap(); + + let format_info = creation_desc.format.describe(); + let width_blocks = creation_desc.dimensions.x / format_info.block_dimensions.0 as u32; + let bytes_per_row_unaligned = width_blocks * format_info.block_size as u32; + + // TODO(andreas): Once we have our own temp buffer for uploading, we can do the padding inplace + // I.e. the only difference will be if we do one memcopy or one memcopy per row, making row padding a nuissance! + let data = creation_desc.data; + + // TODO(andreas): temp allocator for staging data? + // We don't do any further validation of the buffer here as wgpu does so extensively. + crate::profile_scope!("write_texture"); + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + data, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some( + NonZeroU32::new(bytes_per_row_unaligned).expect("invalid bytes per row"), + ), + rows_per_image: Some(creation_desc.dimensions.y.try_into().unwrap()), + }, + size, + ); + + // TODO(andreas): mipmap generation + + GpuTexture3DHandle(texture_handle) + } + + /// Retrieves gpu handle. + /// + /// TODO(andreas): Lifetime dependency from incoming and returned handle will likely be removed in the future. + #[allow(clippy::unnecessary_wraps, clippy::unused_self)] + pub(crate) fn get<'a>( + &self, + handle: &'a GpuTexture3DHandle, + ) -> Result<&'a GpuTextureHandleStrong, ResourceManagerError> { + Ok(&handle.0) + } + + #[allow(clippy::unused_self)] + pub(crate) fn frame_maintenance(&mut self, _frame_index: u64) { + // no-op. + // In the future we might add handling of background processing or introduce frame-lived textures. + } +} From a2017a38dcaf2f7eba669927ef7cdee6d32f8f0d Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Fri, 17 Feb 2023 16:54:40 +0100 Subject: [PATCH 2/3] woopsie --- crates/re_renderer/src/context.rs | 1 + crates/re_renderer/src/resource_managers/texture_manager.rs | 2 +- crates/re_renderer/src/workspace_shaders.rs | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/re_renderer/src/context.rs b/crates/re_renderer/src/context.rs index 5637bd1002ee..71aaf1f35dbe 100644 --- a/crates/re_renderer/src/context.rs +++ b/crates/re_renderer/src/context.rs @@ -184,6 +184,7 @@ impl RenderContext { self.mesh_manager.frame_maintenance(self.frame_index); self.texture_manager_2d.frame_maintenance(self.frame_index); + self.texture_manager_3d.frame_maintenance(self.frame_index); { let WgpuResourcePools { diff --git a/crates/re_renderer/src/resource_managers/texture_manager.rs b/crates/re_renderer/src/resource_managers/texture_manager.rs index 1c17035230fa..0c9af0794c64 100644 --- a/crates/re_renderer/src/resource_managers/texture_manager.rs +++ b/crates/re_renderer/src/resource_managers/texture_manager.rs @@ -380,7 +380,7 @@ impl TextureManager3D { /// Retrieves gpu handle. /// /// TODO(andreas): Lifetime dependency from incoming and returned handle will likely be removed in the future. - #[allow(clippy::unnecessary_wraps, clippy::unused_self)] + #[allow(clippy::unnecessary_wraps, clippy::unused_self, dead_code)] pub(crate) fn get<'a>( &self, handle: &'a GpuTexture3DHandle, diff --git a/crates/re_renderer/src/workspace_shaders.rs b/crates/re_renderer/src/workspace_shaders.rs index 0d45e91f59da..1420d224bcaa 100644 --- a/crates/re_renderer/src/workspace_shaders.rs +++ b/crates/re_renderer/src/workspace_shaders.rs @@ -85,6 +85,12 @@ pub fn init() { fs.create_file(virtpath, content).unwrap(); } + { + let virtpath = Path::new("shader/utils/cube.wgsl"); + let content = include_str!("../shader/utils/cube.wgsl").into(); + fs.create_file(virtpath, content).unwrap(); + } + { let virtpath = Path::new("shader/utils/depth_offset.wgsl"); let content = include_str!("../shader/utils/depth_offset.wgsl").into(); From 02caad91c03a4037029f34efe76a9a191b894e43 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Fri, 17 Feb 2023 16:58:09 +0100 Subject: [PATCH 3/3] that should not be here --- crates/re_renderer/src/resource_managers/texture_manager.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/re_renderer/src/resource_managers/texture_manager.rs b/crates/re_renderer/src/resource_managers/texture_manager.rs index 0c9af0794c64..4c2f50b0cd0a 100644 --- a/crates/re_renderer/src/resource_managers/texture_manager.rs +++ b/crates/re_renderer/src/resource_managers/texture_manager.rs @@ -252,7 +252,6 @@ impl<'a> Texture3DCreationDesc<'a> { pub struct TextureManager3D { // Long lived/short lived doesn't make sense for textures since we don't yet know a way to // optimize for short lived textures as we do with buffer data. - // TODO //manager: ResourceManager, white_texture: GpuTexture3DHandle,