Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renderer: 3D texture manager #1349

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/re_renderer/shader/composite.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions crates/re_renderer/shader/types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
7 changes: 6 additions & 1 deletion crates/re_renderer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -148,6 +151,7 @@ impl RenderContext {

mesh_manager,
texture_manager_2d,
texture_manager_3d,

resolver,

Expand Down Expand Up @@ -180,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 {
Expand Down
5 changes: 4 additions & 1 deletion crates/re_renderer/src/resource_managers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
195 changes: 195 additions & 0 deletions crates/re_renderer/src/resource_managers/texture_manager.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use std::{num::NonZeroU32, sync::Arc};

use glam::UVec3;

use crate::{
wgpu_resources::{GpuTextureHandleStrong, GpuTexturePool, TextureDesc},
DebugLabel,
};

use super::ResourceManagerError;

// --- 2D ---

/// Handle to a 2D resource.
///
/// Currently, this is solely a more strongly typed regular gpu texture handle.
Expand Down Expand Up @@ -198,3 +202,194 @@ 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<u8> {
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.
//manager: ResourceManager<Texture3DHandleInner, GpuTextureHandleStrong>,
white_texture: GpuTexture3DHandle,

// For convenience to reduce amount of times we need to pass them around
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
}

impl TextureManager3D {
pub(crate) fn new(
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
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, dead_code)]
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.
}
}
6 changes: 6 additions & 0 deletions crates/re_renderer/src/workspace_shaders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ pub fn init() {
fs.create_file(virtpath, content).unwrap();
}

{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this entire file shouldn't be modified at all

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();
Expand Down