Skip to main content

SelectionBundle

Struct SelectionBundle 

Source
pub struct SelectionBundle<G: GaussianPod> {
    pub bundles: Vec<ComputeBundle<()>>,
    /* private fields */
}
Expand description

A collection of specialized ComputeBundle for selection operations.

§Custom Operations

All ComputeBundles supplied to this bundle as a SelectionExpr::Unary, SelectionExpr::Binary, or SelectionExpr::Selection custom operation must have the same bind group 0 as the SelectionBundle::GAUSSIANS_BIND_GROUP_LAYOUT_DESCRIPTOR. They must also not have the bind group itself, as it will be supplied automatically during evaluation.

Note that SelectionExpr::Unary will also get the source selection buffer, but it will be empty (all zeros), you should operate on the destination selection buffer only.

It is recommended to use ComputeBundleBuilder to create the custom operation bundles, and build them using ComputeBundleBuilder::build_without_bind_groups.

// Create an editor that holds the buffers for the Gaussians
let editor = Editor::new(
    &device,
    &vec![core::Gaussian {
        rot: Quat::IDENTITY,
        pos: Vec3::ZERO,
        color: U8Vec4::ZERO,
        sh: [Vec3::ZERO; 15],
        scale: Vec3::ONE,
    }],
);

// Create the selection custom operation compute bundle
let my_selection_custom_op_bundle = core::ComputeBundleBuilder::new()
    .label("My Selection")
    .bind_group_layouts([
        &SelectionBundle::<GaussianPod>::GAUSSIANS_BIND_GROUP_LAYOUT_DESCRIPTOR,
        &MY_CUSTOM_BIND_GROUP_LAYOUT_DESCRIPTOR,
    ])
    .resolver({
        let mut resolver =
            wesl::StandardResolver::new("path/to/my/folder/containing/wesl");
        // Required for using core buffer structs.
        resolver.add_package(&core::shader::PACKAGE);
        // Optionally add this for some utility functions.
        resolver.add_package(&shader::PACKAGE);
        resolver
    })
    .main_shader("package::my_wesl_filename".parse().unwrap())
    .entry_point("main")
    .wesl_compile_options(wesl::CompileOptions {
        // Required for enabling the correct features for core struct.
        features: GaussianPod::wesl_features(),
        ..Default::default()
    })
    .build_without_bind_groups(&device)
    .map_err(|e| log::error!("{e}"))
    .expect("my selection custom op bundle");

// Create the selection bundle
let selection_bundle =
    SelectionBundle::<GaussianPod>::new(&device, vec![my_selection_custom_op_bundle]);

// Create the bind group for your custom operation
let my_selection_custom_op_bind_group = selection_bundle.bundles[0]
    .create_bind_group(
        &device,
        1, // Index 0 is always the Gaussians buffer
        [my_buffer.buffer().as_entire_binding()],
    )
    .unwrap();

// Create the selection expression
let selection_expr = SelectionExpr::selection(
    0, // The bundle index for your custom operation in the selection bundle
    vec![my_selection_custom_op_bind_group],
)
.union(
    // Combine with other selection expressions using different functions
    // Here is an existing selection buffer for example
    SelectionExpr::Buffer(my_existing_selection_buffer),
);

// Create a selection buffer for the result
let dest_selection_buffer =
    SelectionBuffer::new(&device, editor.gaussians_buffer.len() as u32);


// Evaluate the selection expression
selection_bundle.evaluate(
    &device,
    &mut encoder,
    &selection_expr,
    &dest_selection_buffer,
    &editor.model_transform_buffer,
    &editor.gaussian_transform_buffer,
    &editor.gaussians_buffer,
);

§Shader Format

You may copy and paste the following shader bindings for SelectionBundle::GAUSSIANS_BIND_GROUP_LAYOUT_DESCRIPTOR into your custom selection operation shader to ensure that the bindings are correct, then add your own bindings after that.

import wgpu_3dgs_core::{
    gaussian::Gaussian,
    gaussian_transform::GaussianTransform,
    model_transform::{model_to_world, ModelTransform},
};

@group(0) @binding(0)
var<uniform> op: u32;

@group(0) @binding(1)
var<storage, read> source: array<u32>;

@group(0) @binding(2)
var<storage, read_write> dest: array<atomic<u32>>;

@group(0) @binding(3)
var<uniform> model_transform: ModelTransform;

@group(0) @binding(4)
var<uniform> gaussian_transform: GaussianTransform;

@group(0) @binding(5)
var<storage, read> gaussians: array<Gaussian>;

// Your custom bindings here...

override workgroup_size: u32;

@compute @workgroup_size(workgroup_size)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
    let index = id.x;

    if index >= arrayLength(&gaussians) {
        return;
    }

    let gaussian = gaussians[index];

    let world_pos = model_to_world(model_transform, gaussian.position);

    // Your custom selection operation code here...

    let word_index = index / 32u;
    let bit_index = index % 32u;
    let bit_mask = 1u << bit_index;
    if /* Condition for selecting the Gaussian */ {
        atomicOr(&dest[word_index], bit_mask);
    } else {
        atomicAnd(&dest[word_index], ~bit_mask);
    }
}

Fields§

§bundles: Vec<ComputeBundle<()>>

The compute bundles for selection custom operations.

Implementations§

Source§

impl<G: GaussianPod> SelectionBundle<G>

Source

pub const GAUSSIANS_BIND_GROUP_LAYOUT_DESCRIPTOR: BindGroupLayoutDescriptor<'static>

The Gaussians bind group layout descriptors.

This bind group layout takes the following buffers:

Source

pub const SPHERE_BIND_GROUP_LAYOUT_DESCRIPTOR: BindGroupLayoutDescriptor<'static>

The sphere selection bind group layout descriptor.

This bind group layout takes the following buffers:

Source

pub const BOX_BIND_GROUP_LAYOUT_DESCRIPTOR: BindGroupLayoutDescriptor<'static>

The box selection bind group layout descriptor.

This bind group layout takes the following buffers:

Source

pub fn new(device: &Device, bundles: Vec<ComputeBundle<()>>) -> Self

Create a new selection bundle.

bundles are used for SelectionExpr::Unary, SelectionExpr::Binary, or SelectionExpr::Selection as custom operations, they must have the same bind group 0 as the SelectionBundle::GAUSSIANS_BIND_GROUP_LAYOUT_DESCRIPTOR, see documentation of SelectionBundle for more details.

Examples found in repository?
examples/filter_selection.rs (line 155)
98async fn main() {
99    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
100
101    let args = Args::parse();
102    let model_path = &args.model;
103    let pos = Vec3::from_slice(&args.pos);
104    let rot = Quat::from_slice(&args.rot);
105    let scale = Vec3::from_slice(&args.scale);
106    let shape = match args.shape {
107        Shape::Sphere => gs::SelectionBundle::<GaussianPod>::create_sphere_bundle,
108        Shape::Box => gs::SelectionBundle::<GaussianPod>::create_box_bundle,
109    };
110    let repeat = args.repeat;
111    let offset = Vec3::from_slice(&args.offset);
112
113    log::debug!("Creating wgpu instance");
114    let instance =
115        wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
116
117    log::debug!("Requesting adapter");
118    let adapter = instance
119        .request_adapter(&wgpu::RequestAdapterOptions::default())
120        .await
121        .expect("adapter");
122
123    log::debug!("Requesting device");
124    let (device, queue) = adapter
125        .request_device(&wgpu::DeviceDescriptor {
126            label: Some("Device"),
127            required_limits: adapter.limits(),
128            ..Default::default()
129        })
130        .await
131        .expect("device");
132
133    log::debug!("Creating gaussians");
134    let gaussians = [
135        gs::core::GaussiansSource::Ply,
136        gs::core::GaussiansSource::Spz,
137    ]
138    .into_iter()
139    .find_map(|source| gs::core::Gaussians::read_from_file(model_path, source).ok())
140    .expect("gaussians");
141
142    log::debug!("Creating gaussians buffer");
143    let gaussians_buffer = gs::core::GaussiansBuffer::<GaussianPod>::new(&device, &gaussians);
144
145    log::debug!("Creating model transform buffer");
146    let model_transform = gs::core::ModelTransformBuffer::new(&device);
147
148    log::debug!("Creating Gaussian transform buffer");
149    let gaussian_transform = gs::core::GaussianTransformBuffer::new(&device);
150
151    log::debug!("Creating shape selection compute bundle");
152    let shape_selection = shape(&device);
153
154    log::debug!("Creating selection bundle");
155    let selection_bundle = gs::SelectionBundle::<GaussianPod>::new(&device, vec![shape_selection]);
156
157    log::debug!("Creating shape selection buffers");
158    let shape_selection_buffers = (0..repeat)
159        .map(|i| {
160            let offset_pos = pos + offset * i as f32;
161            let buffer = gs::InvTransformBuffer::new(&device);
162            buffer.update_with_scale_rot_pos(&queue, scale, rot, offset_pos);
163            buffer
164        })
165        .collect::<Vec<_>>();
166
167    log::debug!("Creating shape selection bind groups");
168    let shape_selection_bind_groups = shape_selection_buffers
169        .iter()
170        .map(|buffer| {
171            selection_bundle.bundles[0]
172                .create_bind_group(
173                    &device,
174                    // index 0 is the Gaussians buffer, so we use 1,
175                    // see docs of create_sphere_bundle or create_box_bundle
176                    1,
177                    [buffer.buffer().as_entire_binding()],
178                )
179                .expect("bind group")
180        })
181        .collect::<Vec<_>>();
182
183    log::debug!("Creating selection expression");
184    let selection_expr = shape_selection_bind_groups.into_iter().fold(
185        gs::SelectionExpr::Identity,
186        |acc, bind_group| {
187            acc.union(gs::SelectionExpr::selection(
188                0, // the 0 here is the bundle index in the selection bundle
189                vec![bind_group],
190            ))
191        },
192    );
193
194    log::debug!("Creating destination buffer");
195    let dest = gs::SelectionBuffer::new(&device, gaussians_buffer.len() as u32);
196
197    log::info!("Starting selection process");
198    let time = std::time::Instant::now();
199
200    log::debug!("Selecting Gaussians");
201    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
202        label: Some("Selection Encoder"),
203    });
204
205    selection_bundle.evaluate(
206        &device,
207        &mut encoder,
208        &selection_expr,
209        &dest,
210        &model_transform,
211        &gaussian_transform,
212        &gaussians_buffer,
213    );
214
215    queue.submit(Some(encoder.finish()));
216
217    device
218        .poll(wgpu::PollType::wait_indefinitely())
219        .expect("poll");
220
221    log::info!("Editing process completed in {:?}", time.elapsed());
222
223    log::debug!("Filtering Gaussians");
224    let selected_gaussians = dest
225        .download::<u32>(&device, &queue)
226        .await
227        .expect("selected download")
228        .iter()
229        .flat_map(|group| {
230            std::iter::repeat_n(group, 32)
231                .enumerate()
232                .map(|(i, g)| g & (1 << i) != 0)
233        })
234        .zip(gaussians.iter_gaussian())
235        .filter(|(selected, _)| *selected)
236        .map(|(_, g)| g)
237        .collect::<Vec<_>>();
238
239    let selected_gaussians = match &args.output[args.output.len().saturating_sub(4)..] {
240        ".ply" => gs::core::Gaussians::Ply(gs::core::PlyGaussians::from_iter(
241            selected_gaussians.into_iter(),
242        )),
243        ".spz" => {
244            gs::core::Gaussians::Spz(
245                gs::core::SpzGaussians::from_gaussians_with_options(
246                    selected_gaussians,
247                    &gs::core::SpzGaussiansFromGaussianSliceOptions {
248                        version: 2, // Version 2 is more widely supported as of now
249                        ..Default::default()
250                    },
251                )
252                .expect("SpzGaussians from gaussians"),
253            )
254        }
255        _ => panic!("Unsupported output file extension, expected .ply or .spz"),
256    };
257
258    log::debug!("Writing modified Gaussians to output file");
259    selected_gaussians
260        .write_to_file(&args.output)
261        .expect("write modified Gaussians to output file");
262
263    log::info!("Filtered Gaussians written to {}", args.output);
264}
Source

pub fn gaussians_bind_group_layout(&self) -> &BindGroupLayout

Get the Gaussians bind group layout.

Source

pub fn evaluate( &self, device: &Device, encoder: &mut CommandEncoder, expr: &SelectionExpr, dest: &SelectionBuffer, model_transform: &ModelTransformBuffer, gaussian_transform: &GaussianTransformBuffer, gaussians: &GaussiansBuffer<G>, )

Evaluate and apply the selection expression.

Examples found in repository?
examples/filter_selection.rs (lines 205-213)
98async fn main() {
99    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
100
101    let args = Args::parse();
102    let model_path = &args.model;
103    let pos = Vec3::from_slice(&args.pos);
104    let rot = Quat::from_slice(&args.rot);
105    let scale = Vec3::from_slice(&args.scale);
106    let shape = match args.shape {
107        Shape::Sphere => gs::SelectionBundle::<GaussianPod>::create_sphere_bundle,
108        Shape::Box => gs::SelectionBundle::<GaussianPod>::create_box_bundle,
109    };
110    let repeat = args.repeat;
111    let offset = Vec3::from_slice(&args.offset);
112
113    log::debug!("Creating wgpu instance");
114    let instance =
115        wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
116
117    log::debug!("Requesting adapter");
118    let adapter = instance
119        .request_adapter(&wgpu::RequestAdapterOptions::default())
120        .await
121        .expect("adapter");
122
123    log::debug!("Requesting device");
124    let (device, queue) = adapter
125        .request_device(&wgpu::DeviceDescriptor {
126            label: Some("Device"),
127            required_limits: adapter.limits(),
128            ..Default::default()
129        })
130        .await
131        .expect("device");
132
133    log::debug!("Creating gaussians");
134    let gaussians = [
135        gs::core::GaussiansSource::Ply,
136        gs::core::GaussiansSource::Spz,
137    ]
138    .into_iter()
139    .find_map(|source| gs::core::Gaussians::read_from_file(model_path, source).ok())
140    .expect("gaussians");
141
142    log::debug!("Creating gaussians buffer");
143    let gaussians_buffer = gs::core::GaussiansBuffer::<GaussianPod>::new(&device, &gaussians);
144
145    log::debug!("Creating model transform buffer");
146    let model_transform = gs::core::ModelTransformBuffer::new(&device);
147
148    log::debug!("Creating Gaussian transform buffer");
149    let gaussian_transform = gs::core::GaussianTransformBuffer::new(&device);
150
151    log::debug!("Creating shape selection compute bundle");
152    let shape_selection = shape(&device);
153
154    log::debug!("Creating selection bundle");
155    let selection_bundle = gs::SelectionBundle::<GaussianPod>::new(&device, vec![shape_selection]);
156
157    log::debug!("Creating shape selection buffers");
158    let shape_selection_buffers = (0..repeat)
159        .map(|i| {
160            let offset_pos = pos + offset * i as f32;
161            let buffer = gs::InvTransformBuffer::new(&device);
162            buffer.update_with_scale_rot_pos(&queue, scale, rot, offset_pos);
163            buffer
164        })
165        .collect::<Vec<_>>();
166
167    log::debug!("Creating shape selection bind groups");
168    let shape_selection_bind_groups = shape_selection_buffers
169        .iter()
170        .map(|buffer| {
171            selection_bundle.bundles[0]
172                .create_bind_group(
173                    &device,
174                    // index 0 is the Gaussians buffer, so we use 1,
175                    // see docs of create_sphere_bundle or create_box_bundle
176                    1,
177                    [buffer.buffer().as_entire_binding()],
178                )
179                .expect("bind group")
180        })
181        .collect::<Vec<_>>();
182
183    log::debug!("Creating selection expression");
184    let selection_expr = shape_selection_bind_groups.into_iter().fold(
185        gs::SelectionExpr::Identity,
186        |acc, bind_group| {
187            acc.union(gs::SelectionExpr::selection(
188                0, // the 0 here is the bundle index in the selection bundle
189                vec![bind_group],
190            ))
191        },
192    );
193
194    log::debug!("Creating destination buffer");
195    let dest = gs::SelectionBuffer::new(&device, gaussians_buffer.len() as u32);
196
197    log::info!("Starting selection process");
198    let time = std::time::Instant::now();
199
200    log::debug!("Selecting Gaussians");
201    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
202        label: Some("Selection Encoder"),
203    });
204
205    selection_bundle.evaluate(
206        &device,
207        &mut encoder,
208        &selection_expr,
209        &dest,
210        &model_transform,
211        &gaussian_transform,
212        &gaussians_buffer,
213    );
214
215    queue.submit(Some(encoder.finish()));
216
217    device
218        .poll(wgpu::PollType::wait_indefinitely())
219        .expect("poll");
220
221    log::info!("Editing process completed in {:?}", time.elapsed());
222
223    log::debug!("Filtering Gaussians");
224    let selected_gaussians = dest
225        .download::<u32>(&device, &queue)
226        .await
227        .expect("selected download")
228        .iter()
229        .flat_map(|group| {
230            std::iter::repeat_n(group, 32)
231                .enumerate()
232                .map(|(i, g)| g & (1 << i) != 0)
233        })
234        .zip(gaussians.iter_gaussian())
235        .filter(|(selected, _)| *selected)
236        .map(|(_, g)| g)
237        .collect::<Vec<_>>();
238
239    let selected_gaussians = match &args.output[args.output.len().saturating_sub(4)..] {
240        ".ply" => gs::core::Gaussians::Ply(gs::core::PlyGaussians::from_iter(
241            selected_gaussians.into_iter(),
242        )),
243        ".spz" => {
244            gs::core::Gaussians::Spz(
245                gs::core::SpzGaussians::from_gaussians_with_options(
246                    selected_gaussians,
247                    &gs::core::SpzGaussiansFromGaussianSliceOptions {
248                        version: 2, // Version 2 is more widely supported as of now
249                        ..Default::default()
250                    },
251                )
252                .expect("SpzGaussians from gaussians"),
253            )
254        }
255        _ => panic!("Unsupported output file extension, expected .ply or .spz"),
256    };
257
258    log::debug!("Writing modified Gaussians to output file");
259    selected_gaussians
260        .write_to_file(&args.output)
261        .expect("write modified Gaussians to output file");
262
263    log::info!("Filtered Gaussians written to {}", args.output);
264}
Source

pub fn create_primitive_bundle(device: &Device) -> ComputeBundle<()>

Create the selection primitive operation ComputeBundle.

You usually do not need to use this method, it is used internally for creating the primitive operation bundle for evaluation.

Source

pub fn create_sphere_bundle(device: &Device) -> ComputeBundle<()>

Create a sphere selection custom operation.

Source

pub fn create_box_bundle(device: &Device) -> ComputeBundle<()>

Create a box selection custom operation.

Trait Implementations§

Source§

impl<G: Debug + GaussianPod> Debug for SelectionBundle<G>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<G> Freeze for SelectionBundle<G>

§

impl<G> !RefUnwindSafe for SelectionBundle<G>

§

impl<G> Send for SelectionBundle<G>

§

impl<G> Sync for SelectionBundle<G>

§

impl<G> Unpin for SelectionBundle<G>
where G: Unpin,

§

impl<G> UnsafeUnpin for SelectionBundle<G>

§

impl<G> !UnwindSafe for SelectionBundle<G>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,