最新代码

This commit is contained in:
Teo
2025-07-29 11:22:30 +08:00
parent d503e64098
commit b01d143ea6
1490 changed files with 680232 additions and 28 deletions

View File

@ -0,0 +1,321 @@
import { Float16BufferAttribute } from 'three';
import { GPUInputStepMode } from './WebGPUConstants.js';
const typedArraysToVertexFormatPrefix = new Map( [
[ Int8Array, [ 'sint8', 'snorm8' ]],
[ Uint8Array, [ 'uint8', 'unorm8' ]],
[ Int16Array, [ 'sint16', 'snorm16' ]],
[ Uint16Array, [ 'uint16', 'unorm16' ]],
[ Int32Array, [ 'sint32', 'snorm32' ]],
[ Uint32Array, [ 'uint32', 'unorm32' ]],
[ Float32Array, [ 'float32', ]],
] );
const typedAttributeToVertexFormatPrefix = new Map( [
[ Float16BufferAttribute, [ 'float16', ]],
] );
const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [
[ Int32Array, 'sint32' ],
[ Int16Array, 'sint32' ], // patch for INT16
[ Uint32Array, 'uint32' ],
[ Uint16Array, 'uint32' ], // patch for UINT16
[ Float32Array, 'float32' ]
] );
class WebGPUAttributeUtils {
constructor( backend ) {
this.backend = backend;
}
createAttribute( attribute, usage ) {
const bufferAttribute = this._getBufferAttribute( attribute );
const backend = this.backend;
const bufferData = backend.get( bufferAttribute );
let buffer = bufferData.buffer;
if ( buffer === undefined ) {
const device = backend.device;
let array = bufferAttribute.array;
// patch for INT16 and UINT16
if ( attribute.normalized === false && ( array.constructor === Int16Array || array.constructor === Uint16Array ) ) {
const tempArray = new Uint32Array( array.length );
for ( let i = 0; i < array.length; i ++ ) {
tempArray[ i ] = array[ i ];
}
array = tempArray;
}
bufferAttribute.array = array;
if ( ( bufferAttribute.isStorageBufferAttribute || bufferAttribute.isStorageInstancedBufferAttribute ) && bufferAttribute.itemSize === 3 ) {
array = new array.constructor( bufferAttribute.count * 4 );
for ( let i = 0; i < bufferAttribute.count; i ++ ) {
array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 );
}
// Update BufferAttribute
bufferAttribute.itemSize = 4;
bufferAttribute.array = array;
}
const size = array.byteLength + ( ( 4 - ( array.byteLength % 4 ) ) % 4 ); // ensure 4 byte alignment, see #20441
buffer = device.createBuffer( {
label: bufferAttribute.name,
size: size,
usage: usage,
mappedAtCreation: true
} );
new array.constructor( buffer.getMappedRange() ).set( array );
buffer.unmap();
bufferData.buffer = buffer;
}
}
updateAttribute( attribute ) {
const bufferAttribute = this._getBufferAttribute( attribute );
const backend = this.backend;
const device = backend.device;
const buffer = backend.get( bufferAttribute ).buffer;
const array = bufferAttribute.array;
const updateRanges = bufferAttribute.updateRanges;
if ( updateRanges.length === 0 ) {
// Not using update ranges
device.queue.writeBuffer(
buffer,
0,
array,
0
);
} else {
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
const range = updateRanges[ i ];
device.queue.writeBuffer(
buffer,
0,
array,
range.start * array.BYTES_PER_ELEMENT,
range.count * array.BYTES_PER_ELEMENT
);
}
bufferAttribute.clearUpdateRanges();
}
}
createShaderVertexBuffers( renderObject ) {
const attributes = renderObject.getAttributes();
const vertexBuffers = new Map();
for ( let slot = 0; slot < attributes.length; slot ++ ) {
const geometryAttribute = attributes[ slot ];
const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
const bufferAttribute = this._getBufferAttribute( geometryAttribute );
let vertexBufferLayout = vertexBuffers.get( bufferAttribute );
if ( vertexBufferLayout === undefined ) {
let arrayStride, stepMode;
if ( geometryAttribute.isInterleavedBufferAttribute === true ) {
arrayStride = geometryAttribute.data.stride * bytesPerElement;
stepMode = geometryAttribute.data.isInstancedInterleavedBuffer ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
} else {
arrayStride = geometryAttribute.itemSize * bytesPerElement;
stepMode = geometryAttribute.isInstancedBufferAttribute ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
}
// patch for INT16 and UINT16
if ( geometryAttribute.normalized === false && ( geometryAttribute.array.constructor === Int16Array || geometryAttribute.array.constructor === Uint16Array ) ) {
arrayStride = 4;
}
vertexBufferLayout = {
arrayStride,
attributes: [],
stepMode
};
vertexBuffers.set( bufferAttribute, vertexBufferLayout );
}
const format = this._getVertexFormat( geometryAttribute );
const offset = ( geometryAttribute.isInterleavedBufferAttribute === true ) ? geometryAttribute.offset * bytesPerElement : 0;
vertexBufferLayout.attributes.push( {
shaderLocation: slot,
offset,
format
} );
}
return Array.from( vertexBuffers.values() );
}
destroyAttribute( attribute ) {
const backend = this.backend;
const data = backend.get( this._getBufferAttribute( attribute ) );
data.buffer.destroy();
backend.delete( attribute );
}
async getArrayBufferAsync( attribute ) {
const backend = this.backend;
const device = backend.device;
const data = backend.get( this._getBufferAttribute( attribute ) );
const bufferGPU = data.buffer;
const size = bufferGPU.size;
let readBufferGPU = data.readBuffer;
let needsUnmap = true;
if ( readBufferGPU === undefined ) {
readBufferGPU = device.createBuffer( {
label: attribute.name,
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
} );
needsUnmap = false;
data.readBuffer = readBufferGPU;
}
const cmdEncoder = device.createCommandEncoder( {} );
cmdEncoder.copyBufferToBuffer(
bufferGPU,
0,
readBufferGPU,
0,
size
);
if ( needsUnmap ) readBufferGPU.unmap();
const gpuCommands = cmdEncoder.finish();
device.queue.submit( [ gpuCommands ] );
await readBufferGPU.mapAsync( GPUMapMode.READ );
const arrayBuffer = readBufferGPU.getMappedRange();
return arrayBuffer;
}
_getVertexFormat( geometryAttribute ) {
const { itemSize, normalized } = geometryAttribute;
const ArrayType = geometryAttribute.array.constructor;
const AttributeType = geometryAttribute.constructor;
let format;
if ( itemSize == 1 ) {
format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType );
} else {
const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType );
const prefix = prefixOptions[ normalized ? 1 : 0 ];
if ( prefix ) {
const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize;
const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT;
if ( paddedItemSize % 1 ) {
throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' );
}
format = `${prefix}x${paddedItemSize}`;
}
}
if ( ! format ) {
console.error( 'THREE.WebGPUAttributeUtils: Vertex format not supported yet.' );
}
return format;
}
_getBufferAttribute( attribute ) {
if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
return attribute;
}
}
export default WebGPUAttributeUtils;

View File

@ -0,0 +1,258 @@
import {
GPUTextureAspect, GPUTextureViewDimension, GPUBufferBindingType, GPUTextureSampleType
} from './WebGPUConstants.js';
import { FloatType, IntType, UnsignedIntType } from 'three';
class WebGPUBindingUtils {
constructor( backend ) {
this.backend = backend;
}
createBindingsLayout( bindings ) {
const backend = this.backend;
const device = backend.device;
const entries = [];
let index = 0;
for ( const binding of bindings ) {
const bindingGPU = {
binding: index ++,
visibility: binding.visibility
};
if ( binding.isUniformBuffer || binding.isStorageBuffer ) {
const buffer = {}; // GPUBufferBindingLayout
if ( binding.isStorageBuffer ) {
buffer.type = GPUBufferBindingType.Storage;
}
bindingGPU.buffer = buffer;
} else if ( binding.isSampler ) {
const sampler = {}; // GPUSamplerBindingLayout
if ( binding.texture.isDepthTexture ) {
if ( binding.texture.compareFunction !== null ) {
sampler.type = 'comparison';
}
}
bindingGPU.sampler = sampler;
} else if ( binding.isSampledTexture && binding.texture.isVideoTexture ) {
bindingGPU.externalTexture = {}; // GPUExternalTextureBindingLayout
} else if ( binding.isSampledTexture && binding.store ) {
const format = this.backend.get( binding.texture ).texture.format;
bindingGPU.storageTexture = { format }; // GPUStorageTextureBindingLayout
} else if ( binding.isSampledTexture ) {
const texture = {}; // GPUTextureBindingLayout
if ( binding.texture.isDepthTexture ) {
texture.sampleType = GPUTextureSampleType.Depth;
} else if ( binding.texture.isDataTexture ) {
const type = binding.texture.type;
if ( type === IntType ) {
texture.sampleType = GPUTextureSampleType.SInt;
} else if ( type === UnsignedIntType ) {
texture.sampleType = GPUTextureSampleType.UInt;
} else if ( type === FloatType ) {
// @TODO: Add support for this soon: backend.hasFeature( 'float32-filterable' )
texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
}
}
if ( binding.isSampledCubeTexture ) {
texture.viewDimension = GPUTextureViewDimension.Cube;
} else if ( binding.texture.isDataArrayTexture ) {
texture.viewDimension = GPUTextureViewDimension.TwoDArray;
}
bindingGPU.texture = texture;
} else {
console.error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` );
}
entries.push( bindingGPU );
}
return device.createBindGroupLayout( { entries } );
}
createBindings( bindings ) {
const backend = this.backend;
const bindingsData = backend.get( bindings );
// setup (static) binding layout and (dynamic) binding group
const bindLayoutGPU = this.createBindingsLayout( bindings );
const bindGroupGPU = this.createBindGroup( bindings, bindLayoutGPU );
bindingsData.layout = bindLayoutGPU;
bindingsData.group = bindGroupGPU;
bindingsData.bindings = bindings;
}
updateBinding( binding ) {
const backend = this.backend;
const device = backend.device;
const buffer = binding.buffer;
const bufferGPU = backend.get( binding ).buffer;
device.queue.writeBuffer( bufferGPU, 0, buffer, 0 );
}
createBindGroup( bindings, layoutGPU ) {
const backend = this.backend;
const device = backend.device;
let bindingPoint = 0;
const entriesGPU = [];
for ( const binding of bindings ) {
if ( binding.isUniformBuffer ) {
const bindingData = backend.get( binding );
if ( bindingData.buffer === undefined ) {
const byteLength = binding.byteLength;
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
const bufferGPU = device.createBuffer( {
label: 'bindingBuffer_' + binding.name,
size: byteLength,
usage: usage
} );
bindingData.buffer = bufferGPU;
}
entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
} else if ( binding.isStorageBuffer ) {
const bindingData = backend.get( binding );
if ( bindingData.buffer === undefined ) {
const attribute = binding.attribute;
//const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | /*GPUBufferUsage.COPY_SRC |*/ GPUBufferUsage.COPY_DST;
//backend.attributeUtils.createAttribute( attribute, usage ); // @TODO: Move it to universal renderer
bindingData.buffer = backend.get( attribute ).buffer;
}
entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
} else if ( binding.isSampler ) {
const textureGPU = backend.get( binding.texture );
entriesGPU.push( { binding: bindingPoint, resource: textureGPU.sampler } );
} else if ( binding.isSampledTexture ) {
const textureData = backend.get( binding.texture );
let dimensionViewGPU;
if ( binding.isSampledCubeTexture ) {
dimensionViewGPU = GPUTextureViewDimension.Cube;
} else if ( binding.texture.isDataArrayTexture ) {
dimensionViewGPU = GPUTextureViewDimension.TwoDArray;
} else {
dimensionViewGPU = GPUTextureViewDimension.TwoD;
}
let resourceGPU;
if ( textureData.externalTexture !== undefined ) {
resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } );
} else {
const aspectGPU = GPUTextureAspect.All;
resourceGPU = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount: binding.store ? 1 : textureData.mipLevelCount } );
}
entriesGPU.push( { binding: bindingPoint, resource: resourceGPU } );
}
bindingPoint ++;
}
return device.createBindGroup( {
layout: layoutGPU,
entries: entriesGPU
} );
}
}
export default WebGPUBindingUtils;

View File

@ -0,0 +1,324 @@
export const GPUPrimitiveTopology = {
PointList: 'point-list',
LineList: 'line-list',
LineStrip: 'line-strip',
TriangleList: 'triangle-list',
TriangleStrip: 'triangle-strip',
};
export const GPUCompareFunction = {
Never: 'never',
Less: 'less',
Equal: 'equal',
LessEqual: 'less-equal',
Greater: 'greater',
NotEqual: 'not-equal',
GreaterEqual: 'greater-equal',
Always: 'always'
};
export const GPUStoreOp = {
Store: 'store',
Discard: 'discard'
};
export const GPULoadOp = {
Load: 'load',
Clear: 'clear'
};
export const GPUFrontFace = {
CCW: 'ccw',
CW: 'cw'
};
export const GPUCullMode = {
None: 'none',
Front: 'front',
Back: 'back'
};
export const GPUIndexFormat = {
Uint16: 'uint16',
Uint32: 'uint32'
};
export const GPUVertexFormat = {
Uint8x2: 'uint8x2',
Uint8x4: 'uint8x4',
Sint8x2: 'sint8x2',
Sint8x4: 'sint8x4',
Unorm8x2: 'unorm8x2',
Unorm8x4: 'unorm8x4',
Snorm8x2: 'snorm8x2',
Snorm8x4: 'snorm8x4',
Uint16x2: 'uint16x2',
Uint16x4: 'uint16x4',
Sint16x2: 'sint16x2',
Sint16x4: 'sint16x4',
Unorm16x2: 'unorm16x2',
Unorm16x4: 'unorm16x4',
Snorm16x2: 'snorm16x2',
Snorm16x4: 'snorm16x4',
Float16x2: 'float16x2',
Float16x4: 'float16x4',
Float32: 'float32',
Float32x2: 'float32x2',
Float32x3: 'float32x3',
Float32x4: 'float32x4',
Uint32: 'uint32',
Uint32x2: 'uint32x2',
Uint32x3: 'uint32x3',
Uint32x4: 'uint32x4',
Sint32: 'sint32',
Sint32x2: 'sint32x2',
Sint32x3: 'sint32x3',
Sint32x4: 'sint32x4'
};
export const GPUTextureFormat = {
// 8-bit formats
R8Unorm: 'r8unorm',
R8Snorm: 'r8snorm',
R8Uint: 'r8uint',
R8Sint: 'r8sint',
// 16-bit formats
R16Uint: 'r16uint',
R16Sint: 'r16sint',
R16Float: 'r16float',
RG8Unorm: 'rg8unorm',
RG8Snorm: 'rg8snorm',
RG8Uint: 'rg8uint',
RG8Sint: 'rg8sint',
// 32-bit formats
R32Uint: 'r32uint',
R32Sint: 'r32sint',
R32Float: 'r32float',
RG16Uint: 'rg16uint',
RG16Sint: 'rg16sint',
RG16Float: 'rg16float',
RGBA8Unorm: 'rgba8unorm',
RGBA8UnormSRGB: 'rgba8unorm-srgb',
RGBA8Snorm: 'rgba8snorm',
RGBA8Uint: 'rgba8uint',
RGBA8Sint: 'rgba8sint',
BGRA8Unorm: 'bgra8unorm',
BGRA8UnormSRGB: 'bgra8unorm-srgb',
// Packed 32-bit formats
RGB9E5UFloat: 'rgb9e5ufloat',
RGB10A2Unorm: 'rgb10a2unorm',
RG11B10uFloat: 'rgb10a2unorm',
// 64-bit formats
RG32Uint: 'rg32uint',
RG32Sint: 'rg32sint',
RG32Float: 'rg32float',
RGBA16Uint: 'rgba16uint',
RGBA16Sint: 'rgba16sint',
RGBA16Float: 'rgba16float',
// 128-bit formats
RGBA32Uint: 'rgba32uint',
RGBA32Sint: 'rgba32sint',
RGBA32Float: 'rgba32float',
// Depth and stencil formats
Stencil8: 'stencil8',
Depth16Unorm: 'depth16unorm',
Depth24Plus: 'depth24plus',
Depth24PlusStencil8: 'depth24plus-stencil8',
Depth32Float: 'depth32float',
// 'depth32float-stencil8' extension
Depth32FloatStencil8: 'depth32float-stencil8',
// BC compressed formats usable if 'texture-compression-bc' is both
// supported by the device/user agent and enabled in requestDevice.
BC1RGBAUnorm: 'bc1-rgba-unorm',
BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb',
BC2RGBAUnorm: 'bc2-rgba-unorm',
BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb',
BC3RGBAUnorm: 'bc3-rgba-unorm',
BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb',
BC4RUnorm: 'bc4-r-unorm',
BC4RSnorm: 'bc4-r-snorm',
BC5RGUnorm: 'bc5-rg-unorm',
BC5RGSnorm: 'bc5-rg-snorm',
BC6HRGBUFloat: 'bc6h-rgb-ufloat',
BC6HRGBFloat: 'bc6h-rgb-float',
BC7RGBAUnorm: 'bc7-rgba-unorm',
BC7RGBAUnormSRGB: 'bc7-rgba-srgb',
// ETC2 compressed formats usable if 'texture-compression-etc2' is both
// supported by the device/user agent and enabled in requestDevice.
ETC2RGB8Unorm: 'etc2-rgb8unorm',
ETC2RGB8UnormSRGB: 'etc2-rgb8unorm-srgb',
ETC2RGB8A1Unorm: 'etc2-rgb8a1unorm',
ETC2RGB8A1UnormSRGB: 'etc2-rgb8a1unorm-srgb',
ETC2RGBA8Unorm: 'etc2-rgba8unorm',
ETC2RGBA8UnormSRGB: 'etc2-rgba8unorm-srgb',
EACR11Unorm: 'eac-r11unorm',
EACR11Snorm: 'eac-r11snorm',
EACRG11Unorm: 'eac-rg11unorm',
EACRG11Snorm: 'eac-rg11snorm',
// ASTC compressed formats usable if 'texture-compression-astc' is both
// supported by the device/user agent and enabled in requestDevice.
ASTC4x4Unorm: 'astc-4x4-unorm',
ASTC4x4UnormSRGB: 'astc-4x4-unorm-srgb',
ASTC5x4Unorm: 'astc-5x4-unorm',
ASTC5x4UnormSRGB: 'astc-5x4-unorm-srgb',
ASTC5x5Unorm: 'astc-5x5-unorm',
ASTC5x5UnormSRGB: 'astc-5x5-unorm-srgb',
ASTC6x5Unorm: 'astc-6x5-unorm',
ASTC6x5UnormSRGB: 'astc-6x5-unorm-srgb',
ASTC6x6Unorm: 'astc-6x6-unorm',
ASTC6x6UnormSRGB: 'astc-6x6-unorm-srgb',
ASTC8x5Unorm: 'astc-8x5-unorm',
ASTC8x5UnormSRGB: 'astc-8x5-unorm-srgb',
ASTC8x6Unorm: 'astc-8x6-unorm',
ASTC8x6UnormSRGB: 'astc-8x6-unorm-srgb',
ASTC8x8Unorm: 'astc-8x8-unorm',
ASTC8x8UnormSRGB: 'astc-8x8-unorm-srgb',
ASTC10x5Unorm: 'astc-10x5-unorm',
ASTC10x5UnormSRGB: 'astc-10x5-unorm-srgb',
ASTC10x6Unorm: 'astc-10x6-unorm',
ASTC10x6UnormSRGB: 'astc-10x6-unorm-srgb',
ASTC10x8Unorm: 'astc-10x8-unorm',
ASTC10x8UnormSRGB: 'astc-10x8-unorm-srgb',
ASTC10x10Unorm: 'astc-10x10-unorm',
ASTC10x10UnormSRGB: 'astc-10x10-unorm-srgb',
ASTC12x10Unorm: 'astc-12x10-unorm',
ASTC12x10UnormSRGB: 'astc-12x10-unorm-srgb',
ASTC12x12Unorm: 'astc-12x12-unorm',
ASTC12x12UnormSRGB: 'astc-12x12-unorm-srgb',
};
export const GPUAddressMode = {
ClampToEdge: 'clamp-to-edge',
Repeat: 'repeat',
MirrorRepeat: 'mirror-repeat'
};
export const GPUFilterMode = {
Linear: 'linear',
Nearest: 'nearest'
};
export const GPUBlendFactor = {
Zero: 'zero',
One: 'one',
Src: 'src',
OneMinusSrc: 'one-minus-src',
SrcAlpha: 'src-alpha',
OneMinusSrcAlpha: 'one-minus-src-alpha',
Dst: 'dst',
OneMinusDstColor: 'one-minus-dst',
DstAlpha: 'dst-alpha',
OneMinusDstAlpha: 'one-minus-dst-alpha',
SrcAlphaSaturated: 'src-alpha-saturated',
Constant: 'constant',
OneMinusConstant: 'one-minus-constant'
};
export const GPUBlendOperation = {
Add: 'add',
Subtract: 'subtract',
ReverseSubtract: 'reverse-subtract',
Min: 'min',
Max: 'max'
};
export const GPUColorWriteFlags = {
None: 0,
Red: 0x1,
Green: 0x2,
Blue: 0x4,
Alpha: 0x8,
All: 0xF
};
export const GPUStencilOperation = {
Keep: 'keep',
Zero: 'zero',
Replace: 'replace',
Invert: 'invert',
IncrementClamp: 'increment-clamp',
DecrementClamp: 'decrement-clamp',
IncrementWrap: 'increment-wrap',
DecrementWrap: 'decrement-wrap'
};
export const GPUBufferBindingType = {
Uniform: 'uniform',
Storage: 'storage',
ReadOnlyStorage: 'read-only-storage'
};
export const GPUSamplerBindingType = {
Filtering: 'filtering',
NonFiltering: 'non-filtering',
Comparison: 'comparison'
};
export const GPUTextureSampleType = {
Float: 'float',
UnfilterableFloat: 'unfilterable-float',
Depth: 'depth',
SInt: 'sint',
UInt: 'uint'
};
export const GPUTextureDimension = {
OneD: '1d',
TwoD: '2d',
ThreeD: '3d'
};
export const GPUTextureViewDimension = {
OneD: '1d',
TwoD: '2d',
TwoDArray: '2d-array',
Cube: 'cube',
CubeArray: 'cube-array',
ThreeD: '3d'
};
export const GPUTextureAspect = {
All: 'all',
StencilOnly: 'stencil-only',
DepthOnly: 'depth-only'
};
export const GPUInputStepMode = {
Vertex: 'vertex',
Instance: 'instance'
};
export const GPUFeatureName = {
DepthClipControl: 'depth-clip-control',
Depth32FloatStencil8: 'depth32float-stencil8',
TextureCompressionBC: 'texture-compression-bc',
TextureCompressionETC2: 'texture-compression-etc2',
TextureCompressionASTC: 'texture-compression-astc',
TimestampQuery: 'timestamp-query',
IndirectFirstInstance: 'indirect-first-instance',
ShaderF16: 'shader-f16',
RG11B10UFloat: 'rg11b10ufloat-renderable',
BGRA8UNormStorage: 'bgra8unorm-storage',
Float32Filterable: 'float32-filterable'
};

View File

@ -0,0 +1,591 @@
import { BlendColorFactor, OneMinusBlendColorFactor, } from '../../common/Constants.js';
import {
GPUFrontFace, GPUCullMode, GPUColorWriteFlags, GPUCompareFunction, GPUBlendFactor, GPUBlendOperation, GPUIndexFormat, GPUStencilOperation
} from './WebGPUConstants.js';
import {
FrontSide, BackSide, DoubleSide,
NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth,
NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending,
ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstColorFactor,
OneMinusDstColorFactor, DstAlphaFactor, OneMinusDstAlphaFactor, SrcAlphaSaturateFactor,
AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation,
KeepStencilOp, ZeroStencilOp, ReplaceStencilOp, InvertStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp,
NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc
} from 'three';
class WebGPUPipelineUtils {
constructor( backend ) {
this.backend = backend;
}
createRenderPipeline( renderObject, promises ) {
const { object, material, geometry, pipeline } = renderObject;
const { vertexProgram, fragmentProgram } = pipeline;
const backend = this.backend;
const device = backend.device;
const utils = backend.utils;
const pipelineData = backend.get( pipeline );
const bindingsData = backend.get( renderObject.getBindings() );
// vertex buffers
const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject );
// blending
let blending;
if ( material.transparent === true && material.blending !== NoBlending ) {
blending = this._getBlending( material );
}
// stencil
let stencilFront = {};
if ( material.stencilWrite === true ) {
stencilFront = {
compare: this._getStencilCompare( material ),
failOp: this._getStencilOperation( material.stencilFail ),
depthFailOp: this._getStencilOperation( material.stencilZFail ),
passOp: this._getStencilOperation( material.stencilZPass )
};
}
const colorWriteMask = this._getColorWriteMask( material );
const targets = [];
if ( renderObject.context.textures !== null ) {
const textures = renderObject.context.textures;
for ( let i = 0; i < textures.length; i ++ ) {
const colorFormat = utils.getTextureFormatGPU( textures[ i ] );
targets.push( {
format: colorFormat,
blend: blending,
writeMask: colorWriteMask
} );
}
} else {
const colorFormat = utils.getCurrentColorFormat( renderObject.context );
targets.push( {
format: colorFormat,
blend: blending,
writeMask: colorWriteMask
} );
}
const vertexModule = backend.get( vertexProgram ).module;
const fragmentModule = backend.get( fragmentProgram ).module;
const primitiveState = this._getPrimitiveState( object, geometry, material );
const depthCompare = this._getDepthCompare( material );
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
let sampleCount = utils.getSampleCount( renderObject.context );
if ( sampleCount > 1 ) {
// WebGPU only supports power-of-two sample counts and 2 is not a valid value
sampleCount = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
if ( sampleCount === 2 ) {
sampleCount = 4;
}
}
const pipelineDescriptor = {
vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ),
fragment: Object.assign( {}, fragmentModule, { targets } ),
primitive: primitiveState,
depthStencil: {
format: depthStencilFormat,
depthWriteEnabled: material.depthWrite,
depthCompare: depthCompare,
stencilFront: stencilFront,
stencilBack: {}, // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used)
stencilReadMask: material.stencilFuncMask,
stencilWriteMask: material.stencilWriteMask
},
multisample: {
count: sampleCount,
alphaToCoverageEnabled: material.alphaToCoverage
},
layout: device.createPipelineLayout( {
bindGroupLayouts: [ bindingsData.layout ]
} )
};
if ( promises === null ) {
pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor );
} else {
const p = new Promise( ( resolve /*, reject*/ ) => {
device.createRenderPipelineAsync( pipelineDescriptor ).then( pipeline => {
pipelineData.pipeline = pipeline;
resolve();
} );
} );
promises.push( p );
}
}
createComputePipeline( pipeline, bindings ) {
const backend = this.backend;
const device = backend.device;
const computeProgram = backend.get( pipeline.computeProgram ).module;
const pipelineGPU = backend.get( pipeline );
const bindingsData = backend.get( bindings );
pipelineGPU.pipeline = device.createComputePipeline( {
compute: computeProgram,
layout: device.createPipelineLayout( {
bindGroupLayouts: [ bindingsData.layout ]
} )
} );
}
_getBlending( material ) {
let color, alpha;
const blending = material.blending;
if ( blending === CustomBlending ) {
const blendSrcAlpha = material.blendSrcAlpha !== null ? material.blendSrcAlpha : GPUBlendFactor.One;
const blendDstAlpha = material.blendDstAlpha !== null ? material.blendDstAlpha : GPUBlendFactor.Zero;
const blendEquationAlpha = material.blendEquationAlpha !== null ? material.blendEquationAlpha : GPUBlendFactor.Add;
color = {
srcFactor: this._getBlendFactor( material.blendSrc ),
dstFactor: this._getBlendFactor( material.blendDst ),
operation: this._getBlendOperation( material.blendEquation )
};
alpha = {
srcFactor: this._getBlendFactor( blendSrcAlpha ),
dstFactor: this._getBlendFactor( blendDstAlpha ),
operation: this._getBlendOperation( blendEquationAlpha )
};
} else {
const premultipliedAlpha = material.premultipliedAlpha;
const setBlend = ( srcRGB, dstRGB, srcAlpha, dstAlpha ) => {
color = {
srcFactor: srcRGB,
dstFactor: dstRGB,
operation: GPUBlendOperation.Add
};
alpha = {
srcFactor: srcAlpha,
dstFactor: dstAlpha,
operation: GPUBlendOperation.Add
};
};
if ( premultipliedAlpha ) {
switch ( blending ) {
case NormalBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha );
break;
case AdditiveBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One );
break;
case SubtractiveBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One );
break;
case MultiplyBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.Src, GPUBlendFactor.Zero, GPUBlendFactor.SrcAlpha );
break;
}
} else {
switch ( blending ) {
case NormalBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha );
break;
case AdditiveBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.SrcAlpha, GPUBlendFactor.One );
break;
case SubtractiveBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One );
break;
case MultiplyBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.Src, GPUBlendFactor.Zero, GPUBlendFactor.Src );
break;
}
}
}
if ( color !== undefined && alpha !== undefined ) {
return { color, alpha };
} else {
console.error( 'THREE.WebGPURenderer: Invalid blending: ', blending );
}
}
_getBlendFactor( blend ) {
let blendFactor;
switch ( blend ) {
case ZeroFactor:
blendFactor = GPUBlendFactor.Zero;
break;
case OneFactor:
blendFactor = GPUBlendFactor.One;
break;
case SrcColorFactor:
blendFactor = GPUBlendFactor.Src;
break;
case OneMinusSrcColorFactor:
blendFactor = GPUBlendFactor.OneMinusSrc;
break;
case SrcAlphaFactor:
blendFactor = GPUBlendFactor.SrcAlpha;
break;
case OneMinusSrcAlphaFactor:
blendFactor = GPUBlendFactor.OneMinusSrcAlpha;
break;
case DstColorFactor:
blendFactor = GPUBlendFactor.Dst;
break;
case OneMinusDstColorFactor:
blendFactor = GPUBlendFactor.OneMinusDstColor;
break;
case DstAlphaFactor:
blendFactor = GPUBlendFactor.DstAlpha;
break;
case OneMinusDstAlphaFactor:
blendFactor = GPUBlendFactor.OneMinusDstAlpha;
break;
case SrcAlphaSaturateFactor:
blendFactor = GPUBlendFactor.SrcAlphaSaturated;
break;
case BlendColorFactor:
blendFactor = GPUBlendFactor.Constant;
break;
case OneMinusBlendColorFactor:
blendFactor = GPUBlendFactor.OneMinusConstant;
break;
default:
console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend );
}
return blendFactor;
}
_getStencilCompare( material ) {
let stencilCompare;
const stencilFunc = material.stencilFunc;
switch ( stencilFunc ) {
case NeverStencilFunc:
stencilCompare = GPUCompareFunction.Never;
break;
case AlwaysStencilFunc:
stencilCompare = GPUCompareFunction.Always;
break;
case LessStencilFunc:
stencilCompare = GPUCompareFunction.Less;
break;
case LessEqualStencilFunc:
stencilCompare = GPUCompareFunction.LessEqual;
break;
case EqualStencilFunc:
stencilCompare = GPUCompareFunction.Equal;
break;
case GreaterEqualStencilFunc:
stencilCompare = GPUCompareFunction.GreaterEqual;
break;
case GreaterStencilFunc:
stencilCompare = GPUCompareFunction.Greater;
break;
case NotEqualStencilFunc:
stencilCompare = GPUCompareFunction.NotEqual;
break;
default:
console.error( 'THREE.WebGPURenderer: Invalid stencil function.', stencilFunc );
}
return stencilCompare;
}
_getStencilOperation( op ) {
let stencilOperation;
switch ( op ) {
case KeepStencilOp:
stencilOperation = GPUStencilOperation.Keep;
break;
case ZeroStencilOp:
stencilOperation = GPUStencilOperation.Zero;
break;
case ReplaceStencilOp:
stencilOperation = GPUStencilOperation.Replace;
break;
case InvertStencilOp:
stencilOperation = GPUStencilOperation.Invert;
break;
case IncrementStencilOp:
stencilOperation = GPUStencilOperation.IncrementClamp;
break;
case DecrementStencilOp:
stencilOperation = GPUStencilOperation.DecrementClamp;
break;
case IncrementWrapStencilOp:
stencilOperation = GPUStencilOperation.IncrementWrap;
break;
case DecrementWrapStencilOp:
stencilOperation = GPUStencilOperation.DecrementWrap;
break;
default:
console.error( 'THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation );
}
return stencilOperation;
}
_getBlendOperation( blendEquation ) {
let blendOperation;
switch ( blendEquation ) {
case AddEquation:
blendOperation = GPUBlendOperation.Add;
break;
case SubtractEquation:
blendOperation = GPUBlendOperation.Subtract;
break;
case ReverseSubtractEquation:
blendOperation = GPUBlendOperation.ReverseSubtract;
break;
case MinEquation:
blendOperation = GPUBlendOperation.Min;
break;
case MaxEquation:
blendOperation = GPUBlendOperation.Max;
break;
default:
console.error( 'THREE.WebGPUPipelineUtils: Blend equation not supported.', blendEquation );
}
return blendOperation;
}
_getPrimitiveState( object, geometry, material ) {
const descriptor = {};
const utils = this.backend.utils;
descriptor.topology = utils.getPrimitiveTopology( object, material );
if ( geometry.index !== null && object.isLine === true && object.isLineSegments !== true ) {
descriptor.stripIndexFormat = ( geometry.index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
}
switch ( material.side ) {
case FrontSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.Back;
break;
case BackSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.Front;
break;
case DoubleSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.None;
break;
default:
console.error( 'THREE.WebGPUPipelineUtils: Unknown material.side value.', material.side );
break;
}
return descriptor;
}
_getColorWriteMask( material ) {
return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None;
}
_getDepthCompare( material ) {
let depthCompare;
if ( material.depthTest === false ) {
depthCompare = GPUCompareFunction.Always;
} else {
const depthFunc = material.depthFunc;
switch ( depthFunc ) {
case NeverDepth:
depthCompare = GPUCompareFunction.Never;
break;
case AlwaysDepth:
depthCompare = GPUCompareFunction.Always;
break;
case LessDepth:
depthCompare = GPUCompareFunction.Less;
break;
case LessEqualDepth:
depthCompare = GPUCompareFunction.LessEqual;
break;
case EqualDepth:
depthCompare = GPUCompareFunction.Equal;
break;
case GreaterEqualDepth:
depthCompare = GPUCompareFunction.GreaterEqual;
break;
case GreaterDepth:
depthCompare = GPUCompareFunction.Greater;
break;
case NotEqualDepth:
depthCompare = GPUCompareFunction.NotEqual;
break;
default:
console.error( 'THREE.WebGPUPipelineUtils: Invalid depth function.', depthFunc );
}
}
return depthCompare;
}
}
export default WebGPUPipelineUtils;

View File

@ -0,0 +1,285 @@
import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
class WebGPUTexturePassUtils {
constructor( device ) {
this.device = device;
const mipmapVertexSource = `
struct VarysStruct {
@builtin( position ) Position: vec4<f32>,
@location( 0 ) vTex : vec2<f32>
};
@vertex
fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {
var Varys : VarysStruct;
var pos = array< vec2<f32>, 4 >(
vec2<f32>( -1.0, 1.0 ),
vec2<f32>( 1.0, 1.0 ),
vec2<f32>( -1.0, -1.0 ),
vec2<f32>( 1.0, -1.0 )
);
var tex = array< vec2<f32>, 4 >(
vec2<f32>( 0.0, 0.0 ),
vec2<f32>( 1.0, 0.0 ),
vec2<f32>( 0.0, 1.0 ),
vec2<f32>( 1.0, 1.0 )
);
Varys.vTex = tex[ vertexIndex ];
Varys.Position = vec4<f32>( pos[ vertexIndex ], 0.0, 1.0 );
return Varys;
}
`;
const mipmapFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;
@group( 0 ) @binding( 1 )
var img : texture_2d<f32>;
@fragment
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
return textureSample( img, imgSampler, vTex );
}
`;
const flipYFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;
@group( 0 ) @binding( 1 )
var img : texture_2d<f32>;
@fragment
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );
}
`;
this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()
// We'll need a new pipeline for every texture format used.
this.transferPipelines = {};
this.flipYPipelines = {};
this.mipmapVertexShaderModule = device.createShaderModule( {
label: 'mipmapVertex',
code: mipmapVertexSource
} );
this.mipmapFragmentShaderModule = device.createShaderModule( {
label: 'mipmapFragment',
code: mipmapFragmentSource
} );
this.flipYFragmentShaderModule = device.createShaderModule( {
label: 'flipYFragment',
code: flipYFragmentSource
} );
}
getTransferPipeline( format ) {
let pipeline = this.transferPipelines[ format ];
if ( pipeline === undefined ) {
pipeline = this.device.createRenderPipeline( {
vertex: {
module: this.mipmapVertexShaderModule,
entryPoint: 'main'
},
fragment: {
module: this.mipmapFragmentShaderModule,
entryPoint: 'main',
targets: [ { format } ]
},
primitive: {
topology: GPUPrimitiveTopology.TriangleStrip,
stripIndexFormat: GPUIndexFormat.Uint32
},
layout: 'auto'
} );
this.transferPipelines[ format ] = pipeline;
}
return pipeline;
}
getFlipYPipeline( format ) {
let pipeline = this.flipYPipelines[ format ];
if ( pipeline === undefined ) {
pipeline = this.device.createRenderPipeline( {
vertex: {
module: this.mipmapVertexShaderModule,
entryPoint: 'main'
},
fragment: {
module: this.flipYFragmentShaderModule,
entryPoint: 'main',
targets: [ { format } ]
},
primitive: {
topology: GPUPrimitiveTopology.TriangleStrip,
stripIndexFormat: GPUIndexFormat.Uint32
},
layout: 'auto'
} );
this.flipYPipelines[ format ] = pipeline;
}
return pipeline;
}
flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
const format = textureGPUDescriptor.format;
const { width, height } = textureGPUDescriptor.size;
const transferPipeline = this.getTransferPipeline( format );
const flipYPipeline = this.getFlipYPipeline( format );
const tempTexture = this.device.createTexture( {
size: { width, height, depthOrArrayLayers: 1 },
format,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
} );
const srcView = textureGPU.createView( {
baseMipLevel: 0,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer
} );
const dstView = tempTexture.createView( {
baseMipLevel: 0,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer: 0
} );
const commandEncoder = this.device.createCommandEncoder( {} );
const pass = ( pipeline, sourceView, destinationView ) => {
const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
const bindGroup = this.device.createBindGroup( {
layout: bindGroupLayout,
entries: [ {
binding: 0,
resource: this.flipYSampler
}, {
binding: 1,
resource: sourceView
} ]
} );
const passEncoder = commandEncoder.beginRenderPass( {
colorAttachments: [ {
view: destinationView,
loadOp: GPULoadOp.Clear,
storeOp: GPUStoreOp.Store,
clearValue: [ 0, 0, 0, 0 ]
} ]
} );
passEncoder.setPipeline( pipeline );
passEncoder.setBindGroup( 0, bindGroup );
passEncoder.draw( 4, 1, 0, 0 );
passEncoder.end();
};
pass( transferPipeline, srcView, dstView );
pass( flipYPipeline, dstView, srcView );
this.device.queue.submit( [ commandEncoder.finish() ] );
tempTexture.destroy();
}
generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
const pipeline = this.getTransferPipeline( textureGPUDescriptor.format );
const commandEncoder = this.device.createCommandEncoder( {} );
const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
let srcView = textureGPU.createView( {
baseMipLevel: 0,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer
} );
for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
const bindGroup = this.device.createBindGroup( {
layout: bindGroupLayout,
entries: [ {
binding: 0,
resource: this.mipmapSampler
}, {
binding: 1,
resource: srcView
} ]
} );
const dstView = textureGPU.createView( {
baseMipLevel: i,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer
} );
const passEncoder = commandEncoder.beginRenderPass( {
colorAttachments: [ {
view: dstView,
loadOp: GPULoadOp.Clear,
storeOp: GPUStoreOp.Store,
clearValue: [ 0, 0, 0, 0 ]
} ]
} );
passEncoder.setPipeline( pipeline );
passEncoder.setBindGroup( 0, bindGroup );
passEncoder.draw( 4, 1, 0, 0 );
passEncoder.end();
srcView = dstView;
}
this.device.queue.submit( [ commandEncoder.finish() ] );
}
}
export default WebGPUTexturePassUtils;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
import { GPUPrimitiveTopology, GPUTextureFormat } from './WebGPUConstants.js';
class WebGPUUtils {
constructor( backend ) {
this.backend = backend;
}
getCurrentDepthStencilFormat( renderContext ) {
let format;
if ( renderContext.depthTexture !== null ) {
format = this.getTextureFormatGPU( renderContext.depthTexture );
} else if ( renderContext.depth && renderContext.stencil ) {
format = GPUTextureFormat.Depth24PlusStencil8;
} else if ( renderContext.depth ) {
format = GPUTextureFormat.Depth24Plus;
}
return format;
}
getTextureFormatGPU( texture ) {
return this.backend.get( texture ).texture.format;
}
getCurrentColorFormat( renderContext ) {
let format;
if ( renderContext.textures !== null ) {
format = this.getTextureFormatGPU( renderContext.textures[ 0 ] );
} else {
format = GPUTextureFormat.BGRA8Unorm; // default context format
}
return format;
}
getCurrentColorSpace( renderContext ) {
if ( renderContext.textures !== null ) {
return renderContext.textures[ 0 ].colorSpace;
}
return this.backend.renderer.outputColorSpace;
}
getPrimitiveTopology( object, material ) {
if ( object.isPoints ) return GPUPrimitiveTopology.PointList;
else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList;
else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip;
else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList;
}
getSampleCount( renderContext ) {
if ( renderContext.textures !== null ) {
return renderContext.sampleCount;
}
return this.backend.parameters.sampleCount;
}
}
export default WebGPUUtils;