diff --git a/package-lock.json b/package-lock.json index 39b1f7489cb..0cfbbf1cdc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15232,6 +15232,7 @@ "version": "9.39.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", diff --git a/packages/dev/core/src/Engines/WebGPU/webgpuDrawContext.ts b/packages/dev/core/src/Engines/WebGPU/webgpuDrawContext.ts index 5f9eee24df9..da548e5ac61 100644 --- a/packages/dev/core/src/Engines/WebGPU/webgpuDrawContext.ts +++ b/packages/dev/core/src/Engines/WebGPU/webgpuDrawContext.ts @@ -170,6 +170,14 @@ export class WebGPUDrawContext implements IDrawContext { this._bufferManager.setRawData(this.indirectDrawBuffer, 0, this._indirectDrawData, 0, 20); } + /** + * Setup or disable vertex pulling as needed. + * @param useVertexPulling Use vertex pulling or not + * @param webgpuPipelineContext The WebGPU pipeline context + * @param vertexBuffers The current vertex buffers + * @param indexBuffer The current index buffer + * @param overrideVertexBuffers The vertex buffers to override + */ public setVertexPulling( useVertexPulling: boolean, webgpuPipelineContext: WebGPUPipelineContext, diff --git a/packages/dev/core/src/Materials/materialHelper.functions.ts b/packages/dev/core/src/Materials/materialHelper.functions.ts index 07b785ac8ee..b3ad7c42260 100644 --- a/packages/dev/core/src/Materials/materialHelper.functions.ts +++ b/packages/dev/core/src/Materials/materialHelper.functions.ts @@ -22,6 +22,7 @@ import { MaterialFlags } from "./materialFlags"; import { Texture } from "./Textures/texture"; import type { CubeTexture } from "./Textures/cubeTexture"; import type { Color3 } from "core/Maths/math.color"; +import type { Geometry } from "../Meshes/geometry"; // For backwards compatibility, we export everything from the pure version of this file. export * from "./materialHelper.functions.pure"; @@ -598,6 +599,91 @@ export function GetFogState(mesh: AbstractMesh, scene: Scene) { return scene.fogEnabled && mesh.applyFog && scene.fogMode !== Constants.FOGMODE_NONE; } +/** + * Interface representing metadata for vertex pulling + */ +export interface IVertexPullingMetadata { + /** + * Offset in vertex buffer where data starts + */ + offset: number; + + /** + * Stride between elements in the vertex buffer + */ + stride: number; + + /** + * Type of the vertex buffer (e.g., float, int) + */ + type: number; // VertexBuffer type constant +} + +// Store vertex pulling metadata per geometry +const _VertexPullingMetadataCache = new WeakMap>(); + +/** + * Prepares vertex pulling uniforms for the given attributes and mesh + * @param geometry The geometry containing the vertex buffers + * @returns A map of attribute names to their metadata, or null if unavailable + */ +export function PrepareVertexPullingUniforms(geometry: Geometry): Nullable> { + const vertexBuffers = geometry.getVertexBuffers(); + if (!vertexBuffers) { + return null; + } + + // Check cache first + let metadata = _VertexPullingMetadataCache.get(geometry); + if (!metadata) { + metadata = new Map(); + _VertexPullingMetadataCache.set(geometry, metadata); + } else { + // Return cached metadata if it exists and hasn't changed + let needsUpdate = false; + for (const vb in vertexBuffers) { + if (!metadata.has(vb)) { + needsUpdate = true; + break; + } + } + if (!needsUpdate) { + return metadata; + } + } + + // Build or update metadata + for (const vb in vertexBuffers) { + const vertexBuffer = vertexBuffers[vb]; + if (vertexBuffer) { + const offset = vertexBuffer.byteOffset; + const stride = vertexBuffer.byteStride; + const type = vertexBuffer.type; + + metadata.set(vb, { + offset: offset, + stride: stride, + type: type, + }); + } + } + + return metadata; +} + +/** + * Bind vertex pulling uniforms to the effect + * @param effect The effect to bind the uniforms to + * @param metadata The vertex pulling metadata + */ +export function BindVertexPullingUniforms(effect: Effect, metadata: Map): void { + metadata.forEach((data, attribute) => { + const uniformName = `vp_${attribute}_info`; + // Pack into vec3: (offset, stride, type) + effect.setFloat3(uniformName, data.offset, data.stride, data.type); + }); +} + /** * Helper used to prepare the list of defines associated with misc. values for shader compilation * @param mesh defines the current mesh diff --git a/packages/dev/core/src/Materials/shaderMaterial.ts b/packages/dev/core/src/Materials/shaderMaterial.ts index d3aa0051188..b5e8343b107 100644 --- a/packages/dev/core/src/Materials/shaderMaterial.ts +++ b/packages/dev/core/src/Materials/shaderMaterial.ts @@ -30,8 +30,11 @@ import { BindMorphTargetParameters, BindSceneUniformBuffer, PrepareDefinesAndAttributesForMorphTargets, + PrepareVertexPullingUniforms, + BindVertexPullingUniforms, PushAttributesForInstances, } from "./materialHelper.functions"; +import type { IVertexPullingMetadata } from "./materialHelper.functions"; import type { IColor3Like, IColor4Like, IVector2Like, IVector3Like, IVector4Like } from "core/Maths/math.like"; import type { InternalTexture } from "./Textures/internalTexture"; @@ -147,6 +150,7 @@ export class ShaderMaterial extends PushMaterial { private _cachedWorldViewMatrix = new Matrix(); private _cachedWorldViewProjectionMatrix = new Matrix(); private _multiview = false; + private _vertexPullingMetadata: Map | null = null; /** * @internal @@ -897,6 +901,20 @@ export class ShaderMaterial extends PushMaterial { } } + const renderingMesh = subMesh ? subMesh.getRenderingMesh() : mesh; + if (renderingMesh && this.useVertexPulling) { + // Add vertex buffer metadata defines for proper stride/offset handling + const geometry = renderingMesh.geometry; + if (geometry) { + this._vertexPullingMetadata = PrepareVertexPullingUniforms(geometry); + if (this._vertexPullingMetadata) { + this._vertexPullingMetadata.forEach((_, attribute) => { + uniforms.push(`vp_${attribute}_info`); + }); + } + } + } + if (this.customShaderNameResolve) { uniforms = uniforms.slice(); uniformBuffers = uniformBuffers.slice(); @@ -904,12 +922,11 @@ export class ShaderMaterial extends PushMaterial { shaderName = this.customShaderNameResolve(this.name, uniforms, uniformBuffers, samplers, defines, attribs); } - const renderingMesh = subMesh ? subMesh.getRenderingMesh() : mesh; if (renderingMesh && this.useVertexPulling) { defines.push("#define USE_VERTEX_PULLING"); const indexBuffer = renderingMesh.geometry?.getIndexBuffer(); - if (indexBuffer) { + if (indexBuffer && !(renderingMesh as Mesh).isUnIndexed) { defines.push("#define VERTEX_PULLING_USE_INDEX_BUFFER"); if (indexBuffer.is32Bits) { defines.push("#define VERTEX_PULLING_INDEX_BUFFER_32BITS"); @@ -1085,6 +1102,10 @@ export class ShaderMaterial extends PushMaterial { // Clip plane BindClipPlane(effect, this, scene); + if (this._vertexPullingMetadata) { + BindVertexPullingUniforms(effect, this._vertexPullingMetadata); + } + // Misc if (this._useLogarithmicDepth) { BindLogDepth(storeEffectOnSubMeshes ? subMesh.materialDefines : effect.defines, effect, scene); diff --git a/packages/dev/core/src/Rendering/IBLShadows/iblShadowsVoxelRenderer.ts b/packages/dev/core/src/Rendering/IBLShadows/iblShadowsVoxelRenderer.ts index b1af8433a09..30450dc3909 100644 --- a/packages/dev/core/src/Rendering/IBLShadows/iblShadowsVoxelRenderer.ts +++ b/packages/dev/core/src/Rendering/IBLShadows/iblShadowsVoxelRenderer.ts @@ -643,6 +643,9 @@ export class _IblShadowsVoxelRenderer { * @param includedMeshes */ public updateVoxelGrid(includedMeshes: Mesh[]) { + if (this._voxelizationInProgress) { + return; + } this._stopVoxelization(); this._includedMeshes = includedMeshes; this._voxelizationInProgress = true; @@ -738,6 +741,7 @@ export class _IblShadowsVoxelRenderer { if (axis === 1) { upDirection = new Vector3(1, 0, 0); } + mrt.onBeforeRenderObservable.clear(); mrt.onBeforeRenderObservable.add(() => { voxelMaterial.setMatrix("viewMatrix", Matrix.LookAtLH(cameraPosition, targetPosition, upDirection)); voxelMaterial.setMatrix("invWorldScale", this._invWorldScaleMatrix); diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/bonesDeclaration.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/bonesDeclaration.fx index 3ab2d1f410b..18770a5c482 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/bonesDeclaration.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/bonesDeclaration.fx @@ -1,9 +1,9 @@ #if NUM_BONE_INFLUENCERS > 0 - attribute matricesIndices : vec4; - attribute matricesWeights : vec4; + attribute matricesIndices : vec4f; + attribute matricesWeights : vec4f; #if NUM_BONE_INFLUENCERS > 4 - attribute matricesIndicesExtra : vec4; - attribute matricesWeightsExtra : vec4; + attribute matricesIndicesExtra : vec4f; + attribute matricesWeightsExtra : vec4f; #endif #ifndef BAKED_VERTEX_ANIMATION_TEXTURE diff --git a/packages/dev/core/src/ShadersWGSL/iblVoxelGrid.vertex.fx b/packages/dev/core/src/ShadersWGSL/iblVoxelGrid.vertex.fx index 069d2f6cac5..454ebdd40cc 100644 --- a/packages/dev/core/src/ShadersWGSL/iblVoxelGrid.vertex.fx +++ b/packages/dev/core/src/ShadersWGSL/iblVoxelGrid.vertex.fx @@ -1,5 +1,5 @@ #include -#include +#include (attribute matricesIndices : vec4f;,,attribute matricesWeights : vec4f;,,attribute matricesIndicesExtra : vec4f;,,attribute matricesWeightsExtra : vec4f;,) #include #include @@ -8,12 +8,24 @@ // This shader uses vertex pulling to determine the // provoked vertex and calculate the normal. Then, based on -// the direction of teh normal, it swizzles the position to +// the direction of the normal, it swizzles the position to // maximize the rasterized area. #ifdef VERTEX_PULLING_USE_INDEX_BUFFER var indices : array; #endif var position : array; +#if NUM_BONE_INFLUENCERS > 0 + var matricesIndices : array; + var matricesWeights : array; + uniform vp_matricesIndices_info: vec3f; + uniform vp_matricesWeights_info: vec3f; + #if NUM_BONE_INFLUENCERS > 4 + var matricesIndicesExtra : array; + var matricesWeightsExtra : array; + uniform vp_matricesIndicesExtra_info: vec3f; + uniform vp_matricesWeightsExtra_info: vec3f; + #endif +#endif uniform world : mat4x4f; uniform invWorldScale: mat4x4f; @@ -21,14 +33,175 @@ uniform invWorldScale: mat4x4f; varying vNormalizedPosition : vec3f; flat varying f_swizzle: i32; -fn readVertexPosition(index : u32)->vec3f { - var pos : vec3f; - pos.x = position[index * 3]; - pos.y = position[index * 3 + 1]; - pos.z = position[index * 3 + 2]; - return pos; +uniform vp_position_info: vec3f; // (offset, stride, type) + +fn convertToFloat(word: u32, byteInWord: u32, dataType: u32) -> f32 { + switch (dataType) { + case 5120u: { // BYTE + let shift = byteInWord * 8u; + let value = (word >> shift) & 0xFFu; + return f32(i32(value << 24u) >> 24u) / 127.0; // Sign extend and normalize + } + case 5121u: { // UNSIGNED_BYTE + let shift = byteInWord * 8u; + let value = (word >> shift) & 0xFFu; + return f32(value) / 255.0; + } + case 5122u: { // SHORT + let shift = (byteInWord & 0xFFFFFFFEu) * 8u; // Align to 2-byte boundary + let value = (word >> shift) & 0xFFFFu; + return f32(i32(value << 16u) >> 16u); + } + case 5123u: { // UNSIGNED_SHORT + let shift = (byteInWord & 0xFFFFFFFEu) * 8u; // Align to 2-byte boundary + let value = (word >> shift) & 0xFFFFu; + return f32(value); + } + case 5126u: { // FLOAT + return bitcast(word); + } + default: { + return 0.0; + } + } +} + +fn readPositionValue(byteOffset: u32, dataType: u32) -> f32 { + let wordOffset = byteOffset / 4u; + let byteInWord = byteOffset % 4u; + let word: u32 = bitcast(position[wordOffset]); + + return convertToFloat(word, byteInWord, dataType); +} + +// Helper function to read a vec3 attribute +fn readVertexPosition(info: vec3f, vertexIndex: u32) -> vec3f { + let baseOffset = u32(info.x); + let stride = u32(info.y); + let dataType = u32(info.z); + + let offset = baseOffset + vertexIndex * stride; + + // Determine component size based on type + let componentSize = select(select(2u, 1u, dataType == 5120u || dataType == 5121u), 4u, dataType == 5126u); + + return vec3f( + readPositionValue(offset, dataType), + readPositionValue(offset + componentSize, dataType), + readPositionValue(offset + componentSize * 2u, dataType) + ); +} + +#if NUM_BONE_INFLUENCERS > 0 + +fn readMatrixIndexValue(byteOffset: u32, dataType: u32) -> f32 { + let wordOffset = byteOffset / 4u; + let byteInWord = byteOffset % 4u; + let word: u32 = matricesIndices[wordOffset]; + + return convertToFloat(word, byteInWord, dataType); +} + +fn readMatrixIndices(info: vec3f, vertexIndex : u32) -> vec4f { + let baseOffset = u32(info.x); + let stride = u32(info.y); + let dataType = u32(info.z); + + let offset = baseOffset + vertexIndex * stride; + + // Determine component size based on type + let componentSize = select(select(2u, 1u, dataType == 5120u || dataType == 5121u), 4u, dataType == 5126u); + + return vec4f( + readMatrixIndexValue(offset, dataType), + readMatrixIndexValue(offset + componentSize, dataType), + readMatrixIndexValue(offset + componentSize * 2u, dataType), + readMatrixIndexValue(offset + componentSize * 3u, dataType) + ); +} + +fn readMatrixWeightValue(byteOffset: u32, dataType: u32) -> f32 { + let wordOffset = byteOffset / 4u; + let byteInWord = byteOffset % 4u; + let word: u32 = bitcast(matricesWeights[wordOffset]); + + return convertToFloat(word, byteInWord, dataType); +} + +fn readMatrixWeights(info: vec3f, vertexIndex : u32) -> vec4f { + let baseOffset = u32(info.x); + let stride = u32(info.y); + let dataType = u32(info.z); + + let offset = baseOffset + vertexIndex * stride; + + // Determine component size based on type + let componentSize = select(select(2u, 1u, dataType == 5120u || dataType == 5121u), 4u, dataType == 5126u); + + return vec4f( + readMatrixWeightValue(offset, dataType), + readMatrixWeightValue(offset + componentSize, dataType), + readMatrixWeightValue(offset + componentSize * 2u, dataType), + readMatrixWeightValue(offset + componentSize * 3u, dataType) + ); } +#if NUM_BONE_INFLUENCERS > 4 + +fn readMatrixIndexExtraValue(byteOffset: u32, dataType: u32) -> f32 { + let wordOffset = byteOffset / 4u; + let byteInWord = byteOffset % 4u; + let word: u32 = bitcast(matricesIndicesExtra[wordOffset]); + + return convertToFloat(word, byteInWord, dataType); +} + +fn readMatrixIndicesExtra(info: vec3f, vertexIndex : u32) -> vec4f { + let baseOffset = u32(info.x); + let stride = u32(info.y); + let dataType = u32(info.z); + + let offset = baseOffset + vertexIndex * stride; + + // Determine component size based on type + let componentSize = select(select(2u, 1u, dataType == 5120u || dataType == 5121u), 4u, dataType == 5126u); + + return vec4f( + readMatrixIndexExtraValue(offset, dataType), + readMatrixIndexExtraValue(offset + componentSize, dataType), + readMatrixIndexExtraValue(offset + componentSize * 2u, dataType), + readMatrixIndexExtraValue(offset + componentSize * 3u, dataType) + ); +} + +fn readMatrixWeightExtraValue(byteOffset: u32, dataType: u32) -> f32 { + let wordOffset = byteOffset / 4u; + let byteInWord = byteOffset % 4u; + let word: u32 = bitcast(matricesWeightsExtra[wordOffset]); + + return convertToFloat(word, byteInWord, dataType); +} + +fn readMatrixIndicesExtra(info: vec3f, vertexIndex : u32) -> vec4f { + let baseOffset = u32(info.x); + let stride = u32(info.y); + let dataType = u32(info.z); + + let offset = baseOffset + vertexIndex * stride; + + // Determine component size based on type + let componentSize = select(select(2u, 1u, dataType == 5120u || dataType == 5121u), 4u, dataType == 5126u); + + return vec4f( + readMatrixWeightExtraValue(offset, dataType), + readMatrixWeightExtraValue(offset + componentSize, dataType), + readMatrixWeightExtraValue(offset + componentSize * 2u, dataType), + readMatrixWeightExtraValue(offset + componentSize * 3u, dataType) + ); +} +#endif +#endif + fn readVertexIndex(index : u32)->u32 { #ifndef VERTEX_PULLING_USE_INDEX_BUFFER return index; @@ -62,28 +235,47 @@ fn calculateTriangleNormal(v0 @vertex fn main(input : VertexInputs) -> FragmentInputs { - var vertIdx = readVertexIndex(input.vertexIndex); - var positionUpdated = readVertexPosition(vertIdx); -#include -let inputPosition: vec3f = positionUpdated; -#include (vertexInputs.position\\),inputPosition))[0..maxSimultaneousMorphTargets] + #include + + var triPositions: array; -#include + // We're going to calculate the updated position for each vertex of the triangle + // so that we can compute the triangle normal. + var thisTriIndex : u32 = input.vertexIndex; // index in the triangle (0,1,2) of this invocation + for (var i: u32 = 0u; i < 3u; i = i + 1u) { + var provokingVertNum : u32 = input.vertexIndex / 3 * 3; + let vertIdx = readVertexIndex(provokingVertNum + i); -#include -#include + // We need to know which vertex of the triangle corresponds to this invocation + // so that we can output the correct position at the end. + if (provokingVertNum + i == input.vertexIndex) { + thisTriIndex = i; + } + var positionUpdated = readVertexPosition(uniforms.vp_position_info, vertIdx); + #include + let inputPosition: vec3f = positionUpdated; + #include (vertexInputs.position\\),inputPosition),vertexInputs.vertexIndex,vertIdx)[0..maxSimultaneousMorphTargets] - let worldPos = finalWorld * vec4f(positionUpdated, 1.0); + #if NUM_BONE_INFLUENCERS > 0 + let matrixIndex = readMatrixIndices(uniforms.vp_matricesIndices_info, vertIdx); + let matrixWeight = readMatrixWeights(uniforms.vp_matricesWeights_info, vertIdx); + #if NUM_BONE_INFLUENCERS > 4 + let matrixIndexExtra = readMatrixIndicesExtra(uniforms.vp_matricesIndicesExtra_info, vertIdx); + let matrixWeightExtra = readMatrixWeightsExtra(uniforms.vp_matricesWeightsExtra_info, vertIdx); + #endif + #endif + #include(vertexInputs.matricesIndices,matrixIndex,vertexInputs.matricesWeights,matrixWeight,vertexInputs.matricesIndicesExtra,matrixIndexExtra,vertexInputs.matricesWeightsExtra,matrixWeightExtra) + #include(vertexInputs.matricesIndices,matrixIndex,vertexInputs.matricesWeights,matrixWeight,vertexInputs.matricesIndicesExtra,matrixIndexExtra,vertexInputs.matricesWeightsExtra,matrixWeightExtra) + triPositions[i] = (finalWorld * vec4(positionUpdated, 1.0)).xyz; + } - // inverse scale this by world scale to put in 0-1 space. - vertexOutputs.position = uniforms.invWorldScale * worldPos; + var N : vec3 = calculateTriangleNormal(triPositions[0], triPositions[1], triPositions[2]); - var provokingVertNum : u32 = input.vertexIndex / 3 * 3; - var pos0 = readVertexPosition(readVertexIndex(provokingVertNum)); - var pos1 = readVertexPosition(readVertexIndex(provokingVertNum + 1)); - var pos2 = readVertexPosition(readVertexIndex(provokingVertNum + 2)); - var N : vec3 = calculateTriangleNormal(pos0, pos1, pos2); + let worldPos = triPositions[thisTriIndex]; + + // inverse scale this by world scale to put in 0-1 space. + vertexOutputs.position = uniforms.invWorldScale * vec4(worldPos, 1.0); // Check the direction that maximizes the rasterized area and swizzle as // appropriate.