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,314 @@
|
|
|
1
|
+
# WebGPU Unified Pipeline System
|
|
2
|
+
|
|
3
|
+
A comprehensive refactoring of the WebGPU pipeline system that provides a unified, configuration-driven approach with user-defined shader support.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The new unified system replaces the fragmented pipeline hierarchy with a single `UnifiedWebGPUPipeline` class that can handle all rendering scenarios through configuration. This results in **60-70% code reduction** and much easier extensibility.
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **Single Pipeline Class**: One class for all primitive types, color modes, and rendering modes
|
|
12
|
+
- **Shader Template System**: Automatic shader generation with 70% reduction in shader code duplication
|
|
13
|
+
- **User-Defined Shaders**: Full support for custom vertex and fragment shaders
|
|
14
|
+
- **Configuration-Driven**: Flexible pipeline configuration through builder pattern
|
|
15
|
+
- **Custom Uniforms & Attributes**: Easy extension with custom data
|
|
16
|
+
- **Backward Compatible**: Existing code continues to work
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Basic Usage
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import wgpu
|
|
24
|
+
from ncca.ngl.webgpu import UnifiedPipelineFactory
|
|
25
|
+
|
|
26
|
+
device = wgpu.gpu.create_device()
|
|
27
|
+
factory = UnifiedPipelineFactory(device)
|
|
28
|
+
|
|
29
|
+
# Create pipelines with simple factory methods
|
|
30
|
+
point_pipeline = factory.create_point_multi_colour()
|
|
31
|
+
line_pipeline = factory.create_line_single_colour()
|
|
32
|
+
triangle_pipeline = factory.create_triangle_multi_colour()
|
|
33
|
+
|
|
34
|
+
# Set data
|
|
35
|
+
positions = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]], dtype=np.float32)
|
|
36
|
+
colours = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
|
|
37
|
+
|
|
38
|
+
triangle_pipeline.set_data(positions, colours)
|
|
39
|
+
triangle_pipeline.update_uniforms(mvp=projection_matrix)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Custom Shaders
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# Custom vertex shader with wave effect
|
|
46
|
+
vertex_shader = """
|
|
47
|
+
struct Uniforms {
|
|
48
|
+
MVP: mat4x4<f32>,
|
|
49
|
+
time: f32,
|
|
50
|
+
padding: f32,
|
|
51
|
+
padding2: f32,
|
|
52
|
+
padding3: f32,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
@binding(0) @group(0) var<uniform> uniforms: Uniforms;
|
|
56
|
+
|
|
57
|
+
struct VertexIn {
|
|
58
|
+
@location(0) position: vec3<f32>,
|
|
59
|
+
@location(1) colour: vec3<f32>,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
struct VertexOut {
|
|
63
|
+
@builtin(position) position: vec4<f32>,
|
|
64
|
+
@location(0) color: vec3<f32>,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
@vertex
|
|
68
|
+
fn vertex_main(input: VertexIn) -> VertexOut {
|
|
69
|
+
var output: VertexOut;
|
|
70
|
+
|
|
71
|
+
// Add wave effect
|
|
72
|
+
let wave_offset = sin(uniforms.time + input.position.x * 2.0) * 0.2;
|
|
73
|
+
let modified_pos = vec3<f32>(input.position.x, input.position.y + wave_offset, input.position.z);
|
|
74
|
+
|
|
75
|
+
output.position = uniforms.MVP * vec4<f32>(modified_pos, 1.0);
|
|
76
|
+
output.color = input.colour;
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Create pipeline with custom shader
|
|
82
|
+
pipeline = factory.create_with_custom_shaders(
|
|
83
|
+
vertex_shader=vertex_shader,
|
|
84
|
+
primitive_type=PrimitiveType.TRIANGLE,
|
|
85
|
+
color_mode=ColorMode.MULTI
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Use with custom uniforms
|
|
89
|
+
pipeline.update_uniforms(
|
|
90
|
+
mvp=projection_matrix,
|
|
91
|
+
time=current_time # Custom uniform
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Configuration Builder
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from ncca.ngl.webgpu import (
|
|
99
|
+
PipelineConfigBuilder, PrimitiveType, ColorMode, RenderMode
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Create custom configuration
|
|
103
|
+
config = (PipelineConfigBuilder()
|
|
104
|
+
.primitive(PrimitiveType.TRIANGLE)
|
|
105
|
+
.color_mode(ColorMode.MULTI)
|
|
106
|
+
.topology(wgpu.PrimitiveTopology.triangle_strip)
|
|
107
|
+
.msaa(8)
|
|
108
|
+
.cull_mode(wgpu.CullMode.back)
|
|
109
|
+
.build())
|
|
110
|
+
|
|
111
|
+
pipeline = factory.create_pipeline(config)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Architecture
|
|
115
|
+
|
|
116
|
+
### Core Components
|
|
117
|
+
|
|
118
|
+
1. **UnifiedWebGPUPipeline**: Single pipeline class for all use cases
|
|
119
|
+
2. **Shader Templates**: Template-based shader generation system
|
|
120
|
+
3. **Configuration System**: Builder pattern for flexible pipeline setup
|
|
121
|
+
4. **Resource Management**: Unified buffer and uniform management
|
|
122
|
+
5. **Factory System**: Simplified pipeline creation with convenience methods
|
|
123
|
+
|
|
124
|
+
### Benefits
|
|
125
|
+
|
|
126
|
+
#### Before (Fragmented System)
|
|
127
|
+
```
|
|
128
|
+
BaseWebGPUPipeline
|
|
129
|
+
├── BasePointPipeline
|
|
130
|
+
│ ├── PointPipelineMultiColour
|
|
131
|
+
│ └── PointPipelineSingleColour
|
|
132
|
+
├── BaseLinePipeline
|
|
133
|
+
│ ├── LinePipelineMultiColour
|
|
134
|
+
│ └── LinePipelineSingleColour
|
|
135
|
+
├── BaseTrianglePipeline
|
|
136
|
+
│ ├── TrianglePipelineMultiColour
|
|
137
|
+
│ └── TrianglePipelineSingleColour
|
|
138
|
+
└── ... (17 total classes with massive duplication)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### After (Unified System)
|
|
142
|
+
```
|
|
143
|
+
UnifiedWebGPUPipeline (1 class)
|
|
144
|
+
├── Supports all primitives via configuration
|
|
145
|
+
├── Supports all color modes via configuration
|
|
146
|
+
├── Supports custom shaders
|
|
147
|
+
└── 60-70% less code
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### Factory Methods
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# Predefined pipelines
|
|
156
|
+
factory.create_point_single_colour()
|
|
157
|
+
factory.create_point_multi_colour()
|
|
158
|
+
factory.create_line_single_colour()
|
|
159
|
+
factory.create_line_multi_colour()
|
|
160
|
+
factory.create_triangle_single_colour()
|
|
161
|
+
factory.create_triangle_multi_colour()
|
|
162
|
+
factory.create_triangle_strip_single_colour()
|
|
163
|
+
factory.create_triangle_strip_multi_colour()
|
|
164
|
+
|
|
165
|
+
# Custom pipelines
|
|
166
|
+
factory.create_custom(primitive_type, color_mode, **kwargs)
|
|
167
|
+
factory.create_with_custom_shaders(vertex_shader, fragment_shader, **kwargs)
|
|
168
|
+
factory.create_with_vertex_shader(vertex_shader, **kwargs)
|
|
169
|
+
factory.create_with_fragment_shader(fragment_shader, **kwargs)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Configuration Options
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# Primitive types
|
|
176
|
+
PrimitiveType.POINT
|
|
177
|
+
PrimitiveType.LINE
|
|
178
|
+
PrimitiveType.TRIANGLE
|
|
179
|
+
|
|
180
|
+
# Color modes
|
|
181
|
+
ColorMode.SINGLE
|
|
182
|
+
ColorMode.MULTI
|
|
183
|
+
|
|
184
|
+
# Render modes
|
|
185
|
+
RenderMode.STANDARD
|
|
186
|
+
RenderMode.BILLBOARDED
|
|
187
|
+
RenderMode.INSTANCED
|
|
188
|
+
|
|
189
|
+
# Configuration options
|
|
190
|
+
PipelineConfigBuilder()
|
|
191
|
+
.primitive(PrimitiveType.TRIANGLE)
|
|
192
|
+
.color_mode(ColorMode.MULTI)
|
|
193
|
+
.topology(wgpu.PrimitiveTopology.triangle_list)
|
|
194
|
+
.render_mode(RenderMode.STANDARD)
|
|
195
|
+
.texture_format(wgpu.TextureFormat.rgba8unorm)
|
|
196
|
+
.depth_format(wgpu.TextureFormat.depth24plus)
|
|
197
|
+
.msaa(4)
|
|
198
|
+
.data_type("Vec3")
|
|
199
|
+
.stride(0)
|
|
200
|
+
.custom_shaders(vertex_shader, fragment_shader)
|
|
201
|
+
.custom_uniforms([UniformField(...), ...])
|
|
202
|
+
.custom_attributes([VertexAttribute(...), ...])
|
|
203
|
+
.cull_mode(wgpu.CullMode.none)
|
|
204
|
+
.front_face(wgpu.FrontFace.ccw)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Custom Data Types
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# Custom uniforms
|
|
211
|
+
custom_uniforms = [
|
|
212
|
+
UniformField("time", "f32"),
|
|
213
|
+
UniformField("wave_amplitude", "f32"),
|
|
214
|
+
UniformField("custom_matrix", "mat4x4<f32>"),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
# Custom vertex attributes
|
|
218
|
+
custom_attributes = [
|
|
219
|
+
VertexAttribute("normal", "vec3<f32>", 2, "vertex"),
|
|
220
|
+
VertexAttribute("uv", "vec2<f32>", 3, "vertex"),
|
|
221
|
+
]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Migration Guide
|
|
225
|
+
|
|
226
|
+
### From Old System
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
# Old way (still works)
|
|
230
|
+
from ncca.ngl.webgpu import PointPipelineMultiColour
|
|
231
|
+
pipeline = PointPipelineMultiColour(device)
|
|
232
|
+
|
|
233
|
+
# New recommended way
|
|
234
|
+
from ncca.ngl.webgpu import create_point_pipeline
|
|
235
|
+
pipeline = create_point_pipeline(device, multi_colour=True)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Advanced Customization
|
|
239
|
+
|
|
240
|
+
The new system makes it much easier to create specialized pipelines:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# Custom particle system with physics
|
|
244
|
+
config = (PipelineConfigBuilder()
|
|
245
|
+
.primitive(PrimitiveType.POINT)
|
|
246
|
+
.color_mode(ColorMode.MULTI)
|
|
247
|
+
.render_mode(RenderMode.BILLBOARDED)
|
|
248
|
+
.custom_uniforms([
|
|
249
|
+
UniformField("time", "f32"),
|
|
250
|
+
UniformField("gravity", "vec3<f32>"),
|
|
251
|
+
UniformField("wind", "vec3<f32>"),
|
|
252
|
+
])
|
|
253
|
+
.custom_attributes([
|
|
254
|
+
VertexAttribute("velocity", "vec3<f32>", 2),
|
|
255
|
+
VertexAttribute("life", "f32", 3),
|
|
256
|
+
])
|
|
257
|
+
.build())
|
|
258
|
+
|
|
259
|
+
pipeline = factory.create_pipeline(config)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Examples
|
|
263
|
+
|
|
264
|
+
See `unified_examples.py` for comprehensive examples including:
|
|
265
|
+
|
|
266
|
+
- Basic pipeline creation
|
|
267
|
+
- Configuration builder usage
|
|
268
|
+
- Custom shader implementation
|
|
269
|
+
- Custom uniforms and attributes
|
|
270
|
+
- Vertex layout building
|
|
271
|
+
- Shader template system
|
|
272
|
+
- Comprehensive testing
|
|
273
|
+
|
|
274
|
+
## Testing
|
|
275
|
+
|
|
276
|
+
Run the examples to verify the system works:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# The examples require a WebGPU context
|
|
280
|
+
python -m ncca.ngl.webgpu.unified_examples
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Backward Compatibility
|
|
284
|
+
|
|
285
|
+
The existing pipeline classes remain available and functional:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
# This still works
|
|
289
|
+
from ncca.ngl.webgpu import PointPipelineMultiColour
|
|
290
|
+
pipeline = PointPipelineMultiColour(device)
|
|
291
|
+
|
|
292
|
+
# But this is now recommended
|
|
293
|
+
from ncca.ngl.webgpu import UnifiedPipelineFactory
|
|
294
|
+
factory = UnifiedPipelineFactory(device)
|
|
295
|
+
pipeline = factory.create_point_multi_colour()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Performance
|
|
299
|
+
|
|
300
|
+
- **Memory**: 60-70% reduction in code size
|
|
301
|
+
- **Runtime**: No performance penalty, same GPU execution
|
|
302
|
+
- **Compilation**: Faster shader compilation due to templates
|
|
303
|
+
- **Development**: Much faster pipeline creation and customization
|
|
304
|
+
|
|
305
|
+
## Future Extensibility
|
|
306
|
+
|
|
307
|
+
The unified system makes it easy to add new features:
|
|
308
|
+
|
|
309
|
+
1. **New Primitive Types**: Add to `PrimitiveType` enum and update templates
|
|
310
|
+
2. **New Render Modes**: Add to `RenderMode` enum and implement in templates
|
|
311
|
+
3. **Custom Effects**: Use custom shaders with the existing infrastructure
|
|
312
|
+
4. **Advanced Features**: Extend configuration system as needed
|
|
313
|
+
|
|
314
|
+
This architecture provides a solid foundation for future WebGPU development while maintaining full compatibility with existing code.
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Buffer management abstractions for WebGPU pipelines.
|
|
3
|
+
Provides unified buffer handling with automatic creation, updates, and cleanup.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import wgpu
|
|
10
|
+
|
|
11
|
+
from .pipeline_config import BufferConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BufferManager:
|
|
15
|
+
"""Manages GPU buffers with automatic creation, updates, and cleanup."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, device: wgpu.GPUDevice):
|
|
18
|
+
"""Initialize buffer manager.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
device: WebGPU device for buffer creation
|
|
22
|
+
"""
|
|
23
|
+
self.device = device
|
|
24
|
+
self._buffers: Dict[str, wgpu.GPUBuffer] = {}
|
|
25
|
+
self._buffer_sizes: Dict[str, int] = {}
|
|
26
|
+
self._buffer_configs: Dict[str, BufferConfig] = {}
|
|
27
|
+
|
|
28
|
+
def create_buffer(
|
|
29
|
+
self,
|
|
30
|
+
name: str,
|
|
31
|
+
data: Union[np.ndarray, wgpu.GPUBuffer],
|
|
32
|
+
usage: wgpu.BufferUsage,
|
|
33
|
+
buffer_label: Optional[str] = None,
|
|
34
|
+
) -> wgpu.GPUBuffer:
|
|
35
|
+
"""Create a new GPU buffer.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: Unique name for the buffer
|
|
39
|
+
data: Buffer data (numpy array or existing GPU buffer)
|
|
40
|
+
usage: Buffer usage flags
|
|
41
|
+
buffer_label: Optional label for debugging
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Created GPU buffer
|
|
45
|
+
"""
|
|
46
|
+
if name in self._buffers:
|
|
47
|
+
self.destroy_buffer(name)
|
|
48
|
+
|
|
49
|
+
if isinstance(data, wgpu.GPUBuffer):
|
|
50
|
+
# Use provided buffer directly
|
|
51
|
+
buffer = data
|
|
52
|
+
else:
|
|
53
|
+
# Create buffer from numpy array
|
|
54
|
+
data_bytes = data.astype(np.float32).tobytes()
|
|
55
|
+
buffer = self.device.create_buffer_with_data(
|
|
56
|
+
data=data_bytes,
|
|
57
|
+
usage=usage,
|
|
58
|
+
label=buffer_label or f"buffer_{name}",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self._buffers[name] = buffer
|
|
62
|
+
self._buffer_sizes[name] = buffer.size
|
|
63
|
+
return buffer
|
|
64
|
+
|
|
65
|
+
def update_buffer(
|
|
66
|
+
self,
|
|
67
|
+
name: str,
|
|
68
|
+
data: np.ndarray,
|
|
69
|
+
usage: Optional[wgpu.BufferUsage] = None,
|
|
70
|
+
buffer_label: Optional[str] = None,
|
|
71
|
+
) -> wgpu.GPUBuffer:
|
|
72
|
+
"""Update an existing buffer or create a new one if needed.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name: Buffer name
|
|
76
|
+
data: New data for the buffer
|
|
77
|
+
usage: Buffer usage flags (for new buffer creation)
|
|
78
|
+
buffer_label: Optional label for new buffer
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Updated or created GPU buffer
|
|
82
|
+
"""
|
|
83
|
+
data_bytes = data.astype(np.float32).tobytes()
|
|
84
|
+
data_size = len(data_bytes)
|
|
85
|
+
|
|
86
|
+
if name in self._buffers:
|
|
87
|
+
buffer = self._buffers[name]
|
|
88
|
+
|
|
89
|
+
# Check if existing buffer is large enough
|
|
90
|
+
if buffer.size >= data_size:
|
|
91
|
+
# Update existing buffer
|
|
92
|
+
self.device.queue.write_buffer(buffer, 0, data_bytes)
|
|
93
|
+
self._buffer_sizes[name] = data_size
|
|
94
|
+
return buffer
|
|
95
|
+
else:
|
|
96
|
+
# Replace with larger buffer
|
|
97
|
+
self.destroy_buffer(name)
|
|
98
|
+
|
|
99
|
+
# Create new buffer
|
|
100
|
+
return self.create_buffer(
|
|
101
|
+
name, data, usage or wgpu.BufferUsage.VERTEX, buffer_label
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def get_buffer(self, name: str) -> Optional[wgpu.GPUBuffer]:
|
|
105
|
+
"""Get a buffer by name.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
name: Buffer name
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
GPU buffer if found, None otherwise
|
|
112
|
+
"""
|
|
113
|
+
return self._buffers.get(name)
|
|
114
|
+
|
|
115
|
+
def get_buffer_size(self, name: str) -> Optional[int]:
|
|
116
|
+
"""Get buffer size by name.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
name: Buffer name
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Buffer size if found, None otherwise
|
|
123
|
+
"""
|
|
124
|
+
return self._buffer_sizes.get(name)
|
|
125
|
+
|
|
126
|
+
def destroy_buffer(self, name: str) -> None:
|
|
127
|
+
"""Destroy a buffer by name.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
name: Buffer name
|
|
131
|
+
"""
|
|
132
|
+
if name in self._buffers:
|
|
133
|
+
self._buffers[name].destroy()
|
|
134
|
+
del self._buffers[name]
|
|
135
|
+
del self._buffer_sizes[name]
|
|
136
|
+
if name in self._buffer_configs:
|
|
137
|
+
del self._buffer_configs[name]
|
|
138
|
+
|
|
139
|
+
def destroy_all(self) -> None:
|
|
140
|
+
"""Destroy all managed buffers."""
|
|
141
|
+
for name in list(self._buffers.keys()):
|
|
142
|
+
self.destroy_buffer(name)
|
|
143
|
+
|
|
144
|
+
def process_vertex_data(
|
|
145
|
+
self,
|
|
146
|
+
name: str,
|
|
147
|
+
data: Optional[Union[np.ndarray, wgpu.GPUBuffer]],
|
|
148
|
+
default_value: Optional[np.ndarray] = None,
|
|
149
|
+
padding_size: Optional[int] = None,
|
|
150
|
+
buffer_label: Optional[str] = None,
|
|
151
|
+
) -> Optional[wgpu.GPUBuffer]:
|
|
152
|
+
"""Process vertex data with automatic default handling and padding.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
name: Buffer name
|
|
156
|
+
data: Input data (numpy array, GPU buffer, or None)
|
|
157
|
+
default_value: Default value if data is None
|
|
158
|
+
padding_size: Size to pad arrays to (for alignment)
|
|
159
|
+
buffer_label: Label for created buffers
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Processed GPU buffer or None
|
|
163
|
+
"""
|
|
164
|
+
if data is None and default_value is not None:
|
|
165
|
+
data = default_value
|
|
166
|
+
|
|
167
|
+
if data is None:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
if isinstance(data, wgpu.GPUBuffer):
|
|
171
|
+
# Store existing buffer
|
|
172
|
+
self._buffers[name] = data
|
|
173
|
+
self._buffer_sizes[name] = data.size
|
|
174
|
+
return data
|
|
175
|
+
|
|
176
|
+
# Handle numpy array
|
|
177
|
+
if padding_size:
|
|
178
|
+
# Pad array to specified size
|
|
179
|
+
if data.ndim == 1:
|
|
180
|
+
padded_data = np.zeros(padding_size, dtype=np.float32)
|
|
181
|
+
padded_data[: len(data)] = data.astype(np.float32)
|
|
182
|
+
else:
|
|
183
|
+
padded_data = np.zeros((data.shape[0], padding_size), dtype=np.float32)
|
|
184
|
+
padded_data[:, : data.shape[1]] = data.astype(np.float32)
|
|
185
|
+
data = padded_data
|
|
186
|
+
|
|
187
|
+
return self.create_buffer(
|
|
188
|
+
name,
|
|
189
|
+
data,
|
|
190
|
+
wgpu.BufferUsage.VERTEX | wgpu.BufferUsage.COPY_DST,
|
|
191
|
+
buffer_label,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class UniformManager:
|
|
196
|
+
"""Manages uniform buffers with automatic struct generation."""
|
|
197
|
+
|
|
198
|
+
def __init__(self, device: wgpu.GPUDevice):
|
|
199
|
+
"""Initialize uniform manager.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
device: WebGPU device for buffer creation
|
|
203
|
+
"""
|
|
204
|
+
self.device = device
|
|
205
|
+
self._uniform_buffer: Optional[wgpu.GPUBuffer] = None
|
|
206
|
+
self._uniform_data: Optional[np.ndarray] = None
|
|
207
|
+
self._dtype: Optional[np.dtype] = None
|
|
208
|
+
self._bind_group: Optional[wgpu.GPUBindGroup] = None
|
|
209
|
+
self._bind_group_layout: Optional[wgpu.GPUBindGroupLayout] = None
|
|
210
|
+
|
|
211
|
+
def initialize(
|
|
212
|
+
self,
|
|
213
|
+
dtype: np.dtype,
|
|
214
|
+
bind_group_layout: wgpu.GPUBindGroupLayout,
|
|
215
|
+
initial_data: Optional[Dict[str, Any]] = None,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Initialize uniform buffer with data type.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
dtype: NumPy dtype for uniform structure
|
|
221
|
+
bind_group_layout: Bind group layout for uniforms
|
|
222
|
+
initial_data: Initial uniform values
|
|
223
|
+
"""
|
|
224
|
+
self._dtype = dtype
|
|
225
|
+
self._bind_group_layout = bind_group_layout
|
|
226
|
+
|
|
227
|
+
# Initialize uniform data with zeros
|
|
228
|
+
self._uniform_data = np.zeros((), dtype=dtype)
|
|
229
|
+
|
|
230
|
+
# Set initial values
|
|
231
|
+
if initial_data:
|
|
232
|
+
self.update_uniforms(initial_data)
|
|
233
|
+
|
|
234
|
+
# Create uniform buffer
|
|
235
|
+
self._uniform_buffer = self.device.create_buffer_with_data(
|
|
236
|
+
data=self._uniform_data.tobytes(),
|
|
237
|
+
usage=int(wgpu.BufferUsage.UNIFORM | wgpu.BufferUsage.COPY_DST),
|
|
238
|
+
label="uniform_buffer",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Create bind group
|
|
242
|
+
self._bind_group = self.device.create_bind_group(
|
|
243
|
+
layout=self._bind_group_layout,
|
|
244
|
+
entries=[
|
|
245
|
+
{
|
|
246
|
+
"binding": 0,
|
|
247
|
+
"resource": {"buffer": self._uniform_buffer},
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def update_uniforms(self, uniforms: Dict[str, Any]) -> None:
|
|
253
|
+
"""Update uniform values.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
uniforms: Dictionary of uniform name -> value mappings
|
|
257
|
+
"""
|
|
258
|
+
if self._uniform_data is None:
|
|
259
|
+
raise RuntimeError("Uniform manager not initialized")
|
|
260
|
+
|
|
261
|
+
for name, value in uniforms.items():
|
|
262
|
+
if value is not None:
|
|
263
|
+
self._uniform_data[name] = value
|
|
264
|
+
|
|
265
|
+
# Update GPU buffer
|
|
266
|
+
if self._uniform_buffer:
|
|
267
|
+
self.device.queue.write_buffer(
|
|
268
|
+
self._uniform_buffer, 0, self._uniform_data.tobytes()
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def get_uniform_data(self) -> Optional[np.ndarray]:
|
|
272
|
+
"""Get current uniform data.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Current uniform data array
|
|
276
|
+
"""
|
|
277
|
+
return self._uniform_data
|
|
278
|
+
|
|
279
|
+
def get_uniform_buffer(self) -> Optional[wgpu.GPUBuffer]:
|
|
280
|
+
"""Get uniform buffer.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Uniform GPU buffer
|
|
284
|
+
"""
|
|
285
|
+
return self._uniform_buffer
|
|
286
|
+
|
|
287
|
+
def get_bind_group(self) -> Optional[wgpu.GPUBindGroup]:
|
|
288
|
+
"""Get bind group for uniforms.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Bind group containing uniform buffer
|
|
292
|
+
"""
|
|
293
|
+
return self._bind_group
|
|
294
|
+
|
|
295
|
+
def cleanup(self) -> None:
|
|
296
|
+
"""Release uniform resources."""
|
|
297
|
+
if self._uniform_buffer:
|
|
298
|
+
self._uniform_buffer.destroy()
|
|
299
|
+
self._uniform_buffer = None
|
|
300
|
+
self._bind_group = None
|
|
301
|
+
self._bind_group_layout = None
|
|
302
|
+
self._uniform_data = None
|
|
303
|
+
self._dtype = None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class RenderStateManager:
|
|
307
|
+
"""Manages render state and pipeline configurations."""
|
|
308
|
+
|
|
309
|
+
def __init__(self):
|
|
310
|
+
"""Initialize render state manager."""
|
|
311
|
+
self._active_pipeline: Optional[wgpu.GPURenderPipeline] = None
|
|
312
|
+
self._active_bind_group: Optional[wgpu.GPUBindGroup] = None
|
|
313
|
+
self._vertex_buffers: Dict[int, wgpu.GPUBuffer] = {}
|
|
314
|
+
|
|
315
|
+
def set_pipeline(self, pipeline: wgpu.GPURenderPipeline) -> None:
|
|
316
|
+
"""Set active render pipeline.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
pipeline: Render pipeline to activate
|
|
320
|
+
"""
|
|
321
|
+
self._active_pipeline = pipeline
|
|
322
|
+
|
|
323
|
+
def set_bind_group(self, bind_group: wgpu.GPUBindGroup) -> None:
|
|
324
|
+
"""Set active bind group.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
bind_group: Bind group to activate
|
|
328
|
+
"""
|
|
329
|
+
self._active_bind_group = bind_group
|
|
330
|
+
|
|
331
|
+
def set_vertex_buffer(self, slot: int, buffer: wgpu.GPUBuffer) -> None:
|
|
332
|
+
"""Set vertex buffer for a slot.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
slot: Buffer slot index
|
|
336
|
+
buffer: Vertex buffer
|
|
337
|
+
"""
|
|
338
|
+
self._vertex_buffers[slot] = buffer
|
|
339
|
+
|
|
340
|
+
def apply_state(self, render_pass: wgpu.GPURenderPassEncoder) -> None:
|
|
341
|
+
"""Apply current render state to a render pass.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
render_pass: Render pass to apply state to
|
|
345
|
+
"""
|
|
346
|
+
if self._active_pipeline:
|
|
347
|
+
render_pass.set_pipeline(self._active_pipeline)
|
|
348
|
+
|
|
349
|
+
if self._active_bind_group:
|
|
350
|
+
render_pass.set_bind_group(0, self._active_bind_group, [], 0, 999999)
|
|
351
|
+
|
|
352
|
+
for slot, buffer in self._vertex_buffers.items():
|
|
353
|
+
render_pass.set_vertex_buffer(slot, buffer)
|
|
354
|
+
|
|
355
|
+
def clear_state(self) -> None:
|
|
356
|
+
"""Clear current render state."""
|
|
357
|
+
self._active_pipeline = None
|
|
358
|
+
self._active_bind_group = None
|
|
359
|
+
self._vertex_buffers.clear()
|
|
360
|
+
|
|
361
|
+
def draw(
|
|
362
|
+
self,
|
|
363
|
+
render_pass: wgpu.GPURenderPassEncoder,
|
|
364
|
+
vertex_count: int,
|
|
365
|
+
instance_count: int = 1,
|
|
366
|
+
) -> None:
|
|
367
|
+
"""Apply state and issue draw call.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
render_pass: Render pass to draw to
|
|
371
|
+
vertex_count: Number of vertices to draw
|
|
372
|
+
instance_count: Number of instances to draw
|
|
373
|
+
"""
|
|
374
|
+
self.apply_state(render_pass)
|
|
375
|
+
render_pass.draw(vertex_count, instance_count)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class UnifiedResourceManager:
|
|
379
|
+
"""Unified resource manager combining buffer, uniform, and state management."""
|
|
380
|
+
|
|
381
|
+
def __init__(self, device: wgpu.GPUDevice):
|
|
382
|
+
"""Initialize unified resource manager.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
device: WebGPU device
|
|
386
|
+
"""
|
|
387
|
+
self.device = device
|
|
388
|
+
self.buffer_manager = BufferManager(device)
|
|
389
|
+
self.uniform_manager = UniformManager(device)
|
|
390
|
+
self.state_manager = RenderStateManager()
|
|
391
|
+
|
|
392
|
+
def cleanup(self) -> None:
|
|
393
|
+
"""Release all managed resources."""
|
|
394
|
+
self.buffer_manager.destroy_all()
|
|
395
|
+
self.uniform_manager.cleanup()
|
|
396
|
+
self.state_manager.clear_state()
|