ncca-ngl 0.3.4__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ncca/ngl/PrimData/pack_arrays.py +2 -3
- ncca/ngl/__init__.py +3 -4
- ncca/ngl/base_mesh.py +28 -20
- ncca/ngl/image.py +1 -3
- ncca/ngl/mat2.py +79 -53
- ncca/ngl/mat3.py +104 -185
- ncca/ngl/mat4.py +144 -309
- ncca/ngl/prim_data.py +42 -36
- ncca/ngl/primitives.py +2 -2
- ncca/ngl/pyside_event_handling_mixin.py +0 -108
- ncca/ngl/quaternion.py +69 -36
- ncca/ngl/shader.py +0 -116
- ncca/ngl/shader_program.py +94 -117
- ncca/ngl/texture.py +5 -2
- ncca/ngl/util.py +7 -0
- ncca/ngl/vec2.py +58 -292
- ncca/ngl/vec2_array.py +79 -28
- ncca/ngl/vec3.py +59 -340
- ncca/ngl/vec3_array.py +76 -23
- ncca/ngl/vec4.py +90 -190
- ncca/ngl/vec4_array.py +78 -27
- ncca/ngl/vector_base.py +542 -0
- ncca/ngl/webgpu/__init__.py +20 -0
- ncca/ngl/webgpu/__main__.py +640 -0
- ncca/ngl/webgpu/__main__.py.backup +640 -0
- ncca/ngl/webgpu/base_webgpu_pipeline.py +354 -0
- ncca/ngl/webgpu/custom_shader_pipeline.py +288 -0
- ncca/ngl/webgpu/instanced_geometry_pipeline.py +594 -0
- ncca/ngl/webgpu/line_pipeline.py +405 -0
- ncca/ngl/webgpu/pipeline_factory.py +190 -0
- ncca/ngl/webgpu/pipeline_shaders.py +497 -0
- ncca/ngl/webgpu/point_list_pipeline.py +349 -0
- ncca/ngl/webgpu/point_pipeline.py +336 -0
- ncca/ngl/webgpu/triangle_pipeline.py +419 -0
- ncca/ngl/webgpu/webgpu_constants.py +31 -0
- ncca/ngl/webgpu/webgpu_widget.py +322 -0
- ncca/ngl/webgpu/wip/REFACTORING_SUMMARY.md +82 -0
- ncca/ngl/webgpu/wip/UNIFIED_SYSTEM.md +314 -0
- ncca/ngl/webgpu/wip/buffer_manager.py +396 -0
- ncca/ngl/webgpu/wip/pipeline_config.py +463 -0
- ncca/ngl/webgpu/wip/shader_constants.py +328 -0
- ncca/ngl/webgpu/wip/shader_templates.py +563 -0
- ncca/ngl/webgpu/wip/unified_examples.py +390 -0
- ncca/ngl/webgpu/wip/unified_factory.py +449 -0
- ncca/ngl/webgpu/wip/unified_pipeline.py +469 -0
- ncca/ngl/widgets/__init__.py +18 -2
- ncca/ngl/widgets/__main__.py +2 -1
- ncca/ngl/widgets/lookatwidget.py +2 -1
- ncca/ngl/widgets/mat4widget.py +2 -2
- ncca/ngl/widgets/vec2widget.py +1 -1
- ncca/ngl/widgets/vec3widget.py +1 -0
- {ncca_ngl-0.3.4.dist-info → ncca_ngl-0.5.0.dist-info}/METADATA +3 -2
- ncca_ngl-0.5.0.dist-info/RECORD +105 -0
- ncca/ngl/widgets/transformation_widget.py +0 -299
- ncca_ngl-0.3.4.dist-info/RECORD +0 -82
- {ncca_ngl-0.3.4.dist-info → ncca_ngl-0.5.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Native point-list rendering pipeline for WebGPU.
|
|
3
|
+
Handles point rendering using WebGPU's native point-list topology instead of billboarding.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import wgpu
|
|
10
|
+
|
|
11
|
+
from .base_webgpu_pipeline import BaseWebGPUPipeline
|
|
12
|
+
from .pipeline_shaders import POINT_LIST_SHADER_MULTI_COLOURED, POINT_LIST_SHADER_SINGLE_COLOUR
|
|
13
|
+
from .webgpu_constants import NGLToWebGPU
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PointListPipelineMultiColour(BaseWebGPUPipeline):
|
|
17
|
+
"""
|
|
18
|
+
A pipeline for rendering points using WebGPU's native point-list topology.
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- Native WebGPU point-list rendering (no billboarding)
|
|
22
|
+
- Model, View Projection matrix support
|
|
23
|
+
- MSAA support
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
device: wgpu.GPUDevice,
|
|
29
|
+
data_type: str = "Vec3",
|
|
30
|
+
texture_format: wgpu.TextureFormat = wgpu.TextureFormat.rgba8unorm,
|
|
31
|
+
depth_format: wgpu.TextureFormat = wgpu.TextureFormat.depth24plus,
|
|
32
|
+
msaa_sample_count: int = 4,
|
|
33
|
+
stride: int = 0,
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Initialize the point list rendering pipeline.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
device: WebGPU device
|
|
40
|
+
data_type: Vertex data type (e.g., "Vec3", "Vec2")
|
|
41
|
+
texture_format: colour attachment format
|
|
42
|
+
depth_format: Depth attachment format
|
|
43
|
+
msaa_sample_count: Number of MSAA samples
|
|
44
|
+
stride: The stride of the vertex buffer. If 0, it is inferred from data_type.
|
|
45
|
+
"""
|
|
46
|
+
# Pipeline-specific buffer tracking
|
|
47
|
+
self.position_buffer: Optional[wgpu.GPUBuffer] = None
|
|
48
|
+
self.colour_buffer: Optional[wgpu.GPUBuffer] = None
|
|
49
|
+
self.num_points: int = 0
|
|
50
|
+
|
|
51
|
+
super().__init__(
|
|
52
|
+
device=device,
|
|
53
|
+
texture_format=texture_format,
|
|
54
|
+
depth_format=depth_format,
|
|
55
|
+
msaa_sample_count=msaa_sample_count,
|
|
56
|
+
data_type=data_type,
|
|
57
|
+
stride=stride,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def get_dtype(self) -> np.dtype:
|
|
61
|
+
"""Get the data type of the pipeline."""
|
|
62
|
+
return np.dtype([
|
|
63
|
+
("MVP", "float32", (4, 4)),
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
def _get_shader_code(self) -> str:
|
|
67
|
+
"""Get the WGSL shader code for this pipeline."""
|
|
68
|
+
return POINT_LIST_SHADER_MULTI_COLOURED
|
|
69
|
+
|
|
70
|
+
def _get_primitive_topology(self) -> wgpu.PrimitiveTopology:
|
|
71
|
+
"""Points are rendered as point list."""
|
|
72
|
+
return wgpu.PrimitiveTopology.point_list
|
|
73
|
+
|
|
74
|
+
def _get_vertex_buffer_layouts(self):
|
|
75
|
+
"""Get vertex buffer layout configurations for the pipeline."""
|
|
76
|
+
position_layout = {
|
|
77
|
+
"array_stride": self._stride,
|
|
78
|
+
"step_mode": wgpu.VertexStepMode.vertex,
|
|
79
|
+
"attributes": [
|
|
80
|
+
{
|
|
81
|
+
"format": NGLToWebGPU.vertex_format(self._data_type),
|
|
82
|
+
"offset": 0,
|
|
83
|
+
"shader_location": 0,
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
colour_layout = {
|
|
89
|
+
"array_stride": 12, # 3 * float32 for RGB
|
|
90
|
+
"step_mode": wgpu.VertexStepMode.vertex,
|
|
91
|
+
"attributes": [
|
|
92
|
+
{
|
|
93
|
+
"format": wgpu.VertexFormat.float32x3,
|
|
94
|
+
"offset": 0,
|
|
95
|
+
"shader_location": 1,
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return [position_layout, colour_layout]
|
|
101
|
+
|
|
102
|
+
def _set_default_uniforms(self) -> None:
|
|
103
|
+
"""Set default values for uniform data."""
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
def _get_pipeline_label(self) -> str:
|
|
107
|
+
"""Get the label for the pipeline."""
|
|
108
|
+
return "point_list_pipeline_multi_coloured"
|
|
109
|
+
|
|
110
|
+
def set_data(
|
|
111
|
+
self,
|
|
112
|
+
positions,
|
|
113
|
+
colours=None,
|
|
114
|
+
) -> None:
|
|
115
|
+
"""
|
|
116
|
+
Set the point data for rendering.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
positions: Nx3 array of point positions or a pre-existing GPUBuffer.
|
|
120
|
+
colours: Nx3 array of point colours (RGB) or a pre-existing GPUBuffer.
|
|
121
|
+
If None, uses white.
|
|
122
|
+
"""
|
|
123
|
+
# Handle positions
|
|
124
|
+
if isinstance(positions, wgpu.GPUBuffer):
|
|
125
|
+
self.position_buffer = positions
|
|
126
|
+
self.num_points = positions.size // self._stride
|
|
127
|
+
else: # numpy array
|
|
128
|
+
self.num_points = len(positions)
|
|
129
|
+
self.position_buffer, _ = self._create_or_update_buffer(
|
|
130
|
+
self.position_buffer,
|
|
131
|
+
positions,
|
|
132
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
133
|
+
"point_list_pipeline_multi_coloured_position_buffer",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Handle colours
|
|
137
|
+
if colours is None:
|
|
138
|
+
# Create default white colours
|
|
139
|
+
default_colours = np.ones((self.num_points, 3), dtype=np.float32)
|
|
140
|
+
self.colour_buffer, _ = self._create_or_update_buffer(
|
|
141
|
+
self.colour_buffer,
|
|
142
|
+
default_colours,
|
|
143
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
144
|
+
"point_list_pipeline_multi_coloured_colour_buffer",
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
self.colour_buffer, _ = self._create_or_update_buffer(
|
|
148
|
+
self.colour_buffer,
|
|
149
|
+
colours,
|
|
150
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
151
|
+
"point_list_pipeline_multi_coloured_colour_buffer",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def update_uniforms(self, **kwargs) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Update uniform buffer values.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
**kwargs: Pipeline-specific uniform parameters
|
|
160
|
+
- mvp: 4x4 model view projection matrix
|
|
161
|
+
"""
|
|
162
|
+
if "mvp" in kwargs and kwargs["mvp"] is not None:
|
|
163
|
+
self.uniform_data["MVP"] = kwargs["mvp"]
|
|
164
|
+
|
|
165
|
+
self.device.queue.write_buffer(self.uniform_buffer, 0, self.uniform_data.tobytes())
|
|
166
|
+
|
|
167
|
+
def render(self, render_pass: wgpu.GPURenderPassEncoder, **kwargs) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Render the points.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
render_pass: Active render pass encoder
|
|
173
|
+
**kwargs: Pipeline-specific render parameters
|
|
174
|
+
- num_points: Number of points to render (defaults to all)
|
|
175
|
+
"""
|
|
176
|
+
num_points = kwargs.get("num_points", None)
|
|
177
|
+
|
|
178
|
+
if self.position_buffer is None or self.colour_buffer is None:
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
count = num_points if num_points is not None else self.num_points
|
|
182
|
+
|
|
183
|
+
render_pass.set_pipeline(self.pipeline)
|
|
184
|
+
if self.bind_group:
|
|
185
|
+
render_pass.set_bind_group(0, self.bind_group, [], 0, 999999)
|
|
186
|
+
render_pass.set_vertex_buffer(0, self.position_buffer)
|
|
187
|
+
render_pass.set_vertex_buffer(1, self.colour_buffer)
|
|
188
|
+
render_pass.draw(count) # Draw points as point list
|
|
189
|
+
|
|
190
|
+
def cleanup(self) -> None:
|
|
191
|
+
"""Release resources."""
|
|
192
|
+
if self.position_buffer:
|
|
193
|
+
self.position_buffer.destroy()
|
|
194
|
+
if self.colour_buffer:
|
|
195
|
+
self.colour_buffer.destroy()
|
|
196
|
+
super().cleanup()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class PointListPipelineSingleColour(BaseWebGPUPipeline):
|
|
200
|
+
"""
|
|
201
|
+
A pipeline for rendering points using WebGPU's native point-list topology.
|
|
202
|
+
|
|
203
|
+
Features:
|
|
204
|
+
- Native WebGPU point-list rendering (no billboarding)
|
|
205
|
+
- Single colour for all points
|
|
206
|
+
- Model, View Projection matrix support
|
|
207
|
+
- MSAA support
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
def __init__(
|
|
211
|
+
self,
|
|
212
|
+
device: wgpu.GPUDevice,
|
|
213
|
+
data_type: str = "Vec3",
|
|
214
|
+
texture_format: wgpu.TextureFormat = wgpu.TextureFormat.rgba8unorm,
|
|
215
|
+
depth_format: wgpu.TextureFormat = wgpu.TextureFormat.depth24plus,
|
|
216
|
+
msaa_sample_count: int = 4,
|
|
217
|
+
stride: int = 0,
|
|
218
|
+
):
|
|
219
|
+
"""
|
|
220
|
+
Initialize the point list rendering pipeline.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
device: WebGPU device
|
|
224
|
+
data_type: Vertex data type (e.g., "Vec3", "Vec2")
|
|
225
|
+
texture_format: colour attachment format
|
|
226
|
+
depth_format: Depth attachment format
|
|
227
|
+
msaa_sample_count: Number of MSAA samples
|
|
228
|
+
stride: The stride of the vertex buffer. If 0, it is inferred from data_type.
|
|
229
|
+
"""
|
|
230
|
+
# Pipeline-specific buffer tracking
|
|
231
|
+
self.position_buffer: Optional[wgpu.GPUBuffer] = None
|
|
232
|
+
self.num_points: int = 0
|
|
233
|
+
|
|
234
|
+
super().__init__(
|
|
235
|
+
device=device,
|
|
236
|
+
texture_format=texture_format,
|
|
237
|
+
depth_format=depth_format,
|
|
238
|
+
msaa_sample_count=msaa_sample_count,
|
|
239
|
+
data_type=data_type,
|
|
240
|
+
stride=stride,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def get_dtype(self) -> np.dtype:
|
|
244
|
+
"""Get the data type of the pipeline."""
|
|
245
|
+
return np.dtype([
|
|
246
|
+
("MVP", "float32", (4, 4)),
|
|
247
|
+
("Colour", "float32", 3),
|
|
248
|
+
("padding", "float32"),
|
|
249
|
+
])
|
|
250
|
+
|
|
251
|
+
def _get_shader_code(self) -> str:
|
|
252
|
+
"""Get the WGSL shader code for this pipeline."""
|
|
253
|
+
return POINT_LIST_SHADER_SINGLE_COLOUR
|
|
254
|
+
|
|
255
|
+
def _get_primitive_topology(self) -> wgpu.PrimitiveTopology:
|
|
256
|
+
"""Points are rendered as point list."""
|
|
257
|
+
return wgpu.PrimitiveTopology.point_list
|
|
258
|
+
|
|
259
|
+
def _get_vertex_buffer_layouts(self):
|
|
260
|
+
"""Get vertex buffer layout configurations for the pipeline."""
|
|
261
|
+
position_layout = {
|
|
262
|
+
"array_stride": self._stride,
|
|
263
|
+
"step_mode": wgpu.VertexStepMode.vertex,
|
|
264
|
+
"attributes": [
|
|
265
|
+
{
|
|
266
|
+
"format": NGLToWebGPU.vertex_format(self._data_type),
|
|
267
|
+
"offset": 0,
|
|
268
|
+
"shader_location": 0,
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return [position_layout]
|
|
274
|
+
|
|
275
|
+
def _set_default_uniforms(self) -> None:
|
|
276
|
+
"""Set default values for uniform data."""
|
|
277
|
+
self.uniform_data["Colour"] = np.array([1.0, 1.0, 1.0], dtype=np.float32) # Default White
|
|
278
|
+
self.uniform_data["padding"] = 0.0
|
|
279
|
+
|
|
280
|
+
def _get_pipeline_label(self) -> str:
|
|
281
|
+
"""Get the label for the pipeline."""
|
|
282
|
+
return "point_list_pipeline_single_colour"
|
|
283
|
+
|
|
284
|
+
def set_data(self, positions, colours=None) -> None:
|
|
285
|
+
"""
|
|
286
|
+
Set the point data for rendering.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
positions: Nx3 array of point positions or a pre-existing GPUBuffer.
|
|
290
|
+
colours: Ignored for single colour pipeline
|
|
291
|
+
"""
|
|
292
|
+
# Handle positions
|
|
293
|
+
if isinstance(positions, wgpu.GPUBuffer):
|
|
294
|
+
self.position_buffer = positions
|
|
295
|
+
self.num_points = positions.size // self._stride
|
|
296
|
+
else: # numpy array
|
|
297
|
+
self.num_points = len(positions)
|
|
298
|
+
self.position_buffer, _ = self._create_or_update_buffer(
|
|
299
|
+
self.position_buffer,
|
|
300
|
+
positions,
|
|
301
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
302
|
+
"point_list_pipeline_single_colour_position_buffer",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def update_uniforms(self, **kwargs) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Update uniform buffer values.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
**kwargs: Pipeline-specific uniform parameters
|
|
311
|
+
- mvp: 4x4 model view projection matrix
|
|
312
|
+
- colour: 3-element array of RGB colour values
|
|
313
|
+
- point_size: Size of points
|
|
314
|
+
"""
|
|
315
|
+
if "mvp" in kwargs and kwargs["mvp"] is not None:
|
|
316
|
+
self.uniform_data["MVP"] = kwargs["mvp"]
|
|
317
|
+
|
|
318
|
+
if "colour" in kwargs and kwargs["colour"] is not None:
|
|
319
|
+
self.uniform_data["Colour"] = kwargs["colour"]
|
|
320
|
+
|
|
321
|
+
self.device.queue.write_buffer(self.uniform_buffer, 0, self.uniform_data.tobytes())
|
|
322
|
+
|
|
323
|
+
def render(self, render_pass: wgpu.GPURenderPassEncoder, **kwargs) -> None:
|
|
324
|
+
"""
|
|
325
|
+
Render the points.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
render_pass: Active render pass encoder
|
|
329
|
+
**kwargs: Pipeline-specific render parameters
|
|
330
|
+
- num_points: Number of points to render (defaults to all)
|
|
331
|
+
"""
|
|
332
|
+
num_points = kwargs.get("num_points", None)
|
|
333
|
+
|
|
334
|
+
if self.position_buffer is None:
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
count = num_points if num_points is not None else self.num_points
|
|
338
|
+
|
|
339
|
+
render_pass.set_pipeline(self.pipeline)
|
|
340
|
+
if self.bind_group:
|
|
341
|
+
render_pass.set_bind_group(0, self.bind_group, [], 0, 999999)
|
|
342
|
+
render_pass.set_vertex_buffer(0, self.position_buffer)
|
|
343
|
+
render_pass.draw(count) # Draw points as point list
|
|
344
|
+
|
|
345
|
+
def cleanup(self) -> None:
|
|
346
|
+
"""Release resources."""
|
|
347
|
+
if self.position_buffer:
|
|
348
|
+
self.position_buffer.destroy()
|
|
349
|
+
super().cleanup()
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generic point rendering pipeline for WebGPU.
|
|
3
|
+
Handles point rendering with customizable size, colour, and projection.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import wgpu
|
|
10
|
+
|
|
11
|
+
from .base_webgpu_pipeline import BasePointPipeline
|
|
12
|
+
from .pipeline_shaders import POINT_SHADER_MULTI_COLOURED, POINT_SHADER_SINGLE_COLOUR
|
|
13
|
+
from .webgpu_constants import NGLToWebGPU
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PointPipelineMultiColour(BasePointPipeline):
|
|
17
|
+
"""
|
|
18
|
+
A reusable pipeline for rendering points in WebGPU.
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- Instanced rendering of points as quads
|
|
22
|
+
- Per-point colours
|
|
23
|
+
- Configurable point size
|
|
24
|
+
- Model, View Projection matrix support pass a projection only for 2D
|
|
25
|
+
- MSAA support
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
device: wgpu.GPUDevice,
|
|
31
|
+
data_type: str = "Vec3",
|
|
32
|
+
texture_format: wgpu.TextureFormat = wgpu.TextureFormat.rgba8unorm,
|
|
33
|
+
depth_format: wgpu.TextureFormat = wgpu.TextureFormat.depth24plus,
|
|
34
|
+
msaa_sample_count: int = 4,
|
|
35
|
+
stride: int = 0,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the point rendering pipeline.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
device: WebGPU device
|
|
42
|
+
texture_format: colour attachment format
|
|
43
|
+
depth_format: Depth attachment format
|
|
44
|
+
msaa_sample_count: Number of MSAA samples
|
|
45
|
+
stride: The stride of the vertex buffer. If 0, it is inferred from data_type.
|
|
46
|
+
"""
|
|
47
|
+
# Pipeline-specific buffer tracking
|
|
48
|
+
self.position_buffer: Optional[wgpu.GPUBuffer] = None
|
|
49
|
+
self.colour_buffer: Optional[wgpu.GPUBuffer] = None
|
|
50
|
+
self.num_points: int = 0
|
|
51
|
+
|
|
52
|
+
super().__init__(
|
|
53
|
+
device=device,
|
|
54
|
+
texture_format=texture_format,
|
|
55
|
+
depth_format=depth_format,
|
|
56
|
+
msaa_sample_count=msaa_sample_count,
|
|
57
|
+
data_type=data_type,
|
|
58
|
+
stride=stride,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def get_dtype(self) -> np.dtype:
|
|
62
|
+
"""Get the data type of the pipeline."""
|
|
63
|
+
return np.dtype([
|
|
64
|
+
("MVP", "float32", (4, 4)),
|
|
65
|
+
("ViewMatrix", "float32", (4, 4)),
|
|
66
|
+
("size", "float32"),
|
|
67
|
+
("padding", np.uint32, 3),
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
def _get_shader_code(self) -> str:
|
|
71
|
+
"""Get the WGSL shader code for this pipeline."""
|
|
72
|
+
return POINT_SHADER_MULTI_COLOURED
|
|
73
|
+
|
|
74
|
+
def _get_vertex_buffer_layouts(self):
|
|
75
|
+
"""Get vertex buffer layout configurations for the pipeline."""
|
|
76
|
+
return self._get_default_vertex_layouts(has_colour_buffer=True)
|
|
77
|
+
|
|
78
|
+
def _set_default_uniforms(self) -> None:
|
|
79
|
+
"""Set default values for uniform data."""
|
|
80
|
+
self.uniform_data["size"] = 1.0 # Default point size
|
|
81
|
+
self.uniform_data["ViewMatrix"] = np.eye(4, dtype=np.float32)
|
|
82
|
+
|
|
83
|
+
def _get_pipeline_label(self) -> str:
|
|
84
|
+
"""Get the label for the pipeline."""
|
|
85
|
+
return "point_pipeline_multi_coloured"
|
|
86
|
+
|
|
87
|
+
def set_data(self, positions, colours=None) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Set the point data for rendering.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
positions: Nx2 array of point positions or a pre-existing GPUBuffer.
|
|
93
|
+
colours: Nx3 array of point colours (RGB) or a pre-existing GPUBuffer.
|
|
94
|
+
If None, uses white.
|
|
95
|
+
"""
|
|
96
|
+
# Handle positions
|
|
97
|
+
if isinstance(positions, wgpu.GPUBuffer):
|
|
98
|
+
self.position_buffer = positions
|
|
99
|
+
self.num_points = positions.size // self._stride
|
|
100
|
+
else: # numpy array
|
|
101
|
+
self.num_points = len(positions)
|
|
102
|
+
self.position_buffer, _ = self._create_or_update_buffer(
|
|
103
|
+
self.position_buffer,
|
|
104
|
+
positions,
|
|
105
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
106
|
+
"point_pipeline_multi_coloured_position_buffer",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Handle colours
|
|
110
|
+
if colours is None:
|
|
111
|
+
# Create default white colours
|
|
112
|
+
default_colours = np.ones((self.num_points, 3), dtype=np.float32)
|
|
113
|
+
colour_result = self._process_vertex_data(
|
|
114
|
+
None,
|
|
115
|
+
default_colours,
|
|
116
|
+
padding_size=4, # Pad to vec4 for alignment
|
|
117
|
+
buffer_label="point_pipeline_multi_coloured_colour_buffer",
|
|
118
|
+
)
|
|
119
|
+
if isinstance(colour_result, wgpu.GPUBuffer):
|
|
120
|
+
self.colour_buffer = colour_result
|
|
121
|
+
elif colour_result:
|
|
122
|
+
self.colour_buffer = colour_result[0]
|
|
123
|
+
else:
|
|
124
|
+
self.colour_buffer = None
|
|
125
|
+
else:
|
|
126
|
+
colour_result = self._process_vertex_data(
|
|
127
|
+
colours,
|
|
128
|
+
None,
|
|
129
|
+
padding_size=4, # Pad to vec4 for alignment
|
|
130
|
+
buffer_label="point_pipeline_multi_coloured_colour_buffer",
|
|
131
|
+
)
|
|
132
|
+
if isinstance(colour_result, wgpu.GPUBuffer):
|
|
133
|
+
self.colour_buffer = colour_result
|
|
134
|
+
elif colour_result:
|
|
135
|
+
self.colour_buffer = colour_result[0]
|
|
136
|
+
else:
|
|
137
|
+
self.colour_buffer = None
|
|
138
|
+
|
|
139
|
+
def update_uniforms(self, **kwargs) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Update uniform buffer values.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
**kwargs: Pipeline-specific uniform parameters
|
|
145
|
+
- mvp: 4x4 model view projection matrix
|
|
146
|
+
- view_matrix: 4x4 view matrix for billboarding calculations
|
|
147
|
+
- point_size: Size of points in world units
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
if "mvp" in kwargs and kwargs["mvp"] is not None:
|
|
151
|
+
self.uniform_data["MVP"] = kwargs["mvp"]
|
|
152
|
+
|
|
153
|
+
if "view_matrix" in kwargs and kwargs["view_matrix"] is not None:
|
|
154
|
+
self.uniform_data["ViewMatrix"] = kwargs["view_matrix"]
|
|
155
|
+
|
|
156
|
+
if "point_size" in kwargs and kwargs["point_size"] is not None:
|
|
157
|
+
self.uniform_data["size"] = kwargs["point_size"]
|
|
158
|
+
|
|
159
|
+
self.device.queue.write_buffer(self.uniform_buffer, 0, self.uniform_data.tobytes())
|
|
160
|
+
|
|
161
|
+
def render(self, render_pass: wgpu.GPURenderPassEncoder, **kwargs) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Render the points.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
render_pass: Active render pass encoder
|
|
167
|
+
**kwargs: Pipeline-specific render parameters
|
|
168
|
+
- num_points: Number of points to render (defaults to all)
|
|
169
|
+
"""
|
|
170
|
+
num_points = kwargs.get("num_points", None)
|
|
171
|
+
|
|
172
|
+
if self.position_buffer is None or self.colour_buffer is None:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
count = num_points if num_points is not None else self.num_points
|
|
176
|
+
|
|
177
|
+
render_pass.set_pipeline(self.pipeline)
|
|
178
|
+
if self.bind_group:
|
|
179
|
+
render_pass.set_bind_group(0, self.bind_group, [], 0, 999999)
|
|
180
|
+
render_pass.set_vertex_buffer(0, self.position_buffer)
|
|
181
|
+
render_pass.set_vertex_buffer(1, self.colour_buffer)
|
|
182
|
+
render_pass.draw(4, count) # 4 vertices per quad, instanced
|
|
183
|
+
|
|
184
|
+
def cleanup(self) -> None:
|
|
185
|
+
"""Release resources."""
|
|
186
|
+
if self.position_buffer:
|
|
187
|
+
self.position_buffer.destroy()
|
|
188
|
+
if self.colour_buffer:
|
|
189
|
+
self.colour_buffer.destroy()
|
|
190
|
+
super().cleanup()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class PointPipelineSingleColour(BasePointPipeline):
|
|
194
|
+
"""
|
|
195
|
+
A reusable pipeline for rendering points in WebGPU.
|
|
196
|
+
|
|
197
|
+
Features:
|
|
198
|
+
- Instanced rendering of points as quads
|
|
199
|
+
- Single colour for all points
|
|
200
|
+
- Configurable point size
|
|
201
|
+
- Model, View Projection matrix support pass a projection only for 2D
|
|
202
|
+
- MSAA support
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
device: wgpu.GPUDevice,
|
|
208
|
+
data_type: str = "Vec3",
|
|
209
|
+
texture_format: wgpu.TextureFormat = wgpu.TextureFormat.rgba8unorm,
|
|
210
|
+
depth_format: wgpu.TextureFormat = wgpu.TextureFormat.depth24plus,
|
|
211
|
+
msaa_sample_count: int = 4,
|
|
212
|
+
stride: int = 0,
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Initialize the point rendering pipeline.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
device: WebGPU device
|
|
219
|
+
texture_format: colour attachment format
|
|
220
|
+
depth_format: Depth attachment format
|
|
221
|
+
msaa_sample_count: Number of MSAA samples
|
|
222
|
+
stride: The stride of the vertex buffer. If 0, it is inferred from data_type.
|
|
223
|
+
"""
|
|
224
|
+
# Pipeline-specific buffer tracking
|
|
225
|
+
self.position_buffer: Optional[wgpu.GPUBuffer] = None
|
|
226
|
+
self.num_points: int = 0
|
|
227
|
+
|
|
228
|
+
super().__init__(
|
|
229
|
+
device=device,
|
|
230
|
+
texture_format=texture_format,
|
|
231
|
+
depth_format=depth_format,
|
|
232
|
+
msaa_sample_count=msaa_sample_count,
|
|
233
|
+
data_type=data_type,
|
|
234
|
+
stride=stride,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def get_dtype(self) -> np.dtype:
|
|
238
|
+
"""Get the data type of the pipeline."""
|
|
239
|
+
return np.dtype([
|
|
240
|
+
("MVP", "float32", (4, 4)),
|
|
241
|
+
("ViewMatrix", "float32", (4, 4)),
|
|
242
|
+
("ColourSize", "float32", 4),
|
|
243
|
+
])
|
|
244
|
+
|
|
245
|
+
def _get_shader_code(self) -> str:
|
|
246
|
+
"""Get the WGSL shader code for this pipeline."""
|
|
247
|
+
return POINT_SHADER_SINGLE_COLOUR
|
|
248
|
+
|
|
249
|
+
def _get_vertex_buffer_layouts(self):
|
|
250
|
+
"""Get vertex buffer layout configurations for the pipeline."""
|
|
251
|
+
return self._get_default_vertex_layouts(has_colour_buffer=False)
|
|
252
|
+
|
|
253
|
+
def _set_default_uniforms(self) -> None:
|
|
254
|
+
"""Set default values for uniform data."""
|
|
255
|
+
self.uniform_data["ColourSize"] = np.array(
|
|
256
|
+
[1.0, 1.0, 1.0, 1.0], dtype=np.float32
|
|
257
|
+
) # Default White with point size 1
|
|
258
|
+
self.uniform_data["ViewMatrix"] = np.eye(4, dtype=np.float32)
|
|
259
|
+
|
|
260
|
+
def _get_pipeline_label(self) -> str:
|
|
261
|
+
"""Get the label for the pipeline."""
|
|
262
|
+
return "point_pipeline_single_colour"
|
|
263
|
+
|
|
264
|
+
def set_data(self, positions, colours=None) -> None:
|
|
265
|
+
"""
|
|
266
|
+
Set the point data for rendering.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
positions: Nx2 array of point positions or a pre-existing GPUBuffer.
|
|
270
|
+
colours: Ignored for single colour pipeline
|
|
271
|
+
"""
|
|
272
|
+
# Handle positions
|
|
273
|
+
if isinstance(positions, wgpu.GPUBuffer):
|
|
274
|
+
self.position_buffer = positions
|
|
275
|
+
self.num_points = positions.size // self._stride
|
|
276
|
+
else: # numpy array
|
|
277
|
+
self.num_points = len(positions)
|
|
278
|
+
self.position_buffer, _ = self._create_or_update_buffer(
|
|
279
|
+
self.position_buffer,
|
|
280
|
+
positions,
|
|
281
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
282
|
+
"point_pipeline_single_colour_position_buffer",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def update_uniforms(self, **kwargs) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Update uniform buffer values.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
**kwargs: Pipeline-specific uniform parameters
|
|
291
|
+
- mvp: 4x4 model view projection matrix
|
|
292
|
+
- view_matrix: 4x4 view matrix for billboarding calculations
|
|
293
|
+
- colour: 3-element array of RGB colour values
|
|
294
|
+
- point_size: Size of points in world units
|
|
295
|
+
"""
|
|
296
|
+
if "mvp" in kwargs and kwargs["mvp"] is not None:
|
|
297
|
+
self.uniform_data["MVP"] = kwargs["mvp"]
|
|
298
|
+
|
|
299
|
+
if "view_matrix" in kwargs and kwargs["view_matrix"] is not None:
|
|
300
|
+
self.uniform_data["ViewMatrix"] = kwargs["view_matrix"]
|
|
301
|
+
|
|
302
|
+
if "colour" in kwargs and kwargs["colour"] is not None:
|
|
303
|
+
self.uniform_data["ColourSize"][:3] = kwargs["colour"]
|
|
304
|
+
|
|
305
|
+
if "point_size" in kwargs and kwargs["point_size"] is not None:
|
|
306
|
+
self.uniform_data["ColourSize"][3] = kwargs["point_size"]
|
|
307
|
+
|
|
308
|
+
self.device.queue.write_buffer(self.uniform_buffer, 0, self.uniform_data.tobytes())
|
|
309
|
+
|
|
310
|
+
def render(self, render_pass: wgpu.GPURenderPassEncoder, **kwargs) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Render the points.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
render_pass: Active render pass encoder
|
|
316
|
+
**kwargs: Pipeline-specific render parameters
|
|
317
|
+
- num_points: Number of points to render (defaults to all)
|
|
318
|
+
"""
|
|
319
|
+
num_points = kwargs.get("num_points", None)
|
|
320
|
+
|
|
321
|
+
if self.position_buffer is None:
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
count = num_points if num_points is not None else self.num_points
|
|
325
|
+
|
|
326
|
+
render_pass.set_pipeline(self.pipeline)
|
|
327
|
+
if self.bind_group:
|
|
328
|
+
render_pass.set_bind_group(0, self.bind_group, [], 0, 999999)
|
|
329
|
+
render_pass.set_vertex_buffer(0, self.position_buffer)
|
|
330
|
+
render_pass.draw(4, count) # 4 vertices per quad, instanced
|
|
331
|
+
|
|
332
|
+
def cleanup(self) -> None:
|
|
333
|
+
"""Release resources."""
|
|
334
|
+
if self.position_buffer:
|
|
335
|
+
self.position_buffer.destroy()
|
|
336
|
+
super().cleanup()
|