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,640 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run --active --script
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from PySide6.QtCore import Qt, QTimer
|
|
8
|
+
from PySide6.QtGui import QColor
|
|
9
|
+
from PySide6.QtWidgets import QApplication
|
|
10
|
+
from wgpu.utils import get_default_device
|
|
11
|
+
|
|
12
|
+
from ncca.ngl import Mat4, PerspMode, PrimData, Vec3, look_at, perspective
|
|
13
|
+
from ncca.ngl.webgpu import PipelineFactory, PipelineType, WebGPUWidget, __version__
|
|
14
|
+
|
|
15
|
+
NUM_POINTS = 10000
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WebGPUScene(WebGPUWidget):
|
|
19
|
+
"""
|
|
20
|
+
A concrete implementation of NumpyBufferWidget for a WebGPU scene.
|
|
21
|
+
|
|
22
|
+
This class implements the abstract methods to provide functionality for initializing,
|
|
23
|
+
painting, and resizing the WebGPU context.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
background_colour: Tuple[float, float, float, float] = (0.4, 0.4, 0.4, 1.0),
|
|
29
|
+
):
|
|
30
|
+
super().__init__(background_colour=background_colour)
|
|
31
|
+
self.setWindowTitle("WebGPU Pipeline Demo - Dynamic Background Colors")
|
|
32
|
+
self.device = None
|
|
33
|
+
self.pipeline = None
|
|
34
|
+
self.vertex_buffer = None
|
|
35
|
+
self.msaa_sample_count = 4
|
|
36
|
+
self.rotation = 0.0
|
|
37
|
+
self.view = look_at(Vec3(0, 2, 14), Vec3(0, 0, 0), Vec3(0, 1, 0))
|
|
38
|
+
self.animate = True
|
|
39
|
+
self.project = Mat4()
|
|
40
|
+
self._initialize_web_gpu()
|
|
41
|
+
self.update()
|
|
42
|
+
|
|
43
|
+
def _initialize_web_gpu(self) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Initialize the WebGPU context.
|
|
46
|
+
|
|
47
|
+
This method sets up the WebGPU context for the scene.
|
|
48
|
+
"""
|
|
49
|
+
print("initializeWebGPU")
|
|
50
|
+
try:
|
|
51
|
+
self.device = get_default_device()
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Failed to initialize WebGPU: {e}")
|
|
54
|
+
return
|
|
55
|
+
self._create_buffers(NUM_POINTS)
|
|
56
|
+
|
|
57
|
+
self.pipelines = []
|
|
58
|
+
self.pipeline_backgrounds = {} # Store background colors for each pipeline
|
|
59
|
+
|
|
60
|
+
# Define subtle background colors for each pipeline type
|
|
61
|
+
pipeline_colors = {
|
|
62
|
+
"MULTI_COLOURED_POINTS": (0.1, 0.1, 0.3, 1.0), # Dark blue
|
|
63
|
+
"SINGLE_COLOUR_POINTS": (0.3, 0.1, 0.1, 1.0), # Dark red
|
|
64
|
+
"MULTI_COLOURED_LINES": (0.1, 0.3, 0.1, 1.0), # Dark green
|
|
65
|
+
"SINGLE_COLOUR_LINES": (0.3, 0.3, 0.1, 1.0), # Dark yellow
|
|
66
|
+
"MULTI_COLOURED_TRIANGLES": (0.3, 0.1, 0.3, 1.0), # Dark magenta
|
|
67
|
+
"SINGLE_COLOUR_TRIANGLES": (0.1, 0.3, 0.3, 1.0), # Dark cyan
|
|
68
|
+
"TRIANGLE_LIST_MULTI_COLOURED": (0.2, 0.1, 0.4, 1.0), # Dark purple
|
|
69
|
+
"TRIANGLE_LIST_SINGLE_COLOUR": (0.4, 0.2, 0.1, 1.0), # Dark orange
|
|
70
|
+
"TRIANGLE_STRIP_MULTI_COLOURED": (0.1, 0.4, 0.2, 1.0), # Dark teal
|
|
71
|
+
"TRIANGLE_STRIP_SINGLE_COLOUR": (0.2, 0.2, 0.2, 1.0), # Dark gray
|
|
72
|
+
"POINT_LIST_MULTI_COLOURED": (0.4, 0.1, 0.2, 1.0), # Dark pink
|
|
73
|
+
"POINT_LIST_SINGLE_COLOUR": (0.1, 0.2, 0.4, 1.0), # Dark indigo
|
|
74
|
+
"MULTI_COLOURED_INSTANCED_GEOMETRY": (0.3, 0.15, 0.05, 1.0), # Dark gold
|
|
75
|
+
"SINGLE_COLOUR_INSTANCED_GEOMETRY": (0.05, 0.15, 0.3, 1.0), # Dark navy
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
self.pipelines.append(
|
|
79
|
+
(
|
|
80
|
+
PipelineFactory.create_pipeline(
|
|
81
|
+
self.device, PipelineType.MULTI_COLOURED_POINTS
|
|
82
|
+
),
|
|
83
|
+
self._render_multi_colour_point_pipeline,
|
|
84
|
+
"PipelineType.MULTI_COLOURED_POINTS",
|
|
85
|
+
pipeline_colors["MULTI_COLOURED_POINTS"],
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
self.pipelines.append(
|
|
89
|
+
(
|
|
90
|
+
PipelineFactory.create_pipeline(
|
|
91
|
+
self.device, PipelineType.SINGLE_COLOUR_POINTS
|
|
92
|
+
),
|
|
93
|
+
self._render_single_colour_point_pipeline,
|
|
94
|
+
"PipelineType.SINGLE_COLOUR_POINTS",
|
|
95
|
+
pipeline_colors["SINGLE_COLOUR_POINTS"],
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
self.pipelines.append(
|
|
99
|
+
(
|
|
100
|
+
PipelineFactory.create_pipeline(
|
|
101
|
+
self.device, PipelineType.MULTI_COLOURED_LINES
|
|
102
|
+
),
|
|
103
|
+
self._render_multi_colour_line_pipeline,
|
|
104
|
+
"PipelineType.MULTI_COLOURED_LINES",
|
|
105
|
+
pipeline_colors["MULTI_COLOURED_LINES"],
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
self.pipelines.append(
|
|
109
|
+
(
|
|
110
|
+
PipelineFactory.create_pipeline(
|
|
111
|
+
self.device, PipelineType.SINGLE_COLOUR_LINES
|
|
112
|
+
),
|
|
113
|
+
self._render_single_colour_line_pipeline,
|
|
114
|
+
"PipelineType.SINGLE_COLOUR_LINES",
|
|
115
|
+
pipeline_colors["SINGLE_COLOUR_LINES"],
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
self.pipelines.append(
|
|
119
|
+
(
|
|
120
|
+
PipelineFactory.create_pipeline(
|
|
121
|
+
self.device, PipelineType.MULTI_COLOURED_TRIANGLES
|
|
122
|
+
),
|
|
123
|
+
self._render_multi_colour_triangle_pipeline,
|
|
124
|
+
"PipelineType.MULTI_COLOURED_TRIANGLES",
|
|
125
|
+
pipeline_colors["MULTI_COLOURED_TRIANGLES"],
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
self.pipelines.append(
|
|
129
|
+
(
|
|
130
|
+
PipelineFactory.create_pipeline(
|
|
131
|
+
self.device, PipelineType.SINGLE_COLOUR_TRIANGLES
|
|
132
|
+
),
|
|
133
|
+
self._render_single_colour_triangle_pipeline,
|
|
134
|
+
"PipelineType.SINGLE_COLOUR_TRIANGLES",
|
|
135
|
+
pipeline_colors["SINGLE_COLOUR_TRIANGLES"],
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
self.pipelines.append(
|
|
139
|
+
(
|
|
140
|
+
PipelineFactory.create_pipeline(
|
|
141
|
+
self.device, PipelineType.TRIANGLE_LIST_MULTI_COLOURED
|
|
142
|
+
),
|
|
143
|
+
self._render_multi_colour_triangle_pipeline,
|
|
144
|
+
"PipelineType.TRIANGLE_LIST_MULTI_COLOURED",
|
|
145
|
+
pipeline_colors["TRIANGLE_LIST_MULTI_COLOURED"],
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
self.pipelines.append(
|
|
149
|
+
(
|
|
150
|
+
PipelineFactory.create_pipeline(
|
|
151
|
+
self.device, PipelineType.TRIANGLE_LIST_SINGLE_COLOUR
|
|
152
|
+
),
|
|
153
|
+
self._render_triangle_list_single_colour_pipeline,
|
|
154
|
+
"PipelineType.TRIANGLE_LIST_SINGLE_COLOUR",
|
|
155
|
+
pipeline_colors["TRIANGLE_LIST_SINGLE_COLOUR"],
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
self.pipelines.append(
|
|
159
|
+
(
|
|
160
|
+
PipelineFactory.create_pipeline(
|
|
161
|
+
self.device, PipelineType.TRIANGLE_STRIP_MULTI_COLOURED
|
|
162
|
+
),
|
|
163
|
+
self._render_triangle_strip_multi_colour_pipeline,
|
|
164
|
+
"PipelineType.TRIANGLE_STRIP_MULTI_COLOURED",
|
|
165
|
+
pipeline_colors["TRIANGLE_STRIP_MULTI_COLOURED"],
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
self.pipelines.append(
|
|
169
|
+
(
|
|
170
|
+
PipelineFactory.create_pipeline(
|
|
171
|
+
self.device, PipelineType.TRIANGLE_STRIP_SINGLE_COLOUR
|
|
172
|
+
),
|
|
173
|
+
self._render_triangle_strip_single_colour_pipeline,
|
|
174
|
+
"PipelineType.TRIANGLE_STRIP_SINGLE_COLOUR",
|
|
175
|
+
pipeline_colors["TRIANGLE_STRIP_SINGLE_COLOUR"],
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
self.pipelines.append(
|
|
179
|
+
(
|
|
180
|
+
PipelineFactory.create_pipeline(
|
|
181
|
+
self.device, PipelineType.POINT_LIST_MULTI_COLOURED
|
|
182
|
+
),
|
|
183
|
+
self._render_point_list_multi_colour_pipeline,
|
|
184
|
+
"PipelineType.POINT_LIST_MULTI_COLOURED",
|
|
185
|
+
pipeline_colors["POINT_LIST_MULTI_COLOURED"],
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
self.pipelines.append(
|
|
189
|
+
(
|
|
190
|
+
PipelineFactory.create_pipeline(
|
|
191
|
+
self.device, PipelineType.POINT_LIST_SINGLE_COLOUR
|
|
192
|
+
),
|
|
193
|
+
self._render_point_list_single_colour_pipeline,
|
|
194
|
+
"PipelineType.POINT_LIST_SINGLE_COLOUR",
|
|
195
|
+
pipeline_colors["POINT_LIST_SINGLE_COLOUR"],
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Add instanced geometry pipelines
|
|
200
|
+
self.pipelines.append(
|
|
201
|
+
(
|
|
202
|
+
PipelineFactory.create_pipeline(
|
|
203
|
+
self.device, PipelineType.MULTI_COLOURED_INSTANCED_GEOMETRY
|
|
204
|
+
),
|
|
205
|
+
self._render_multi_colour_instanced_geometry_pipeline,
|
|
206
|
+
"PipelineType.MULTI_COLOURED_INSTANCED_GEOMETRY",
|
|
207
|
+
pipeline_colors["MULTI_COLOURED_INSTANCED_GEOMETRY"],
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
self.pipelines.append(
|
|
211
|
+
(
|
|
212
|
+
PipelineFactory.create_pipeline(
|
|
213
|
+
self.device, PipelineType.SINGLE_COLOUR_INSTANCED_GEOMETRY
|
|
214
|
+
),
|
|
215
|
+
self._render_single_colour_instanced_geometry_pipeline,
|
|
216
|
+
"PipelineType.SINGLE_COLOUR_INSTANCED_GEOMETRY",
|
|
217
|
+
pipeline_colors["SINGLE_COLOUR_INSTANCED_GEOMETRY"],
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self.current_pipeline_index = 1
|
|
222
|
+
# Initialize render textures with default size
|
|
223
|
+
self.texture_size = (1024, 720)
|
|
224
|
+
self._create_render_buffer()
|
|
225
|
+
|
|
226
|
+
# Setup timers
|
|
227
|
+
self.startTimer(16) # Animation timer (~60 FPS)
|
|
228
|
+
self.pipeline_timer = QTimer()
|
|
229
|
+
self.pipeline_timer.timeout.connect(self.switch_pipeline)
|
|
230
|
+
self.pipeline_timer.start(1000) # Switch every 5 seconds
|
|
231
|
+
|
|
232
|
+
def _create_buffers(self, num_points):
|
|
233
|
+
rng = np.random.default_rng(int(time.time()))
|
|
234
|
+
self.colours = rng.random((num_points, 3)).astype(np.float32)
|
|
235
|
+
# Create 3D positions for line rendering with Z elevation
|
|
236
|
+
self.positions = rng.uniform(-4.0, 4.0, size=(num_points, 3)).astype(np.float32)
|
|
237
|
+
# For line rendering, we can use the full 3D positions or just X,Y depending on desired effect
|
|
238
|
+
self.positions_2d = self.positions[
|
|
239
|
+
:, :2
|
|
240
|
+
] # Take only X,Y components for 2D line effects
|
|
241
|
+
|
|
242
|
+
# Create triangle data for triangle list
|
|
243
|
+
num_triangles = 100
|
|
244
|
+
self.triangle_positions = np.zeros((num_triangles * 3, 3), dtype=np.float32)
|
|
245
|
+
self.triangle_colours = rng.random((num_triangles * 3, 3)).astype(np.float32)
|
|
246
|
+
|
|
247
|
+
for i in range(num_triangles):
|
|
248
|
+
# Create a triangle around a random center point in 3D space
|
|
249
|
+
center = rng.uniform(-3.0, 3.0, 3)
|
|
250
|
+
radius = rng.uniform(0.2, 0.8)
|
|
251
|
+
|
|
252
|
+
# Generate random triangle vertices in 3D using spherical coordinates
|
|
253
|
+
# First vertex
|
|
254
|
+
theta1 = rng.uniform(0, 2 * np.pi) # azimuth
|
|
255
|
+
phi1 = rng.uniform(0, np.pi) # polar
|
|
256
|
+
vertex1 = radius * np.array(
|
|
257
|
+
[
|
|
258
|
+
np.sin(phi1) * np.cos(theta1),
|
|
259
|
+
np.sin(phi1) * np.sin(theta1),
|
|
260
|
+
np.cos(phi1),
|
|
261
|
+
]
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Second vertex (different orientation)
|
|
265
|
+
theta2 = theta1 + rng.uniform(2.0, 3.0)
|
|
266
|
+
phi2 = rng.uniform(0, np.pi)
|
|
267
|
+
vertex2 = radius * np.array(
|
|
268
|
+
[
|
|
269
|
+
np.sin(phi2) * np.cos(theta2),
|
|
270
|
+
np.sin(phi2) * np.sin(theta2),
|
|
271
|
+
np.cos(phi2),
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Third vertex (completes the triangle)
|
|
276
|
+
theta3 = theta2 + rng.uniform(2.0, 3.0)
|
|
277
|
+
phi3 = rng.uniform(0, np.pi)
|
|
278
|
+
vertex3 = radius * np.array(
|
|
279
|
+
[
|
|
280
|
+
np.sin(phi3) * np.cos(theta3),
|
|
281
|
+
np.sin(phi3) * np.sin(theta3),
|
|
282
|
+
np.cos(phi3),
|
|
283
|
+
]
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
self.triangle_positions[i * 3] = center + vertex1
|
|
287
|
+
self.triangle_positions[i * 3 + 1] = center + vertex2
|
|
288
|
+
self.triangle_positions[i * 3 + 2] = center + vertex3
|
|
289
|
+
|
|
290
|
+
# Create triangle strip data with 3D helix/spring pattern
|
|
291
|
+
strip_length = 50
|
|
292
|
+
self.triangle_strip_positions = np.zeros((strip_length, 3), dtype=np.float32)
|
|
293
|
+
self.triangle_strip_colours = rng.random((strip_length, 3)).astype(np.float32)
|
|
294
|
+
|
|
295
|
+
for i in range(strip_length):
|
|
296
|
+
t = i / (strip_length - 1)
|
|
297
|
+
# Create a 3D helix/spring pattern for triangle strip
|
|
298
|
+
angle = t * 4 * np.pi # Multiple rotations
|
|
299
|
+
radius = 2.0 + 0.5 * np.sin(angle * 2) # Varying radius
|
|
300
|
+
|
|
301
|
+
# Alternate between upper and lower points of strip
|
|
302
|
+
if i % 2 == 0:
|
|
303
|
+
# Upper points
|
|
304
|
+
self.triangle_strip_positions[i] = [
|
|
305
|
+
radius * np.cos(angle), # X
|
|
306
|
+
radius * np.sin(angle) + 1.0, # Y (offset up)
|
|
307
|
+
-2 + t * 4, # Z (forward progression)
|
|
308
|
+
]
|
|
309
|
+
else:
|
|
310
|
+
# Lower points
|
|
311
|
+
self.triangle_strip_positions[i] = [
|
|
312
|
+
radius * np.cos(angle), # X
|
|
313
|
+
radius * np.sin(angle) - 1.0, # Y (offset down)
|
|
314
|
+
-2 + t * 4, # Z (forward progression)
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
# Create instanced geometry data
|
|
318
|
+
self._create_instanced_geometry_data(rng)
|
|
319
|
+
|
|
320
|
+
def _create_instanced_geometry_data(self, rng):
|
|
321
|
+
"""Create data for instanced geometry rendering."""
|
|
322
|
+
# Create geometry using PrimData (in correct interleaved format)
|
|
323
|
+
geometry_data = PrimData.primitive("teapot")
|
|
324
|
+
|
|
325
|
+
# Ensure data is in (num_vertices, 8) format for new API
|
|
326
|
+
if geometry_data.ndim == 1:
|
|
327
|
+
geometry_data = geometry_data.reshape(-1, 8)
|
|
328
|
+
elif geometry_data.shape[1] != 8:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
f"Expected 8 components per vertex, got {geometry_data.shape[1]}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Store the complete interleaved geometry data (x,y,z,nx,ny,nz,u,v)
|
|
334
|
+
self.geometry_data = geometry_data
|
|
335
|
+
|
|
336
|
+
# Create instance positions (grid of geometry instances)
|
|
337
|
+
grid_size = 15
|
|
338
|
+
self.instance_positions = []
|
|
339
|
+
self.instance_colours = []
|
|
340
|
+
|
|
341
|
+
for i in range(grid_size):
|
|
342
|
+
for j in range(grid_size):
|
|
343
|
+
x = (i - grid_size / 2 + 0.5) * 1.2
|
|
344
|
+
y = (j - grid_size / 2 + 0.5) * 1.2
|
|
345
|
+
z = rng.uniform(-grid_size / 2, grid_size / 2)
|
|
346
|
+
self.instance_positions.append([x, y, z])
|
|
347
|
+
|
|
348
|
+
self.instance_colours.append(rng.random((1, 3)).astype(np.float32))
|
|
349
|
+
|
|
350
|
+
self.instance_positions = np.array(self.instance_positions, dtype=np.float32)
|
|
351
|
+
self.instance_colours = np.array(self.instance_colours, dtype=np.float32)
|
|
352
|
+
|
|
353
|
+
def _create_render_buffer(self):
|
|
354
|
+
"""Delegate to parent class method."""
|
|
355
|
+
super()._create_render_buffer()
|
|
356
|
+
|
|
357
|
+
def paintWebGPU(self) -> None:
|
|
358
|
+
"""
|
|
359
|
+
Paint WebGPU content.
|
|
360
|
+
|
|
361
|
+
This method renders WebGPU content for the scene.
|
|
362
|
+
"""
|
|
363
|
+
# Get the current pipeline and its background color
|
|
364
|
+
current_pipeline = self.pipelines[self.current_pipeline_index]
|
|
365
|
+
pipeline_name = current_pipeline[2]
|
|
366
|
+
pipeline_bg_color = current_pipeline[3]
|
|
367
|
+
|
|
368
|
+
# Temporarily update the background color for this pipeline
|
|
369
|
+
original_bg_color = self.background_colour
|
|
370
|
+
self.background_colour = pipeline_bg_color
|
|
371
|
+
|
|
372
|
+
self.render_text(
|
|
373
|
+
10,
|
|
374
|
+
20,
|
|
375
|
+
f"{pipeline_name}",
|
|
376
|
+
size=20,
|
|
377
|
+
colour=QColor(255, 255, 255), # White text for better contrast
|
|
378
|
+
)
|
|
379
|
+
try:
|
|
380
|
+
# Create a new command encoder for the render pass
|
|
381
|
+
command_encoder = self.device.create_command_encoder()
|
|
382
|
+
render_pass = self._create_render_pass(command_encoder)
|
|
383
|
+
self.update_uniform_buffers()
|
|
384
|
+
render_pass.set_viewport(
|
|
385
|
+
0, 0, self.texture_size[0], self.texture_size[1], 0, 1
|
|
386
|
+
)
|
|
387
|
+
self.pipelines[self.current_pipeline_index][1](render_pass)
|
|
388
|
+
render_pass.end()
|
|
389
|
+
self.device.queue.submit([command_encoder.finish()])
|
|
390
|
+
except Exception as e:
|
|
391
|
+
print(f"Failed to paint WebGPU content: {e}")
|
|
392
|
+
finally:
|
|
393
|
+
# Restore original background color
|
|
394
|
+
self.background_colour = original_bg_color
|
|
395
|
+
|
|
396
|
+
def resizeWebGPU(self, w, h) -> None:
|
|
397
|
+
"""
|
|
398
|
+
Called whenever the window is resized.
|
|
399
|
+
It's crucial to update the viewport and projection matrix here.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
w: The new width of the window.
|
|
403
|
+
h: The new height of the window.
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
# Update texture size to match window dimensions
|
|
407
|
+
self.texture_size = (w, h)
|
|
408
|
+
|
|
409
|
+
# Update projection matrix
|
|
410
|
+
self.project = perspective(
|
|
411
|
+
45.0, w / h if h > 0 else 1, 0.1, 100.0, PerspMode.WebGPU
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
self.update()
|
|
415
|
+
|
|
416
|
+
def _render_pipeline(
|
|
417
|
+
self, render_pass, positions, colours=None, point_size=None, colour=None
|
|
418
|
+
):
|
|
419
|
+
"""Generic pipeline rendering method to eliminate code duplication."""
|
|
420
|
+
pipeline = self.pipelines[self.current_pipeline_index][0]
|
|
421
|
+
|
|
422
|
+
# Set data based on whether we have colours
|
|
423
|
+
if colours is not None:
|
|
424
|
+
pipeline.set_data(positions, colours)
|
|
425
|
+
else:
|
|
426
|
+
pipeline.set_data(positions)
|
|
427
|
+
|
|
428
|
+
pipeline.render(render_pass)
|
|
429
|
+
|
|
430
|
+
# Update uniforms based on what's provided
|
|
431
|
+
if point_size is not None:
|
|
432
|
+
if colours is not None:
|
|
433
|
+
pipeline.update_uniforms(
|
|
434
|
+
mvp=self.mvp_matrix,
|
|
435
|
+
view_matrix=self.view_matrix,
|
|
436
|
+
point_size=point_size,
|
|
437
|
+
)
|
|
438
|
+
else:
|
|
439
|
+
pipeline.update_uniforms(
|
|
440
|
+
mvp=self.mvp_matrix,
|
|
441
|
+
view_matrix=self.view_matrix,
|
|
442
|
+
colour=colour
|
|
443
|
+
if colour is not None
|
|
444
|
+
else np.array([1, 1, 1], dtype=np.float32),
|
|
445
|
+
point_size=point_size,
|
|
446
|
+
)
|
|
447
|
+
elif colour is not None:
|
|
448
|
+
# Handle color for single-colour pipelines without point_size
|
|
449
|
+
pipeline.update_uniforms(
|
|
450
|
+
mvp=self.mvp_matrix,
|
|
451
|
+
colour=colour,
|
|
452
|
+
)
|
|
453
|
+
else:
|
|
454
|
+
pipeline.update_uniforms(mvp=self.mvp_matrix)
|
|
455
|
+
|
|
456
|
+
def _render_multi_colour_point_pipeline(self, render_pass):
|
|
457
|
+
self._render_pipeline(
|
|
458
|
+
render_pass, self.positions, colours=self.colours, point_size=0.05
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
def _render_single_colour_point_pipeline(self, render_pass):
|
|
462
|
+
# Use bright yellow for single colour points
|
|
463
|
+
yellow_color = np.array([1.0, 1.0, 0.0], dtype=np.float32)
|
|
464
|
+
self._render_pipeline(
|
|
465
|
+
render_pass, self.positions, point_size=0.05, colour=yellow_color
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
def _render_multi_colour_line_pipeline(self, render_pass):
|
|
469
|
+
self._render_pipeline(render_pass, self.positions_2d, colours=self.colours)
|
|
470
|
+
|
|
471
|
+
def _render_single_colour_line_pipeline(self, render_pass):
|
|
472
|
+
# Use bright magenta for single colour lines
|
|
473
|
+
magenta_color = np.array([1.0, 0.0, 1.0], dtype=np.float32)
|
|
474
|
+
self._render_pipeline(render_pass, self.positions_2d, colour=magenta_color)
|
|
475
|
+
|
|
476
|
+
def _render_multi_colour_triangle_pipeline(self, render_pass):
|
|
477
|
+
self._render_pipeline(
|
|
478
|
+
render_pass, self.triangle_positions, colours=self.triangle_colours
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def _render_single_colour_triangle_pipeline(self, render_pass):
|
|
482
|
+
# Use a bright orange color for single colour triangles
|
|
483
|
+
orange_color = np.array([1.0, 0.5, 0.0], dtype=np.float32)
|
|
484
|
+
self._render_pipeline(render_pass, self.triangle_positions, colour=orange_color)
|
|
485
|
+
|
|
486
|
+
def _render_triangle_list_single_colour_pipeline(self, render_pass):
|
|
487
|
+
# Use a bright purple color for triangle list single colour
|
|
488
|
+
purple_color = np.array([0.8, 0.2, 0.8], dtype=np.float32)
|
|
489
|
+
self._render_pipeline(render_pass, self.triangle_positions, colour=purple_color)
|
|
490
|
+
|
|
491
|
+
def _render_triangle_strip_multi_colour_pipeline(self, render_pass):
|
|
492
|
+
self._render_pipeline(
|
|
493
|
+
render_pass,
|
|
494
|
+
self.triangle_strip_positions,
|
|
495
|
+
colours=self.triangle_strip_colours,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def _render_triangle_strip_single_colour_pipeline(self, render_pass):
|
|
499
|
+
# Use a bright cyan color for triangle strip single colour
|
|
500
|
+
cyan_color = np.array([0.0, 0.8, 0.8], dtype=np.float32)
|
|
501
|
+
self._render_pipeline(
|
|
502
|
+
render_pass, self.triangle_strip_positions, colour=cyan_color
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def _render_point_list_multi_colour_pipeline(self, render_pass):
|
|
506
|
+
self._render_pipeline(
|
|
507
|
+
render_pass, self.positions, colours=self.colours, point_size=5.0
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
def _render_point_list_single_colour_pipeline(self, render_pass):
|
|
511
|
+
# Use bright lime green for point list single colour
|
|
512
|
+
lime_color = np.array([0.5, 1.0, 0.0], dtype=np.float32)
|
|
513
|
+
self._render_pipeline(
|
|
514
|
+
render_pass, self.positions, point_size=5.0, colour=lime_color
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
def _render_multi_colour_instanced_geometry_pipeline(self, render_pass):
|
|
518
|
+
"""Render multi-colour instanced geometry."""
|
|
519
|
+
pipeline = self.pipelines[self.current_pipeline_index][0]
|
|
520
|
+
|
|
521
|
+
# Set instanced geometry data using new simplified API
|
|
522
|
+
pipeline.set_data(
|
|
523
|
+
positions=self.instance_positions,
|
|
524
|
+
colours=self.instance_colours,
|
|
525
|
+
geometry_data=self.geometry_data, # Single interleaved parameter!
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Update uniforms
|
|
529
|
+
pipeline.update_uniforms(
|
|
530
|
+
mvp=self.mvp_matrix,
|
|
531
|
+
view_matrix=self.view_matrix,
|
|
532
|
+
instance_transform=np.eye(4, dtype=np.float32),
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
pipeline.render(render_pass, num_instances=len(self.instance_positions))
|
|
536
|
+
|
|
537
|
+
def _render_single_colour_instanced_geometry_pipeline(self, render_pass):
|
|
538
|
+
"""Render single-colour instanced geometry."""
|
|
539
|
+
pipeline = self.pipelines[self.current_pipeline_index][0]
|
|
540
|
+
|
|
541
|
+
# Set instanced geometry data using new simplified API
|
|
542
|
+
pipeline.set_data(
|
|
543
|
+
positions=self.instance_positions,
|
|
544
|
+
geometry_data=self.geometry_data, # Single interleaved parameter!
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# Update uniforms with orange color
|
|
548
|
+
pipeline.update_uniforms(
|
|
549
|
+
mvp=self.mvp_matrix,
|
|
550
|
+
view_matrix=self.view_matrix,
|
|
551
|
+
colour=np.array([1.0, 0.6, 0.1], dtype=np.float32), # Orange
|
|
552
|
+
instance_transform=np.eye(4, dtype=np.float32),
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
pipeline.render(render_pass, num_instances=len(self.instance_positions))
|
|
556
|
+
|
|
557
|
+
def update_uniform_buffers(self) -> None:
|
|
558
|
+
"""
|
|
559
|
+
update the uniform buffers for the line pipeline.
|
|
560
|
+
"""
|
|
561
|
+
rotation = Mat4.rotate_y(self.rotation)
|
|
562
|
+
self.mvp_matrix = (
|
|
563
|
+
(self.project @ self.view @ rotation).to_numpy().astype(np.float32)
|
|
564
|
+
)
|
|
565
|
+
self.view_matrix = (self.view @ rotation).to_numpy().astype(np.float32)
|
|
566
|
+
|
|
567
|
+
def keyPressEvent(self, event) -> None:
|
|
568
|
+
"""
|
|
569
|
+
Handles keyboard press events.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
event: The QKeyEvent object containing information about the key press.
|
|
573
|
+
"""
|
|
574
|
+
key = event.key()
|
|
575
|
+
if key == Qt.Key.Key_Escape:
|
|
576
|
+
self.close() # Exit the application
|
|
577
|
+
elif key == Qt.Key.Key_Space:
|
|
578
|
+
self.animate = not self.animate
|
|
579
|
+
elif key == Qt.Key.Key_Left:
|
|
580
|
+
# Switch to previous pipeline
|
|
581
|
+
self.current_pipeline_index = (self.current_pipeline_index - 1) % len(
|
|
582
|
+
self.pipelines
|
|
583
|
+
)
|
|
584
|
+
print(f"Switched to {self.pipelines[self.current_pipeline_index][2]}")
|
|
585
|
+
elif key == Qt.Key.Key_Right:
|
|
586
|
+
# Switch to next pipeline
|
|
587
|
+
self.current_pipeline_index = (self.current_pipeline_index + 1) % len(
|
|
588
|
+
self.pipelines
|
|
589
|
+
)
|
|
590
|
+
print(f"Switched to {self.pipelines[self.current_pipeline_index][2]}")
|
|
591
|
+
elif key == Qt.Key.Key_A:
|
|
592
|
+
# Toggle automatic pipeline switching
|
|
593
|
+
if self.pipeline_timer.isActive():
|
|
594
|
+
self.pipeline_timer.stop()
|
|
595
|
+
print("Pipeline auto-switch disabled")
|
|
596
|
+
else:
|
|
597
|
+
self.pipeline_timer.start(1000)
|
|
598
|
+
print("Pipeline auto-switch enabled")
|
|
599
|
+
self.update()
|
|
600
|
+
|
|
601
|
+
# Call the base class implementation for any unhandled events
|
|
602
|
+
super().keyPressEvent(event)
|
|
603
|
+
|
|
604
|
+
def switch_pipeline(self) -> None:
|
|
605
|
+
"""Switch to the next pipeline in the list."""
|
|
606
|
+
self.current_pipeline_index = (self.current_pipeline_index + 1) % len(
|
|
607
|
+
self.pipelines
|
|
608
|
+
)
|
|
609
|
+
print(f"Switched to {self.pipelines[self.current_pipeline_index][2]}")
|
|
610
|
+
self.update()
|
|
611
|
+
|
|
612
|
+
def timerEvent(self, event) -> None:
|
|
613
|
+
"""
|
|
614
|
+
Handle timer events to update the scene.
|
|
615
|
+
"""
|
|
616
|
+
if self.animate:
|
|
617
|
+
self.rotation += 0.5
|
|
618
|
+
self.update()
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def main():
|
|
622
|
+
"""
|
|
623
|
+
Main function to run the application.
|
|
624
|
+
Parses command line arguments and initializes the WebGPUScene.
|
|
625
|
+
"""
|
|
626
|
+
app = QApplication(sys.argv)
|
|
627
|
+
|
|
628
|
+
# Use basic blue background as requested
|
|
629
|
+
# Each pipeline will override this with its own subtle background color
|
|
630
|
+
background_colour = (0.1, 0.1, 0.3, 1.0) # Basic blue background
|
|
631
|
+
|
|
632
|
+
win = WebGPUScene(background_colour=background_colour)
|
|
633
|
+
win.resize(1024, 720)
|
|
634
|
+
win.show()
|
|
635
|
+
sys.exit(app.exec())
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
if __name__ == "__main__":
|
|
639
|
+
print(f"ncca-ngl.WebGPU {__version__}")
|
|
640
|
+
main()
|