-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathintegration_test.go
More file actions
687 lines (596 loc) · 18.3 KB
/
Copy pathintegration_test.go
File metadata and controls
687 lines (596 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
//go:build !rust && !(js && wasm)
package wgpu_test
import (
"context"
"encoding/binary"
"testing"
"time"
"github.com/gogpu/gputypes"
"github.com/gogpu/wgpu"
// Import the software backend so it registers with HAL.
// Note: The core Instance currently skips BackendEmpty during adapter
// enumeration, so the software backend is not used by CreateInstance
// directly. When a real GPU backend (Vulkan, DX12, GLES) is available,
// these tests exercise the full HAL integration path. Otherwise, the
// tests skip gracefully. Future architecture changes may allow the
// software backend to be selected directly.
_ "github.com/gogpu/wgpu/hal/software"
)
// createTestDevice creates an Instance, Adapter, and Device for integration testing.
// It skips the test if HAL integration is not available (e.g., no real GPU drivers
// installed or running in headless CI). All returned resources should be released
// by the caller.
func createTestDevice(t *testing.T) (*wgpu.Instance, *wgpu.Adapter, *wgpu.Device) {
t.Helper()
instance, err := wgpu.CreateInstance(nil)
if err != nil {
t.Skipf("cannot create instance: %v", err)
}
adapter, err := instance.RequestAdapter(nil)
if err != nil {
instance.Release()
t.Skipf("cannot request adapter: %v", err)
}
device, err := adapter.RequestDevice(nil)
if err != nil {
adapter.Release()
instance.Release()
t.Skipf("cannot request device: %v", err)
}
// Check that the device has actual HAL integration (not a mock adapter).
// Mock adapters have no queue and cannot create GPU resources.
if device.Queue() == nil {
device.Release()
adapter.Release()
instance.Release()
t.Skip("skipping: device has no HAL integration (mock adapter; no GPU backend available)")
}
return instance, adapter, device
}
// --- Instance tests ---
// TestIntegrationCreateInstance tests the full CreateInstance -> Release cycle.
func TestIntegrationCreateInstance(t *testing.T) {
instance, err := wgpu.CreateInstance(nil)
if err != nil {
t.Fatalf("CreateInstance: %v", err)
}
if instance == nil {
t.Fatal("CreateInstance returned nil")
}
// Release should be idempotent.
instance.Release()
instance.Release()
}
// --- Adapter tests ---
// TestIntegrationRequestAdapter verifies the adapter has a non-empty name and driver.
func TestIntegrationRequestAdapter(t *testing.T) {
instance, err := wgpu.CreateInstance(nil)
if err != nil {
t.Fatalf("CreateInstance: %v", err)
}
defer instance.Release()
adapter, err := instance.RequestAdapter(nil)
if err != nil {
t.Fatalf("RequestAdapter: %v", err)
}
if adapter == nil {
t.Fatal("RequestAdapter returned nil")
}
defer adapter.Release()
info := adapter.Info()
if info.Name == "" {
t.Error("adapter info Name is empty")
}
if info.Driver == "" {
t.Error("adapter info Driver is empty")
}
t.Logf("adapter: name=%q driver=%q vendor=%q deviceType=%v",
info.Name, info.Driver, info.Vendor, info.DeviceType)
}
// --- Device tests ---
// TestIntegrationRequestDevice verifies device creation produces a working device
// with queue and non-zero limits.
func TestIntegrationRequestDevice(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
q := device.Queue()
if q == nil {
t.Fatal("device.Queue() returned nil")
}
limits := device.Limits()
if limits.MaxBufferSize == 0 {
t.Error("device limits MaxBufferSize should be non-zero")
}
if limits.MaxTextureDimension2D == 0 {
t.Error("device limits MaxTextureDimension2D should be non-zero")
}
}
// --- Buffer tests ---
// TestIntegrationCreateBuffer creates a buffer and verifies Size, Usage, and Label.
func TestIntegrationCreateBuffer(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
desc := &wgpu.BufferDescriptor{
Label: "integration-buffer",
Size: 1024,
Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopyDst | wgpu.BufferUsageCopySrc,
}
buf, err := device.CreateBuffer(desc)
if err != nil {
t.Fatalf("CreateBuffer: %v", err)
}
defer buf.Release()
if buf.Size() != desc.Size {
t.Errorf("Size() = %d, want %d", buf.Size(), desc.Size)
}
if buf.Usage() != desc.Usage {
t.Errorf("Usage() = %v, want %v", buf.Usage(), desc.Usage)
}
if buf.Label() != desc.Label {
t.Errorf("Label() = %q, want %q", buf.Label(), desc.Label)
}
}
// --- Texture tests ---
// TestIntegrationCreateTexture creates a texture and verifies its format.
func TestIntegrationCreateTexture(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
tex, err := device.CreateTexture(&wgpu.TextureDescriptor{
Label: "integration-texture",
Size: wgpu.Extent3D{Width: 128, Height: 128, DepthOrArrayLayers: 1},
MipLevelCount: 1,
SampleCount: 1,
Dimension: gputypes.TextureDimension2D,
Format: wgpu.TextureFormatRGBA8Unorm,
Usage: wgpu.TextureUsageTextureBinding | wgpu.TextureUsageCopyDst,
})
if err != nil {
t.Fatalf("CreateTexture: %v", err)
}
defer tex.Release()
if tex.Format() != wgpu.TextureFormatRGBA8Unorm {
t.Errorf("Format() = %v, want RGBA8Unorm", tex.Format())
}
}
// TestIntegrationCreateTextureView creates a texture and then a view into it.
func TestIntegrationCreateTextureView(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
tex, err := device.CreateTexture(&wgpu.TextureDescriptor{
Label: "view-texture",
Size: wgpu.Extent3D{Width: 64, Height: 64, DepthOrArrayLayers: 1},
MipLevelCount: 1,
SampleCount: 1,
Dimension: gputypes.TextureDimension2D,
Format: wgpu.TextureFormatRGBA8Unorm,
Usage: wgpu.TextureUsageTextureBinding,
})
if err != nil {
t.Fatalf("CreateTexture: %v", err)
}
defer tex.Release()
view, err := device.CreateTextureView(tex, &wgpu.TextureViewDescriptor{
Label: "integration-view",
Format: wgpu.TextureFormatRGBA8Unorm,
BaseMipLevel: 0,
MipLevelCount: 1,
BaseArrayLayer: 0,
ArrayLayerCount: 1,
})
if err != nil {
t.Fatalf("CreateTextureView: %v", err)
}
view.Release()
}
// --- Sampler tests ---
// TestIntegrationCreateSampler creates a sampler with explicit and nil descriptors.
func TestIntegrationCreateSampler(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
sampler, err := device.CreateSampler(&wgpu.SamplerDescriptor{
Label: "integration-sampler",
LodMinClamp: 0,
LodMaxClamp: 32,
Anisotropy: 1,
})
if err != nil {
t.Fatalf("CreateSampler: %v", err)
}
defer sampler.Release()
// nil descriptor creates a default sampler.
samplerDefault, err := device.CreateSampler(nil)
if err != nil {
t.Fatalf("CreateSampler(nil): %v", err)
}
samplerDefault.Release()
}
// --- Shader module tests ---
// TestIntegrationCreateShaderModule creates a shader module with WGSL source.
func TestIntegrationCreateShaderModule(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
mod, err := device.CreateShaderModule(&wgpu.ShaderModuleDescriptor{
Label: "integration-shader",
WGSL: `
@group(0) @binding(0)
var<storage, read_write> data: array<u32>;
@compute @workgroup_size(1)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
data[id.x] = data[id.x] * 2u;
}
`,
})
if err != nil {
t.Fatalf("CreateShaderModule: %v", err)
}
mod.Release()
}
// --- Bind group layout tests ---
// TestIntegrationCreateBindGroupLayout creates a bind group layout with a storage
// buffer entry.
func TestIntegrationCreateBindGroupLayout(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
layout, err := device.CreateBindGroupLayout(&wgpu.BindGroupLayoutDescriptor{
Label: "integration-bgl",
Entries: []wgpu.BindGroupLayoutEntry{
{
Binding: 0,
Visibility: wgpu.ShaderStageCompute,
Buffer: &gputypes.BufferBindingLayout{
Type: gputypes.BufferBindingTypeStorage,
},
},
},
})
if err != nil {
t.Fatalf("CreateBindGroupLayout: %v", err)
}
layout.Release()
}
// --- Pipeline layout tests ---
// TestIntegrationCreatePipelineLayout creates a pipeline layout with one bind group
// layout containing a storage buffer entry.
func TestIntegrationCreatePipelineLayout(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
bgl, err := device.CreateBindGroupLayout(&wgpu.BindGroupLayoutDescriptor{
Label: "pipeline-bgl",
Entries: []wgpu.BindGroupLayoutEntry{
{
Binding: 0,
Visibility: wgpu.ShaderStageCompute,
Buffer: &gputypes.BufferBindingLayout{
Type: gputypes.BufferBindingTypeStorage,
},
},
},
})
if err != nil {
t.Fatalf("CreateBindGroupLayout: %v", err)
}
defer bgl.Release()
pipelineLayout, err := device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
Label: "integration-pipeline-layout",
BindGroupLayouts: []*wgpu.BindGroupLayout{bgl},
})
if err != nil {
t.Fatalf("CreatePipelineLayout: %v", err)
}
pipelineLayout.Release()
}
// --- Command encoder tests ---
// TestIntegrationCreateCommandEncoder creates a command encoder, records nothing,
// and finishes it to produce a CommandBuffer.
func TestIntegrationCreateCommandEncoder(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
encoder, err := device.CreateCommandEncoder(&wgpu.CommandEncoderDescriptor{
Label: "integration-encoder",
})
if err != nil {
t.Fatalf("CreateCommandEncoder: %v", err)
}
cmdBuf, err := encoder.Finish()
if err != nil {
t.Fatalf("Finish: %v", err)
}
if cmdBuf == nil {
t.Fatal("Finish returned nil CommandBuffer")
}
}
// --- Queue tests ---
// TestIntegrationQueueWriteBuffer writes data to a buffer using Queue.WriteBuffer.
func TestIntegrationQueueWriteBuffer(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
buf, err := device.CreateBuffer(&wgpu.BufferDescriptor{
Label: "write-test-buf",
Size: 256,
Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopyDst,
})
if err != nil {
t.Fatalf("CreateBuffer: %v", err)
}
defer buf.Release()
q := device.Queue()
if q == nil {
t.Fatal("Queue is nil")
}
data := make([]byte, 16)
for i := range data {
data[i] = byte(i + 1)
}
// WriteBuffer should not panic and should store the data.
if err := q.WriteBuffer(buf, 0, data); err != nil {
t.Fatalf("WriteBuffer failed: %v", err)
}
}
// --- WaitIdle tests ---
// TestIntegrationDeviceWaitIdle verifies WaitIdle returns without error on a
// fresh device with no pending work.
func TestIntegrationDeviceWaitIdle(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
err := device.WaitIdle()
if err != nil {
t.Fatalf("WaitIdle: %v", err)
}
}
// --- Full compute workflow ---
// TestIntegrationFullComputeWorkflow exercises the full compute pipeline creation
// workflow: shader -> bind group layout -> pipeline layout -> compute pipeline ->
// bind group -> encoder -> compute pass -> dispatch -> finish -> submit.
//
// The software backend does NOT support compute pipelines and returns an error.
// In that case, the test still exercises everything else and submits an empty
// command buffer.
func TestIntegrationFullComputeWorkflow(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
// 1. Create shader module.
shader, err := device.CreateShaderModule(&wgpu.ShaderModuleDescriptor{
Label: "compute-workflow-shader",
WGSL: `
@group(0) @binding(0)
var<storage, read_write> data: array<u32>;
@compute @workgroup_size(1)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
data[id.x] = data[id.x] * 2u;
}
`,
})
if err != nil {
t.Fatalf("CreateShaderModule: %v", err)
}
defer shader.Release()
// 2. Create bind group layout.
bgl, err := device.CreateBindGroupLayout(&wgpu.BindGroupLayoutDescriptor{
Label: "compute-bgl",
Entries: []wgpu.BindGroupLayoutEntry{
{
Binding: 0,
Visibility: wgpu.ShaderStageCompute,
Buffer: &gputypes.BufferBindingLayout{
Type: gputypes.BufferBindingTypeStorage,
},
},
},
})
if err != nil {
t.Fatalf("CreateBindGroupLayout: %v", err)
}
defer bgl.Release()
// 3. Create pipeline layout.
pipelineLayout, err := device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
Label: "compute-pipeline-layout",
BindGroupLayouts: []*wgpu.BindGroupLayout{bgl},
})
if err != nil {
t.Fatalf("CreatePipelineLayout: %v", err)
}
defer pipelineLayout.Release()
// 4. Attempt to create compute pipeline.
// The software backend returns ErrComputeNotSupported.
computePipeline, cpErr := device.CreateComputePipeline(&wgpu.ComputePipelineDescriptor{
Label: "compute-pipeline",
Layout: pipelineLayout,
Module: shader,
EntryPoint: "main",
})
if cpErr != nil {
t.Logf("CreateComputePipeline returned expected error: %v", cpErr)
} else {
defer computePipeline.Release()
}
// 5. Create a storage buffer.
buf, err := device.CreateBuffer(&wgpu.BufferDescriptor{
Label: "compute-data-buf",
Size: 256,
Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopyDst | wgpu.BufferUsageCopySrc,
})
if err != nil {
t.Fatalf("CreateBuffer: %v", err)
}
defer buf.Release()
// 6. Create bind group.
bg, err := device.CreateBindGroup(&wgpu.BindGroupDescriptor{
Label: "compute-bind-group",
Layout: bgl,
Entries: []wgpu.BindGroupEntry{
{
Binding: 0,
Buffer: buf,
Offset: 0,
Size: 256,
},
},
})
if err != nil {
t.Fatalf("CreateBindGroup: %v", err)
}
defer bg.Release()
// 7. Create command encoder, begin compute pass, dispatch, end, finish, submit.
encoder, err := device.CreateCommandEncoder(&wgpu.CommandEncoderDescriptor{
Label: "compute-encoder",
})
if err != nil {
t.Fatalf("CreateCommandEncoder: %v", err)
}
pass, err := encoder.BeginComputePass(&wgpu.ComputePassDescriptor{
Label: "compute-pass",
})
if err != nil {
t.Fatalf("BeginComputePass: %v", err)
}
// SetPipeline and SetBindGroup are recorded even if the compute pipeline
// creation failed (they are no-ops in that case).
if computePipeline != nil {
pass.SetPipeline(computePipeline)
pass.SetBindGroup(0, bg, nil)
pass.Dispatch(1, 1, 1)
}
err = pass.End()
if err != nil {
// End may fail if pipeline was never set (software backend doesn't support compute)
t.Logf("End: %v (expected on software backend)", err)
}
cmdBuf, err := encoder.Finish()
if err != nil {
t.Fatalf("Finish: %v", err)
}
_, err = device.Queue().Submit(cmdBuf)
if err != nil {
t.Fatalf("Submit: %v", err)
}
}
// --- Read buffer tests ---
// TestIntegrationQueueReadBuffer writes data to a buffer via Queue.WriteBuffer,
// reads it back via Buffer.Map + MappedRange (the WebGPU spec-compliant
// replacement for the removed Queue.ReadBuffer), and verifies the
// contents match.
func TestIntegrationQueueReadBuffer(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
const bufSize = 64
buf, err := device.CreateBuffer(&wgpu.BufferDescriptor{
Label: "readback-buf",
Size: bufSize,
Usage: wgpu.BufferUsageMapRead | wgpu.BufferUsageCopyDst,
})
if err != nil {
t.Fatalf("CreateBuffer: %v", err)
}
defer buf.Release()
q := device.Queue()
if q == nil {
t.Fatal("Queue is nil")
}
// Write 16 uint32 values (4 bytes each = 64 bytes total).
writeData := make([]byte, bufSize)
for i := 0; i < 16; i++ {
binary.LittleEndian.PutUint32(writeData[i*4:], uint32(i*10+1))
}
if err := q.WriteBuffer(buf, 0, writeData); err != nil {
t.Fatalf("WriteBuffer failed: %v", err)
}
// Read it back via the WebGPU buffer map API.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := buf.Map(ctx, wgpu.MapModeRead, 0, bufSize); err != nil {
t.Fatalf("Map: %v", err)
}
rng, rErr := buf.MappedRange(0, bufSize)
if rErr != nil {
_ = buf.Unmap()
t.Fatalf("MappedRange: %v", rErr)
}
readData := make([]byte, bufSize)
copy(readData, rng.Bytes())
if err := buf.Unmap(); err != nil {
t.Fatalf("Unmap: %v", err)
}
// Verify contents match.
for i := 0; i < 16; i++ {
got := binary.LittleEndian.Uint32(readData[i*4:])
want := uint32(i*10 + 1)
if got != want {
t.Errorf("readData[%d] = %d, want %d", i, got, want)
}
}
}
// --- Write texture tests ---
// TestIntegrationQueueWriteTexture creates a texture, writes data to it via Queue.WriteTexture, and verifies the call succeeds. The test does not read
// back the texture data, but it verifies the full integration path for writing texture data.
func TestIntegrationQueueWriteTexture(t *testing.T) {
instance, adapter, device := createTestDevice(t)
defer instance.Release()
defer adapter.Release()
defer device.Release()
tex, err := device.CreateTexture(&wgpu.TextureDescriptor{
Label: "write-texture",
Size: wgpu.Extent3D{Width: 2, Height: 2, DepthOrArrayLayers: 1},
MipLevelCount: 1,
SampleCount: 1,
Dimension: gputypes.TextureDimension2D,
Format: wgpu.TextureFormatRGBA8Unorm,
Usage: wgpu.TextureUsageTextureBinding | wgpu.TextureUsageCopyDst,
ViewFormats: []wgpu.TextureFormat{wgpu.TextureFormatRGBA8Unorm},
})
if err != nil {
t.Fatalf("CreateTexture: %v", err)
}
defer tex.Release()
q := device.Queue()
if q == nil {
t.Fatal("Queue is nil")
}
writeData := []byte{
255, 0, 0, 255, // red
0, 255, 0, 255, // green
0, 0, 255, 255, // blue
255, 255, 0, 255, // yellow
}
layout := &wgpu.ImageDataLayout{
Offset: 0,
BytesPerRow: 8,
RowsPerImage: 0,
}
copyTexture := &wgpu.ImageCopyTexture{
Texture: tex,
MipLevel: 0,
Origin: wgpu.Origin3D{X: 0, Y: 0, Z: 0},
Aspect: gputypes.TextureAspectAll,
}
size := &wgpu.Extent3D{Width: 2, Height: 2, DepthOrArrayLayers: 1}
err = q.WriteTexture(copyTexture, writeData, layout, size)
if err != nil {
t.Fatalf("WriteTexture: %v", err)
}
}