@driftengine/core 4.1.1 → 4.1.2
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.
|
@@ -613,6 +613,18 @@ export declare class GpuDrivenPass implements PassDefinition {
|
|
|
613
613
|
* origin so nothing ever said so. `StreamingScene` computes them when a mesh is placed.
|
|
614
614
|
*/
|
|
615
615
|
private buildBuffers;
|
|
616
|
+
/**
|
|
617
|
+
* Close the two scopes the blend pipeline was built in, and disown it where the device refused.
|
|
618
|
+
*
|
|
619
|
+
* **Asynchronous because a pipeline failure is.** Nothing can be known at the call that creates
|
|
620
|
+
* it, so the first frames draw no transparency and then either it arrives or it never does. That
|
|
621
|
+
* is the honest order: a pane missing for two frames is invisible, and a frame missing entirely
|
|
622
|
+
* is what this exists to prevent.
|
|
623
|
+
*
|
|
624
|
+
* The warning is once, in the words the device used, because the whole difficulty of the report
|
|
625
|
+
* this came from was five errors that were all the same one.
|
|
626
|
+
*/
|
|
627
|
+
private watchBlendPipeline;
|
|
616
628
|
private buildPipelines;
|
|
617
629
|
/**
|
|
618
630
|
* The groups that name `history` and `drawn`, rebuilt when the two swap.
|
|
@@ -941,6 +941,29 @@ export class GpuDrivenPass {
|
|
|
941
941
|
this.zeroHistory = this.empty(device, 'history zero', count * 4);
|
|
942
942
|
this.historySink = this.empty(device, 'history sink', count * 4);
|
|
943
943
|
}
|
|
944
|
+
/**
|
|
945
|
+
* Close the two scopes the blend pipeline was built in, and disown it where the device refused.
|
|
946
|
+
*
|
|
947
|
+
* **Asynchronous because a pipeline failure is.** Nothing can be known at the call that creates
|
|
948
|
+
* it, so the first frames draw no transparency and then either it arrives or it never does. That
|
|
949
|
+
* is the honest order: a pane missing for two frames is invisible, and a frame missing entirely
|
|
950
|
+
* is what this exists to prevent.
|
|
951
|
+
*
|
|
952
|
+
* The warning is once, in the words the device used, because the whole difficulty of the report
|
|
953
|
+
* this came from was five errors that were all the same one.
|
|
954
|
+
*/
|
|
955
|
+
watchBlendPipeline(device) {
|
|
956
|
+
const refused = (error, scope) => {
|
|
957
|
+
if (error === null || this.blendPipeline === null)
|
|
958
|
+
return;
|
|
959
|
+
this.blendPipeline = null;
|
|
960
|
+
this.blendRasterGroup = null;
|
|
961
|
+
console.warn(`[driftengine] this device refused the gpu-driven blend pipeline (${scope}), so the ` +
|
|
962
|
+
`transparent pass is off and everything opaque still draws: ${error.message}`);
|
|
963
|
+
};
|
|
964
|
+
void device.popErrorScope().then((error) => refused(error, 'internal'));
|
|
965
|
+
void device.popErrorScope().then((error) => refused(error, 'validation'));
|
|
966
|
+
}
|
|
944
967
|
buildPipelines(device, format, samples) {
|
|
945
968
|
const compute = (label, code) => device.createComputePipeline({
|
|
946
969
|
label: `gpu-driven ${label}`,
|
|
@@ -1045,6 +1068,23 @@ export class GpuDrivenPass {
|
|
|
1045
1068
|
* hide. The opaque raster culls back faces, where a closed hull's far side is a wasted draw.
|
|
1046
1069
|
*/
|
|
1047
1070
|
const blend = device.createShaderModule({ label: 'blend raster', code: BLEND_RASTER_WGSL });
|
|
1071
|
+
/*
|
|
1072
|
+
* **Built inside error scopes, because a driver that refuses this must not take the frame.**
|
|
1073
|
+
*
|
|
1074
|
+
* Reported from a Galaxy S23 Ultra: `CreateGraphicsPipelines failed with VK_ERROR_UNKNOWN`
|
|
1075
|
+
* for this pipeline, and then four more errors that were all the same one. `createRenderPipeline`
|
|
1076
|
+
* does not throw where a driver refuses — it hands back an *invalid* pipeline and reports
|
|
1077
|
+
* asynchronously — so the code that followed built a bind group from its layout, set it on a
|
|
1078
|
+
* pass, and submitted a command buffer, each of which is invalid because the one before it was.
|
|
1079
|
+
* The city drew **nothing**, on a device that could have drawn all of it but the glass.
|
|
1080
|
+
*
|
|
1081
|
+
* So the handle is disowned where the device refused it and the transparent pass skips itself.
|
|
1082
|
+
* Both scopes, because a shader a driver cannot compile is reported as internal on some
|
|
1083
|
+
* backends and as validation on others, and this is exactly the case where guessing which
|
|
1084
|
+
* costs the whole frame.
|
|
1085
|
+
*/
|
|
1086
|
+
device.pushErrorScope('validation');
|
|
1087
|
+
device.pushErrorScope('internal');
|
|
1048
1088
|
this.blendPipeline = device.createRenderPipeline({
|
|
1049
1089
|
label: 'gpu-driven blend',
|
|
1050
1090
|
layout: 'auto',
|
|
@@ -1077,6 +1117,7 @@ export class GpuDrivenPass {
|
|
|
1077
1117
|
depthCompare: DEPTH_COMPARE,
|
|
1078
1118
|
},
|
|
1079
1119
|
});
|
|
1120
|
+
this.watchBlendPipeline(device);
|
|
1080
1121
|
/*
|
|
1081
1122
|
* **Premultiplied over, where `renderer.ts`'s composite is a lerp**, and `blendRaster.wgsl.ts`
|
|
1082
1123
|
* carries why: this pipeline's colour target has a coverage mask in its alpha, which the blit
|
|
@@ -1585,30 +1626,42 @@ export class GpuDrivenPass {
|
|
|
1585
1626
|
],
|
|
1586
1627
|
});
|
|
1587
1628
|
/* Everything the transparent raster reads: the same scene, the same frame, its own list. */
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1629
|
+
/*
|
|
1630
|
+
* Nothing to bind against where the device refused the pipeline, and the resolve below still
|
|
1631
|
+
* wants building — it is a different pipeline and this one's absence is not its problem. An
|
|
1632
|
+
* early return here would have taken it with us, which is the same shape of mistake as the
|
|
1633
|
+
* cascade this guard exists to stop.
|
|
1634
|
+
*/
|
|
1635
|
+
this.blendRasterGroup =
|
|
1636
|
+
this.blendPipeline === null
|
|
1637
|
+
? null
|
|
1638
|
+
: device.createBindGroup({
|
|
1639
|
+
layout: this.blendPipeline.getBindGroupLayout(0),
|
|
1640
|
+
entries: [
|
|
1641
|
+
{ binding: 0, resource: { buffer: this.viewProjBuffer } },
|
|
1642
|
+
{ binding: 1, resource: { buffer: this.blendList } },
|
|
1643
|
+
{ binding: 2, resource: { buffer: this.clusterMeta } },
|
|
1644
|
+
{ binding: 3, resource: { buffer: this.indices } },
|
|
1645
|
+
{ binding: 4, resource: { buffer: this.vertices } },
|
|
1646
|
+
{ binding: 5, resource: { buffer: this.transformBuffer } },
|
|
1647
|
+
{ binding: 6, resource: { buffer: this.materialOf } },
|
|
1648
|
+
{ binding: 7, resource: { buffer: this.materialTable } },
|
|
1649
|
+
{ binding: 8, resource: { buffer: this.frameBuffer } },
|
|
1650
|
+
{
|
|
1651
|
+
binding: 9,
|
|
1652
|
+
resource: this.environmentView ?? this.emptyEnvironmentView,
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
binding: 10,
|
|
1656
|
+
resource: this.environmentSampler ?? this.emptySampler,
|
|
1657
|
+
},
|
|
1658
|
+
{ binding: 11, resource: this.latentsView },
|
|
1659
|
+
{ binding: 12, resource: this.latentClamp },
|
|
1660
|
+
{ binding: 13, resource: this.latentRepeat },
|
|
1661
|
+
{ binding: 14, resource: { buffer: this.decodeNodes } },
|
|
1662
|
+
{ binding: 15, resource: { buffer: this.decodeWeights } },
|
|
1663
|
+
],
|
|
1664
|
+
});
|
|
1612
1665
|
this.blendResolveGroup = device.createBindGroup({
|
|
1613
1666
|
layout: this.blendResolvePipeline.getBindGroupLayout(0),
|
|
1614
1667
|
entries: [
|
|
@@ -2028,6 +2081,13 @@ export class GpuDrivenPass {
|
|
|
2028
2081
|
return;
|
|
2029
2082
|
}
|
|
2030
2083
|
case 'blendDraw': {
|
|
2084
|
+
/*
|
|
2085
|
+
* **Skipped where the device refused the pipeline, rather than drawn with an invalid one.**
|
|
2086
|
+
* Everything opaque has already been drawn by the time this runs, so what is lost is the
|
|
2087
|
+
* glass and not the city. See `watchBlendPipeline`.
|
|
2088
|
+
*/
|
|
2089
|
+
if (this.blendPipeline === null || this.blendRasterGroup === null)
|
|
2090
|
+
return;
|
|
2031
2091
|
const pass = encoder.beginRenderPass({
|
|
2032
2092
|
label: 'gpu-driven blend draw',
|
|
2033
2093
|
colorAttachments: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftengine/core",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "DriftEngine reusable browser 3D runtime",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@driftengine/drft": "4.1.
|
|
33
|
-
"@driftengine/physics": "4.1.
|
|
32
|
+
"@driftengine/drft": "4.1.2",
|
|
33
|
+
"@driftengine/physics": "4.1.2",
|
|
34
34
|
"gl-matrix": "^3.4.3"
|
|
35
35
|
},
|
|
36
36
|
"author": "Drift Technologies",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"prepack": "node ../../scripts/build.mjs @driftengine/core"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@driftengine/nav": "4.1.
|
|
67
|
-
"@driftengine/network": "4.1.
|
|
68
|
-
"@driftengine/texture": "4.1.
|
|
66
|
+
"@driftengine/nav": "4.1.2",
|
|
67
|
+
"@driftengine/network": "4.1.2",
|
|
68
|
+
"@driftengine/texture": "4.1.2"
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -1299,6 +1299,31 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
1299
1299
|
this.historySink = this.empty(device, 'history sink', count * 4);
|
|
1300
1300
|
}
|
|
1301
1301
|
|
|
1302
|
+
/**
|
|
1303
|
+
* Close the two scopes the blend pipeline was built in, and disown it where the device refused.
|
|
1304
|
+
*
|
|
1305
|
+
* **Asynchronous because a pipeline failure is.** Nothing can be known at the call that creates
|
|
1306
|
+
* it, so the first frames draw no transparency and then either it arrives or it never does. That
|
|
1307
|
+
* is the honest order: a pane missing for two frames is invisible, and a frame missing entirely
|
|
1308
|
+
* is what this exists to prevent.
|
|
1309
|
+
*
|
|
1310
|
+
* The warning is once, in the words the device used, because the whole difficulty of the report
|
|
1311
|
+
* this came from was five errors that were all the same one.
|
|
1312
|
+
*/
|
|
1313
|
+
private watchBlendPipeline(device: GPUDevice): void {
|
|
1314
|
+
const refused = (error: GPUError | null, scope: string): void => {
|
|
1315
|
+
if (error === null || this.blendPipeline === null) return;
|
|
1316
|
+
this.blendPipeline = null;
|
|
1317
|
+
this.blendRasterGroup = null;
|
|
1318
|
+
console.warn(
|
|
1319
|
+
`[driftengine] this device refused the gpu-driven blend pipeline (${scope}), so the ` +
|
|
1320
|
+
`transparent pass is off and everything opaque still draws: ${error.message}`,
|
|
1321
|
+
);
|
|
1322
|
+
};
|
|
1323
|
+
void device.popErrorScope().then((error) => refused(error, 'internal'));
|
|
1324
|
+
void device.popErrorScope().then((error) => refused(error, 'validation'));
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1302
1327
|
private buildPipelines(device: GPUDevice, format: GPUTextureFormat, samples: number): void {
|
|
1303
1328
|
const compute = (label: string, code: string): GPUComputePipeline =>
|
|
1304
1329
|
device.createComputePipeline({
|
|
@@ -1409,6 +1434,23 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
1409
1434
|
* hide. The opaque raster culls back faces, where a closed hull's far side is a wasted draw.
|
|
1410
1435
|
*/
|
|
1411
1436
|
const blend = device.createShaderModule({ label: 'blend raster', code: BLEND_RASTER_WGSL });
|
|
1437
|
+
/*
|
|
1438
|
+
* **Built inside error scopes, because a driver that refuses this must not take the frame.**
|
|
1439
|
+
*
|
|
1440
|
+
* Reported from a Galaxy S23 Ultra: `CreateGraphicsPipelines failed with VK_ERROR_UNKNOWN`
|
|
1441
|
+
* for this pipeline, and then four more errors that were all the same one. `createRenderPipeline`
|
|
1442
|
+
* does not throw where a driver refuses — it hands back an *invalid* pipeline and reports
|
|
1443
|
+
* asynchronously — so the code that followed built a bind group from its layout, set it on a
|
|
1444
|
+
* pass, and submitted a command buffer, each of which is invalid because the one before it was.
|
|
1445
|
+
* The city drew **nothing**, on a device that could have drawn all of it but the glass.
|
|
1446
|
+
*
|
|
1447
|
+
* So the handle is disowned where the device refused it and the transparent pass skips itself.
|
|
1448
|
+
* Both scopes, because a shader a driver cannot compile is reported as internal on some
|
|
1449
|
+
* backends and as validation on others, and this is exactly the case where guessing which
|
|
1450
|
+
* costs the whole frame.
|
|
1451
|
+
*/
|
|
1452
|
+
device.pushErrorScope('validation');
|
|
1453
|
+
device.pushErrorScope('internal');
|
|
1412
1454
|
this.blendPipeline = device.createRenderPipeline({
|
|
1413
1455
|
label: 'gpu-driven blend',
|
|
1414
1456
|
layout: 'auto',
|
|
@@ -1441,6 +1483,7 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
1441
1483
|
depthCompare: DEPTH_COMPARE,
|
|
1442
1484
|
},
|
|
1443
1485
|
});
|
|
1486
|
+
this.watchBlendPipeline(device);
|
|
1444
1487
|
|
|
1445
1488
|
/*
|
|
1446
1489
|
* **Premultiplied over, where `renderer.ts`'s composite is a lerp**, and `blendRaster.wgsl.ts`
|
|
@@ -2015,30 +2058,42 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
2015
2058
|
});
|
|
2016
2059
|
|
|
2017
2060
|
/* Everything the transparent raster reads: the same scene, the same frame, its own list. */
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2061
|
+
/*
|
|
2062
|
+
* Nothing to bind against where the device refused the pipeline, and the resolve below still
|
|
2063
|
+
* wants building — it is a different pipeline and this one's absence is not its problem. An
|
|
2064
|
+
* early return here would have taken it with us, which is the same shape of mistake as the
|
|
2065
|
+
* cascade this guard exists to stop.
|
|
2066
|
+
*/
|
|
2067
|
+
this.blendRasterGroup =
|
|
2068
|
+
this.blendPipeline === null
|
|
2069
|
+
? null
|
|
2070
|
+
: device.createBindGroup({
|
|
2071
|
+
layout: this.blendPipeline.getBindGroupLayout(0),
|
|
2072
|
+
entries: [
|
|
2073
|
+
{ binding: 0, resource: { buffer: this.viewProjBuffer as GPUBuffer } },
|
|
2074
|
+
{ binding: 1, resource: { buffer: this.blendList as GPUBuffer } },
|
|
2075
|
+
{ binding: 2, resource: { buffer: this.clusterMeta as GPUBuffer } },
|
|
2076
|
+
{ binding: 3, resource: { buffer: this.indices as GPUBuffer } },
|
|
2077
|
+
{ binding: 4, resource: { buffer: this.vertices as GPUBuffer } },
|
|
2078
|
+
{ binding: 5, resource: { buffer: this.transformBuffer as GPUBuffer } },
|
|
2079
|
+
{ binding: 6, resource: { buffer: this.materialOf as GPUBuffer } },
|
|
2080
|
+
{ binding: 7, resource: { buffer: this.materialTable as GPUBuffer } },
|
|
2081
|
+
{ binding: 8, resource: { buffer: this.frameBuffer as GPUBuffer } },
|
|
2082
|
+
{
|
|
2083
|
+
binding: 9,
|
|
2084
|
+
resource: this.environmentView ?? (this.emptyEnvironmentView as GPUTextureView),
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
binding: 10,
|
|
2088
|
+
resource: this.environmentSampler ?? (this.emptySampler as GPUSampler),
|
|
2089
|
+
},
|
|
2090
|
+
{ binding: 11, resource: this.latentsView as GPUTextureView },
|
|
2091
|
+
{ binding: 12, resource: this.latentClamp as GPUSampler },
|
|
2092
|
+
{ binding: 13, resource: this.latentRepeat as GPUSampler },
|
|
2093
|
+
{ binding: 14, resource: { buffer: this.decodeNodes as GPUBuffer } },
|
|
2094
|
+
{ binding: 15, resource: { buffer: this.decodeWeights as GPUBuffer } },
|
|
2095
|
+
],
|
|
2096
|
+
});
|
|
2042
2097
|
this.blendResolveGroup = device.createBindGroup({
|
|
2043
2098
|
layout: (this.blendResolvePipeline as GPURenderPipeline).getBindGroupLayout(0),
|
|
2044
2099
|
entries: [
|
|
@@ -2519,6 +2574,12 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
2519
2574
|
return;
|
|
2520
2575
|
}
|
|
2521
2576
|
case 'blendDraw': {
|
|
2577
|
+
/*
|
|
2578
|
+
* **Skipped where the device refused the pipeline, rather than drawn with an invalid one.**
|
|
2579
|
+
* Everything opaque has already been drawn by the time this runs, so what is lost is the
|
|
2580
|
+
* glass and not the city. See `watchBlendPipeline`.
|
|
2581
|
+
*/
|
|
2582
|
+
if (this.blendPipeline === null || this.blendRasterGroup === null) return;
|
|
2522
2583
|
const pass = encoder.beginRenderPass({
|
|
2523
2584
|
label: 'gpu-driven blend draw',
|
|
2524
2585
|
colorAttachments: [
|
|
@@ -2546,8 +2607,8 @@ export class GpuDrivenPass implements PassDefinition {
|
|
|
2546
2607
|
},
|
|
2547
2608
|
timestampWrites: this.writesFor('blendDraw'),
|
|
2548
2609
|
});
|
|
2549
|
-
pass.setPipeline(this.blendPipeline
|
|
2550
|
-
pass.setBindGroup(0, this.blendRasterGroup
|
|
2610
|
+
pass.setPipeline(this.blendPipeline);
|
|
2611
|
+
pass.setBindGroup(0, this.blendRasterGroup);
|
|
2551
2612
|
pass.setViewport(0, 0, this.width, this.height, 0, 1);
|
|
2552
2613
|
pass.drawIndirect(this.blendArgs as GPUBuffer, 0);
|
|
2553
2614
|
pass.end();
|