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/src/engine.ts
CHANGED
|
@@ -8,9 +8,7 @@ import { VMDKeyFrame, VMDLoader } from "./vmd-loader"
|
|
|
8
8
|
export type EngineOptions = {
|
|
9
9
|
ambient?: number
|
|
10
10
|
bloomIntensity?: number
|
|
11
|
-
bloomThreshold?: number
|
|
12
11
|
rimLightIntensity?: number
|
|
13
|
-
rimLightPower?: number
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
export interface EngineStats {
|
|
@@ -31,7 +29,7 @@ export class Engine {
|
|
|
31
29
|
private device!: GPUDevice
|
|
32
30
|
private context!: GPUCanvasContext
|
|
33
31
|
private presentationFormat!: GPUTextureFormat
|
|
34
|
-
|
|
32
|
+
private camera!: Camera
|
|
35
33
|
private cameraUniformBuffer!: GPUBuffer
|
|
36
34
|
private cameraMatrixData = new Float32Array(36)
|
|
37
35
|
private lightUniformBuffer!: GPUBuffer
|
|
@@ -41,14 +39,14 @@ export class Engine {
|
|
|
41
39
|
private indexBuffer?: GPUBuffer
|
|
42
40
|
private resizeObserver: ResizeObserver | null = null
|
|
43
41
|
private depthTexture!: GPUTexture
|
|
44
|
-
private
|
|
42
|
+
private modelPipeline!: GPURenderPipeline
|
|
45
43
|
private outlinePipeline!: GPURenderPipeline
|
|
46
|
-
private
|
|
47
|
-
private
|
|
48
|
-
private
|
|
44
|
+
private hairOutlinePipeline!: GPURenderPipeline
|
|
45
|
+
private hairPipelineOverEyes!: GPURenderPipeline
|
|
46
|
+
private hairPipelineOverNonEyes!: GPURenderPipeline
|
|
49
47
|
private hairDepthPipeline!: GPURenderPipeline
|
|
50
48
|
private eyePipeline!: GPURenderPipeline
|
|
51
|
-
private
|
|
49
|
+
private mainBindGroupLayout!: GPUBindGroupLayout
|
|
52
50
|
private outlineBindGroupLayout!: GPUBindGroupLayout
|
|
53
51
|
private jointsBuffer!: GPUBuffer
|
|
54
52
|
private weightsBuffer!: GPUBuffer
|
|
@@ -59,8 +57,12 @@ export class Engine {
|
|
|
59
57
|
private skinMatrixComputeBindGroup?: GPUBindGroup
|
|
60
58
|
private boneCountBuffer?: GPUBuffer
|
|
61
59
|
private multisampleTexture!: GPUTexture
|
|
62
|
-
private readonly sampleCount = 4
|
|
60
|
+
private readonly sampleCount = 4
|
|
63
61
|
private renderPassDescriptor!: GPURenderPassDescriptor
|
|
62
|
+
// Constants
|
|
63
|
+
private readonly STENCIL_EYE_VALUE = 1
|
|
64
|
+
private readonly COMPUTE_WORKGROUP_SIZE = 64
|
|
65
|
+
private readonly BLOOM_DOWNSCALE_FACTOR = 2
|
|
64
66
|
// Ambient light settings
|
|
65
67
|
private ambient: number = 1.0
|
|
66
68
|
// Bloom post-processing textures
|
|
@@ -96,7 +98,6 @@ export class Engine {
|
|
|
96
98
|
private physics: Physics | null = null
|
|
97
99
|
private textureSampler!: GPUSampler
|
|
98
100
|
private textureCache = new Map<string, GPUTexture>()
|
|
99
|
-
private textureSizes = new Map<string, { width: number; height: number }>()
|
|
100
101
|
|
|
101
102
|
private lastFpsUpdate = performance.now()
|
|
102
103
|
private framesSinceLastUpdate = 0
|
|
@@ -114,15 +115,14 @@ export class Engine {
|
|
|
114
115
|
|
|
115
116
|
private animationFrames: VMDKeyFrame[] = []
|
|
116
117
|
private animationTimeouts: number[] = []
|
|
118
|
+
private gpuMemoryMB: number = 0
|
|
117
119
|
|
|
118
120
|
constructor(canvas: HTMLCanvasElement, options?: EngineOptions) {
|
|
119
121
|
this.canvas = canvas
|
|
120
122
|
if (options) {
|
|
121
123
|
this.ambient = options.ambient ?? 1.0
|
|
122
|
-
this.bloomThreshold = options.bloomThreshold ?? 0.3
|
|
123
124
|
this.bloomIntensity = options.bloomIntensity ?? 0.12
|
|
124
125
|
this.rimLightIntensity = options.rimLightIntensity ?? 0.45
|
|
125
|
-
this.rimLightPower = options.rimLightPower ?? 2.0
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -291,8 +291,8 @@ export class Engine {
|
|
|
291
291
|
})
|
|
292
292
|
|
|
293
293
|
// Create explicit bind group layout for all pipelines using the main shader
|
|
294
|
-
this.
|
|
295
|
-
label: "
|
|
294
|
+
this.mainBindGroupLayout = this.device.createBindGroupLayout({
|
|
295
|
+
label: "main material bind group layout",
|
|
296
296
|
entries: [
|
|
297
297
|
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // camera
|
|
298
298
|
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // light
|
|
@@ -305,15 +305,14 @@ export class Engine {
|
|
|
305
305
|
],
|
|
306
306
|
})
|
|
307
307
|
|
|
308
|
-
const
|
|
309
|
-
label: "
|
|
310
|
-
bindGroupLayouts: [this.
|
|
308
|
+
const mainPipelineLayout = this.device.createPipelineLayout({
|
|
309
|
+
label: "main pipeline layout",
|
|
310
|
+
bindGroupLayouts: [this.mainBindGroupLayout],
|
|
311
311
|
})
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
this.pipeline = this.device.createRenderPipeline({
|
|
313
|
+
this.modelPipeline = this.device.createRenderPipeline({
|
|
315
314
|
label: "model pipeline",
|
|
316
|
-
layout:
|
|
315
|
+
layout: mainPipelineLayout,
|
|
317
316
|
vertex: {
|
|
318
317
|
module: shaderModule,
|
|
319
318
|
buffers: [
|
|
@@ -521,9 +520,9 @@ export class Engine {
|
|
|
521
520
|
},
|
|
522
521
|
})
|
|
523
522
|
|
|
524
|
-
//
|
|
525
|
-
this.
|
|
526
|
-
label: "
|
|
523
|
+
// Hair outline pipeline
|
|
524
|
+
this.hairOutlinePipeline = this.device.createRenderPipeline({
|
|
525
|
+
label: "hair outline pipeline",
|
|
527
526
|
layout: outlinePipelineLayout,
|
|
528
527
|
vertex: {
|
|
529
528
|
module: outlineShaderModule,
|
|
@@ -592,7 +591,7 @@ export class Engine {
|
|
|
592
591
|
// Eye overlay pipeline (renders after opaque, writes stencil)
|
|
593
592
|
this.eyePipeline = this.device.createRenderPipeline({
|
|
594
593
|
label: "eye overlay pipeline",
|
|
595
|
-
layout:
|
|
594
|
+
layout: mainPipelineLayout,
|
|
596
595
|
vertex: {
|
|
597
596
|
module: shaderModule,
|
|
598
597
|
buffers: [
|
|
@@ -707,7 +706,7 @@ export class Engine {
|
|
|
707
706
|
// Hair depth pre-pass pipeline: depth-only with color writes disabled to eliminate overdraw
|
|
708
707
|
this.hairDepthPipeline = this.device.createRenderPipeline({
|
|
709
708
|
label: "hair depth pre-pass",
|
|
710
|
-
layout:
|
|
709
|
+
layout: mainPipelineLayout,
|
|
711
710
|
vertex: {
|
|
712
711
|
module: depthOnlyShaderModule,
|
|
713
712
|
buffers: [
|
|
@@ -747,10 +746,10 @@ export class Engine {
|
|
|
747
746
|
multisample: { count: this.sampleCount },
|
|
748
747
|
})
|
|
749
748
|
|
|
750
|
-
//
|
|
751
|
-
this.
|
|
752
|
-
label: "
|
|
753
|
-
layout:
|
|
749
|
+
// Hair pipeline for rendering over eyes (stencil == 1)
|
|
750
|
+
this.hairPipelineOverEyes = this.device.createRenderPipeline({
|
|
751
|
+
label: "hair pipeline (over eyes)",
|
|
752
|
+
layout: mainPipelineLayout,
|
|
754
753
|
vertex: {
|
|
755
754
|
module: shaderModule,
|
|
756
755
|
buffers: [
|
|
@@ -813,10 +812,10 @@ export class Engine {
|
|
|
813
812
|
multisample: { count: this.sampleCount },
|
|
814
813
|
})
|
|
815
814
|
|
|
816
|
-
//
|
|
817
|
-
this.
|
|
818
|
-
label: "
|
|
819
|
-
layout:
|
|
815
|
+
// Hair pipeline for rendering over non-eyes (stencil != 1)
|
|
816
|
+
this.hairPipelineOverNonEyes = this.device.createRenderPipeline({
|
|
817
|
+
label: "hair pipeline (over non-eyes)",
|
|
818
|
+
layout: mainPipelineLayout,
|
|
820
819
|
vertex: {
|
|
821
820
|
module: shaderModule,
|
|
822
821
|
buffers: [
|
|
@@ -898,10 +897,9 @@ export class Engine {
|
|
|
898
897
|
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
899
898
|
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
900
899
|
|
|
901
|
-
@compute @workgroup_size(64)
|
|
900
|
+
@compute @workgroup_size(64) // Must match COMPUTE_WORKGROUP_SIZE
|
|
902
901
|
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
903
902
|
let boneIndex = globalId.x;
|
|
904
|
-
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
905
903
|
if (boneIndex >= boneCount.count) {
|
|
906
904
|
return;
|
|
907
905
|
}
|
|
@@ -1039,21 +1037,16 @@ export class Engine {
|
|
|
1039
1037
|
@group(0) @binding(1) var inputSampler: sampler;
|
|
1040
1038
|
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
1041
1039
|
|
|
1042
|
-
//
|
|
1040
|
+
// 5-tap gaussian blur
|
|
1043
1041
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1044
1042
|
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
1045
1043
|
var result = vec4f(0.0);
|
|
1046
1044
|
|
|
1047
|
-
// Gaussian
|
|
1048
|
-
let weights = array<f32,
|
|
1049
|
-
|
|
1050
|
-
0.19459459, 0.22702703,
|
|
1051
|
-
0.19459459, 0.12162162, 0.05405405, 0.01621622
|
|
1052
|
-
);
|
|
1045
|
+
// Optimized 5-tap Gaussian filter (faster, nearly same quality)
|
|
1046
|
+
let weights = array<f32, 5>(0.06136, 0.24477, 0.38774, 0.24477, 0.06136);
|
|
1047
|
+
let offsets = array<f32, 5>(-2.0, -1.0, 0.0, 1.0, 2.0);
|
|
1053
1048
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
for (var i = 0u; i < 9u; i++) {
|
|
1049
|
+
for (var i = 0u; i < 5u; i++) {
|
|
1057
1050
|
let offset = offsets[i] * texelSize * blurUniforms.direction;
|
|
1058
1051
|
result += textureSample(inputTexture, inputSampler, input.uv + offset) * weights[i];
|
|
1059
1052
|
}
|
|
@@ -1201,11 +1194,9 @@ export class Engine {
|
|
|
1201
1194
|
this.linearSampler = linearSampler
|
|
1202
1195
|
}
|
|
1203
1196
|
|
|
1204
|
-
// Setup bloom textures and bind groups (called when canvas is resized)
|
|
1205
1197
|
private setupBloom(width: number, height: number) {
|
|
1206
|
-
|
|
1207
|
-
const
|
|
1208
|
-
const bloomHeight = Math.floor(height / 2)
|
|
1198
|
+
const bloomWidth = Math.floor(width / this.BLOOM_DOWNSCALE_FACTOR)
|
|
1199
|
+
const bloomHeight = Math.floor(height / this.BLOOM_DOWNSCALE_FACTOR)
|
|
1209
1200
|
this.bloomExtractTexture = this.device.createTexture({
|
|
1210
1201
|
label: "bloom extract",
|
|
1211
1202
|
size: [bloomWidth, bloomHeight],
|
|
@@ -1406,7 +1397,6 @@ export class Engine {
|
|
|
1406
1397
|
public async loadAnimation(url: string) {
|
|
1407
1398
|
const frames = await VMDLoader.load(url)
|
|
1408
1399
|
this.animationFrames = frames
|
|
1409
|
-
console.log(this.animationFrames)
|
|
1410
1400
|
}
|
|
1411
1401
|
|
|
1412
1402
|
public playAnimation() {
|
|
@@ -1485,7 +1475,9 @@ export class Engine {
|
|
|
1485
1475
|
worldMats.byteOffset,
|
|
1486
1476
|
worldMats.byteLength
|
|
1487
1477
|
)
|
|
1488
|
-
this.
|
|
1478
|
+
const encoder = this.device.createCommandEncoder()
|
|
1479
|
+
this.computeSkinMatrices(encoder)
|
|
1480
|
+
this.device.queue.submit([encoder.finish()])
|
|
1489
1481
|
}
|
|
1490
1482
|
}
|
|
1491
1483
|
for (const [_, keyFrames] of boneKeyFramesByBone.entries()) {
|
|
@@ -1570,6 +1562,14 @@ export class Engine {
|
|
|
1570
1562
|
this.modelDir = dir
|
|
1571
1563
|
|
|
1572
1564
|
const model = await PmxLoader.load(path)
|
|
1565
|
+
// console.log({
|
|
1566
|
+
// vertices: Array.from(model.getVertices()),
|
|
1567
|
+
// indices: Array.from(model.getIndices()),
|
|
1568
|
+
// materials: model.getMaterials(),
|
|
1569
|
+
// textures: model.getTextures(),
|
|
1570
|
+
// bones: model.getSkeleton().bones,
|
|
1571
|
+
// skinning: { joints: Array.from(model.getSkinning().joints), weights: Array.from(model.getSkinning().weights) },
|
|
1572
|
+
// })
|
|
1573
1573
|
this.physics = new Physics(model.getRigidbodies(), model.getJoints())
|
|
1574
1574
|
await this.setupModelBuffers(model)
|
|
1575
1575
|
}
|
|
@@ -1776,7 +1776,6 @@ export class Engine {
|
|
|
1776
1776
|
[256, 2]
|
|
1777
1777
|
)
|
|
1778
1778
|
this.textureCache.set(defaultToonPath, defaultToonTexture)
|
|
1779
|
-
this.textureSizes.set(defaultToonPath, { width: 256, height: 2 })
|
|
1780
1779
|
return defaultToonTexture
|
|
1781
1780
|
}
|
|
1782
1781
|
|
|
@@ -1789,11 +1788,11 @@ export class Engine {
|
|
|
1789
1788
|
this.eyeOutlineDraws = []
|
|
1790
1789
|
this.hairOutlineDraws = []
|
|
1791
1790
|
this.transparentNonEyeNonHairOutlineDraws = []
|
|
1792
|
-
let
|
|
1791
|
+
let currentIndexOffset = 0
|
|
1793
1792
|
|
|
1794
1793
|
for (const mat of materials) {
|
|
1795
|
-
const
|
|
1796
|
-
if (
|
|
1794
|
+
const indexCount = mat.vertexCount
|
|
1795
|
+
if (indexCount === 0) continue
|
|
1797
1796
|
|
|
1798
1797
|
const diffuseTexture = await loadTextureByIndex(mat.diffuseTextureIndex)
|
|
1799
1798
|
if (!diffuseTexture) throw new Error(`Material "${mat.name}" has no diffuse texture`)
|
|
@@ -1825,7 +1824,7 @@ export class Engine {
|
|
|
1825
1824
|
// Create bind groups using the shared bind group layout - All pipelines (main, eye, hair multiply, hair opaque) use the same shader and layout
|
|
1826
1825
|
const bindGroup = this.device.createBindGroup({
|
|
1827
1826
|
label: `material bind group: ${mat.name}`,
|
|
1828
|
-
layout: this.
|
|
1827
|
+
layout: this.mainBindGroupLayout,
|
|
1829
1828
|
entries: [
|
|
1830
1829
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1831
1830
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
@@ -1841,104 +1840,80 @@ export class Engine {
|
|
|
1841
1840
|
// Classify materials into appropriate draw lists
|
|
1842
1841
|
if (mat.isEye) {
|
|
1843
1842
|
this.eyeDraws.push({
|
|
1844
|
-
count:
|
|
1845
|
-
firstIndex:
|
|
1843
|
+
count: indexCount,
|
|
1844
|
+
firstIndex: currentIndexOffset,
|
|
1846
1845
|
bindGroup,
|
|
1847
1846
|
isTransparent,
|
|
1848
1847
|
})
|
|
1849
1848
|
} else if (mat.isHair) {
|
|
1850
|
-
// Hair materials: create bind groups for
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
entries: [
|
|
1885
|
-
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1886
|
-
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1887
|
-
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1888
|
-
{ binding: 3, resource: this.textureSampler },
|
|
1889
|
-
{ binding: 4, resource: { buffer: this.skinMatrixBuffer! } },
|
|
1890
|
-
{ binding: 5, resource: toonTexture.createView() },
|
|
1891
|
-
{ binding: 6, resource: this.textureSampler },
|
|
1892
|
-
{ binding: 7, resource: { buffer: materialUniformBufferOverEyes } },
|
|
1893
|
-
],
|
|
1894
|
-
})
|
|
1849
|
+
// Hair materials: create separate bind groups for over-eyes vs over-non-eyes
|
|
1850
|
+
const createHairBindGroup = (isOverEyes: boolean) => {
|
|
1851
|
+
const uniformData = new Float32Array(8)
|
|
1852
|
+
uniformData[0] = materialAlpha
|
|
1853
|
+
uniformData[1] = 1.0 // alphaMultiplier (shader adjusts based on isOverEyes)
|
|
1854
|
+
uniformData[2] = this.rimLightIntensity
|
|
1855
|
+
uniformData[3] = this.rimLightPower
|
|
1856
|
+
uniformData[4] = 1.0 // rimColor.rgb
|
|
1857
|
+
uniformData[5] = 1.0
|
|
1858
|
+
uniformData[6] = 1.0
|
|
1859
|
+
uniformData[7] = isOverEyes ? 1.0 : 0.0
|
|
1860
|
+
|
|
1861
|
+
const buffer = this.device.createBuffer({
|
|
1862
|
+
label: `material uniform (${isOverEyes ? "over eyes" : "over non-eyes"}): ${mat.name}`,
|
|
1863
|
+
size: uniformData.byteLength,
|
|
1864
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1865
|
+
})
|
|
1866
|
+
this.device.queue.writeBuffer(buffer, 0, uniformData)
|
|
1867
|
+
|
|
1868
|
+
return this.device.createBindGroup({
|
|
1869
|
+
label: `material bind group (${isOverEyes ? "over eyes" : "over non-eyes"}): ${mat.name}`,
|
|
1870
|
+
layout: this.mainBindGroupLayout,
|
|
1871
|
+
entries: [
|
|
1872
|
+
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1873
|
+
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1874
|
+
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1875
|
+
{ binding: 3, resource: this.textureSampler },
|
|
1876
|
+
{ binding: 4, resource: { buffer: this.skinMatrixBuffer! } },
|
|
1877
|
+
{ binding: 5, resource: toonTexture.createView() },
|
|
1878
|
+
{ binding: 6, resource: this.textureSampler },
|
|
1879
|
+
{ binding: 7, resource: { buffer: buffer } },
|
|
1880
|
+
],
|
|
1881
|
+
})
|
|
1882
|
+
}
|
|
1895
1883
|
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
layout: this.hairBindGroupLayout,
|
|
1899
|
-
entries: [
|
|
1900
|
-
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1901
|
-
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1902
|
-
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1903
|
-
{ binding: 3, resource: this.textureSampler },
|
|
1904
|
-
{ binding: 4, resource: { buffer: this.skinMatrixBuffer! } },
|
|
1905
|
-
{ binding: 5, resource: toonTexture.createView() },
|
|
1906
|
-
{ binding: 6, resource: this.textureSampler },
|
|
1907
|
-
{ binding: 7, resource: { buffer: materialUniformBufferOverNonEyes } },
|
|
1908
|
-
],
|
|
1909
|
-
})
|
|
1884
|
+
const bindGroupOverEyes = createHairBindGroup(true)
|
|
1885
|
+
const bindGroupOverNonEyes = createHairBindGroup(false)
|
|
1910
1886
|
|
|
1911
|
-
// Store both bind groups for unified pipeline
|
|
1912
1887
|
this.hairDrawsOverEyes.push({
|
|
1913
|
-
count:
|
|
1914
|
-
firstIndex:
|
|
1888
|
+
count: indexCount,
|
|
1889
|
+
firstIndex: currentIndexOffset,
|
|
1915
1890
|
bindGroup: bindGroupOverEyes,
|
|
1916
1891
|
isTransparent,
|
|
1917
1892
|
})
|
|
1918
1893
|
|
|
1919
1894
|
this.hairDrawsOverNonEyes.push({
|
|
1920
|
-
count:
|
|
1921
|
-
firstIndex:
|
|
1895
|
+
count: indexCount,
|
|
1896
|
+
firstIndex: currentIndexOffset,
|
|
1922
1897
|
bindGroup: bindGroupOverNonEyes,
|
|
1923
1898
|
isTransparent,
|
|
1924
1899
|
})
|
|
1925
1900
|
} else if (isTransparent) {
|
|
1926
1901
|
this.transparentNonEyeNonHairDraws.push({
|
|
1927
|
-
count:
|
|
1928
|
-
firstIndex:
|
|
1902
|
+
count: indexCount,
|
|
1903
|
+
firstIndex: currentIndexOffset,
|
|
1929
1904
|
bindGroup,
|
|
1930
1905
|
isTransparent,
|
|
1931
1906
|
})
|
|
1932
1907
|
} else {
|
|
1933
1908
|
this.opaqueNonEyeNonHairDraws.push({
|
|
1934
|
-
count:
|
|
1935
|
-
firstIndex:
|
|
1909
|
+
count: indexCount,
|
|
1910
|
+
firstIndex: currentIndexOffset,
|
|
1936
1911
|
bindGroup,
|
|
1937
1912
|
isTransparent,
|
|
1938
1913
|
})
|
|
1939
1914
|
}
|
|
1940
1915
|
|
|
1941
|
-
//
|
|
1916
|
+
// Edge flag is at bit 4 (0x10) in PMX format
|
|
1942
1917
|
if ((mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0) {
|
|
1943
1918
|
const materialUniformData = new Float32Array(8)
|
|
1944
1919
|
materialUniformData[0] = mat.edgeColor[0] // edgeColor.r
|
|
@@ -1946,9 +1921,9 @@ export class Engine {
|
|
|
1946
1921
|
materialUniformData[2] = mat.edgeColor[2] // edgeColor.b
|
|
1947
1922
|
materialUniformData[3] = mat.edgeColor[3] // edgeColor.a
|
|
1948
1923
|
materialUniformData[4] = mat.edgeSize
|
|
1949
|
-
materialUniformData[5] = 0.0 // isOverEyes
|
|
1950
|
-
materialUniformData[6] = 0.0
|
|
1951
|
-
materialUniformData[7] = 0.0
|
|
1924
|
+
materialUniformData[5] = 0.0 // isOverEyes
|
|
1925
|
+
materialUniformData[6] = 0.0
|
|
1926
|
+
materialUniformData[7] = 0.0
|
|
1952
1927
|
|
|
1953
1928
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1954
1929
|
label: `outline material uniform: ${mat.name}`,
|
|
@@ -1967,44 +1942,44 @@ export class Engine {
|
|
|
1967
1942
|
],
|
|
1968
1943
|
})
|
|
1969
1944
|
|
|
1970
|
-
// Classify outlines into appropriate draw lists
|
|
1971
1945
|
if (mat.isEye) {
|
|
1972
1946
|
this.eyeOutlineDraws.push({
|
|
1973
|
-
count:
|
|
1974
|
-
firstIndex:
|
|
1947
|
+
count: indexCount,
|
|
1948
|
+
firstIndex: currentIndexOffset,
|
|
1975
1949
|
bindGroup: outlineBindGroup,
|
|
1976
1950
|
isTransparent,
|
|
1977
1951
|
})
|
|
1978
1952
|
} else if (mat.isHair) {
|
|
1979
1953
|
this.hairOutlineDraws.push({
|
|
1980
|
-
count:
|
|
1981
|
-
firstIndex:
|
|
1954
|
+
count: indexCount,
|
|
1955
|
+
firstIndex: currentIndexOffset,
|
|
1982
1956
|
bindGroup: outlineBindGroup,
|
|
1983
1957
|
isTransparent,
|
|
1984
1958
|
})
|
|
1985
1959
|
} else if (isTransparent) {
|
|
1986
1960
|
this.transparentNonEyeNonHairOutlineDraws.push({
|
|
1987
|
-
count:
|
|
1988
|
-
firstIndex:
|
|
1961
|
+
count: indexCount,
|
|
1962
|
+
firstIndex: currentIndexOffset,
|
|
1989
1963
|
bindGroup: outlineBindGroup,
|
|
1990
1964
|
isTransparent,
|
|
1991
1965
|
})
|
|
1992
1966
|
} else {
|
|
1993
1967
|
this.opaqueNonEyeNonHairOutlineDraws.push({
|
|
1994
|
-
count:
|
|
1995
|
-
firstIndex:
|
|
1968
|
+
count: indexCount,
|
|
1969
|
+
firstIndex: currentIndexOffset,
|
|
1996
1970
|
bindGroup: outlineBindGroup,
|
|
1997
1971
|
isTransparent,
|
|
1998
1972
|
})
|
|
1999
1973
|
}
|
|
2000
1974
|
}
|
|
2001
1975
|
|
|
2002
|
-
|
|
1976
|
+
currentIndexOffset += indexCount
|
|
2003
1977
|
}
|
|
1978
|
+
|
|
1979
|
+
this.gpuMemoryMB = this.calculateGpuMemory()
|
|
2004
1980
|
}
|
|
2005
1981
|
|
|
2006
|
-
|
|
2007
|
-
private async createTextureFromPath(path: string, maxSize: number = 2048): Promise<GPUTexture | null> {
|
|
1982
|
+
private async createTextureFromPath(path: string): Promise<GPUTexture | null> {
|
|
2008
1983
|
const cached = this.textureCache.get(path)
|
|
2009
1984
|
if (cached) {
|
|
2010
1985
|
return cached
|
|
@@ -2015,45 +1990,30 @@ export class Engine {
|
|
|
2015
1990
|
if (!response.ok) {
|
|
2016
1991
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
2017
1992
|
}
|
|
2018
|
-
|
|
1993
|
+
const imageBitmap = await createImageBitmap(await response.blob(), {
|
|
2019
1994
|
premultiplyAlpha: "none",
|
|
2020
1995
|
colorSpaceConversion: "none",
|
|
2021
1996
|
})
|
|
2022
1997
|
|
|
2023
|
-
// Downscale if texture is too large
|
|
2024
|
-
let finalWidth = imageBitmap.width
|
|
2025
|
-
let finalHeight = imageBitmap.height
|
|
2026
|
-
if (finalWidth > maxSize || finalHeight > maxSize) {
|
|
2027
|
-
const scale = Math.min(maxSize / finalWidth, maxSize / finalHeight)
|
|
2028
|
-
finalWidth = Math.floor(finalWidth * scale)
|
|
2029
|
-
finalHeight = Math.floor(finalHeight * scale)
|
|
2030
|
-
|
|
2031
|
-
// Create canvas to downscale
|
|
2032
|
-
const canvas = new OffscreenCanvas(finalWidth, finalHeight)
|
|
2033
|
-
const ctx = canvas.getContext("2d")
|
|
2034
|
-
if (ctx) {
|
|
2035
|
-
ctx.drawImage(imageBitmap, 0, 0, finalWidth, finalHeight)
|
|
2036
|
-
imageBitmap = await createImageBitmap(canvas)
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
1998
|
const texture = this.device.createTexture({
|
|
2041
1999
|
label: `texture: ${path}`,
|
|
2042
|
-
size: [
|
|
2000
|
+
size: [imageBitmap.width, imageBitmap.height],
|
|
2043
2001
|
format: "rgba8unorm",
|
|
2044
2002
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
2045
2003
|
})
|
|
2046
|
-
this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [
|
|
2004
|
+
this.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [
|
|
2005
|
+
imageBitmap.width,
|
|
2006
|
+
imageBitmap.height,
|
|
2007
|
+
])
|
|
2047
2008
|
|
|
2048
2009
|
this.textureCache.set(path, texture)
|
|
2049
|
-
this.textureSizes.set(path, { width: finalWidth, height: finalHeight })
|
|
2050
2010
|
return texture
|
|
2051
2011
|
} catch {
|
|
2052
2012
|
return null
|
|
2053
2013
|
}
|
|
2054
2014
|
}
|
|
2055
2015
|
|
|
2056
|
-
//
|
|
2016
|
+
// Render strategy: 1) Opaque non-eye/hair 2) Eyes (stencil=1) 3) Hair (depth pre-pass + split by stencil) 4) Transparent 5) Bloom
|
|
2057
2017
|
public render() {
|
|
2058
2018
|
if (this.multisampleTexture && this.camera && this.device && this.currentModel) {
|
|
2059
2019
|
const currentTime = performance.now()
|
|
@@ -2063,9 +2023,11 @@ export class Engine {
|
|
|
2063
2023
|
this.updateCameraUniforms()
|
|
2064
2024
|
this.updateRenderTarget()
|
|
2065
2025
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2026
|
+
// Use single encoder for both compute and render (reduces sync points)
|
|
2068
2027
|
const encoder = this.device.createCommandEncoder()
|
|
2028
|
+
|
|
2029
|
+
this.updateModelPose(deltaTime, encoder)
|
|
2030
|
+
|
|
2069
2031
|
const pass = encoder.beginRenderPass(this.renderPassDescriptor)
|
|
2070
2032
|
|
|
2071
2033
|
pass.setVertexBuffer(0, this.vertexBuffer)
|
|
@@ -2075,8 +2037,8 @@ export class Engine {
|
|
|
2075
2037
|
|
|
2076
2038
|
this.drawCallCount = 0
|
|
2077
2039
|
|
|
2078
|
-
//
|
|
2079
|
-
pass.setPipeline(this.
|
|
2040
|
+
// Pass 1: Opaque non-eye, non-hair
|
|
2041
|
+
pass.setPipeline(this.modelPipeline)
|
|
2080
2042
|
for (const draw of this.opaqueNonEyeNonHairDraws) {
|
|
2081
2043
|
if (draw.count > 0) {
|
|
2082
2044
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2085,9 +2047,9 @@ export class Engine {
|
|
|
2085
2047
|
}
|
|
2086
2048
|
}
|
|
2087
2049
|
|
|
2088
|
-
//
|
|
2050
|
+
// Pass 2: Eyes (writes stencil value for hair to test against)
|
|
2089
2051
|
pass.setPipeline(this.eyePipeline)
|
|
2090
|
-
pass.setStencilReference(
|
|
2052
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE)
|
|
2091
2053
|
for (const draw of this.eyeDraws) {
|
|
2092
2054
|
if (draw.count > 0) {
|
|
2093
2055
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2096,10 +2058,10 @@ export class Engine {
|
|
|
2096
2058
|
}
|
|
2097
2059
|
}
|
|
2098
2060
|
|
|
2099
|
-
//
|
|
2061
|
+
// Pass 3: Hair rendering (depth pre-pass + shading + outlines)
|
|
2100
2062
|
this.drawOutlines(pass, false)
|
|
2101
2063
|
|
|
2102
|
-
// 3a: Hair depth pre-pass (
|
|
2064
|
+
// 3a: Hair depth pre-pass (reduces overdraw via early depth rejection)
|
|
2103
2065
|
if (this.hairDrawsOverEyes.length > 0 || this.hairDrawsOverNonEyes.length > 0) {
|
|
2104
2066
|
pass.setPipeline(this.hairDepthPipeline)
|
|
2105
2067
|
for (const draw of this.hairDrawsOverEyes) {
|
|
@@ -2116,10 +2078,10 @@ export class Engine {
|
|
|
2116
2078
|
}
|
|
2117
2079
|
}
|
|
2118
2080
|
|
|
2119
|
-
// 3b: Hair shading
|
|
2081
|
+
// 3b: Hair shading (split by stencil for transparency over eyes)
|
|
2120
2082
|
if (this.hairDrawsOverEyes.length > 0) {
|
|
2121
|
-
pass.setPipeline(this.
|
|
2122
|
-
pass.setStencilReference(
|
|
2083
|
+
pass.setPipeline(this.hairPipelineOverEyes)
|
|
2084
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE)
|
|
2123
2085
|
for (const draw of this.hairDrawsOverEyes) {
|
|
2124
2086
|
if (draw.count > 0) {
|
|
2125
2087
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2130,8 +2092,8 @@ export class Engine {
|
|
|
2130
2092
|
}
|
|
2131
2093
|
|
|
2132
2094
|
if (this.hairDrawsOverNonEyes.length > 0) {
|
|
2133
|
-
pass.setPipeline(this.
|
|
2134
|
-
pass.setStencilReference(
|
|
2095
|
+
pass.setPipeline(this.hairPipelineOverNonEyes)
|
|
2096
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE)
|
|
2135
2097
|
for (const draw of this.hairDrawsOverNonEyes) {
|
|
2136
2098
|
if (draw.count > 0) {
|
|
2137
2099
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2141,9 +2103,9 @@ export class Engine {
|
|
|
2141
2103
|
}
|
|
2142
2104
|
}
|
|
2143
2105
|
|
|
2144
|
-
// 3c: Hair outlines
|
|
2106
|
+
// 3c: Hair outlines
|
|
2145
2107
|
if (this.hairOutlineDraws.length > 0) {
|
|
2146
|
-
pass.setPipeline(this.
|
|
2108
|
+
pass.setPipeline(this.hairOutlinePipeline)
|
|
2147
2109
|
for (const draw of this.hairOutlineDraws) {
|
|
2148
2110
|
if (draw.count > 0) {
|
|
2149
2111
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2152,8 +2114,8 @@ export class Engine {
|
|
|
2152
2114
|
}
|
|
2153
2115
|
}
|
|
2154
2116
|
|
|
2155
|
-
//
|
|
2156
|
-
pass.setPipeline(this.
|
|
2117
|
+
// Pass 4: Transparent non-eye, non-hair
|
|
2118
|
+
pass.setPipeline(this.modelPipeline)
|
|
2157
2119
|
for (const draw of this.transparentNonEyeNonHairDraws) {
|
|
2158
2120
|
if (draw.count > 0) {
|
|
2159
2121
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2167,14 +2129,12 @@ export class Engine {
|
|
|
2167
2129
|
pass.end()
|
|
2168
2130
|
this.device.queue.submit([encoder.finish()])
|
|
2169
2131
|
|
|
2170
|
-
// Apply bloom post-processing
|
|
2171
2132
|
this.applyBloom()
|
|
2172
2133
|
|
|
2173
2134
|
this.updateStats(performance.now() - currentTime)
|
|
2174
2135
|
}
|
|
2175
2136
|
}
|
|
2176
2137
|
|
|
2177
|
-
// Apply bloom post-processing
|
|
2178
2138
|
private applyBloom() {
|
|
2179
2139
|
if (!this.sceneRenderTexture || !this.bloomExtractTexture) {
|
|
2180
2140
|
return
|
|
@@ -2192,10 +2152,10 @@ export class Engine {
|
|
|
2192
2152
|
const encoder = this.device.createCommandEncoder()
|
|
2193
2153
|
const width = this.canvas.width
|
|
2194
2154
|
const height = this.canvas.height
|
|
2195
|
-
const bloomWidth = Math.floor(width /
|
|
2196
|
-
const bloomHeight = Math.floor(height /
|
|
2155
|
+
const bloomWidth = Math.floor(width / this.BLOOM_DOWNSCALE_FACTOR)
|
|
2156
|
+
const bloomHeight = Math.floor(height / this.BLOOM_DOWNSCALE_FACTOR)
|
|
2197
2157
|
|
|
2198
|
-
//
|
|
2158
|
+
// Extract bright areas
|
|
2199
2159
|
const extractPass = encoder.beginRenderPass({
|
|
2200
2160
|
label: "bloom extract",
|
|
2201
2161
|
colorAttachments: [
|
|
@@ -2213,8 +2173,8 @@ export class Engine {
|
|
|
2213
2173
|
extractPass.draw(6, 1, 0, 0)
|
|
2214
2174
|
extractPass.end()
|
|
2215
2175
|
|
|
2216
|
-
//
|
|
2217
|
-
const hBlurData = new Float32Array(4)
|
|
2176
|
+
// Horizontal blur
|
|
2177
|
+
const hBlurData = new Float32Array(4)
|
|
2218
2178
|
hBlurData[0] = 1.0
|
|
2219
2179
|
hBlurData[1] = 0.0
|
|
2220
2180
|
this.device.queue.writeBuffer(this.blurDirectionBuffer, 0, hBlurData)
|
|
@@ -2235,8 +2195,8 @@ export class Engine {
|
|
|
2235
2195
|
blurHPass.draw(6, 1, 0, 0)
|
|
2236
2196
|
blurHPass.end()
|
|
2237
2197
|
|
|
2238
|
-
//
|
|
2239
|
-
const vBlurData = new Float32Array(4)
|
|
2198
|
+
// Vertical blur
|
|
2199
|
+
const vBlurData = new Float32Array(4)
|
|
2240
2200
|
vBlurData[0] = 0.0
|
|
2241
2201
|
vBlurData[1] = 1.0
|
|
2242
2202
|
this.device.queue.writeBuffer(this.blurDirectionBuffer, 0, vBlurData)
|
|
@@ -2257,7 +2217,7 @@ export class Engine {
|
|
|
2257
2217
|
blurVPass.draw(6, 1, 0, 0)
|
|
2258
2218
|
blurVPass.end()
|
|
2259
2219
|
|
|
2260
|
-
//
|
|
2220
|
+
// Compose to canvas
|
|
2261
2221
|
const composePass = encoder.beginRenderPass({
|
|
2262
2222
|
label: "bloom compose",
|
|
2263
2223
|
colorAttachments: [
|
|
@@ -2278,7 +2238,6 @@ export class Engine {
|
|
|
2278
2238
|
this.device.queue.submit([encoder.finish()])
|
|
2279
2239
|
}
|
|
2280
2240
|
|
|
2281
|
-
// Update camera uniform buffer each frame
|
|
2282
2241
|
private updateCameraUniforms() {
|
|
2283
2242
|
const viewMatrix = this.camera.getViewMatrix()
|
|
2284
2243
|
const projectionMatrix = this.camera.getProjectionMatrix()
|
|
@@ -2291,19 +2250,16 @@ export class Engine {
|
|
|
2291
2250
|
this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraMatrixData)
|
|
2292
2251
|
}
|
|
2293
2252
|
|
|
2294
|
-
// Update render target texture view
|
|
2295
2253
|
private updateRenderTarget() {
|
|
2296
2254
|
const colorAttachment = (this.renderPassDescriptor.colorAttachments as GPURenderPassColorAttachment[])[0]
|
|
2297
2255
|
if (this.sampleCount > 1) {
|
|
2298
|
-
// Resolve to scene render texture for post-processing
|
|
2299
2256
|
colorAttachment.resolveTarget = this.sceneRenderTextureView
|
|
2300
2257
|
} else {
|
|
2301
|
-
// Render directly to scene render texture
|
|
2302
2258
|
colorAttachment.view = this.sceneRenderTextureView
|
|
2303
2259
|
}
|
|
2304
2260
|
}
|
|
2305
2261
|
|
|
2306
|
-
private updateModelPose(deltaTime: number) {
|
|
2262
|
+
private updateModelPose(deltaTime: number, encoder: GPUCommandEncoder) {
|
|
2307
2263
|
this.currentModel!.evaluatePose()
|
|
2308
2264
|
const worldMats = this.currentModel!.getBoneWorldMatrices()
|
|
2309
2265
|
|
|
@@ -2318,32 +2274,23 @@ export class Engine {
|
|
|
2318
2274
|
worldMats.byteOffset,
|
|
2319
2275
|
worldMats.byteLength
|
|
2320
2276
|
)
|
|
2321
|
-
this.computeSkinMatrices()
|
|
2277
|
+
this.computeSkinMatrices(encoder)
|
|
2322
2278
|
}
|
|
2323
2279
|
|
|
2324
|
-
|
|
2325
|
-
private computeSkinMatrices() {
|
|
2280
|
+
private computeSkinMatrices(encoder: GPUCommandEncoder) {
|
|
2326
2281
|
const boneCount = this.currentModel!.getSkeleton().bones.length
|
|
2327
|
-
const
|
|
2328
|
-
// Dispatch exactly enough threads for all bones (no bounds check needed)
|
|
2329
|
-
const workgroupCount = Math.ceil(boneCount / workgroupSize)
|
|
2330
|
-
|
|
2331
|
-
// Bone count is written once in setupModelBuffers() and never changes
|
|
2282
|
+
const workgroupCount = Math.ceil(boneCount / this.COMPUTE_WORKGROUP_SIZE)
|
|
2332
2283
|
|
|
2333
|
-
const encoder = this.device.createCommandEncoder()
|
|
2334
2284
|
const pass = encoder.beginComputePass()
|
|
2335
2285
|
pass.setPipeline(this.skinMatrixComputePipeline!)
|
|
2336
2286
|
pass.setBindGroup(0, this.skinMatrixComputeBindGroup!)
|
|
2337
2287
|
pass.dispatchWorkgroups(workgroupCount)
|
|
2338
2288
|
pass.end()
|
|
2339
|
-
this.device.queue.submit([encoder.finish()])
|
|
2340
2289
|
}
|
|
2341
2290
|
|
|
2342
|
-
// Draw outlines (opaque or transparent)
|
|
2343
2291
|
private drawOutlines(pass: GPURenderPassEncoder, transparent: boolean) {
|
|
2344
2292
|
pass.setPipeline(this.outlinePipeline)
|
|
2345
2293
|
if (transparent) {
|
|
2346
|
-
// Draw transparent outlines (if any)
|
|
2347
2294
|
for (const draw of this.transparentNonEyeNonHairOutlineDraws) {
|
|
2348
2295
|
if (draw.count > 0) {
|
|
2349
2296
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2351,7 +2298,6 @@ export class Engine {
|
|
|
2351
2298
|
}
|
|
2352
2299
|
}
|
|
2353
2300
|
} else {
|
|
2354
|
-
// Draw opaque outlines before main geometry
|
|
2355
2301
|
for (const draw of this.opaqueNonEyeNonHairOutlineDraws) {
|
|
2356
2302
|
if (draw.count > 0) {
|
|
2357
2303
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -2382,12 +2328,13 @@ export class Engine {
|
|
|
2382
2328
|
this.lastFpsUpdate = now
|
|
2383
2329
|
}
|
|
2384
2330
|
|
|
2385
|
-
|
|
2331
|
+
this.stats.gpuMemory = this.gpuMemoryMB
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
private calculateGpuMemory(): number {
|
|
2386
2335
|
let textureMemoryBytes = 0
|
|
2387
|
-
for (const
|
|
2388
|
-
|
|
2389
|
-
textureMemoryBytes += size.width * size.height * 4 // RGBA8 = 4 bytes per pixel
|
|
2390
|
-
}
|
|
2336
|
+
for (const texture of this.textureCache.values()) {
|
|
2337
|
+
textureMemoryBytes += texture.width * texture.height * 4
|
|
2391
2338
|
}
|
|
2392
2339
|
|
|
2393
2340
|
let bufferMemoryBytes = 0
|
|
@@ -2419,54 +2366,49 @@ export class Engine {
|
|
|
2419
2366
|
const skeleton = this.currentModel?.getSkeleton()
|
|
2420
2367
|
if (skeleton) bufferMemoryBytes += Math.max(256, skeleton.bones.length * 16 * 4)
|
|
2421
2368
|
}
|
|
2422
|
-
bufferMemoryBytes += 40 * 4
|
|
2423
|
-
bufferMemoryBytes += 64 * 4
|
|
2424
|
-
bufferMemoryBytes += 32
|
|
2425
|
-
bufferMemoryBytes += 32
|
|
2426
|
-
bufferMemoryBytes += 32
|
|
2427
|
-
bufferMemoryBytes += 32
|
|
2369
|
+
bufferMemoryBytes += 40 * 4
|
|
2370
|
+
bufferMemoryBytes += 64 * 4
|
|
2371
|
+
bufferMemoryBytes += 32
|
|
2372
|
+
bufferMemoryBytes += 32
|
|
2373
|
+
bufferMemoryBytes += 32
|
|
2374
|
+
bufferMemoryBytes += 32
|
|
2428
2375
|
if (this.fullscreenQuadBuffer) {
|
|
2429
|
-
bufferMemoryBytes += 24 * 4
|
|
2376
|
+
bufferMemoryBytes += 24 * 4
|
|
2430
2377
|
}
|
|
2431
|
-
|
|
2432
|
-
// Material uniform buffers: Float32Array(8) = 32 bytes each
|
|
2433
2378
|
const totalMaterialDraws =
|
|
2434
2379
|
this.opaqueNonEyeNonHairDraws.length +
|
|
2435
2380
|
this.eyeDraws.length +
|
|
2436
2381
|
this.hairDrawsOverEyes.length +
|
|
2437
2382
|
this.hairDrawsOverNonEyes.length +
|
|
2438
2383
|
this.transparentNonEyeNonHairDraws.length
|
|
2439
|
-
bufferMemoryBytes += totalMaterialDraws * 32
|
|
2384
|
+
bufferMemoryBytes += totalMaterialDraws * 32
|
|
2440
2385
|
|
|
2441
|
-
// Outline material uniform buffers: Float32Array(8) = 32 bytes each
|
|
2442
2386
|
const totalOutlineDraws =
|
|
2443
2387
|
this.opaqueNonEyeNonHairOutlineDraws.length +
|
|
2444
2388
|
this.eyeOutlineDraws.length +
|
|
2445
2389
|
this.hairOutlineDraws.length +
|
|
2446
2390
|
this.transparentNonEyeNonHairOutlineDraws.length
|
|
2447
|
-
bufferMemoryBytes += totalOutlineDraws * 32
|
|
2391
|
+
bufferMemoryBytes += totalOutlineDraws * 32
|
|
2448
2392
|
|
|
2449
2393
|
let renderTargetMemoryBytes = 0
|
|
2450
2394
|
if (this.multisampleTexture) {
|
|
2451
2395
|
const width = this.canvas.width
|
|
2452
2396
|
const height = this.canvas.height
|
|
2453
|
-
renderTargetMemoryBytes += width * height * 4 * this.sampleCount
|
|
2454
|
-
renderTargetMemoryBytes += width * height * 4
|
|
2397
|
+
renderTargetMemoryBytes += width * height * 4 * this.sampleCount
|
|
2398
|
+
renderTargetMemoryBytes += width * height * 4
|
|
2455
2399
|
}
|
|
2456
2400
|
if (this.sceneRenderTexture) {
|
|
2457
2401
|
const width = this.canvas.width
|
|
2458
2402
|
const height = this.canvas.height
|
|
2459
|
-
renderTargetMemoryBytes += width * height * 4
|
|
2403
|
+
renderTargetMemoryBytes += width * height * 4
|
|
2460
2404
|
}
|
|
2461
2405
|
if (this.bloomExtractTexture) {
|
|
2462
|
-
const width = Math.floor(this.canvas.width /
|
|
2463
|
-
const height = Math.floor(this.canvas.height /
|
|
2464
|
-
renderTargetMemoryBytes += width * height * 4
|
|
2465
|
-
renderTargetMemoryBytes += width * height * 4 // bloomBlurTexture1
|
|
2466
|
-
renderTargetMemoryBytes += width * height * 4 // bloomBlurTexture2
|
|
2406
|
+
const width = Math.floor(this.canvas.width / this.BLOOM_DOWNSCALE_FACTOR)
|
|
2407
|
+
const height = Math.floor(this.canvas.height / this.BLOOM_DOWNSCALE_FACTOR)
|
|
2408
|
+
renderTargetMemoryBytes += width * height * 4 * 3
|
|
2467
2409
|
}
|
|
2468
2410
|
|
|
2469
2411
|
const totalGPUMemoryBytes = textureMemoryBytes + bufferMemoryBytes + renderTargetMemoryBytes
|
|
2470
|
-
|
|
2412
|
+
return Math.round((totalGPUMemoryBytes / 1024 / 1024) * 100) / 100
|
|
2471
2413
|
}
|
|
2472
2414
|
}
|