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

Lift point cloud size limitations #5192

Merged
merged 9 commits into from
Feb 14, 2024
15 changes: 10 additions & 5 deletions crates/re_renderer/shader/point_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,17 @@ struct PointData {

// Read and unpack data at a given location
fn read_data(idx: u32) -> PointData {
let texture_size = textureDimensions(position_data_texture);
let coord = vec2u(idx % texture_size.x, idx / texture_size.x);
let position_data_texture_size = textureDimensions(position_data_texture);
let position_data = textureLoad(position_data_texture,
vec2u(idx % position_data_texture_size.x, idx / position_data_texture_size.x), 0);

let position_data = textureLoad(position_data_texture, coord, 0);
let color = textureLoad(color_texture, coord, 0);
let picking_instance_id = textureLoad(picking_instance_id_texture, coord, 0).rg;
let color_texture_size = textureDimensions(color_texture);
let color = textureLoad(color_texture,
vec2u(idx % color_texture_size.x, idx / color_texture_size.x), 0);

let picking_instance_id_texture_size = textureDimensions(picking_instance_id_texture);
let picking_instance_id = textureLoad(picking_instance_id_texture,
vec2u(idx % picking_instance_id_texture_size.x, idx / picking_instance_id_texture_size.x), 0).xy;

var data: PointData;
let pos_4d = batch.world_from_obj * vec4f(position_data.xyz, 1.0);
Expand Down
25 changes: 24 additions & 1 deletion crates/re_renderer/src/allocator/cpu_write_gpu_read_belt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::mpsc;

use crate::{
texture_info::Texture2DBufferInfo,
wgpu_resources::{BufferDesc, GpuBuffer, GpuBufferPool},
wgpu_resources::{BufferDesc, GpuBuffer, GpuBufferPool, GpuTexture},
};

#[derive(thiserror::Error, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -224,6 +224,29 @@ where
self.unwritten_element_range.end
}

/// Copies all all so far written data to the first layer of a 2d texture.
///
/// Assumes that the buffer consists of as-tightly-packed-as-possible rows of data.
/// (taking into account required padding as specified by [`wgpu::COPY_BYTES_PER_ROW_ALIGNMENT`])
///
/// Fails if the buffer size is not sufficient to fill the entire texture.
pub fn copy_to_texture2d_entire_first_layer(
self,
encoder: &mut wgpu::CommandEncoder,
destination: &GpuTexture,
) -> Result<(), CpuWriteGpuReadError> {
self.copy_to_texture2d(
encoder,
wgpu::ImageCopyTexture {
texture: &destination.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
destination.texture.size(),
)
}

/// Copies all so far written data to a rectangle on a single 2d texture layer.
///
/// Assumes that the buffer consists of as-tightly-packed-as-possible rows of data.
Expand Down
24 changes: 18 additions & 6 deletions crates/re_renderer/src/point_cloud_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::{
allocator::CpuWriteGpuReadBuffer,
draw_phases::PickingLayerObjectId,
renderer::{
PointCloudBatchFlags, PointCloudBatchInfo, PointCloudDrawData, PointCloudDrawDataError,
PositionRadius,
data_texture_source_buffer_element_count, PointCloudBatchFlags, PointCloudBatchInfo,
PointCloudDrawData, PointCloudDrawDataError, PositionRadius,
},
Color32, DebugLabel, DepthOffset, OutlineMaskPreference, PickingLayerInstanceId, RenderContext,
Size,
Expand All @@ -30,21 +30,33 @@ pub struct PointCloudBuilder {

impl PointCloudBuilder {
pub fn new(ctx: &RenderContext, max_num_points: u32) -> Self {
let num_buffer_elements =
PointCloudDrawData::padded_buffer_element_count(ctx, max_num_points);
let max_texture_dimension_2d = ctx.device.limits().max_texture_dimension_2d;

let color_buffer = ctx
.cpu_write_gpu_read_belt
.lock()
.allocate::<Color32>(&ctx.device, &ctx.gpu_resources.buffers, num_buffer_elements)
.allocate::<Color32>(
&ctx.device,
&ctx.gpu_resources.buffers,
data_texture_source_buffer_element_count(
PointCloudDrawData::COLOR_TEXTURE_FORMAT,
max_num_points,
max_texture_dimension_2d,
),
)
.expect("Failed to allocate color buffer"); // TODO(#3408): Should never happen but should propagate error anyways

let picking_instance_ids_buffer = ctx
.cpu_write_gpu_read_belt
.lock()
.allocate::<PickingLayerInstanceId>(
&ctx.device,
&ctx.gpu_resources.buffers,
num_buffer_elements,
data_texture_source_buffer_element_count(
PointCloudDrawData::PICKING_INSTANCE_ID_TEXTURE_FORMAT,
max_num_points,
max_texture_dimension_2d,
),
)
.expect("Failed to allocate picking layer buffer"); // TODO(#3408): Should never happen but should propagate error anyways

Expand Down
97 changes: 96 additions & 1 deletion crates/re_renderer/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ use crate::{
context::RenderContext,
draw_phases::DrawPhase,
include_shader_module,
wgpu_resources::{GpuRenderPipelinePoolAccessor, PoolError},
wgpu_resources::{self, GpuRenderPipelinePoolAccessor, PoolError},
DebugLabel,
};

/// GPU sided data used by a [`Renderer`] to draw things to the screen.
Expand Down Expand Up @@ -89,3 +90,97 @@ pub fn screen_triangle_vertex_shader(
&include_shader_module!("../../shader/screen_triangle.wgsl"),
)
}

/// Texture size for storing a given amount of data.
///
/// For WebGL compatibility we sometimes have to use textures instead of buffers.
/// We call these textures "data textures".
/// This method determines the size of a data texture holding a given number of used texels.
/// Each texel is typically a single data entry (think `struct`).
///
/// `max_texture_dimension_2d` must be a power of two and is the maximum supported size of 2D textures.
///
/// For convenience, the returned texture size has a width such that its
/// row size in bytes is a multiple of `wgpu::COPY_BYTES_PER_ROW_ALIGNMENT`.
/// This makes it a lot easier to copy data from a continuous buffer to the texture.
/// If we wouldn't do that, we'd need to do a copy for each row in some cases.
pub fn data_texture_size(
format: wgpu::TextureFormat,
num_texels_written: u32,
max_texture_dimension_2d: u32,
) -> wgpu::Extent3d {
debug_assert!(max_texture_dimension_2d.is_power_of_two());
debug_assert!(!format.has_depth_aspect());
debug_assert!(!format.has_stencil_aspect());
debug_assert!(!format.is_compressed());

let texel_size_in_bytes = format
.block_copy_size(None)
.expect("Depth/stencil formats are not supported as data textures");

// Our data textures are usually accessed in a linear fashion, so ideally we'd be using a 1D texture.
// However, 1D textures are very limited in size on many platforms, we we have to use 2D textures instead.
// 2D textures perform a lot better when their dimensions are powers of two, so we'll strictly stick to that even
// when it seems to cause memory overhead.

// We fill row by row. With the power-of-two requirement, this is the optimal strategy:
// if there were a texture with less padding that uses half the width,
// then we'd need to increase the height. We can't increase without doubling it, thus creating a texture
// with the exact same mount of padding as before.

let width = if num_texels_written < max_texture_dimension_2d {
num_texels_written
.next_power_of_two()
// For too few number of written texels, or too small texels we might need to increase the size to stay
// above a row **byte** size of `wgpu::COPY_BYTES_PER_ROW_ALIGNMENT`.
// Note that this implies that for very large texels, we need less wide textures to stay above this limit.
// (width is in number of texels, but alignment cares about bytes!)
.next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT / texel_size_in_bytes)
} else {
max_texture_dimension_2d
};

let height = num_texels_written.div_ceil(width);

wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
}
}

/// Texture descriptor for data storage.
///
/// See [`data_texture_size`]
pub fn data_texture_desc(
label: impl Into<DebugLabel>,
format: wgpu::TextureFormat,
num_texels_written: u32,
max_texture_dimension_2d: u32,
) -> wgpu_resources::TextureDesc {
wgpu_resources::TextureDesc {
label: label.into(),
size: data_texture_size(format, num_texels_written, max_texture_dimension_2d),
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
}
}

/// Pendent to [`data_texture_size`] for determining the element size (==texels on data texture)
/// need to be in a buffer that fills an entire data texture.
pub fn data_texture_source_buffer_element_count(
texture_format: wgpu::TextureFormat,
num_texels_written: u32,
max_texture_dimension_2d: u32,
) -> usize {
let data_texture_size =
data_texture_size(texture_format, num_texels_written, max_texture_dimension_2d);
let element_count = data_texture_size.width as usize * data_texture_size.height as usize;

debug_assert!(element_count >= num_texels_written as usize);

element_count
}
Loading
Loading