Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions src/platform/graphics/webgpu/webgpu-shader-processor-wgsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,25 @@ class ResourceLine {
shader.failed = true;
}
}

equals(other) {
if (this.name !== other.name) return false;
if (this.type !== other.type) return false;
if (this.isTexture !== other.isTexture) return false;
if (this.isSampler !== other.isSampler) return false;
if (this.isStorageTexture !== other.isStorageTexture) return false;
if (this.isStorageBuffer !== other.isStorageBuffer) return false;
if (this.isExternalTexture !== other.isExternalTexture) return false;
if (this.textureFormat !== other.textureFormat) return false;
if (this.textureDimension !== other.textureDimension) return false;
if (this.sampleType !== other.sampleType) return false;
if (this.textureType !== other.textureType) return false;
if (this.format !== other.format) return false;
if (this.access !== other.access) return false;
if (this.accessMode !== other.accessMode) return false;
if (this.samplerType !== other.samplerType) return false;
return true;
}
}

/**
Expand Down Expand Up @@ -340,9 +359,7 @@ class WebgpuShaderProcessorWGSL {
fragmentExtracted.src = WebgpuShaderProcessorWGSL.renameUniformAccess(fragmentExtracted.src, parsedUniforms);

// parse resource lines
const concatResources = vertexExtracted.resources.concat(fragmentExtracted.resources);
const resources = Array.from(new Set(concatResources));
const parsedResources = resources.map(line => new ResourceLine(line, shader));
const parsedResources = WebgpuShaderProcessorWGSL.mergeResources(vertexExtracted.resources, fragmentExtracted.resources, shader);
const resourcesData = WebgpuShaderProcessorWGSL.processResources(device, parsedResources, shaderDefinition.processingOptions, shader);

// generate fragment output struct
Expand Down Expand Up @@ -508,6 +525,34 @@ class WebgpuShaderProcessorWGSL {
return source;
}

static mergeResources(vertex, fragment, shader) {

const resources = vertex.map(line => new ResourceLine(line, shader));
const fragmentResources = fragment.map(line => new ResourceLine(line, shader));

// merge fragment list to resources, removing exact duplicates
fragmentResources.forEach((fragmentResource) => {
const existing = resources.find(resource => resource.name === fragmentResource.name);
if (existing) {
// if the resource is already in the list, check if it matches
if (!existing.equals(fragmentResource)) {
Debug.error(`Resource '${fragmentResource.name}' is declared with different types in vertex and fragment shaders.`, {
vertexLine: existing.line,
fragmentLine: fragmentResource.line,
shader,
vertexResource: existing,
fragmentResource
});
shader.failed = true;
}
} else {
resources.push(fragmentResource);
}
});

return resources;
}

static processResources(device, resources, processingOptions, shader) {

// build mesh bind group format - this contains the textures, but not the uniform buffer as that is a separate binding
Expand Down Expand Up @@ -810,7 +855,7 @@ class WebgpuShaderProcessorWGSL {
// copy input variable to the private variable - convert type if needed
blockCopy += ` ${name} = ${originalType}(input.${name});\n`;
} else {
Debug.error(`Attribute ${name} is not defined in the shader definition.`, shaderDefinitionAttributes);
Debug.error(`Attribute ${name} is specified in the shader source, but is not defined in the shader definition, and so will be removed from the shader, as it cannot be used without a known semantic.`, shaderDefinitionAttributes);
}
});

Expand Down
1 change: 0 additions & 1 deletion src/scene/materials/standard-material.js
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,6 @@ function _defineMaterialProps() {
_defineFlag('fresnelModel', FRESNEL_SCHLICK); // NOTE: this has been made to match the default shading model (to fix a bug)
_defineFlag('useDynamicRefraction', false);
_defineFlag('cubeMapProjection', CUBEPROJ_NONE);
_defineFlag('customFragmentShader', null);
_defineFlag('useFog', true);
_defineFlag('useLighting', true);
_defineFlag('useTonemap', true);
Expand Down
110 changes: 54 additions & 56 deletions src/scene/particle-system/particle-emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import {
TYPE_FLOAT32,
typedArrayIndexFormats,
requiresManualGamma,
PIXELFORMAT_SRGBA8
PIXELFORMAT_SRGBA8,
SHADERLANGUAGE_WGSL,
SHADERLANGUAGE_GLSL,
SEMANTIC_POSITION
} from '../../platform/graphics/constants.js';
import { DeviceCache } from '../../platform/graphics/device-cache.js';
import { IndexBuffer } from '../../platform/graphics/index-buffer.js';
Expand All @@ -40,6 +43,7 @@ import { Mesh } from '../mesh.js';
import { MeshInstance } from '../mesh-instance.js';
import { createShaderFromCode } from '../shader-lib/utils.js';
import { shaderChunks } from '../shader-lib/chunks/chunks.js';
import { shaderChunksWGSL } from '../shader-lib/chunks-wgsl/chunks-wgsl.js';
import { ParticleCPUUpdater } from './cpu-updater.js';
import { ParticleGPUUpdater } from './gpu-updater.js';
import { ParticleMaterial } from './particle-material.js';
Expand Down Expand Up @@ -662,27 +666,38 @@ class ParticleEmitter {
if (this.pack8) defines.set('PACK8', '');
if (this.emitterShape === EMITTERSHAPE_BOX) defines.set('EMITTERSHAPE_BOX', '');

const includes = new Map(Object.entries(shaderChunks));
const shaderLanguage = gd.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL;
const chunks = shaderLanguage === SHADERLANGUAGE_WGSL ? shaderChunksWGSL : shaderChunks;
const includes = new Map(Object.entries(chunks));

const shaderCodeRespawn = `#define RESPAWN\n ${shaderChunks.particle_simulationPS}`;
const shaderCodeNoRespawn = `#define NO_RESPAWN\n ${shaderChunks.particle_simulationPS}`;
const shaderCodeOnStop = `#define ON_STOP\n ${shaderChunks.particle_simulationPS}`;
const shaderCodeRespawn = `#define RESPAWN\n ${chunks.particle_simulationPS}`;
const shaderCodeNoRespawn = `#define NO_RESPAWN\n ${chunks.particle_simulationPS}`;
const shaderCodeOnStop = `#define ON_STOP\n ${chunks.particle_simulationPS}`;

// Note: createShaderFromCode can return a shader from the cache (not a new shader) so we *should not* delete these shaders
// when the particle emitter is destroyed
const params = `Shape:${this.emitterShape}-Pack:${this.pack8}-Local:${this.localSpace}`;
this.shaderParticleUpdateRespawn = createShaderFromCode(gd, shaderChunks.fullscreenQuadVS, shaderCodeRespawn, `ParticleUpdateRespawn-${params}`, undefined, false, {
fragmentDefines: defines,
fragmentIncludes: includes
});
this.shaderParticleUpdateNoRespawn = createShaderFromCode(gd, shaderChunks.fullscreenQuadVS, shaderCodeNoRespawn, `ParticleUpdateNoRespawn-${params}`, undefined, false, {
fragmentDefines: defines,
fragmentIncludes: includes
});
this.shaderParticleUpdateOnStop = createShaderFromCode(gd, shaderChunks.fullscreenQuadVS, shaderCodeOnStop, `ParticleUpdateStop-${params}`, undefined, false, {
fragmentDefines: defines,
fragmentIncludes: includes
});
this.shaderParticleUpdateRespawn = createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeRespawn, `ParticleUpdateRespawn-${params}`,
{ vertex_position: SEMANTIC_POSITION }, {
fragmentDefines: defines,
fragmentIncludes: includes,
shaderLanguage: shaderLanguage
}
);
this.shaderParticleUpdateNoRespawn = createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeNoRespawn, `ParticleUpdateNoRespawn-${params}`,
{ vertex_position: SEMANTIC_POSITION }, {
fragmentDefines: defines,
fragmentIncludes: includes,
shaderLanguage: shaderLanguage
}
);
this.shaderParticleUpdateOnStop = createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeOnStop, `ParticleUpdateStop-${params}`,
{ vertex_position: SEMANTIC_POSITION }, {
fragmentDefines: defines,
fragmentIncludes: includes,
shaderLanguage: shaderLanguage
}
);

this.numParticleVerts = this.useMesh ? this.mesh.vertexBuffer.numVertices : 4;
this.numParticleIndices = this.useMesh ? this.mesh.indexBuffer[0].numIndices : 6;
Expand Down Expand Up @@ -919,52 +934,35 @@ class ParticleEmitter {
this.material.setParameter('faceBinorm', binormal);
}

getVertexInfo() {
const elements = [];
if (!this.useCpu) {
// GPU: XYZ = quad vertex position; W = INT: particle ID, FRAC: random factor
elements.push({ semantic: SEMANTIC_ATTR0, components: 4, type: TYPE_FLOAT32 });
if (this.useMesh) {
elements.push({ semantic: SEMANTIC_ATTR1, components: 2, type: TYPE_FLOAT32 });
}
} else {
elements.push(
{ semantic: SEMANTIC_ATTR0, components: 4, type: TYPE_FLOAT32 },
{ semantic: SEMANTIC_ATTR1, components: 4, type: TYPE_FLOAT32 },
{ semantic: SEMANTIC_ATTR2, components: 4, type: TYPE_FLOAT32 },
{ semantic: SEMANTIC_ATTR3, components: 1, type: TYPE_FLOAT32 },
{ semantic: SEMANTIC_ATTR4, components: this.useMesh ? 4 : 2, type: TYPE_FLOAT32 }
);
}

return elements;
}

// Declares vertex format, creates VB and IB
_allocate(numParticles) {
const psysVertCount = numParticles * this.numParticleVerts;
const psysIndexCount = numParticles * this.numParticleIndices;

if ((this.vertexBuffer === undefined) || (this.vertexBuffer.getNumVertices() !== psysVertCount)) {
// Create the particle vertex format
const elements = [];
if (!this.useCpu) {
// GPU: XYZ = quad vertex position; W = INT: particle ID, FRAC: random factor
elements.push({
semantic: SEMANTIC_ATTR0,
components: 4,
type: TYPE_FLOAT32
});
if (this.useMesh) {
elements.push({
semantic: SEMANTIC_ATTR1,
components: 2,
type: TYPE_FLOAT32
});
}
} else {
elements.push({
semantic: SEMANTIC_ATTR0,
components: 4,
type: TYPE_FLOAT32
}, {
semantic: SEMANTIC_ATTR1,
components: 4,
type: TYPE_FLOAT32
}, {
semantic: SEMANTIC_ATTR2,
components: 4,
type: TYPE_FLOAT32
}, {
semantic: SEMANTIC_ATTR3,
components: 1,
type: TYPE_FLOAT32
}, {
semantic: SEMANTIC_ATTR4,
components: this.useMesh ? 4 : 2,
type: TYPE_FLOAT32
});
}

const elements = this.getVertexInfo();
const vertexFormat = new VertexFormat(this.graphicsDevice, elements);

this.vertexBuffer = new VertexBuffer(this.graphicsDevice, vertexFormat, psysVertCount, {
Expand Down
4 changes: 3 additions & 1 deletion src/scene/particle-system/particle-material.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
GAMMA_NONE,
PARTICLEORIENTATION_SCREEN,
SHADER_FORWARD,
SHADERDEF_UV0,
TONEMAP_LINEAR
} from '../constants.js';
import { getProgramLibrary } from '../shader-lib/get-program-library.js';
Expand Down Expand Up @@ -37,7 +38,7 @@ class ParticleMaterial extends Material {

getShaderVariant(params) {

const { device, scene, cameraShaderParams } = params;
const { device, scene, cameraShaderParams, objDefs } = params;
const { emitter } = this;
const options = {
defines: getCoreDefines(this, params),
Expand All @@ -49,6 +50,7 @@ class ParticleMaterial extends Material {
alignToMotion: this.emitter.alignToMotion,
soft: this.emitter.depthSoftening,
mesh: this.emitter.useMesh,
meshUv: objDefs & SHADERDEF_UV0,
gamma: cameraShaderParams?.shaderOutputGamma ?? GAMMA_NONE,
toneMap: cameraShaderParams?.toneMapping ?? TONEMAP_LINEAR,
fog: (scene && !this.emitter.noFog) ? scene.fog.type : 'none',
Expand Down
Loading