Skip to main content

wgpu_3dgs_viewer/selection/
viewport_texture_rectangle.rs

1use crate::{
2    CameraBuffer, RendererCreateError,
3    core::BufferWrapper,
4    selection::{ViewportTexture, ViewportTexturePosBuffer},
5    wesl_utils,
6};
7
8/// A renderer for applying a rectangle selection to [`ViewportTexture`].
9#[derive(Debug)]
10pub struct ViewportTextureRectangleRenderer<B = wgpu::BindGroup> {
11    /// The bind group layout.
12    #[allow(dead_code)]
13    bind_group_layout: wgpu::BindGroupLayout,
14    /// The bind group.
15    bind_group: B,
16    /// The render pipeline.
17    pipeline: wgpu::RenderPipeline,
18}
19
20impl<B> ViewportTextureRectangleRenderer<B> {
21    /// Create the bind group.
22    pub fn create_bind_group(
23        &self,
24        device: &wgpu::Device,
25        camera: &CameraBuffer,
26        top_left: &ViewportTexturePosBuffer,
27        bottom_right: &ViewportTexturePosBuffer,
28    ) -> wgpu::BindGroup {
29        ViewportTextureRectangleRenderer::create_bind_group_static(
30            device,
31            &self.bind_group_layout,
32            camera,
33            top_left,
34            bottom_right,
35        )
36    }
37}
38
39impl ViewportTextureRectangleRenderer {
40    /// The bind group layout descriptor.
41    pub const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> =
42        wgpu::BindGroupLayoutDescriptor {
43            label: Some("Viewport Selection Texture Rectangle Renderer Bind Group Layout"),
44            entries: &[
45                // Camera uniform buffer
46                wgpu::BindGroupLayoutEntry {
47                    binding: 0,
48                    visibility: wgpu::ShaderStages::VERTEX,
49                    ty: wgpu::BindingType::Buffer {
50                        ty: wgpu::BufferBindingType::Uniform,
51                        has_dynamic_offset: false,
52                        min_binding_size: None,
53                    },
54                    count: None,
55                },
56                // Top left uniform buffer
57                wgpu::BindGroupLayoutEntry {
58                    binding: 1,
59                    visibility: wgpu::ShaderStages::VERTEX,
60                    ty: wgpu::BindingType::Buffer {
61                        ty: wgpu::BufferBindingType::Uniform,
62                        has_dynamic_offset: false,
63                        min_binding_size: None,
64                    },
65                    count: None,
66                },
67                // Bottom right uniform buffer
68                wgpu::BindGroupLayoutEntry {
69                    binding: 2,
70                    visibility: wgpu::ShaderStages::VERTEX,
71                    ty: wgpu::BindingType::Buffer {
72                        ty: wgpu::BufferBindingType::Uniform,
73                        has_dynamic_offset: false,
74                        min_binding_size: None,
75                    },
76                    count: None,
77                },
78            ],
79        };
80
81    /// Create a new renderer.
82    pub fn new(
83        device: &wgpu::Device,
84        texture: &ViewportTexture,
85        camera: &CameraBuffer,
86        top_left: &ViewportTexturePosBuffer,
87        bottom_right: &ViewportTexturePosBuffer,
88    ) -> Result<Self, RendererCreateError> {
89        let this = ViewportTextureRectangleRenderer::new_without_bind_group(device, texture)?;
90
91        log::debug!("Creating viewport texture rectangle renderer bind group");
92        let bind_group = this.create_bind_group(device, camera, top_left, bottom_right);
93
94        Ok(Self {
95            bind_group_layout: this.bind_group_layout,
96            bind_group,
97            pipeline: this.pipeline,
98        })
99    }
100
101    /// Render the rectangle.
102    pub fn render(&self, encoder: &mut wgpu::CommandEncoder, texture: &ViewportTexture) {
103        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
104            label: Some("Viewport Texture Rectangle Renderer Render Pass"),
105            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
106                view: texture.view(),
107                resolve_target: None,
108                ops: wgpu::Operations {
109                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
110                    store: wgpu::StoreOp::Store,
111                },
112                depth_slice: None,
113            })],
114            ..Default::default()
115        });
116
117        self.render_with_pass(&mut render_pass);
118    }
119
120    /// Render the rectangle with a [`wgpu::RenderPass`].
121    pub fn render_with_pass(&self, pass: &mut wgpu::RenderPass<'_>) {
122        pass.set_pipeline(&self.pipeline);
123        pass.set_bind_group(0, &self.bind_group, &[]);
124        pass.draw(0..6, 0..1);
125    }
126
127    /// Create the bind group statically.
128    fn create_bind_group_static(
129        device: &wgpu::Device,
130        bind_group_layout: &wgpu::BindGroupLayout,
131        camera: &CameraBuffer,
132        top_left: &ViewportTexturePosBuffer,
133        bottom_right: &ViewportTexturePosBuffer,
134    ) -> wgpu::BindGroup {
135        device.create_bind_group(&wgpu::BindGroupDescriptor {
136            label: Some("Viewport Texture Rectangle Renderer Bind Group"),
137            layout: bind_group_layout,
138            entries: &[
139                // Camera uniform buffer
140                wgpu::BindGroupEntry {
141                    binding: 0,
142                    resource: camera.buffer().as_entire_binding(),
143                },
144                // Top left uniform buffer
145                wgpu::BindGroupEntry {
146                    binding: 1,
147                    resource: top_left.buffer().as_entire_binding(),
148                },
149                // Bottom right uniform buffer
150                wgpu::BindGroupEntry {
151                    binding: 2,
152                    resource: bottom_right.buffer().as_entire_binding(),
153                },
154            ],
155        })
156    }
157}
158
159impl ViewportTextureRectangleRenderer<()> {
160    /// Create a new renderer without internally managed bind group.
161    ///
162    /// To create a bind group with layout matched to this renderer, use the
163    /// [`ViewportTextureRectangleRenderer::create_bind_group`] method.
164    pub fn new_without_bind_group(
165        device: &wgpu::Device,
166        texture: &ViewportTexture,
167    ) -> Result<Self, RendererCreateError> {
168        log::debug!("Creating viewport texture rectangle renderer bind group layout");
169        let bind_group_layout = device.create_bind_group_layout(
170            &ViewportTextureRectangleRenderer::BIND_GROUP_LAYOUT_DESCRIPTOR,
171        );
172
173        log::debug!("Creating viewport texture rectangle renderer pipeline layout");
174        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
175            label: Some("Viewport Texture Rectangle Renderer Pipeline Layout"),
176            bind_group_layouts: &[&bind_group_layout],
177            ..Default::default()
178        });
179
180        log::debug!("Creating viewport texture rectangle renderer shader");
181        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
182            label: Some("Viewport Texture Rectangle Renderer Shader"),
183            source: wgpu::ShaderSource::Wgsl(
184                wesl::compile_sourcemap(
185                    &"wgpu_3dgs_viewer::selection::viewport_texture_rectangle"
186                        .parse()
187                        .expect("selection::viewport_texture_rectangle module path"),
188                    &wesl_utils::resolver(),
189                    &wesl::NoMangler,
190                    &wesl::CompileOptions::default(),
191                )?
192                .to_string()
193                .into(),
194            ),
195        });
196
197        log::debug!("Creating viewport texture rectangle renderer pipeline");
198        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
199            label: Some("Viewport Texture Rectangle Renderer Pipeline"),
200            layout: Some(&pipeline_layout),
201            vertex: wgpu::VertexState {
202                module: &shader,
203                entry_point: Some("vert_main"),
204                buffers: &[],
205                compilation_options: wgpu::PipelineCompilationOptions::default(),
206            },
207            fragment: Some(wgpu::FragmentState {
208                module: &shader,
209                entry_point: Some("frag_main"),
210                targets: &[Some(wgpu::ColorTargetState {
211                    format: texture.texture().format(),
212                    blend: None,
213                    write_mask: wgpu::ColorWrites::ALL,
214                })],
215                compilation_options: wgpu::PipelineCompilationOptions::default(),
216            }),
217            primitive: wgpu::PrimitiveState::default(),
218            depth_stencil: None,
219            multisample: wgpu::MultisampleState::default(),
220            multiview_mask: None,
221            cache: None,
222        });
223
224        log::info!("Viewport texture rectangle renderer created");
225
226        Ok(Self {
227            bind_group_layout,
228            bind_group: (),
229            pipeline,
230        })
231    }
232
233    /// Render the rectangle.
234    pub fn render(
235        &self,
236        encoder: &mut wgpu::CommandEncoder,
237        texture: &ViewportTexture,
238        bind_group: &wgpu::BindGroup,
239    ) {
240        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
241            label: Some("Viewport Texture Rectangle Renderer Render Pass"),
242            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
243                view: texture.view(),
244                resolve_target: None,
245                ops: wgpu::Operations {
246                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
247                    store: wgpu::StoreOp::Store,
248                },
249                depth_slice: None,
250            })],
251            ..Default::default()
252        });
253
254        self.render_with_pass(&mut render_pass, bind_group);
255    }
256
257    /// Render the rectangle with a [`wgpu::RenderPass`].
258    pub fn render_with_pass(&self, pass: &mut wgpu::RenderPass<'_>, bind_group: &wgpu::BindGroup) {
259        pass.set_pipeline(&self.pipeline);
260        pass.set_bind_group(0, bind_group, &[]);
261        pass.draw(0..6, 0..1);
262    }
263}