reze-engine 0.2.3 → 0.2.5
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.
- package/dist/engine.d.ts +11 -10
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +165 -219
- package/package.json +1 -1
- package/src/engine.ts +177 -235
package/dist/engine.js
CHANGED
|
@@ -9,7 +9,11 @@ export class Engine {
|
|
|
9
9
|
this.lightData = new Float32Array(64);
|
|
10
10
|
this.lightCount = 0;
|
|
11
11
|
this.resizeObserver = null;
|
|
12
|
-
this.sampleCount = 4;
|
|
12
|
+
this.sampleCount = 4;
|
|
13
|
+
// Constants
|
|
14
|
+
this.STENCIL_EYE_VALUE = 1;
|
|
15
|
+
this.COMPUTE_WORKGROUP_SIZE = 64;
|
|
16
|
+
this.BLOOM_DOWNSCALE_FACTOR = 2;
|
|
13
17
|
// Ambient light settings
|
|
14
18
|
this.ambient = 1.0;
|
|
15
19
|
// Bloom settings
|
|
@@ -22,7 +26,6 @@ export class Engine {
|
|
|
22
26
|
this.modelDir = "";
|
|
23
27
|
this.physics = null;
|
|
24
28
|
this.textureCache = new Map();
|
|
25
|
-
this.textureSizes = new Map();
|
|
26
29
|
this.lastFpsUpdate = performance.now();
|
|
27
30
|
this.framesSinceLastUpdate = 0;
|
|
28
31
|
this.frameTimeSamples = [];
|
|
@@ -38,6 +41,7 @@ export class Engine {
|
|
|
38
41
|
this.renderLoopCallback = null;
|
|
39
42
|
this.animationFrames = [];
|
|
40
43
|
this.animationTimeouts = [];
|
|
44
|
+
this.gpuMemoryMB = 0;
|
|
41
45
|
this.opaqueNonEyeNonHairDraws = [];
|
|
42
46
|
this.eyeDraws = [];
|
|
43
47
|
this.hairDrawsOverEyes = [];
|
|
@@ -50,10 +54,8 @@ export class Engine {
|
|
|
50
54
|
this.canvas = canvas;
|
|
51
55
|
if (options) {
|
|
52
56
|
this.ambient = options.ambient ?? 1.0;
|
|
53
|
-
this.bloomThreshold = options.bloomThreshold ?? 0.3;
|
|
54
57
|
this.bloomIntensity = options.bloomIntensity ?? 0.12;
|
|
55
58
|
this.rimLightIntensity = options.rimLightIntensity ?? 0.45;
|
|
56
|
-
this.rimLightPower = options.rimLightPower ?? 2.0;
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
// Step 1: Get WebGPU device and context
|
|
@@ -214,8 +216,8 @@ export class Engine {
|
|
|
214
216
|
`,
|
|
215
217
|
});
|
|
216
218
|
// Create explicit bind group layout for all pipelines using the main shader
|
|
217
|
-
this.
|
|
218
|
-
label: "
|
|
219
|
+
this.mainBindGroupLayout = this.device.createBindGroupLayout({
|
|
220
|
+
label: "main material bind group layout",
|
|
219
221
|
entries: [
|
|
220
222
|
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // camera
|
|
221
223
|
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // light
|
|
@@ -227,14 +229,13 @@ export class Engine {
|
|
|
227
229
|
{ binding: 7, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // material
|
|
228
230
|
],
|
|
229
231
|
});
|
|
230
|
-
const
|
|
231
|
-
label: "
|
|
232
|
-
bindGroupLayouts: [this.
|
|
232
|
+
const mainPipelineLayout = this.device.createPipelineLayout({
|
|
233
|
+
label: "main pipeline layout",
|
|
234
|
+
bindGroupLayouts: [this.mainBindGroupLayout],
|
|
233
235
|
});
|
|
234
|
-
|
|
235
|
-
this.pipeline = this.device.createRenderPipeline({
|
|
236
|
+
this.modelPipeline = this.device.createRenderPipeline({
|
|
236
237
|
label: "model pipeline",
|
|
237
|
-
layout:
|
|
238
|
+
layout: mainPipelineLayout,
|
|
238
239
|
vertex: {
|
|
239
240
|
module: shaderModule,
|
|
240
241
|
buffers: [
|
|
@@ -437,9 +438,9 @@ export class Engine {
|
|
|
437
438
|
count: this.sampleCount,
|
|
438
439
|
},
|
|
439
440
|
});
|
|
440
|
-
//
|
|
441
|
-
this.
|
|
442
|
-
label: "
|
|
441
|
+
// Hair outline pipeline
|
|
442
|
+
this.hairOutlinePipeline = this.device.createRenderPipeline({
|
|
443
|
+
label: "hair outline pipeline",
|
|
443
444
|
layout: outlinePipelineLayout,
|
|
444
445
|
vertex: {
|
|
445
446
|
module: outlineShaderModule,
|
|
@@ -507,7 +508,7 @@ export class Engine {
|
|
|
507
508
|
// Eye overlay pipeline (renders after opaque, writes stencil)
|
|
508
509
|
this.eyePipeline = this.device.createRenderPipeline({
|
|
509
510
|
label: "eye overlay pipeline",
|
|
510
|
-
layout:
|
|
511
|
+
layout: mainPipelineLayout,
|
|
511
512
|
vertex: {
|
|
512
513
|
module: shaderModule,
|
|
513
514
|
buffers: [
|
|
@@ -620,7 +621,7 @@ export class Engine {
|
|
|
620
621
|
// Hair depth pre-pass pipeline: depth-only with color writes disabled to eliminate overdraw
|
|
621
622
|
this.hairDepthPipeline = this.device.createRenderPipeline({
|
|
622
623
|
label: "hair depth pre-pass",
|
|
623
|
-
layout:
|
|
624
|
+
layout: mainPipelineLayout,
|
|
624
625
|
vertex: {
|
|
625
626
|
module: depthOnlyShaderModule,
|
|
626
627
|
buffers: [
|
|
@@ -659,10 +660,10 @@ export class Engine {
|
|
|
659
660
|
},
|
|
660
661
|
multisample: { count: this.sampleCount },
|
|
661
662
|
});
|
|
662
|
-
//
|
|
663
|
-
this.
|
|
664
|
-
label: "
|
|
665
|
-
layout:
|
|
663
|
+
// Hair pipeline for rendering over eyes (stencil == 1)
|
|
664
|
+
this.hairPipelineOverEyes = this.device.createRenderPipeline({
|
|
665
|
+
label: "hair pipeline (over eyes)",
|
|
666
|
+
layout: mainPipelineLayout,
|
|
666
667
|
vertex: {
|
|
667
668
|
module: shaderModule,
|
|
668
669
|
buffers: [
|
|
@@ -724,10 +725,10 @@ export class Engine {
|
|
|
724
725
|
},
|
|
725
726
|
multisample: { count: this.sampleCount },
|
|
726
727
|
});
|
|
727
|
-
//
|
|
728
|
-
this.
|
|
729
|
-
label: "
|
|
730
|
-
layout:
|
|
728
|
+
// Hair pipeline for rendering over non-eyes (stencil != 1)
|
|
729
|
+
this.hairPipelineOverNonEyes = this.device.createRenderPipeline({
|
|
730
|
+
label: "hair pipeline (over non-eyes)",
|
|
731
|
+
layout: mainPipelineLayout,
|
|
731
732
|
vertex: {
|
|
732
733
|
module: shaderModule,
|
|
733
734
|
buffers: [
|
|
@@ -808,10 +809,9 @@ export class Engine {
|
|
|
808
809
|
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
809
810
|
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
810
811
|
|
|
811
|
-
@compute @workgroup_size(64)
|
|
812
|
+
@compute @workgroup_size(64) // Must match COMPUTE_WORKGROUP_SIZE
|
|
812
813
|
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
813
814
|
let boneIndex = globalId.x;
|
|
814
|
-
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
815
815
|
if (boneIndex >= boneCount.count) {
|
|
816
816
|
return;
|
|
817
817
|
}
|
|
@@ -944,21 +944,16 @@ export class Engine {
|
|
|
944
944
|
@group(0) @binding(1) var inputSampler: sampler;
|
|
945
945
|
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
946
946
|
|
|
947
|
-
//
|
|
947
|
+
// 5-tap gaussian blur
|
|
948
948
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
949
949
|
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
950
950
|
var result = vec4f(0.0);
|
|
951
951
|
|
|
952
|
-
// Gaussian
|
|
953
|
-
let weights = array<f32,
|
|
954
|
-
|
|
955
|
-
0.19459459, 0.22702703,
|
|
956
|
-
0.19459459, 0.12162162, 0.05405405, 0.01621622
|
|
957
|
-
);
|
|
952
|
+
// Optimized 5-tap Gaussian filter (faster, nearly same quality)
|
|
953
|
+
let weights = array<f32, 5>(0.06136, 0.24477, 0.38774, 0.24477, 0.06136);
|
|
954
|
+
let offsets = array<f32, 5>(-2.0, -1.0, 0.0, 1.0, 2.0);
|
|
958
955
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
for (var i = 0u; i < 9u; i++) {
|
|
956
|
+
for (var i = 0u; i < 5u; i++) {
|
|
962
957
|
let offset = offsets[i] * texelSize * blurUniforms.direction;
|
|
963
958
|
result += textureSample(inputTexture, inputSampler, input.uv + offset) * weights[i];
|
|
964
959
|
}
|
|
@@ -1094,11 +1089,9 @@ export class Engine {
|
|
|
1094
1089
|
this.bloomThresholdBuffer = bloomThresholdBuffer;
|
|
1095
1090
|
this.linearSampler = linearSampler;
|
|
1096
1091
|
}
|
|
1097
|
-
// Setup bloom textures and bind groups (called when canvas is resized)
|
|
1098
1092
|
setupBloom(width, height) {
|
|
1099
|
-
|
|
1100
|
-
const
|
|
1101
|
-
const bloomHeight = Math.floor(height / 2);
|
|
1093
|
+
const bloomWidth = Math.floor(width / this.BLOOM_DOWNSCALE_FACTOR);
|
|
1094
|
+
const bloomHeight = Math.floor(height / this.BLOOM_DOWNSCALE_FACTOR);
|
|
1102
1095
|
this.bloomExtractTexture = this.device.createTexture({
|
|
1103
1096
|
label: "bloom extract",
|
|
1104
1097
|
size: [bloomWidth, bloomHeight],
|
|
@@ -1272,7 +1265,6 @@ export class Engine {
|
|
|
1272
1265
|
async loadAnimation(url) {
|
|
1273
1266
|
const frames = await VMDLoader.load(url);
|
|
1274
1267
|
this.animationFrames = frames;
|
|
1275
|
-
console.log(this.animationFrames);
|
|
1276
1268
|
}
|
|
1277
1269
|
playAnimation() {
|
|
1278
1270
|
if (this.animationFrames.length === 0)
|
|
@@ -1334,7 +1326,9 @@ export class Engine {
|
|
|
1334
1326
|
this.physics.reset(worldMats, this.currentModel.getBoneInverseBindMatrices());
|
|
1335
1327
|
// Upload matrices immediately so next frame shows correct pose
|
|
1336
1328
|
this.device.queue.writeBuffer(this.worldMatrixBuffer, 0, worldMats.buffer, worldMats.byteOffset, worldMats.byteLength);
|
|
1337
|
-
this.
|
|
1329
|
+
const encoder = this.device.createCommandEncoder();
|
|
1330
|
+
this.computeSkinMatrices(encoder);
|
|
1331
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1338
1332
|
}
|
|
1339
1333
|
}
|
|
1340
1334
|
for (const [_, keyFrames] of boneKeyFramesByBone.entries()) {
|
|
@@ -1408,6 +1402,14 @@ export class Engine {
|
|
|
1408
1402
|
const dir = pathParts.join("/") + "/";
|
|
1409
1403
|
this.modelDir = dir;
|
|
1410
1404
|
const model = await PmxLoader.load(path);
|
|
1405
|
+
// console.log({
|
|
1406
|
+
// vertices: Array.from(model.getVertices()),
|
|
1407
|
+
// indices: Array.from(model.getIndices()),
|
|
1408
|
+
// materials: model.getMaterials(),
|
|
1409
|
+
// textures: model.getTextures(),
|
|
1410
|
+
// bones: model.getSkeleton().bones,
|
|
1411
|
+
// skinning: { joints: Array.from(model.getSkinning().joints), weights: Array.from(model.getSkinning().weights) },
|
|
1412
|
+
// })
|
|
1411
1413
|
this.physics = new Physics(model.getRigidbodies(), model.getJoints());
|
|
1412
1414
|
await this.setupModelBuffers(model);
|
|
1413
1415
|
}
|
|
@@ -1535,7 +1537,6 @@ export class Engine {
|
|
|
1535
1537
|
});
|
|
1536
1538
|
this.device.queue.writeTexture({ texture: defaultToonTexture }, defaultToonData, { bytesPerRow: 256 * 4 }, [256, 2]);
|
|
1537
1539
|
this.textureCache.set(defaultToonPath, defaultToonTexture);
|
|
1538
|
-
this.textureSizes.set(defaultToonPath, { width: 256, height: 2 });
|
|
1539
1540
|
return defaultToonTexture;
|
|
1540
1541
|
};
|
|
1541
1542
|
this.opaqueNonEyeNonHairDraws = [];
|
|
@@ -1547,10 +1548,10 @@ export class Engine {
|
|
|
1547
1548
|
this.eyeOutlineDraws = [];
|
|
1548
1549
|
this.hairOutlineDraws = [];
|
|
1549
1550
|
this.transparentNonEyeNonHairOutlineDraws = [];
|
|
1550
|
-
let
|
|
1551
|
+
let currentIndexOffset = 0;
|
|
1551
1552
|
for (const mat of materials) {
|
|
1552
|
-
const
|
|
1553
|
-
if (
|
|
1553
|
+
const indexCount = mat.vertexCount;
|
|
1554
|
+
if (indexCount === 0)
|
|
1554
1555
|
continue;
|
|
1555
1556
|
const diffuseTexture = await loadTextureByIndex(mat.diffuseTextureIndex);
|
|
1556
1557
|
if (!diffuseTexture)
|
|
@@ -1578,7 +1579,7 @@ export class Engine {
|
|
|
1578
1579
|
// Create bind groups using the shared bind group layout - All pipelines (main, eye, hair multiply, hair opaque) use the same shader and layout
|
|
1579
1580
|
const bindGroup = this.device.createBindGroup({
|
|
1580
1581
|
label: `material bind group: ${mat.name}`,
|
|
1581
|
-
layout: this.
|
|
1582
|
+
layout: this.mainBindGroupLayout,
|
|
1582
1583
|
entries: [
|
|
1583
1584
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1584
1585
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
@@ -1593,100 +1594,77 @@ export class Engine {
|
|
|
1593
1594
|
// Classify materials into appropriate draw lists
|
|
1594
1595
|
if (mat.isEye) {
|
|
1595
1596
|
this.eyeDraws.push({
|
|
1596
|
-
count:
|
|
1597
|
-
firstIndex:
|
|
1597
|
+
count: indexCount,
|
|
1598
|
+
firstIndex: currentIndexOffset,
|
|
1598
1599
|
bindGroup,
|
|
1599
1600
|
isTransparent,
|
|
1600
1601
|
});
|
|
1601
1602
|
}
|
|
1602
1603
|
else if (mat.isHair) {
|
|
1603
|
-
// Hair materials: create bind groups for
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1638
|
-
{ binding: 3, resource: this.textureSampler },
|
|
1639
|
-
{ binding: 4, resource: { buffer: this.skinMatrixBuffer } },
|
|
1640
|
-
{ binding: 5, resource: toonTexture.createView() },
|
|
1641
|
-
{ binding: 6, resource: this.textureSampler },
|
|
1642
|
-
{ binding: 7, resource: { buffer: materialUniformBufferOverEyes } },
|
|
1643
|
-
],
|
|
1644
|
-
});
|
|
1645
|
-
const bindGroupOverNonEyes = this.device.createBindGroup({
|
|
1646
|
-
label: `material bind group (over non-eyes): ${mat.name}`,
|
|
1647
|
-
layout: this.hairBindGroupLayout,
|
|
1648
|
-
entries: [
|
|
1649
|
-
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1650
|
-
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1651
|
-
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1652
|
-
{ binding: 3, resource: this.textureSampler },
|
|
1653
|
-
{ binding: 4, resource: { buffer: this.skinMatrixBuffer } },
|
|
1654
|
-
{ binding: 5, resource: toonTexture.createView() },
|
|
1655
|
-
{ binding: 6, resource: this.textureSampler },
|
|
1656
|
-
{ binding: 7, resource: { buffer: materialUniformBufferOverNonEyes } },
|
|
1657
|
-
],
|
|
1658
|
-
});
|
|
1659
|
-
// Store both bind groups for unified pipeline
|
|
1604
|
+
// Hair materials: create separate bind groups for over-eyes vs over-non-eyes
|
|
1605
|
+
const createHairBindGroup = (isOverEyes) => {
|
|
1606
|
+
const uniformData = new Float32Array(8);
|
|
1607
|
+
uniformData[0] = materialAlpha;
|
|
1608
|
+
uniformData[1] = 1.0; // alphaMultiplier (shader adjusts based on isOverEyes)
|
|
1609
|
+
uniformData[2] = this.rimLightIntensity;
|
|
1610
|
+
uniformData[3] = this.rimLightPower;
|
|
1611
|
+
uniformData[4] = 1.0; // rimColor.rgb
|
|
1612
|
+
uniformData[5] = 1.0;
|
|
1613
|
+
uniformData[6] = 1.0;
|
|
1614
|
+
uniformData[7] = isOverEyes ? 1.0 : 0.0;
|
|
1615
|
+
const buffer = this.device.createBuffer({
|
|
1616
|
+
label: `material uniform (${isOverEyes ? "over eyes" : "over non-eyes"}): ${mat.name}`,
|
|
1617
|
+
size: uniformData.byteLength,
|
|
1618
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1619
|
+
});
|
|
1620
|
+
this.device.queue.writeBuffer(buffer, 0, uniformData);
|
|
1621
|
+
return this.device.createBindGroup({
|
|
1622
|
+
label: `material bind group (${isOverEyes ? "over eyes" : "over non-eyes"}): ${mat.name}`,
|
|
1623
|
+
layout: this.mainBindGroupLayout,
|
|
1624
|
+
entries: [
|
|
1625
|
+
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1626
|
+
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1627
|
+
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1628
|
+
{ binding: 3, resource: this.textureSampler },
|
|
1629
|
+
{ binding: 4, resource: { buffer: this.skinMatrixBuffer } },
|
|
1630
|
+
{ binding: 5, resource: toonTexture.createView() },
|
|
1631
|
+
{ binding: 6, resource: this.textureSampler },
|
|
1632
|
+
{ binding: 7, resource: { buffer: buffer } },
|
|
1633
|
+
],
|
|
1634
|
+
});
|
|
1635
|
+
};
|
|
1636
|
+
const bindGroupOverEyes = createHairBindGroup(true);
|
|
1637
|
+
const bindGroupOverNonEyes = createHairBindGroup(false);
|
|
1660
1638
|
this.hairDrawsOverEyes.push({
|
|
1661
|
-
count:
|
|
1662
|
-
firstIndex:
|
|
1639
|
+
count: indexCount,
|
|
1640
|
+
firstIndex: currentIndexOffset,
|
|
1663
1641
|
bindGroup: bindGroupOverEyes,
|
|
1664
1642
|
isTransparent,
|
|
1665
1643
|
});
|
|
1666
1644
|
this.hairDrawsOverNonEyes.push({
|
|
1667
|
-
count:
|
|
1668
|
-
firstIndex:
|
|
1645
|
+
count: indexCount,
|
|
1646
|
+
firstIndex: currentIndexOffset,
|
|
1669
1647
|
bindGroup: bindGroupOverNonEyes,
|
|
1670
1648
|
isTransparent,
|
|
1671
1649
|
});
|
|
1672
1650
|
}
|
|
1673
1651
|
else if (isTransparent) {
|
|
1674
1652
|
this.transparentNonEyeNonHairDraws.push({
|
|
1675
|
-
count:
|
|
1676
|
-
firstIndex:
|
|
1653
|
+
count: indexCount,
|
|
1654
|
+
firstIndex: currentIndexOffset,
|
|
1677
1655
|
bindGroup,
|
|
1678
1656
|
isTransparent,
|
|
1679
1657
|
});
|
|
1680
1658
|
}
|
|
1681
1659
|
else {
|
|
1682
1660
|
this.opaqueNonEyeNonHairDraws.push({
|
|
1683
|
-
count:
|
|
1684
|
-
firstIndex:
|
|
1661
|
+
count: indexCount,
|
|
1662
|
+
firstIndex: currentIndexOffset,
|
|
1685
1663
|
bindGroup,
|
|
1686
1664
|
isTransparent,
|
|
1687
1665
|
});
|
|
1688
1666
|
}
|
|
1689
|
-
//
|
|
1667
|
+
// Edge flag is at bit 4 (0x10) in PMX format
|
|
1690
1668
|
if ((mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0) {
|
|
1691
1669
|
const materialUniformData = new Float32Array(8);
|
|
1692
1670
|
materialUniformData[0] = mat.edgeColor[0]; // edgeColor.r
|
|
@@ -1694,9 +1672,9 @@ export class Engine {
|
|
|
1694
1672
|
materialUniformData[2] = mat.edgeColor[2]; // edgeColor.b
|
|
1695
1673
|
materialUniformData[3] = mat.edgeColor[3]; // edgeColor.a
|
|
1696
1674
|
materialUniformData[4] = mat.edgeSize;
|
|
1697
|
-
materialUniformData[5] = 0.0; // isOverEyes
|
|
1698
|
-
materialUniformData[6] = 0.0;
|
|
1699
|
-
materialUniformData[7] = 0.0;
|
|
1675
|
+
materialUniformData[5] = 0.0; // isOverEyes
|
|
1676
|
+
materialUniformData[6] = 0.0;
|
|
1677
|
+
materialUniformData[7] = 0.0;
|
|
1700
1678
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1701
1679
|
label: `outline material uniform: ${mat.name}`,
|
|
1702
1680
|
size: materialUniformData.byteLength,
|
|
@@ -1712,45 +1690,44 @@ export class Engine {
|
|
|
1712
1690
|
{ binding: 2, resource: { buffer: this.skinMatrixBuffer } },
|
|
1713
1691
|
],
|
|
1714
1692
|
});
|
|
1715
|
-
// Classify outlines into appropriate draw lists
|
|
1716
1693
|
if (mat.isEye) {
|
|
1717
1694
|
this.eyeOutlineDraws.push({
|
|
1718
|
-
count:
|
|
1719
|
-
firstIndex:
|
|
1695
|
+
count: indexCount,
|
|
1696
|
+
firstIndex: currentIndexOffset,
|
|
1720
1697
|
bindGroup: outlineBindGroup,
|
|
1721
1698
|
isTransparent,
|
|
1722
1699
|
});
|
|
1723
1700
|
}
|
|
1724
1701
|
else if (mat.isHair) {
|
|
1725
1702
|
this.hairOutlineDraws.push({
|
|
1726
|
-
count:
|
|
1727
|
-
firstIndex:
|
|
1703
|
+
count: indexCount,
|
|
1704
|
+
firstIndex: currentIndexOffset,
|
|
1728
1705
|
bindGroup: outlineBindGroup,
|
|
1729
1706
|
isTransparent,
|
|
1730
1707
|
});
|
|
1731
1708
|
}
|
|
1732
1709
|
else if (isTransparent) {
|
|
1733
1710
|
this.transparentNonEyeNonHairOutlineDraws.push({
|
|
1734
|
-
count:
|
|
1735
|
-
firstIndex:
|
|
1711
|
+
count: indexCount,
|
|
1712
|
+
firstIndex: currentIndexOffset,
|
|
1736
1713
|
bindGroup: outlineBindGroup,
|
|
1737
1714
|
isTransparent,
|
|
1738
1715
|
});
|
|
1739
1716
|
}
|
|
1740
1717
|
else {
|
|
1741
1718
|
this.opaqueNonEyeNonHairOutlineDraws.push({
|
|
1742
|
-
count:
|
|
1743
|
-
firstIndex:
|
|
1719
|
+
count: indexCount,
|
|
1720
|
+
firstIndex: currentIndexOffset,
|
|
1744
1721
|
bindGroup: outlineBindGroup,
|
|
1745
1722
|
isTransparent,
|
|
1746
1723
|
});
|
|
1747
1724
|
}
|
|
1748
1725
|
}
|
|
1749
|
-
|
|
1726
|
+
currentIndexOffset += indexCount;
|
|
1750
1727
|
}
|
|
1728
|
+
this.gpuMemoryMB = this.calculateGpuMemory();
|
|
1751
1729
|
}
|
|
1752
|
-
|
|
1753
|
-
async createTextureFromPath(path, maxSize = 2048) {
|
|
1730
|
+
async createTextureFromPath(path) {
|
|
1754
1731
|
const cached = this.textureCache.get(path);
|
|
1755
1732
|
if (cached) {
|
|
1756
1733
|
return cached;
|
|
@@ -1760,41 +1737,28 @@ export class Engine {
|
|
|
1760
1737
|
if (!response.ok) {
|
|
1761
1738
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1762
1739
|
}
|
|
1763
|
-
|
|
1740
|
+
const imageBitmap = await createImageBitmap(await response.blob(), {
|
|
1764
1741
|
premultiplyAlpha: "none",
|
|
1765
1742
|
colorSpaceConversion: "none",
|
|
1766
1743
|
});
|
|
1767
|
-
// Downscale if texture is too large
|
|
1768
|
-
let finalWidth = imageBitmap.width;
|
|
1769
|
-
let finalHeight = imageBitmap.height;
|
|
1770
|
-
if (finalWidth > maxSize || finalHeight > maxSize) {
|
|
1771
|
-
const scale = Math.min(maxSize / finalWidth, maxSize / finalHeight);
|
|
1772
|
-
finalWidth = Math.floor(finalWidth * scale);
|
|
1773
|
-
finalHeight = Math.floor(finalHeight * scale);
|
|
1774
|
-
// Create canvas to downscale
|
|
1775
|
-
const canvas = new OffscreenCanvas(finalWidth, finalHeight);
|
|
1776
|
-
const ctx = canvas.getContext("2d");
|
|
1777
|
-
if (ctx) {
|
|
1778
|
-
ctx.drawImage(imageBitmap, 0, 0, finalWidth, finalHeight);
|
|
1779
|
-
imageBitmap = await createImageBitmap(canvas);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
1744
|
const texture = this.device.createTexture({
|
|
1783
1745
|
label: `texture: ${path}`,
|
|
1784
|
-
size: [
|
|
1746
|
+
size: [imageBitmap.width, imageBitmap.height],
|
|
1785
1747
|
format: "rgba8unorm",
|
|
1786
1748
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1787
1749
|
});
|
|
1788
|
-
this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [
|
|
1750
|
+
this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [
|
|
1751
|
+
imageBitmap.width,
|
|
1752
|
+
imageBitmap.height,
|
|
1753
|
+
]);
|
|
1789
1754
|
this.textureCache.set(path, texture);
|
|
1790
|
-
this.textureSizes.set(path, { width: finalWidth, height: finalHeight });
|
|
1791
1755
|
return texture;
|
|
1792
1756
|
}
|
|
1793
1757
|
catch {
|
|
1794
1758
|
return null;
|
|
1795
1759
|
}
|
|
1796
1760
|
}
|
|
1797
|
-
//
|
|
1761
|
+
// Render strategy: 1) Opaque non-eye/hair 2) Eyes (stencil=1) 3) Hair (depth pre-pass + split by stencil) 4) Transparent 5) Bloom
|
|
1798
1762
|
render() {
|
|
1799
1763
|
if (this.multisampleTexture && this.camera && this.device && this.currentModel) {
|
|
1800
1764
|
const currentTime = performance.now();
|
|
@@ -1802,16 +1766,17 @@ export class Engine {
|
|
|
1802
1766
|
this.lastFrameTime = currentTime;
|
|
1803
1767
|
this.updateCameraUniforms();
|
|
1804
1768
|
this.updateRenderTarget();
|
|
1805
|
-
|
|
1769
|
+
// Use single encoder for both compute and render (reduces sync points)
|
|
1806
1770
|
const encoder = this.device.createCommandEncoder();
|
|
1771
|
+
this.updateModelPose(deltaTime, encoder);
|
|
1807
1772
|
const pass = encoder.beginRenderPass(this.renderPassDescriptor);
|
|
1808
1773
|
pass.setVertexBuffer(0, this.vertexBuffer);
|
|
1809
1774
|
pass.setVertexBuffer(1, this.jointsBuffer);
|
|
1810
1775
|
pass.setVertexBuffer(2, this.weightsBuffer);
|
|
1811
1776
|
pass.setIndexBuffer(this.indexBuffer, "uint32");
|
|
1812
1777
|
this.drawCallCount = 0;
|
|
1813
|
-
//
|
|
1814
|
-
pass.setPipeline(this.
|
|
1778
|
+
// Pass 1: Opaque non-eye, non-hair
|
|
1779
|
+
pass.setPipeline(this.modelPipeline);
|
|
1815
1780
|
for (const draw of this.opaqueNonEyeNonHairDraws) {
|
|
1816
1781
|
if (draw.count > 0) {
|
|
1817
1782
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -1819,9 +1784,9 @@ export class Engine {
|
|
|
1819
1784
|
this.drawCallCount++;
|
|
1820
1785
|
}
|
|
1821
1786
|
}
|
|
1822
|
-
//
|
|
1787
|
+
// Pass 2: Eyes (writes stencil value for hair to test against)
|
|
1823
1788
|
pass.setPipeline(this.eyePipeline);
|
|
1824
|
-
pass.setStencilReference(
|
|
1789
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
1825
1790
|
for (const draw of this.eyeDraws) {
|
|
1826
1791
|
if (draw.count > 0) {
|
|
1827
1792
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -1829,9 +1794,9 @@ export class Engine {
|
|
|
1829
1794
|
this.drawCallCount++;
|
|
1830
1795
|
}
|
|
1831
1796
|
}
|
|
1832
|
-
//
|
|
1797
|
+
// Pass 3: Hair rendering (depth pre-pass + shading + outlines)
|
|
1833
1798
|
this.drawOutlines(pass, false);
|
|
1834
|
-
// 3a: Hair depth pre-pass (
|
|
1799
|
+
// 3a: Hair depth pre-pass (reduces overdraw via early depth rejection)
|
|
1835
1800
|
if (this.hairDrawsOverEyes.length > 0 || this.hairDrawsOverNonEyes.length > 0) {
|
|
1836
1801
|
pass.setPipeline(this.hairDepthPipeline);
|
|
1837
1802
|
for (const draw of this.hairDrawsOverEyes) {
|
|
@@ -1847,10 +1812,10 @@ export class Engine {
|
|
|
1847
1812
|
}
|
|
1848
1813
|
}
|
|
1849
1814
|
}
|
|
1850
|
-
// 3b: Hair shading
|
|
1815
|
+
// 3b: Hair shading (split by stencil for transparency over eyes)
|
|
1851
1816
|
if (this.hairDrawsOverEyes.length > 0) {
|
|
1852
|
-
pass.setPipeline(this.
|
|
1853
|
-
pass.setStencilReference(
|
|
1817
|
+
pass.setPipeline(this.hairPipelineOverEyes);
|
|
1818
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
1854
1819
|
for (const draw of this.hairDrawsOverEyes) {
|
|
1855
1820
|
if (draw.count > 0) {
|
|
1856
1821
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -1860,8 +1825,8 @@ export class Engine {
|
|
|
1860
1825
|
}
|
|
1861
1826
|
}
|
|
1862
1827
|
if (this.hairDrawsOverNonEyes.length > 0) {
|
|
1863
|
-
pass.setPipeline(this.
|
|
1864
|
-
pass.setStencilReference(
|
|
1828
|
+
pass.setPipeline(this.hairPipelineOverNonEyes);
|
|
1829
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
1865
1830
|
for (const draw of this.hairDrawsOverNonEyes) {
|
|
1866
1831
|
if (draw.count > 0) {
|
|
1867
1832
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -1870,9 +1835,9 @@ export class Engine {
|
|
|
1870
1835
|
}
|
|
1871
1836
|
}
|
|
1872
1837
|
}
|
|
1873
|
-
// 3c: Hair outlines
|
|
1838
|
+
// 3c: Hair outlines
|
|
1874
1839
|
if (this.hairOutlineDraws.length > 0) {
|
|
1875
|
-
pass.setPipeline(this.
|
|
1840
|
+
pass.setPipeline(this.hairOutlinePipeline);
|
|
1876
1841
|
for (const draw of this.hairOutlineDraws) {
|
|
1877
1842
|
if (draw.count > 0) {
|
|
1878
1843
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -1880,8 +1845,8 @@ export class Engine {
|
|
|
1880
1845
|
}
|
|
1881
1846
|
}
|
|
1882
1847
|
}
|
|
1883
|
-
//
|
|
1884
|
-
pass.setPipeline(this.
|
|
1848
|
+
// Pass 4: Transparent non-eye, non-hair
|
|
1849
|
+
pass.setPipeline(this.modelPipeline);
|
|
1885
1850
|
for (const draw of this.transparentNonEyeNonHairDraws) {
|
|
1886
1851
|
if (draw.count > 0) {
|
|
1887
1852
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -1892,12 +1857,10 @@ export class Engine {
|
|
|
1892
1857
|
this.drawOutlines(pass, true);
|
|
1893
1858
|
pass.end();
|
|
1894
1859
|
this.device.queue.submit([encoder.finish()]);
|
|
1895
|
-
// Apply bloom post-processing
|
|
1896
1860
|
this.applyBloom();
|
|
1897
1861
|
this.updateStats(performance.now() - currentTime);
|
|
1898
1862
|
}
|
|
1899
1863
|
}
|
|
1900
|
-
// Apply bloom post-processing
|
|
1901
1864
|
applyBloom() {
|
|
1902
1865
|
if (!this.sceneRenderTexture || !this.bloomExtractTexture) {
|
|
1903
1866
|
return;
|
|
@@ -1912,9 +1875,9 @@ export class Engine {
|
|
|
1912
1875
|
const encoder = this.device.createCommandEncoder();
|
|
1913
1876
|
const width = this.canvas.width;
|
|
1914
1877
|
const height = this.canvas.height;
|
|
1915
|
-
const bloomWidth = Math.floor(width /
|
|
1916
|
-
const bloomHeight = Math.floor(height /
|
|
1917
|
-
//
|
|
1878
|
+
const bloomWidth = Math.floor(width / this.BLOOM_DOWNSCALE_FACTOR);
|
|
1879
|
+
const bloomHeight = Math.floor(height / this.BLOOM_DOWNSCALE_FACTOR);
|
|
1880
|
+
// Extract bright areas
|
|
1918
1881
|
const extractPass = encoder.beginRenderPass({
|
|
1919
1882
|
label: "bloom extract",
|
|
1920
1883
|
colorAttachments: [
|
|
@@ -1930,8 +1893,8 @@ export class Engine {
|
|
|
1930
1893
|
extractPass.setBindGroup(0, this.bloomExtractBindGroup);
|
|
1931
1894
|
extractPass.draw(6, 1, 0, 0);
|
|
1932
1895
|
extractPass.end();
|
|
1933
|
-
//
|
|
1934
|
-
const hBlurData = new Float32Array(4);
|
|
1896
|
+
// Horizontal blur
|
|
1897
|
+
const hBlurData = new Float32Array(4);
|
|
1935
1898
|
hBlurData[0] = 1.0;
|
|
1936
1899
|
hBlurData[1] = 0.0;
|
|
1937
1900
|
this.device.queue.writeBuffer(this.blurDirectionBuffer, 0, hBlurData);
|
|
@@ -1950,8 +1913,8 @@ export class Engine {
|
|
|
1950
1913
|
blurHPass.setBindGroup(0, this.bloomBlurHBindGroup);
|
|
1951
1914
|
blurHPass.draw(6, 1, 0, 0);
|
|
1952
1915
|
blurHPass.end();
|
|
1953
|
-
//
|
|
1954
|
-
const vBlurData = new Float32Array(4);
|
|
1916
|
+
// Vertical blur
|
|
1917
|
+
const vBlurData = new Float32Array(4);
|
|
1955
1918
|
vBlurData[0] = 0.0;
|
|
1956
1919
|
vBlurData[1] = 1.0;
|
|
1957
1920
|
this.device.queue.writeBuffer(this.blurDirectionBuffer, 0, vBlurData);
|
|
@@ -1970,7 +1933,7 @@ export class Engine {
|
|
|
1970
1933
|
blurVPass.setBindGroup(0, this.bloomBlurVBindGroup);
|
|
1971
1934
|
blurVPass.draw(6, 1, 0, 0);
|
|
1972
1935
|
blurVPass.end();
|
|
1973
|
-
//
|
|
1936
|
+
// Compose to canvas
|
|
1974
1937
|
const composePass = encoder.beginRenderPass({
|
|
1975
1938
|
label: "bloom compose",
|
|
1976
1939
|
colorAttachments: [
|
|
@@ -1988,7 +1951,6 @@ export class Engine {
|
|
|
1988
1951
|
composePass.end();
|
|
1989
1952
|
this.device.queue.submit([encoder.finish()]);
|
|
1990
1953
|
}
|
|
1991
|
-
// Update camera uniform buffer each frame
|
|
1992
1954
|
updateCameraUniforms() {
|
|
1993
1955
|
const viewMatrix = this.camera.getViewMatrix();
|
|
1994
1956
|
const projectionMatrix = this.camera.getProjectionMatrix();
|
|
@@ -2000,47 +1962,36 @@ export class Engine {
|
|
|
2000
1962
|
this.cameraMatrixData[34] = cameraPos.z;
|
|
2001
1963
|
this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraMatrixData);
|
|
2002
1964
|
}
|
|
2003
|
-
// Update render target texture view
|
|
2004
1965
|
updateRenderTarget() {
|
|
2005
1966
|
const colorAttachment = this.renderPassDescriptor.colorAttachments[0];
|
|
2006
1967
|
if (this.sampleCount > 1) {
|
|
2007
|
-
// Resolve to scene render texture for post-processing
|
|
2008
1968
|
colorAttachment.resolveTarget = this.sceneRenderTextureView;
|
|
2009
1969
|
}
|
|
2010
1970
|
else {
|
|
2011
|
-
// Render directly to scene render texture
|
|
2012
1971
|
colorAttachment.view = this.sceneRenderTextureView;
|
|
2013
1972
|
}
|
|
2014
1973
|
}
|
|
2015
|
-
updateModelPose(deltaTime) {
|
|
1974
|
+
updateModelPose(deltaTime, encoder) {
|
|
2016
1975
|
this.currentModel.evaluatePose();
|
|
2017
1976
|
const worldMats = this.currentModel.getBoneWorldMatrices();
|
|
2018
1977
|
if (this.physics) {
|
|
2019
1978
|
this.physics.step(deltaTime, worldMats, this.currentModel.getBoneInverseBindMatrices());
|
|
2020
1979
|
}
|
|
2021
1980
|
this.device.queue.writeBuffer(this.worldMatrixBuffer, 0, worldMats.buffer, worldMats.byteOffset, worldMats.byteLength);
|
|
2022
|
-
this.computeSkinMatrices();
|
|
1981
|
+
this.computeSkinMatrices(encoder);
|
|
2023
1982
|
}
|
|
2024
|
-
|
|
2025
|
-
computeSkinMatrices() {
|
|
1983
|
+
computeSkinMatrices(encoder) {
|
|
2026
1984
|
const boneCount = this.currentModel.getSkeleton().bones.length;
|
|
2027
|
-
const
|
|
2028
|
-
// Dispatch exactly enough threads for all bones (no bounds check needed)
|
|
2029
|
-
const workgroupCount = Math.ceil(boneCount / workgroupSize);
|
|
2030
|
-
// Bone count is written once in setupModelBuffers() and never changes
|
|
2031
|
-
const encoder = this.device.createCommandEncoder();
|
|
1985
|
+
const workgroupCount = Math.ceil(boneCount / this.COMPUTE_WORKGROUP_SIZE);
|
|
2032
1986
|
const pass = encoder.beginComputePass();
|
|
2033
1987
|
pass.setPipeline(this.skinMatrixComputePipeline);
|
|
2034
1988
|
pass.setBindGroup(0, this.skinMatrixComputeBindGroup);
|
|
2035
1989
|
pass.dispatchWorkgroups(workgroupCount);
|
|
2036
1990
|
pass.end();
|
|
2037
|
-
this.device.queue.submit([encoder.finish()]);
|
|
2038
1991
|
}
|
|
2039
|
-
// Draw outlines (opaque or transparent)
|
|
2040
1992
|
drawOutlines(pass, transparent) {
|
|
2041
1993
|
pass.setPipeline(this.outlinePipeline);
|
|
2042
1994
|
if (transparent) {
|
|
2043
|
-
// Draw transparent outlines (if any)
|
|
2044
1995
|
for (const draw of this.transparentNonEyeNonHairOutlineDraws) {
|
|
2045
1996
|
if (draw.count > 0) {
|
|
2046
1997
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -2049,7 +2000,6 @@ export class Engine {
|
|
|
2049
2000
|
}
|
|
2050
2001
|
}
|
|
2051
2002
|
else {
|
|
2052
|
-
// Draw opaque outlines before main geometry
|
|
2053
2003
|
for (const draw of this.opaqueNonEyeNonHairOutlineDraws) {
|
|
2054
2004
|
if (draw.count > 0) {
|
|
2055
2005
|
pass.setBindGroup(0, draw.bindGroup);
|
|
@@ -2076,12 +2026,12 @@ export class Engine {
|
|
|
2076
2026
|
this.framesSinceLastUpdate = 0;
|
|
2077
2027
|
this.lastFpsUpdate = now;
|
|
2078
2028
|
}
|
|
2079
|
-
|
|
2029
|
+
this.stats.gpuMemory = this.gpuMemoryMB;
|
|
2030
|
+
}
|
|
2031
|
+
calculateGpuMemory() {
|
|
2080
2032
|
let textureMemoryBytes = 0;
|
|
2081
|
-
for (const
|
|
2082
|
-
|
|
2083
|
-
textureMemoryBytes += size.width * size.height * 4; // RGBA8 = 4 bytes per pixel
|
|
2084
|
-
}
|
|
2033
|
+
for (const texture of this.textureCache.values()) {
|
|
2034
|
+
textureMemoryBytes += texture.width * texture.height * 4;
|
|
2085
2035
|
}
|
|
2086
2036
|
let bufferMemoryBytes = 0;
|
|
2087
2037
|
if (this.vertexBuffer) {
|
|
@@ -2119,48 +2069,44 @@ export class Engine {
|
|
|
2119
2069
|
if (skeleton)
|
|
2120
2070
|
bufferMemoryBytes += Math.max(256, skeleton.bones.length * 16 * 4);
|
|
2121
2071
|
}
|
|
2122
|
-
bufferMemoryBytes += 40 * 4;
|
|
2123
|
-
bufferMemoryBytes += 64 * 4;
|
|
2124
|
-
bufferMemoryBytes += 32;
|
|
2125
|
-
bufferMemoryBytes += 32;
|
|
2126
|
-
bufferMemoryBytes += 32;
|
|
2127
|
-
bufferMemoryBytes += 32;
|
|
2072
|
+
bufferMemoryBytes += 40 * 4;
|
|
2073
|
+
bufferMemoryBytes += 64 * 4;
|
|
2074
|
+
bufferMemoryBytes += 32;
|
|
2075
|
+
bufferMemoryBytes += 32;
|
|
2076
|
+
bufferMemoryBytes += 32;
|
|
2077
|
+
bufferMemoryBytes += 32;
|
|
2128
2078
|
if (this.fullscreenQuadBuffer) {
|
|
2129
|
-
bufferMemoryBytes += 24 * 4;
|
|
2079
|
+
bufferMemoryBytes += 24 * 4;
|
|
2130
2080
|
}
|
|
2131
|
-
// Material uniform buffers: Float32Array(8) = 32 bytes each
|
|
2132
2081
|
const totalMaterialDraws = this.opaqueNonEyeNonHairDraws.length +
|
|
2133
2082
|
this.eyeDraws.length +
|
|
2134
2083
|
this.hairDrawsOverEyes.length +
|
|
2135
2084
|
this.hairDrawsOverNonEyes.length +
|
|
2136
2085
|
this.transparentNonEyeNonHairDraws.length;
|
|
2137
|
-
bufferMemoryBytes += totalMaterialDraws * 32;
|
|
2138
|
-
// Outline material uniform buffers: Float32Array(8) = 32 bytes each
|
|
2086
|
+
bufferMemoryBytes += totalMaterialDraws * 32;
|
|
2139
2087
|
const totalOutlineDraws = this.opaqueNonEyeNonHairOutlineDraws.length +
|
|
2140
2088
|
this.eyeOutlineDraws.length +
|
|
2141
2089
|
this.hairOutlineDraws.length +
|
|
2142
2090
|
this.transparentNonEyeNonHairOutlineDraws.length;
|
|
2143
|
-
bufferMemoryBytes += totalOutlineDraws * 32;
|
|
2091
|
+
bufferMemoryBytes += totalOutlineDraws * 32;
|
|
2144
2092
|
let renderTargetMemoryBytes = 0;
|
|
2145
2093
|
if (this.multisampleTexture) {
|
|
2146
2094
|
const width = this.canvas.width;
|
|
2147
2095
|
const height = this.canvas.height;
|
|
2148
|
-
renderTargetMemoryBytes += width * height * 4 * this.sampleCount;
|
|
2149
|
-
renderTargetMemoryBytes += width * height * 4;
|
|
2096
|
+
renderTargetMemoryBytes += width * height * 4 * this.sampleCount;
|
|
2097
|
+
renderTargetMemoryBytes += width * height * 4;
|
|
2150
2098
|
}
|
|
2151
2099
|
if (this.sceneRenderTexture) {
|
|
2152
2100
|
const width = this.canvas.width;
|
|
2153
2101
|
const height = this.canvas.height;
|
|
2154
|
-
renderTargetMemoryBytes += width * height * 4;
|
|
2102
|
+
renderTargetMemoryBytes += width * height * 4;
|
|
2155
2103
|
}
|
|
2156
2104
|
if (this.bloomExtractTexture) {
|
|
2157
|
-
const width = Math.floor(this.canvas.width /
|
|
2158
|
-
const height = Math.floor(this.canvas.height /
|
|
2159
|
-
renderTargetMemoryBytes += width * height * 4
|
|
2160
|
-
renderTargetMemoryBytes += width * height * 4; // bloomBlurTexture1
|
|
2161
|
-
renderTargetMemoryBytes += width * height * 4; // bloomBlurTexture2
|
|
2105
|
+
const width = Math.floor(this.canvas.width / this.BLOOM_DOWNSCALE_FACTOR);
|
|
2106
|
+
const height = Math.floor(this.canvas.height / this.BLOOM_DOWNSCALE_FACTOR);
|
|
2107
|
+
renderTargetMemoryBytes += width * height * 4 * 3;
|
|
2162
2108
|
}
|
|
2163
2109
|
const totalGPUMemoryBytes = textureMemoryBytes + bufferMemoryBytes + renderTargetMemoryBytes;
|
|
2164
|
-
|
|
2110
|
+
return Math.round((totalGPUMemoryBytes / 1024 / 1024) * 100) / 100;
|
|
2165
2111
|
}
|
|
2166
2112
|
}
|