feat(webgpu): Add rotating cube example (#1557)

This commit is contained in:
Ib Green 2021-12-20 17:33:01 -08:00 committed by GitHub
parent d3051a379f
commit 58dfd02c1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1387 additions and 573 deletions

View File

@ -1,4 +1,4 @@
/// <reference types="@webgpu/types" />
/// <reference types='@webgpu/types' />
import {Model, WebGPUDevice} from '@luma.gl/webgpu';
@ -16,7 +16,7 @@ void main() {
}
`,
fragment: `#version 450
fragment: `#version 450
layout(location = 0) out vec4 outColor;
void main() {
@ -52,8 +52,20 @@ export async function init(canvas: HTMLCanvasElement, language: 'glsl' | 'wgsl')
const model = new Model(device, {
vs: SHADERS[language].vertex,
fs: SHADERS[language].fragment,
topology: "triangle-list",
vertexCount: 3
topology: 'triangle-list',
vertexCount: 3,
parameters: {
// Enable depth testing so that the fragment closest to the camera
// is rendered in front.
depthWriteEnabled: true,
depthCompare: 'less',
depthFormat: 'depth24plus',
// Backface culling since the cube is solid piece of geometry.
// Faces pointing away from the camera will be occluded by faces
// pointing toward the camera.
cullMode: 'back',
}
});
function frame() {

View File

@ -1,5 +1,5 @@
{
"name": "luma.gl-examples-hello-triangle-webgpu",
"name": "luma.gl-examples-webgpu-hello-triangle",
"version": "1.0.0",
"private": true,
"scripts": {

View File

@ -0,0 +1,166 @@
import {Model, WebGPUDevice} from '@luma.gl/webgpu';
import {Matrix4} from '@math.gl/core';
import {
cubePositions,
cubeUVs,
cubeVertexCount
} from './cube';
export const title = 'Rotating Cube';
export const description = 'Shows rendering a basic triangle.';
/** Provide both GLSL and WGSL shaders */
const SHADERS = {
wgsl: {
vertex: `
struct Uniforms {
modelViewProjectionMatrix : mat4x4<f32>;
};
[[binding(0), group(0)]] var<uniform> uniforms : Uniforms;
struct VertexOutput {
[[builtin(position)]] Position : vec4<f32>;
[[location(0)]] fragUV : vec2<f32>;
[[location(1)]] fragPosition: vec4<f32>;
};
[[stage(vertex)]]
fn main([[location(0)]] position : vec4<f32>,
[[location(1)]] uv : vec2<f32>) -> VertexOutput {
var output : VertexOutput;
output.Position = uniforms.modelViewProjectionMatrix * position;
output.fragUV = uv;
output.fragPosition = 0.5 * (position + vec4<f32>(1.0, 1.0, 1.0, 1.0));
return output;
}
`,
fragment: `
[[stage(fragment)]]
fn main([[location(0)]] fragUV: vec2<f32>,
[[location(1)]] fragPosition: vec4<f32>) -> [[location(0)]] vec4<f32> {
return fragPosition;
}
`
}
};
// const vertexBufferLayout = {
// arrayStride: cubeVertexSize,
// attributes: [
// {
// // position
// shaderLocation: 0,
// offset: cubePositionOffset,
// format: 'float32x4',
// },
// {
// // uv
// shaderLocation: 1,
// offset: cubeUVOffset,
// format: 'float32x2',
// },
// ],
// };
// const uniformBindGroup = device.handle.createBindGroup({
// layout: pipeline.handle.getBindGroupLayout(0),
// entries: [
// {
// binding: 0,
// resource: {
// buffer: uniformBuffer.handle,
// },
// },
// ],
// });
const UNIFORM_BUFFER_SIZE = 4 * 16; // 4x4 matrix
export async function init(canvas: HTMLCanvasElement, language: 'glsl' | 'wgsl') {
const device = await WebGPUDevice.create({canvas});
// Create a vertex buffer from the cube data.
const positionBuffer = device.createBuffer({id: 'cube-positions', data: cubePositions});
const uvBuffer = device.createBuffer({id: 'cube-uvs', data: cubeUVs});
const uniformBuffer = device.createBuffer({
byteLength: UNIFORM_BUFFER_SIZE,
// TODO - use API constants instead of WebGPU constants
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const model = new Model(device, {
id: 'cube',
vs: SHADERS[language].vertex,
fs: SHADERS[language].fragment,
topology: 'triangle-list',
attributeLayouts: [
{name: 'position', location: 0, accessor: {format: 'float32x4'}},
{name: 'uv', location: 1, accessor: {format: 'float32x2'}}
],
attributeBuffers: [positionBuffer, uvBuffer],
bindings: [uniformBuffer],
vertexCount: cubeVertexCount,
parameters: {
// Enable depth testing so that the fragment closest to the camera
// is rendered in front.
depthWriteEnabled: true,
depthCompare: 'less',
depthFormat: 'depth24plus',
// Backface culling since the cube is solid piece of geometry.
// Faces pointing away from the camera will be occluded by faces
// pointing toward the camera.
cullMode: 'back',
},
});
const projectionMatrix = new Matrix4();
const viewMatrix = new Matrix4();
const modelViewProjectionMatrix = new Matrix4();
function frame() {
const aspect = canvas.width / canvas.height;
const now = Date.now() / 1000;
viewMatrix.identity().translate([0, 0, -4]).rotateAxis(1, [Math.sin(now), Math.cos(now), 0]);
projectionMatrix.perspective({fov: (2 * Math.PI) / 5, aspect, near: 1, far: 100.0});
modelViewProjectionMatrix.copy(viewMatrix).multiplyLeft(projectionMatrix);
uniformBuffer.write(new Float32Array(modelViewProjectionMatrix));
device.beginRenderPass();
model.draw();
device.submit();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
init(document.getElementById('canvas') as HTMLCanvasElement, 'wgsl');
/*
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, );
function getTransformationMatrix() {
const viewMatrix = mat4.create();
mat4.translate(viewMatrix, viewMatrix, vec3.fromValues(0, 0, -4));
const now = Date.now() / 1000;
mat4.rotate(
viewMatrix,
viewMatrix,
1,
vec3.fromValues(Math.sin(now), Math.cos(now), 0)
);
const modelViewProjectionMatrix = mat4.create();
mat4.multiply(modelViewProjectionMatrix, projectionMatrix, viewMatrix);
return modelViewProjectionMatrix as Float32Array;
}
*/

View File

@ -0,0 +1,142 @@
export const cubeVertexSize = 4 * 10; // Byte size of one cube vertex.
export const cubePositionOffset = 0;
export const cubeColorOffset = 4 * 4; // Byte offset of cube vertex color attribute.
export const cubeUVOffset = 4 * 8;
export const cubeVertexCount = 36;
// float4 position
// prettier-ignore
export const cubePositions = new Float32Array([
1, -1, 1, 1,
-1, -1, 1, 1,
-1, -1, -1, 1,
1, -1, -1, 1,
1, -1, 1, 1,
-1, -1, -1, 1,
1, 1, 1, 1,
1, -1, 1, 1,
1, -1, -1, 1,
1, 1, -1, 1,
1, 1, 1, 1,
1, -1, -1, 1,
-1, 1, 1, 1,
1, 1, 1, 1,
1, 1, -1, 1,
-1, 1, -1, 1,
-1, 1, 1, 1,
1, 1, -1, 1,
-1, -1, 1, 1,
-1, 1, 1, 1,
-1, 1, -1, 1,
-1, -1, -1, 1,
-1, -1, 1, 1,
-1, 1, -1, 1,
1, 1, 1, 1,
-1, 1, 1, 1,
-1, -1, 1, 1,
-1, -1, 1, 1,
1, -1, 1, 1,
1, 1, 1, 1,
1, -1, -1, 1,
-1, -1, -1, 1,
-1, 1, -1, 1,
1, 1, -1, 1,
1, -1, -1, 1,
-1, 1, -1, 1,
]);
// float2 uv,
// prettier-ignore
export const cubeUVs = new Float32Array([
1, 1,
0, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 1,
0, 1,
0, 0,
0, 0,
1, 0,
1, 1,
1, 1,
0, 1,
0, 0,
1, 0,
1, 1,
0, 0,
]);
export const cubeColors = new Float32Array([
// float4 position, float4 color, float2 uv,
1, 0, 1, 1,
0, 0, 1, 1,
0, 0, 0, 1,
1, 0, 0, 1,
1, 0, 1, 1,
0, 0, 0, 1,
1, 1, 1, 1,
1, 0, 1, 1,
1, 0, 0, 1,
1, 1, 0, 1,
1, 1, 1, 1,
1, 0, 0, 1,
0, 1, 1, 1,
1, 1, 1, 1,
1, 1, 0, 1,
0, 1, 0, 1,
0, 1, 1, 1,
1, 1, 0, 1,
0, 0, 1, 1,
0, 1, 1, 1,
0, 1, 0, 1,
0, 0, 0, 1,
0, 0, 1, 1,
0, 1, 0, 1,
1, 1, 1, 1,
0, 1, 1, 1,
0, 0, 1, 1,
0, 0, 1, 1,
1, 0, 1, 1,
1, 1, 1, 1,
1, 0, 0, 1,
0, 0, 0, 1,
0, 1, 0, 1,
1, 1, 0, 1,
1, 0, 0, 1,
0, 1, 0, 1,
]);

View File

@ -0,0 +1,7 @@
<!doctype html>
<script type="module">
import './app.ts';
</script>
<body>
<canvas id="canvas"></canvas>
</body>

View File

@ -0,0 +1,20 @@
{
"name": "luma.gl-examples-webgpu-rotating-cube",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"dependencies": {
"@luma.gl/engine": "8.6.0-alpha.5",
"@luma.gl/shadertools": "8.6.0-alpha.5",
"@luma.gl/webgpu": "8.6.0-alpha.5",
"@math.gl/core": "^3.5.0"
},
"devDependencies": {
"typescript": "^4.3.2",
"vite": "^2.6.4"
}
}

View File

@ -0,0 +1,18 @@
import fs from 'fs/promises';
import { defineConfig } from 'vite'
/** @see https://vitejs.dev/config/ */
export default defineConfig(async () => ({
resolve: {alias: await getAliases('@luma.gl', `${__dirname}/../../..`)},
server: {open: true}
}));
/** Run against local source */
const getAliases = async (frameworkName, frameworkRootDir) => {
const modules = await fs.readdir(`${frameworkRootDir}/modules`)
const aliases = {}
for (const module of modules) {
aliases[`${frameworkName}/${module}`] = `${frameworkRootDir}/modules/${module}/src`
}
return aliases
}

View File

@ -1,8 +1,8 @@
// luma.gl, MIT license
import StatsManager, {lumaStats} from '../utils/stats-manager';
import type {default as Buffer, BufferProps} from './buffer';
import type {default as Texture, TextureProps} from './texture';
import type {default as Shader, ShaderProps} from './shader';
import type {default as Buffer, BufferProps} from './resources/buffer';
import type {default as Texture, TextureProps} from './resources/texture';
import type {default as Shader, ShaderProps} from './resources/shader';
// import type {RenderPipeline, RenderPipelineProps, ComputePipeline, ComputePipelineProps} from './pipeline';
export type ShadingLanguage = 'glsl' | 'wgsl';

View File

@ -1,5 +1,5 @@
// luma.gl, MIT license
import type Device from './device';
import type Device from '../device';
import Resource, {ResourceProps, DEFAULT_RESOURCE_PROPS} from './resource';
export type BufferProps = ResourceProps & {
@ -30,7 +30,7 @@ const DEFAULT_BUFFER_PROPS: Required<BufferProps> = {
byteLength: 0,
data: undefined,
usage: undefined, // GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
mappedAtCreation: true
mappedAtCreation: false
};
/** Abstract GPU buffer */
@ -53,16 +53,7 @@ export default abstract class Buffer extends Resource<BufferProps> {
super(device, props, DEFAULT_BUFFER_PROPS);
}
// Mapped API (WebGPU)
/** Maps the memory so that it can be read */
// abstract mapAsync(mode, byteOffset, byteLength): Promise<void>
/** Get the mapped range of data for reading or writing */
// abstract getMappedRange(byteOffset, byteLength): ArrayBuffer;
/** unmap makes the contents of the buffer available to the GPU again */
// abstract unmap(): void;
write(data: ArrayBufferView, byteOffset: number = 0): void { throw new Error('not implemented'); }
// Convenience API
@ -103,4 +94,15 @@ export default abstract class Buffer extends Resource<BufferProps> {
}
}
*/
// Mapped API (WebGPU)
/** Maps the memory so that it can be read */
// abstract mapAsync(mode, byteOffset, byteLength): Promise<void>
/** Get the mapped range of data for reading or writing */
// abstract getMappedRange(byteOffset, byteLength): ArrayBuffer;
/** unmap makes the contents of the buffer available to the GPU again */
// abstract unmap(): void;
}

View File

@ -1,9 +1,10 @@
// luma.gl, MIT license
import type Device from './device';
import type Device from '../device';
import Resource, {ResourceProps, DEFAULT_RESOURCE_PROPS} from './resource';
import type {default as Shader} from './shader';
import {RenderPipelineParameters} from './parameters';
import type {BindingLayout} from './types';
import type {RenderPipelineParameters} from '../types/parameters';
import type {BindingLayout} from '../types/types';
import type {AttributeBinding} from '../types/program-bindings';
export type RenderPipelineProps = ResourceProps & {
vertexShader: Shader;
@ -16,6 +17,7 @@ export type RenderPipelineProps = ResourceProps & {
topology?: 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip';
parameters?: RenderPipelineParameters;
attributeLayouts?: AttributeBinding[];
layout?: BindingLayout[];
};

View File

@ -1,6 +1,6 @@
//
import Device from './device';
// import {uid} from '../utils';
// luma.gl, MIT license
import type Device from '../device';
import {uid} from '../../utils/utils';
export type ResourceProps = {
id?: string;
@ -38,8 +38,10 @@ export default abstract class Resource<Props extends ResourceProps> {
throw new Error('no device');
}
this._device = device;
this.props = this.initializeProps(props, defaultProps);
this.id = this.props.id || 'no-id'; // TODO uid(this[Symbol.toStringTag] || this.constructor.name);
this.props = selectivelyMerge<Props>(props, defaultProps);
this.props.id = this.props.id || uid(this[Symbol.toStringTag]);
this.id = this.props.id;
this.userData = this.props.userData || {};
this.addStats();
}
@ -105,19 +107,20 @@ export default abstract class Resource<Props extends ResourceProps> {
stats.get(`${name}s Active`).incrementCount();
}
/**
* Combines a map of user props and default props, only including props from defaultProps
* @param props
* @param defaultProps
* @returns returns a map of overridden default props
*/
private initializeProps(props: Props, defaultProps: Required<Props>): Required<Props> {
const mergedProps = {...defaultProps};
for (const key in props) {
if (props[key] !== undefined) {
mergedProps[key] = props[key];
}
}
return mergedProps;
}
}
/**
* Combines a map of user props and default props, only including props from defaultProps
* @param props
* @param defaultProps
* @returns returns a map of overridden default props
*/
function selectivelyMerge<Props>(props: Props, defaultProps: Required<Props>): Required<Props> {
const mergedProps = {...defaultProps};
for (const key in props) {
if (props[key] !== undefined) {
mergedProps[key] = props[key];
}
}
return mergedProps;
}

View File

@ -1,6 +1,6 @@
import {CompareFunction} from './parameters';
import type Device from '../device';
import {CompareFunction} from '../types/parameters';
import Resource, {ResourceProps, DEFAULT_RESOURCE_PROPS} from './resource';
import type Device from './device';
export type SamplerAddressMode = 'clamp-to-edge' | 'repeat' | 'mirror-repeat';
export type SamplerFilterMode = 'nearest' | 'linear';

View File

@ -1,5 +1,5 @@
// luma.gl, MIT license
import type Device from './device';
import type Device from '../device';
import Resource, {ResourceProps, DEFAULT_RESOURCE_PROPS} from './resource';
export type CompilerMessageType = 'error' | 'warning' | 'info';

View File

@ -1,6 +1,7 @@
// luma.gl, MIT license
import type Device from '../device';
import type {TextureFormat} from '../types/types';
import Resource, {ResourceProps, DEFAULT_RESOURCE_PROPS} from './resource';
import type Device from './device';
// required GPUExtent3D size;
// GPUIntegerCoordinate mipLevelCount = 1;
@ -11,35 +12,42 @@ import type Device from './device';
/** Abstract Texture interface */
export type TextureProps = ResourceProps & {
data?: any;
format?: TextureFormat | number;
dimension?: '1d' | '2d' | '3d';
width?: number;
height?: number;
depth?: number;
usage?: number;
data?: any;
mipmaps?: boolean;
parameters?: object;
pixels?: any;
format?: number;
dataFormat?: number;
border?: number;
recreate?: boolean;
type?: number;
compressed?: boolean;
mipmaps?: boolean;
parameters?: object;
/** @deprecated use data */
pixels?: any;
/** @deprecated use format */
dataFormat?: number;
/** @deprecated rarely supported */
border?: number;
/** @deprecated WebGL only. */
pixelStore?: object;
/** @deprecated WebGL only. */
textureUnit?: number;
/** @deprecated WebGL only. Use dimension. */
target?: number;
/** @deprecated not supported */
recreate?: boolean;
};
export type WebGPUTextureProps = ResourceProps & {
dimension?: '1d' | '2d' | '3d';
width: number;
height: number;
depth?: number;
mipLevels?: number;
format?: string;
usage?: number;
};
export type TextureViewProps = {
@ -48,22 +56,35 @@ export type TextureViewProps = {
aspect?: 'all', 'stencil-only', 'depth-only';
arrayLayerCount: number;
baseArrayLayer?: number;
mipLevelCount: number;
mipLevels?: number;
baseMipLevel?: number;
};
// @ts-expect-error
const DEFAULT_TEXTURE_PROPS: Required<TextureProps> = {
...DEFAULT_RESOURCE_PROPS,
data: undefined,
dimension: '2d',
width: 1,
height: 1,
depth: 1,
mipLevels: 1,
// @ts-expect-error
format: 'unorm8',
mipmaps: false,
parameters: {},
type: undefined,
compressed: false,
// mipLevels: 1,
format: 'rgba8unorm',
usage: 0
};
// const DEFAULT_TEXTURE_PROPS: Required<TextureProps> = {
// handle: undefined,
// id: undefined,
// depth: 1,
// format: 'rgba8unorm',
// usage: GPUTextureUsage.COPY_DST
// };
/**
* Abstract Texture interface
* Texture Object

View File

@ -1,135 +0,0 @@
// luma.gl, MIT license
enum TextureFormat {
// 8-bit formats
'r8unorm',
'r8snorm',
'r8uint',
'r8sint',
// 16-bit formats
'r16uint',
'r16sint',
'r16float',
'rg8unorm',
'rg8snorm',
'rg8uint',
'rg8sint',
// 32-bit formats
'r32uint',
'r32sint',
'r32float',
'rg16uint',
'rg16sint',
'rg16float',
'rgba8unorm',
'rgba8unorm-srgb',
'rgba8snorm',
'rgba8uint',
'rgba8sint',
'bgra8unorm',
'bgra8unorm-srgb',
// Packed 32-bit formats
'rgb9e5ufloat',
'rgb10a2unorm',
'rg11b10ufloat',
// 64-bit formats
'rg32uint',
'rg32sint',
'rg32float',
'rgba16uint',
'rgba16sint',
'rgba16float',
// 128-bit formats
'rgba32uint',
'rgba32sint',
'rgba32float',
// Depth and stencil formats
'stencil8',
'depth16unorm',
'depth24plus',
'depth24plus-stencil8',
'depth32float',
// BC compressed formats usable if 'texture-compression-bc' is both
// supported by the device/user agent and enabled in requestDevice.
'bc1-rgba-unorm',
'bc1-rgba-unorm-srgb',
'bc2-rgba-unorm',
'bc2-rgba-unorm-srgb',
'bc3-rgba-unorm',
'bc3-rgba-unorm-srgb',
'bc4-r-unorm',
'bc4-r-snorm',
'bc5-rg-unorm',
'bc5-rg-snorm',
'bc6h-rgb-ufloat',
'bc6h-rgb-float',
'bc7-rgba-unorm',
'bc7-rgba-unorm-srgb',
// 'depth24unorm-stencil8' feature
'depth24unorm-stencil8',
// 'depth32float-stencil8' feature
'depth32float-stencil8',
};
// BINDING LAYOUTS
type BufferBindingLayout = {
location?: number;
visibility: number;
type: 'uniform' | 'storage' | 'read-only-storage';
hasDynamicOffset?: boolean;
minBindingSize?: number;
}
type TextureBindingLayout = {
location?: number;
visibility: number;
viewDimension?: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d';
sampleType?: 'float' | 'unfilterable-float' | 'depth' | 'sint' | 'uint';
multisampled?: boolean;
};
type StorageTextureBindingLayout = {
location?: number;
visibility: number;
access?: 'write-only';
format: TextureFormat;
viewDimension?: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d';
};
export type BindingLayout = BufferBindingLayout | TextureBindingLayout | StorageTextureBindingLayout;
// BINDINGS
import type Buffer from './buffer';
import type Texture from './texture'; // TextureView...
export type Binding = Texture | Buffer | {buffer: Buffer, offset?: number, size?: number};
// Attachments
export type ColorAttachment = {
// attachment: GPUTextureView;
// resolveTarget?: GPUTextureView;
// loadValue: GPULoadOp | GPUColor;
// storeOp?: GPUStoreOp;
};
export type DepthStencilAttachment = {
// attachment: GPUTextureView;
// depthLoadValue: GPULoadOp | number;
// depthStoreOp: GPUStoreOp;
// depthReadOnly?: boolean;
// stencilLoadValue: GPULoadOp | number;
// stencilStoreOp: GPUStoreOp;
// stencilReadOnly?: boolean;
};

View File

@ -1,30 +1,38 @@
// luma.gl, MIT license
import type {VertexFormat} from './types';
// ACCESSORS
/**
* Attribute descriptor object
*/
export type Accessor = {
format?: string;
format: VertexFormat;
offset?: number;
location?: number;
// can now be described with single WebGPU-style `format` string
type?: number;
size?: number;
normalized?: boolean;
integer?: boolean;
// deprecated - now shared between accessors in the surrounding BufferAccessor object
//
stride?: number;
stepMode?: 'vertex' | 'instance';
/** @deprecated - Use accessor.stepMode */
divisor?: number;
/** @deprecated - Infer from format */
type?: number;
/** @deprecated - Infer from format */
size?: number;
/** @deprecated - Infer from format */
normalized?: boolean;
/** @deprecated - Infer from format */
integer?: boolean;
};
/**
* List of attribute descriptors for one interleaved buffer
*/
export type BufferAccessors = {
export type InterleavedAccessors = {
stride?: number;
stepMode?: 'vertex' | 'instance';
attributes: Accessor[];
}
};

View File

@ -1,3 +1,5 @@
import {DepthOrStencilTextureFormat} from './types';
export type CompareFunction =
'never' |
'less' |
@ -53,6 +55,7 @@ export type StencilOperation =
export type DepthStencilParameters = {
depthWriteEnabled?: boolean;
depthCompare?: CompareFunction;
depthFormat?: DepthOrStencilTextureFormat;
stencilReadMask?: number;
stencilWriteMask?: number;
@ -156,8 +159,8 @@ export type RenderPipelineParameters =
export type Parameters =
_RenderParameters &
DepthStencilParameters &
ColorParameters;
// MultisampleParameters;
ColorParameters &
MultisampleParameters;
// export const DEFAULT_PARAMETERS: Parameters;
@ -167,15 +170,19 @@ export const DEFAULT_PARAMETERS: Required<Parameters> = {
cullMode: 'none',
frontFace: 'ccw',
// Depth Parameters
depthWriteEnabled: false,
depthCompare: 'always',
depthFormat: 'depth24plus',
depthClamp: false,
depthBias: 0,
depthBiasSlopeScale: 0,
depthBiasClamp: 0,
// Depth Stencil Parameters
depthWriteEnabled: false,
depthCompare: 'always',
// Stencil parameters
stencilReadMask: 0xFFFFFFFF,
stencilWriteMask: 0xFFFFFFFF,
@ -185,6 +192,11 @@ export const DEFAULT_PARAMETERS: Required<Parameters> = {
stencilFailOperation: 'keep',
stencilDepthFailOperation: 'keep',
// Multisample parameters
sampleCount: 0,
sampleMask: 0xFFFFFFFF,
sampleAlphaToCoverageEnabled: false,
// Color and blend parameters
blendColorOperation: 'add',

View File

@ -0,0 +1,173 @@
// luma.gl, MIT license
/** Depth and stencil texture formats */
export type DepthOrStencilTextureFormat =
'stencil8' |
'depth16unorm' |
'depth24plus' |
'depth24plus-stencil8' |
'depth32float' |
// device.features.has('depth24unorm-stencil8')
'depth24unorm-stencil8' |
// device.features.has('depth32float-stencil8')
'depth32float-stencil8';
/** Texture formats */
export type TextureFormat = DepthOrStencilTextureFormat |
// 8-bit formats
'r8unorm' |
'r8snorm' |
'r8uint' |
'r8sint' |
// 16-bit formats
'r16uint' |
'r16sint' |
'r16float' |
'rg8unorm' |
'rg8snorm' |
'rg8uint' |
'rg8sint' |
// 32-bit formats
'r32uint' |
'r32sint' |
'r32float' |
'rg16uint' |
'rg16sint' |
'rg16float' |
'rgba8unorm' |
'rgba8unorm-srgb' |
'rgba8snorm' |
'rgba8uint' |
'rgba8sint' |
'bgra8unorm' |
'bgra8unorm-srgb' |
// Packed 32-bit formats
'rgb9e5ufloat' |
'rgb10a2unorm' |
'rg11b10ufloat' |
// 64-bit formats
'rg32uint' |
'rg32sint' |
'rg32float' |
'rgba16uint' |
'rgba16sint' |
'rgba16float' |
// 128-bit formats
'rgba32uint' |
'rgba32sint' |
'rgba32float' |
// BC compressed formats usable if 'texture-compression-bc' is both
// supported by the device/user agent and enabled in requestDevice.
'bc1-rgba-unorm' |
'bc1-rgba-unorm-srgb' |
'bc2-rgba-unorm' |
'bc2-rgba-unorm-srgb' |
'bc3-rgba-unorm' |
'bc3-rgba-unorm-srgb' |
'bc4-r-unorm' |
'bc4-r-snorm' |
'bc5-rg-unorm' |
'bc5-rg-snorm' |
'bc6h-rgb-ufloat' |
'bc6h-rgb-float' |
'bc7-rgba-unorm' |
'bc7-rgba-unorm-srgb';
/** Attribute formats */
export type VertexFormat =
'uint8x2' |
'uint8x4' |
'sint8x2' |
'sint8x4' |
'unorm8x2' |
'unorm8x4' |
'snorm8x2' |
'snorm8x4' |
'uint16x2' |
'uint16x4' |
'sint16x2' |
'sint16x4' |
'unorm16x2' |
'unorm16x4' |
'snorm16x2' |
'snorm16x4' |
'float16x2' |
'float16x4' |
'float32' |
'float32x2' |
'float32x3' |
'float32x4' |
'uint32' |
'uint32x2' |
'uint32x3' |
'uint32x4' |
'sint32' |
'sint32x2' |
'sint32x3' |
'sint32x4';
// BINDING LAYOUTS
type BufferBindingLayout = {
location?: number;
visibility: number;
type: 'uniform' | 'storage' | 'read-only-storage';
hasDynamicOffset?: boolean;
minBindingSize?: number;
}
type TextureBindingLayout = {
location?: number;
visibility: number;
viewDimension?: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d';
sampleType?: 'float' | 'unfilterable-float' | 'depth' | 'sint' | 'uint';
multisampled?: boolean;
};
type StorageTextureBindingLayout = {
location?: number;
visibility: number;
access?: 'write-only';
format: TextureFormat;
viewDimension?: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d';
};
export type BindingLayout = BufferBindingLayout | TextureBindingLayout | StorageTextureBindingLayout;
// BINDINGS
import type Buffer from '../resources/buffer';
import type Texture from '../resources/texture'; // TextureView...
export type Binding = Texture | Buffer | {buffer: Buffer, offset?: number, size?: number};
// ATTRIBUTE LAYOUTS
// Attachments
export type ColorAttachment = {
// attachment: GPUTextureView;
// resolveTarget?: GPUTextureView;
// loadValue: GPULoadOp | GPUColor;
// storeOp?: GPUStoreOp;
};
export type DepthStencilAttachment = {
// attachment: GPUTextureView;
// depthLoadValue: GPULoadOp | number;
// depthStoreOp: GPUStoreOp;
// depthReadOnly?: boolean;
// stencilLoadValue: GPULoadOp | number;
// stencilStoreOp: GPUStoreOp;
// stencilReadOnly?: boolean;
};

View File

@ -1,24 +1,27 @@
// Initialize any global state
import './init';
// MAIN API ACCESS POINTS
export {default as luma} from './lib/luma';
export type {DeviceLimits, DeviceInfo} from './adapter/device';
export {default as Device} from './adapter/device';
// GPU RESOURCES
export type {ResourceProps} from './adapter/resource';
export {default as Resource} from './adapter/resource';
export type {BufferProps} from './adapter/buffer';
export {default as Buffer} from './adapter/buffer';
export type {TextureProps} from './adapter/texture';
export {default as Texture} from './adapter/texture';
export type {ShaderProps, CompilerMessage} from './adapter/shader';
export {default as Shader} from './adapter/shader';
export type {SamplerProps} from './adapter/sampler';
export {default as Sampler} from './adapter/sampler';
export type {RenderPipelineProps} from './adapter/render-pipeline';
export {default as RenderPipeline} from './adapter/render-pipeline';
export type {ResourceProps} from './adapter/resources/resource';
export {default as Resource} from './adapter/resources/resource';
export type {BufferProps} from './adapter/resources/buffer';
export {default as Buffer} from './adapter/resources/buffer';
export type {TextureProps} from './adapter/resources/texture';
export {default as Texture} from './adapter/resources/texture';
export type {ShaderProps, CompilerMessage} from './adapter/resources/shader';
export {default as Shader} from './adapter/resources/shader';
export type {SamplerProps} from './adapter/resources/sampler';
export {default as Sampler} from './adapter/resources/sampler';
export type {RenderPipelineProps} from './adapter/resources/render-pipeline';
export {default as RenderPipeline} from './adapter/resources/render-pipeline';
// API TYPES
export type {Accessor, BufferAccessors} from './adapter/accessor';
export type {Accessor, InterleavedAccessors} from './adapter/types/accessor';
export type {
Parameters,
PrimitiveTopology,
@ -35,9 +38,24 @@ export type {
MultisampleParameters,
RenderPassParameters,
RenderPipelineParameters
} from './adapter/parameters';
} from './adapter/types/parameters';
export type {BindingLayout, Binding, ColorAttachment, DepthStencilAttachment} from './adapter/types';
export type {
TextureFormat,
VertexFormat,
BindingLayout,
Binding,
ColorAttachment,
DepthStencilAttachment
} from './adapter/types/types';
export type {
ProgramBindings,
AttributeBinding,
UniformBinding,
UniformBlockBinding,
VaryingBinding
} from './adapter/types/program-bindings';
// UTILS
export type {TypedArray, NumberArray} from './types';

35
modules/api/src/init.ts Normal file
View File

@ -0,0 +1,35 @@
import {isBrowser} from '@probe.gl/env';
import {log} from './utils/log';
import {lumaStats} from './utils/stats-manager';
// Version detection using babel plugin
// @ts-expect-error
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'untranspiled source';
const STARTUP_MESSAGE = 'set luma.log.level=1 (or higher) to trace rendering';
// Assign luma.log.level in console to control logging: \
// 0: none, 1: minimal, 2: verbose, 3: attribute/uniforms, 4: gl logs
// luma.log.break[], set to gl funcs, luma.log.profile[] set to model names`;
if (globalThis.luma && globalThis.luma.VERSION !== VERSION) {
throw new Error(`luma.gl - multiple VERSIONs detected: ${globalThis.luma.VERSION} vs ${VERSION}`);
}
if (!globalThis.luma) {
if (isBrowser()) {
log.log(1, `luma.gl ${VERSION} - ${STARTUP_MESSAGE}`)();
}
globalThis.luma = globalThis.luma || {
VERSION,
version: VERSION,
log,
// A global stats object that various components can add information to
// E.g. see webgl/resource.js
stats: lumaStats,
};
}
export {lumaStats};
export default globalThis.luma;

View File

@ -2,7 +2,7 @@ import test from 'tape-promise/tape';
import {webgl1TestDevice} from '@luma.gl/test-utils';
import GL from '@luma.gl/constants';
import luma from '@luma.gl/webgl/init';
import {luma} from '@luma.gl/api';
// TODO - Model test should not depend on Cube
import {Model, ProgramManager} from '@luma.gl/engine';
import {Buffer} from '@luma.gl/webgl';

View File

@ -1,9 +1,9 @@
// Contains metadata describing attribute configurations for a program's shaders
// Much of this is automatically extracted from shaders after program linking
// import Accessor from './accessor';
import type Program from './program';
import type {AttributeBinding, VaryingBinding} from '../helpers/program-bindings';
import type {AttributeBinding, VaryingBinding} from '@luma.gl/api';
import {getProgramBindings} from '../helpers/get-program-bindings';
import type Program from './program';
/**
* is a mechanism for taking a program object and querying information

View File

@ -4,6 +4,7 @@ import Texture, {TextureProps, TextureSupportOptions} from './texture';
export type Texture2DProps = TextureProps & {
format?: number;
};
export default class Texture2D extends Texture {

View File

@ -1,9 +1,8 @@
import {log, assert} from '@luma.gl/api';
import {log, assert, AttributeBinding} from '@luma.gl/api';
import GL from '@luma.gl/constants';
import Accessor from './accessor';
import Buffer from './webgl-buffer';
import Program from './program';
import type {AttributeBinding} from '../helpers/program-bindings'
import ProgramConfiguration from './program-configuration';
import VertexArrayObject, {VertexArrayObjectProps} from './vertex-array-object';

View File

@ -1,6 +1,6 @@
import {ProgramBindings, AttributeBinding, UniformBinding, UniformBlockBinding, VaryingBinding} from '@luma.gl/api';
import GL from '@luma.gl/constants';
import {isWebGL2} from '../context/context/webgl-checks';
import {ProgramBindings, AttributeBinding, UniformBinding, UniformBlockBinding, VaryingBinding} from './program-bindings'
import Accessor from '../classes/accessor';
import {decomposeCompositeGLType} from '../webgl-utils/attribute-utils';
@ -44,6 +44,7 @@ function readAttributeBindings(gl: WebGL2RenderingContext, program: WebGLProgram
inferProperties(location, name, accessor);
const attributeInfo = {location, name, accessor: new Accessor(accessor)}; // Base values
// @ts-expect-error
attributes.push(attributeInfo);
}
}

View File

@ -5,7 +5,8 @@
// Higher level abstractions can be built on these classes
// Initialize any global state
import './init';
import '@luma.gl/api';
import './init'
// export type {WebGLDeviceProps, WebGLDeviceInfo, WebGPUDeviceLimits} from './lib/webgl-device';
export type {WebGLDeviceProps} from './adapter/webgl-device';
@ -116,7 +117,7 @@ export {getProgramBindings} from './helpers/get-program-bindings';
// DEPRECATED
// Deprecated re-exports
export {lumaStats} from './init';
export {lumaStats} from '@luma.gl/api';
export {log, assert, uid, isObjectEmpty} from '@luma.gl/api';
export {setPathPrefix, loadFile, loadImage} from '@luma.gl/api';

View File

@ -1,48 +1,4 @@
import {log} from '@luma.gl/api';
import {isBrowser} from '@probe.gl/env';
import {luma, lumaStats} from '@luma.gl/api';
import {luma} from '@luma.gl/api';
import WebGLDevice from './adapter/webgl-device';
// Version detection using babel plugin
// @ts-expect-error
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'untranspiled source';
const STARTUP_MESSAGE = 'set luma.log.level=1 (or higher) to trace rendering';
// Assign luma.log.level in console to control logging: \
// 0: none, 1: minimal, 2: verbose, 3: attribute/uniforms, 4: gl logs
// luma.log.break[], set to gl funcs, luma.log.profile[] set to model names`;
if (globalThis.luma && globalThis.luma.VERSION !== VERSION) {
throw new Error(`luma.gl - multiple VERSIONs detected: ${globalThis.luma.VERSION} vs ${VERSION}`);
}
if (!globalThis.luma) {
luma.registerDevices([WebGLDevice]);
if (isBrowser()) {
log.log(1, `luma.gl ${VERSION} - ${STARTUP_MESSAGE}`)();
}
globalThis.luma = globalThis.luma || {
VERSION,
version: VERSION,
log,
// A global stats object that various components can add information to
// E.g. see webgl/resource.js
stats: lumaStats,
// Keep some luma globals in a sub-object
// This allows us to dynamically detect if certain modules have been
// included (such as IO and headless) and enable related functionality,
// without unconditionally requiring and thus bundling big dependencies
// into the app.
globals: {
modules: {},
nodeIO: {}
}
};
}
export {lumaStats};
export default globalThis.luma;
luma.registerDevices([WebGLDevice]);

View File

@ -0,0 +1,101 @@
/*
import {assert} from '@luma.gl/api';
import GL from '@luma.gl/constants';
type Accessor = Record<string, any>;
const FORMAT_TO_ACCESSOR: Record<GPUVertexFormat, Accessor> = {
uchar2: {type: 'uchar', size: 2},
uchar4: {type: 'uchar', size: 4},
char2: {type: 'char', size: 2},
char4: {type: 'char', size: 4},
uchar2norm: {type: 'uchar', size: 2, normalized: true},
uchar4norm: {type: 'uchar', size: 4, normalized: true},
char2norm: {type: 'char', size: 2, normalized: true},
char4norm: {type: 'char', size: 4, normalized: true},
ushort2: {type: 'ushort', size: 2},
ushort4: {type: 'ushort', size: 4},
short2: {type: 'short', size: 2},
short4: {type: 'short', size: 4},
ushort2norm: {type: 'ushort', size: 2, normalized: true},
ushort4norm: {type: 'ushort', size: 4, normalized: true},
short2norm: {type: 'short', size: 1, normalized: true},
short4norm: {type: 'short', size: 1, normalized: true},
half2: {type: 'half', size: 2},
half4: {type: 'half', size: 4},
float: {type: 'float', size: 1},
float2: {type: 'float', size: 2},
float3: {type: 'float', size: 3},
float4: {type: 'float', size: 4},
uint: {type: 'uint', size: 1, integer: true},
uint2: {type: 'uint', size: 2, integer: true},
uint3: {type: 'uint', size: 3, integer: true},
uint4: {type: 'uint', size: 4, integer: true},
int: {type: 'int', size: 1, integer: true},
int2: {type: 'int', size: 2, integer: true},
int3: {type: 'int', size: 3, integer: true},
int4: {type: 'int', size: 4, integer: true}
};
/**
* Convert from WebGPU attribute format strings to accessor {type, size, normalized, integer}
* @param {*} format
*
export function mapWebGPUFormatToAccessor(format) {
const accessorDefinition = FORMAT_TO_ACCESSOR[format];
assert(accessorDefinition, 'invalid attribute format');
return Object.freeze(accessorDefinition);
}
/**
* Convert from accessor {type, size, normalized, integer} to WebGPU attribute format strings
* @param {*} format
*
export function mapAccessorToWebGPUFormat(accessor) {
const {type = GL.FLOAT, size = 1, normalized = false, integer = false} = accessor;
assert(size >=1 && size <=4);
// `norm` suffix (uchar4norm)
const norm = normalized ? 'norm' : '';
// size 1 is ommitted in format names (float vs float2)
const count = size === 1 ? '' : size;
switch (type) {
case GL.UNSIGNED_BYTE:
switch (size) {
case 2:
case 4:
return `uchar${count}${norm}`;
}
case GL.BYTE:
switch (size) {
case 2:
case 4:
return `char${count}${norm}`;
}
case GL.UNSIGNED_SHORT:
switch (size) {
case 2:
case 4:
return `ushort${count}${norm}`;
}
case GL.SHORT:
switch (size) {
case 2:
case 4:
return `short${count}${norm}`;
}
case GL.HALF_FLOAT:
switch (size) {
case 2:
case 4:
return `half${count}`;
}
case GL.FLOAT:
return `float${count}`;
case GL.UNSIGNED_INT:
return `uint${count}`;
case GL.INT:
return `int${count}`;
}
throw new Error('illegal accessor');
}
*/

View File

@ -0,0 +1,107 @@
import {VertexFormat, AttributeBinding, assert} from '@luma.gl/api';
export type Attribute = {
name: string;
location: number;
accessor: {
format: VertexFormat;
offset?: number;
stride?: number;
stepMode?: 'vertex' | 'instance';
divisor?: number;
}
};
// type InterleavedAttributes = {
// byteStride?: number;
// stepMode?:
// attributes: Attribute[];
// }
export function convertAttributesVertexBufferToLayout(attributes: Attribute[]): GPUVertexBufferLayout[] {
const vertexBufferLayouts: GPUVertexBufferLayout[] = [];
for (const attribute of attributes) {
const arrayStride = attribute.accessor.stride || getVertexFormatBytes(attribute.accessor.format);
const stepMode = attribute.accessor.stepMode || (attribute.accessor.divisor ? 'instance' : 'vertex');
vertexBufferLayouts.push({
arrayStride,
stepMode,
attributes: [
{
format: attribute.accessor.format,
offset: attribute.accessor.offset || 0,
shaderLocation: attribute.location
}
]
});
}
return vertexBufferLayouts;
}
function getVertexFormatBytes(format: GPUVertexFormat): number {
const [type, count] = format.split('x');
const bytes = getTypeBytes(type);
assert(bytes);
return bytes * parseInt(count);
}
const TYPE_SIZES = {
uint8: 1,
sint8: 1,
unorm8: 1,
snorm8: 1,
uint16: 2,
sint16: 2,
unorm16: 2,
snorm16: 2,
float16: 2,
float32: 4,
uint32: 4,
sint32: 4,
};
function getTypeBytes(type: string): number {
const bytes = TYPE_SIZES[type];
assert(bytes);
return bytes;
}
/**
* Attempt to convert legacy luma.gl accessors to attribute infos
* @param bufferAccessors
*
function getVertexBuffers(bufferAccessors: BufferAccessors[]) {
const vertexBuffers = [];
for (const buffer of bufferAccessors) {
let stride = null;
let divisor = null;
const attributes = [];
for (const accessor of buffer.attributes) {
if ('stride' in accessor) {
stride = accessor.stride;
}
if ('divisor' in accessor) {
divisor = accessor.divisor;
}
attributes.push({
format: accessor.format || mapAccessorToWebGPUFormat(accessor),
offset: accessor.offset || 0,
location: accessor.location
});
}
vertexBuffers.push({
stride: buffer.stride || stride || 0,
stepMode: buffer.stepMode || (divisor ? 'instance' : 'vertex'),
attributes
});
}
return vertexBuffers;
}
*/

View File

@ -1,8 +1,8 @@
//
import {Binding, Buffer, Sampler, Texture, cast} from '@luma.gl/api';
import type WebGPUBuffer from '../webgpu-buffer';
import type WebGPUSampler from '../webgpu-sampler';
import type WebGPUTexture from '../webgpu-texture';
import type WebGPUBuffer from '../resources/webgpu-buffer';
import type WebGPUSampler from '../resources/webgpu-sampler';
import type WebGPUTexture from '../resources/webgpu-texture';
/**
* Create a WebGPU bind group layout from an array of luma.gl bindings

View File

@ -1,8 +1,8 @@
//
import {Binding, Buffer, Sampler, Texture, cast} from '@luma.gl/api';
import type WebGPUBuffer from '../webgpu-buffer';
import type WebGPUSampler from '../webgpu-sampler';
import type WebGPUTexture from '../webgpu-texture';
import type WebGPUBuffer from '../resources/webgpu-buffer';
import type WebGPUSampler from '../resources/webgpu-sampler';
import type WebGPUTexture from '../resources/webgpu-texture';
/**
* Create a WebGPU bind group from an array of luma.gl bindings

View File

@ -2,131 +2,178 @@ import {Parameters} from '@luma.gl/api';
type PipelineDescriptor = Omit<GPURenderPipelineDescriptor, 'vertexStage'>;
function addDepthStencil(descriptor: GPURenderPipelineDescriptor): void {
descriptor.depthStencil = descriptor.depthStencil || {
// required, set something
format: 'depth24plus',
stencilFront: {},
stencilBack: {},
};
}
/**
* Supports for luma.gl's flat parameter space
* Populates the corresponding sub-objects in a PipelineDescriptor
*/
export const PARAMETER_TABLE = {
// @ts-expect-error
export const PARAMETER_TABLE: Record<keyof Parameters, Function> = {
// RASTERIZATION PARAMETERS
/*
cullMode: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.rasterizationState.cullMode = value;
descriptor.primitive.cullMode = value;
},
cullFace: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.rasterizationState.frontFace = value;
},
depthBias: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.rasterizationState.depthBias = value;
},
depthBiasSlopeScale: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.rasterizationState.depthBiasSlopeScale = value;
},
depthBiasClamp: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.rasterizationState.depthBiasClamp = value;
},
// STENCIL
stencilReadMask: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.stencilReadMask = value;
},
stencilWriteMask: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.stencilWriteMask = value;
},
stencilCompare: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.stencilFront.compare = value;
descriptor.depthStencilState.stencilBack.compare = value;
},
stencilPassOperation: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.stencilFront.passOp = value;
descriptor.depthStencilState.stencilBack.passOp = value;
},
stencilFailOperation: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.stencilFront.failOp = value;
descriptor.depthStencilState.stencilBack.failOp = value;
},
stencilDepthFailOperation: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.stencilFront.depthFailOp = value;
descriptor.depthStencilState.stencilBack.depthFailOp = value;
frontFace: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.primitive.frontFace = value;
},
// DEPTH
depthWriteEnabled: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.depthWriteEnabled = value;
addDepthStencil(descriptor);
descriptor.depthStencil.depthWriteEnabled = value;
},
depthCompare: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.depthStencilState.depthCompare = value;
addDepthStencil(descriptor);
descriptor.depthStencil.depthCompare = value;
},
depthFormat: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.format = value;
},
depthBias: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.depthBias = value;
},
depthBiasSlopeScale: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.depthBiasSlopeScale = value;
},
depthBiasClamp: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.depthBiasClamp = value;
},
// STENCIL
stencilReadMask: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.stencilReadMask = value;
},
stencilWriteMask: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.stencilWriteMask = value;
},
stencilCompare: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.stencilFront.compare = value;
descriptor.depthStencil.stencilBack.compare = value;
},
stencilPassOperation: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.stencilFront.passOp = value;
descriptor.depthStencil.stencilBack.passOp = value;
},
stencilFailOperation: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.stencilFront.failOp = value;
descriptor.depthStencil.stencilBack.failOp = value;
},
stencilDepthFailOperation: (parameter, value, descriptor: PipelineDescriptor) => {
addDepthStencil(descriptor);
descriptor.depthStencil.stencilFront.depthFailOp = value;
descriptor.depthStencil.stencilBack.depthFailOp = value;
},
// MULTISAMPLE
sampleCount: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.multisample = descriptor.multisample || {};
descriptor.multisample.count = value;
},
sampleMask: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.multisample = descriptor.multisample || {};
descriptor.multisample.mask = value;
},
sampleAlphaToCoverageEnabled: (parameter, value, descriptor: PipelineDescriptor) => {
descriptor.multisample = descriptor.multisample || {};
descriptor.multisample.alphaToCoverageEnabled = value;
},
// COLOR
colorMask: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].writeMask = value;
descriptor.fragment.targets[0].writeMask = value;
},
blendColorOperation: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].blend = descriptor.colorStates[0].blend || {};
descriptor.colorStates[0].blend.color = descriptor.colorStates[0].blend.color || {};
descriptor.colorStates[0].blend.color.operation = value;
descriptor.fragment.targets[0].blend = descriptor.fragment.targets[0].blend || {};
descriptor.fragment.targets[0].blend.color = descriptor.fragment.targets[0].blend.color || {};
descriptor.fragment.targets[0].blend.color.operation = value;
},
/*
blendColorSrcTarget: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].blend = descriptor.colorStates[0].blend || {};
descriptor.colorStates[0].blend.color = descriptor.colorStates[0].blend.color || {};
descriptor.colorStates[0].blend.color.srcTarget = value;
descriptor.fragment.targets[0].blend = descriptor.fragment.targets[0].blend || {};
descriptor.fragment.targets[0].blend.color = descriptor.fragment.targets[0].blend.color || {};
descriptor.fragment.targets[0].blend.color.srcTarget = value;
},
blendColorDstTarget: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].blend = descriptor.colorStates[0].blend || {};
descriptor.colorStates[0].blend.color = descriptor.colorStates[0].blend.color || {};
descriptor.colorStates[0].blend.color.dstTarget = value;
descriptor.fragment.targets[0].blend = descriptor.fragment.targets[0].blend || {};
descriptor.fragment.targets[0].blend.color = descriptor.fragment.targets[0].blend.color || {};
descriptor.fragment.targets[0].blend.color.dstTarget = value;
},
blendAlphaOperation: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].blend = descriptor.colorStates[0].blend || {};
descriptor.colorStates[0].blend.alpha = descriptor.colorStates[0].blend.alpha || {};
descriptor.colorStates[0].blend.alpha.operation = value;
descriptor.fragment.targets[0].blend = descriptor.fragment.targets[0].blend || {};
descriptor.fragment.targets[0].blend.alpha = descriptor.fragment.targets[0].blend.alpha || {};
descriptor.fragment.targets[0].blend.alpha.operation = value;
},
blendAlphaSrcTarget: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].blend = descriptor.colorStates[0].blend || {};
descriptor.colorStates[0].blend.alpha = descriptor.colorStates[0].blend.alpha || {};
descriptor.colorStates[0].blend.alpha.srcTarget = value;
descriptor.fragment.targets[0].blend = descriptor.fragment.targets[0].blend || {};
descriptor.fragment.targets[0].blend.alpha = descriptor.fragment.targets[0].blend.alpha || {};
descriptor.fragment.targets[0].blend.alpha.srcTarget = value;
},
blendAlphaDstTarget: (parameter, value, descriptor: PipelineDescriptor) => {
addColorState(descriptor);
descriptor.colorStates[0].blend = descriptor.colorStates[0].blend || {};
descriptor.colorStates[0].blend.alpha = descriptor.colorStates[0].blend.alpha || {};
descriptor.colorStates[0].blend.alpha.dstTarget = value;
descriptor.fragment.targets[0].blend = descriptor.fragment.targets[0].blend || {};
descriptor.fragment.targets[0].blend.alpha = descriptor.fragment.targets[0].blend.alpha || {};
descriptor.fragment.targets[0].blend.alpha.dstTarget = value;
},
*/
};
const DEFAULT_PIPELINE_DESCRIPTOR: PipelineDescriptor = {
depthStencil: {
stencilFront: {},
stencilBack: {},
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus-stencil8',
},
// depthStencil: {
// stencilFront: {},
// stencilBack: {},
// // depthWriteEnabled: true,
// // depthCompare: 'less',
// // format: 'depth24plus-stencil8',
// },
primitive: {
cullMode: 'back',
@ -142,35 +189,18 @@ const DEFAULT_PIPELINE_DESCRIPTOR: PipelineDescriptor = {
module: undefined,
entryPoint: 'main',
targets: [
// { format: props.color0Format || 'bgra8unorm' }
]
}
}
export function getRenderPipelineDescriptor(parameters: Parameters = {}, props = {}): PipelineDescriptor {
const pipelineDescriptor: PipelineDescriptor = {...DEFAULT_PIPELINE_DESCRIPTOR};
// pipelineDescriptor.
// rasterizationState: {
// cullMode: 'back',
// },
// depthStencilState: {
// depthWriteEnabled: true,
// depthCompare: 'less',
// format: props.depthStencilFormat || 'depth24plus-stencil8',
// },
// colorStates: [
// {
// format: props.color0Format || 'bgra8unorm',
// }
// ],
export function applyParametersToRenderPipelineDescriptor(pipelineDescriptor, parameters: Parameters = {}): PipelineDescriptor {
pipelineDescriptor = {...DEFAULT_PIPELINE_DESCRIPTOR, ...pipelineDescriptor};
setParameters(pipelineDescriptor, parameters);
return pipelineDescriptor;
}
// Apply any supplied parameters
// Apply any supplied parameters
function setParameters(pipelineDescriptor: PipelineDescriptor, parameters: Parameters): void {
for (const key in parameters) {
const value = parameters[key];
@ -182,11 +212,11 @@ function setParameters(pipelineDescriptor: PipelineDescriptor, parameters: Param
}
}
// function addColorState(descriptor: PipelineDescriptor): void {
// descriptor.colorStates = descriptor.colorStates || [];
// // @ts-expect-error
// if (descriptor.colorStates.length === 0) {
// // @ts-expect-error
// descriptor.colorStates.push({});
// }
// }
function addColorState(descriptor: PipelineDescriptor): void {
descriptor.fragment.targets = descriptor.fragment.targets || [];
// @ts-expect-error
if (descriptor.fragment.targets.length === 0) {
// @ts-expect-error
descriptor.fragment.targets.push({});
}
}

View File

@ -0,0 +1,77 @@
// WEBGPU Buffer implementation
import {Buffer, BufferProps, assert} from '@luma.gl/api';
import type WebGPUDevice from '../webgpu-device';
function getByteLength(props: BufferProps): number {
return props.byteLength >= 0 ? props.byteLength : props.data.byteLength;
}
export default class WebGPUBuffer extends Buffer {
readonly device: WebGPUDevice;
readonly handle: GPUBuffer;
readonly byteLength: number;
constructor(device: WebGPUDevice, props: BufferProps) {
super(device, props);
this.device = device;
this.byteLength = getByteLength(props);
const mapBuffer = Boolean(props.data);
this.handle = this.props.handle || this.createHandle(mapBuffer);
this.handle.label = this.props.id;
if (props.data) {
this._writeMapped(props.data);
// this.handle.writeAsync({data: props.data, map: false, unmap: false});
}
if (mapBuffer && !props.mappedAtCreation) {
this.handle.unmap();
}
}
protected createHandle(mapBuffer: boolean): GPUBuffer {
return this.device.handle.createBuffer({
size: this.byteLength,
// usage defaults to vertex
usage: this.props.usage || (GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST),
mappedAtCreation: this.props.mappedAtCreation || mapBuffer
});
}
destroy(): void {
this.handle.destroy();
}
// WebGPU provides multiple ways to write a buffer...
write(data: ArrayBufferView, byteOffset = 0) {
this.device.handle.queue.writeBuffer(
this.handle,
byteOffset,
data.buffer,
data.byteOffset,
data.byteLength
);
}
_writeMapped<TypedArray>(typedArray: TypedArray): void {
const arrayBuffer = this.handle.getMappedRange();
// @ts-expect-error
new typedArray.constructor(arrayBuffer).set(typedArray);
}
// WEBGPU API
mapAsync(mode: number, offset: number = 0, size?: number): Promise<void> {
return this.handle.mapAsync(mode, offset, size);
}
getMappedRange(offset: number = 0, size?: number): ArrayBuffer {
return this.handle.getMappedRange(offset, size);
}
unmap(): void {
this.handle.unmap();
}
}

View File

@ -0,0 +1,51 @@
import WebGPUDevice from "../webgpu-device";
export type FramebufferProps = { // ResourceProps & {
width: number;
height: number;
attachments?: Record<string, any>;
readBuffer?: number;
drawBuffers?: number[];
check?: boolean;
};
export class WebGPUFramebuffer {
readonly device: WebGPUDevice;
renderPassDescriptor: GPURenderPassDescriptor;
constructor(device: WebGPUDevice, props: FramebufferProps) {
this.device = device;
const depthTexture = this.device.createTexture({
id: "depth-stencil",
width: props.width,
height: props.height,
depth: 1,
format: "depth24plus",
usage: GPUTextureUsage.RENDER_ATTACHMENT
});
const depthStencilAttachment = depthTexture.handle.createView();
depthStencilAttachment.label = 'depth-stencil-attachment';
const renderPassDescriptor: GPURenderPassDescriptor = {
colorAttachments: [{
// @ts-expect-error
attachment: undefined, // Assigned later
loadValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
}],
depthStencil: {
attachment: depthStencilAttachment,
depthLoadValue: 1.0,
depthStoreOp: "store",
// stencilLoadValue: 0,
// stencilStoreOp: "store",
}
};
}
}

View File

@ -1,12 +1,13 @@
/// <reference types="@webgpu/types" />
import {RenderPipeline, RenderPipelineProps, cast } from '@luma.gl/api';
import {RenderPipeline, RenderPipelineProps, cast, log} from '@luma.gl/api';
import WebGPUDevice from './webgpu-device';
import type WebGPUDevice from '../webgpu-device';
import {getRenderPipelineDescriptor} from './helpers/webgpu-parameters';
import {applyParametersToRenderPipelineDescriptor} from '../helpers/webgpu-parameters';
import {convertAttributesVertexBufferToLayout} from '../helpers/get-vertex-buffer-layout';
// import {mapAccessorToWebGPUFormat} from './helpers/accessor-to-format';
import {WebGPUShader} from '..';
import WebGPUShader from './webgpu-shader';
// import type {BufferAccessors} from './webgpu-pipeline';
// BIND GROUP LAYOUTS
@ -57,7 +58,8 @@ export default class WebGPURenderPipeline extends RenderPipeline {
const vertex: GPUVertexState = {
module: cast<WebGPUShader>(this.props.vertexShader).handle,
entryPoint: this.props.vertexShaderEntryPoint || 'main'
entryPoint: this.props.vertexShaderEntryPoint || 'main',
buffers: convertAttributesVertexBufferToLayout(this.props.attributeLayouts)
};
let fragment: GPUFragmentState | undefined;
@ -73,27 +75,19 @@ export default class WebGPURenderPipeline extends RenderPipeline {
};
}
const descriptor: GPURenderPipelineDescriptor = {
let descriptor: GPURenderPipelineDescriptor = {
vertex,
fragment,
primitive: {
topology: this.props.topology
}
// WebGPU spec seems updated
// primitive: {
// topology: this.props.topology
// },
};
// const ceDescriptor: GPUCommandEncoderDescriptor;
// const commandEncoder = this.device.handle.createCommandEncoder({
// // label
// // measureExecutionTime
// });
descriptor = applyParametersToRenderPipelineDescriptor(descriptor, this.props.parameters);
getRenderPipelineDescriptor(this.props.parameters, descriptor);
log.groupCollapsed(1, 'RenderPipeline.GPURenderPipelineDescriptor')();
log.log(1, JSON.stringify(descriptor, null, 2))();
log.groupEnd(1)();
const renderPipeline = this.device.handle.createRenderPipeline(descriptor);
return renderPipeline;

View File

@ -1,5 +1,5 @@
import {Sampler, SamplerProps} from '@luma.gl/api';
import type WebGPUDevice from './webgpu-device';
import type WebGPUDevice from '../webgpu-device';
export type WebGPUSamplerProps = SamplerProps & {
handle?: GPUSampler;

View File

@ -1,5 +1,5 @@
import {Shader, ShaderProps, CompilerMessage} from '@luma.gl/api';
import type WebGPUDevice from './webgpu-device';
import type WebGPUDevice from '../webgpu-device';
export type WebGPUShaderProps = ShaderProps & {
handle?: GPUShaderModule;
@ -14,7 +14,6 @@ export default class WebGPUShader extends Shader {
constructor(device: WebGPUDevice, props: WebGPUShaderProps) {
super(device, props);
this.device = device;
this.handle = this.props.handle || this.createHandle();

View File

@ -1,18 +1,10 @@
// Inspired by webgpu samples at https://github.com/austinEng/webgpu-samples/blob/master/src/glslang.ts
// under BSD 3-clause license
/// <reference types="@webgpu/types" />
import {Resource, Texture, TextureProps, Device, Sampler, SamplerProps} from '@luma.gl/api';
import type WebGPUDevice from './webgpu-device';
import {Texture, TextureProps, Sampler, SamplerProps, assert} from '@luma.gl/api';
import type WebGPUDevice from '../webgpu-device';
import WebGPUSampler from './webgpu-sampler';
// const DEFAULT_TEXTURE_PROPS: Required<TextureProps> = {
// handle: undefined,
// id: undefined,
// depth: 1,
// format: 'rgba8unorm',
// usage: GPUTextureUsage.COPY_DST
// };
export default class WebGPUTexture extends Texture {
readonly device: WebGPUDevice;
readonly handle: GPUTexture;
@ -28,26 +20,26 @@ export default class WebGPUTexture extends Texture {
// static createFromImage(img, usage = 0) {
// return new WebGPUTexture({width: img.width, height:img.height, usage}).setImage(image, usage);
// }
constructor(device: WebGPUDevice, props: TextureProps) {
super(device, props);
this.device = device;
this.handle = this.props.handle || this.createHandle();
this.sampler = null;
}
protected createHandle(): GPUTexture {
if (typeof this.props.format === 'number') {
throw new Error('number format');
}
return this.device.handle.createTexture({
size: {
width: this.props.width,
height: this.props.height,
depthOrArrayLayers: this.props.depth
},
// @ts-expect-error
format: this.props.format,
// @ts-expect-error
usage: GPUTextureUsage.COPY_DST | this.props.usage
usage: this.props.usage
});
}

View File

@ -1,47 +0,0 @@
// WEBGPU Buffer implementation
import {Buffer, BufferProps} from '@luma.gl/api';
import WebGPUDevice from './webgpu-device';
export default class WebGPUBuffer extends Buffer {
readonly device: WebGPUDevice;
readonly handle: GPUBuffer;
constructor(device: WebGPUDevice, props: BufferProps) {
super(device, props);
this.handle = this.props.handle || this.createHandle();
this.handle.label = this.props.id;
if (props.data) {
// this.handle.writeAsync({data: props.data, map: false, unmap: false});
}
if (!props.mappedAtCreation) {
this.handle.unmap();
}
}
protected createHandle(): GPUBuffer {
return this.device.handle.createBuffer({
size: this.props.byteLength,
usage: this.props.usage || (GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC),
mappedAtCreation: this.props.mappedAtCreation
});
}
destroy(): void {
this.handle.destroy();
}
mapAsync(mode: number, offset: number = 0, size?: number): Promise<void> {
return this.handle.mapAsync(mode, offset, size);
}
getMappedRange(offset: number = 0, size?: number): ArrayBuffer {
return this.handle.getMappedRange(offset, size);
}
unmap(): void {
this.handle.unmap();
}
}

View File

@ -9,7 +9,8 @@ import {
TextureProps,
RenderPipelineProps,
// ComputePipelineProps,
assert
assert,
log
} from '@luma.gl/api';
// import type {
// CopyBufferToBufferOptions,
@ -17,11 +18,11 @@ import {
// CopyTextureToBufferOptions,
// CopyTextureToTextureOptions
// } from '@luma.gl/api';
import WebGPUBuffer from './webgpu-buffer';
import WebGPUTexture from './webgpu-texture';
import WebGPUSampler from './webgpu-sampler';
import WebGPUShader from './webgpu-shader';
import WebGPURenderPipeline from './webgpu-render-pipeline';
import WebGPUBuffer from './resources/webgpu-buffer';
import WebGPUTexture from './resources/webgpu-texture';
import WebGPUSampler from './resources/webgpu-sampler';
import WebGPUShader from './resources/webgpu-shader';
import WebGPURenderPipeline from './resources/webgpu-render-pipeline';
// import WebGPUComputePipeline from './webgpu-compute-pipeline';
// import {loadGlslangModule} from '../glsl/glslang';
@ -41,6 +42,7 @@ export default class WebGPUDevice extends Device {
readonly presentationFormat: GPUTextureFormat;
presentationSize = [1, 1];
private _renderPassDescriptor: GPURenderPassDescriptor;
private _info: DeviceInfo;
static isSupported(): boolean {
return true;
@ -48,13 +50,19 @@ export default class WebGPUDevice extends Device {
static async create(props) {
if (!navigator.gpu) {
throw new Error('WebGPU not available');
throw new Error('WebGPU not available. Use Chrome Canary and turn on chrome://flags/#enable-unsafe-webgpu');
}
log.groupCollapsed(1, 'Creating device')();
const adapter = await navigator.gpu.requestAdapter({
powerPreference: "high-performance"
});
const device = await adapter.requestDevice();
return new WebGPUDevice(device, adapter, props);
log.log(1, "Adapter available")();
const gpuDevice = await adapter.requestDevice();
log.log(1, "GPUDevice available")();
const device = new WebGPUDevice(gpuDevice, adapter, props);
log.log(1, "Device created", device.info)();
log.groupEnd(1)();
return device;
}
constructor(device: GPUDevice, adapter: GPUAdapter, props: DeviceProps) {
@ -62,6 +70,21 @@ export default class WebGPUDevice extends Device {
this.handle = device;
this.adapter = adapter;
this._info = {
type: 'webgpu',
vendor: this.adapter.name,
renderer: '',
version: '',
gpuVendor: 'UNKNOWN', // 'NVIDIA' | 'AMD' | 'INTEL' | 'APPLE' | 'UNKNOWN',
shadingLanguages: ['glsl', 'wgsl'],
shadingLanguageVersions: {
glsl: '450',
wgsl: '100'
},
vendorMasked: '',
rendererMasked: ''
};
// Configure swap chain
assert(props.canvas);
this.context = props.canvas.getContext('webgpu') as GPUCanvasContext;
@ -78,7 +101,6 @@ export default class WebGPUDevice extends Device {
format: this.presentationFormat,
size: this.presentationSize,
});
this._initializeRenderPassDescriptor(props.canvas)
}
destroy() {
@ -86,20 +108,7 @@ export default class WebGPUDevice extends Device {
}
get info(): DeviceInfo {
return {
type: 'webgpu',
vendor: '',
renderer: '',
version: '',
gpuVendor: 'UNKNOWN', // 'NVIDIA' | 'AMD' | 'INTEL' | 'APPLE' | 'UNKNOWN',
shadingLanguages: ['glsl', 'wgsl'],
shadingLanguageVersions: {
glsl: '450',
wgsl: '100'
},
vendorMasked: '',
rendererMasked: ''
};
return this._info;
}
get features(): Set<Feature> {
@ -136,18 +145,7 @@ export default class WebGPUDevice extends Device {
beginRenderPass(): GPURenderPassEncoder {
if (!this.renderPass) {
this.commandEncoder = this.handle.createCommandEncoder();
const textureView = this.context.getCurrentTexture().createView();
const renderPassDescriptor: GPURenderPassDescriptor = {
colorAttachments: [
{
view: textureView,
loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
storeOp: 'store',
},
],
};
const renderPassDescriptor = this._updateRenderPassDescriptor();
this.renderPass = this.commandEncoder.beginRenderPass(renderPassDescriptor);
}
return this.renderPass;
@ -165,25 +163,27 @@ export default class WebGPUDevice extends Device {
}
// TODO: Possible to support multiple canvases with one device?
_initializeRenderPassDescriptor(canvas) {
_initializeRenderPassDescriptor() {
const depthTexture = this.createTexture({
width: canvas.width,
height: canvas.height,
width: this.presentationSize[0],
height: this.presentationSize[1],
depth: 1,
// @ts-expect-error
format: "depth24plus-stencil8",
format: "depth24plus",
usage: GPUTextureUsage.RENDER_ATTACHMENT
});
const renderPassDescriptor: GPURenderPassDescriptor = {
const depthStencilAttachment = depthTexture.handle.createView();
depthStencilAttachment.label = 'depth-stencil-attachment';
this._renderPassDescriptor = {
colorAttachments: [{
// @ts-expect-error
attachment: undefined, // Assigned later
view: undefined, // Assigned later
loadValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
storeOp: 'store'
}],
depthStencil: {
attachment: depthTexture.handle.createView(),
depthStencilAttachment: {
view: depthStencilAttachment,
depthLoadValue: 1.0,
depthStoreOp: "store",
stencilLoadValue: 0,
@ -191,7 +191,18 @@ export default class WebGPUDevice extends Device {
}
};
this._renderPassDescriptor = renderPassDescriptor;
log.groupCollapsed(1, 'Device.GPURenderPassDescriptor')();
log.log(1, JSON.stringify(this._renderPassDescriptor, null, 2))();
log.groupEnd(1)();
}
_updateRenderPassDescriptor() {
if (!this._renderPassDescriptor) {
this._initializeRenderPassDescriptor();
}
const textureView = this.context.getCurrentTexture().createView();
this._renderPassDescriptor.colorAttachments[0].view = textureView;
return this._renderPassDescriptor;
}
}

View File

@ -1,81 +1,112 @@
import {Shader, RenderPipeline, PrimitiveTopology, assert} from '@luma.gl/api';
import {
Buffer,
Shader,
RenderPipeline,
RenderPipelineParameters,
PrimitiveTopology,
assert,
AttributeBinding,
Binding
} from '@luma.gl/api';
import {cast} from '@luma.gl/api';
import {VertexShader} from '@luma.gl/webgl/';
import {WebGPUShader} from '..';
import WebGPUDevice from '../adapter/webgpu-device';
import WebGPURenderPipeline from '../adapter/webgpu-render-pipeline';
// import glslangModule from '../glslang';
import WebGPUBuffer from '../adapter/resources/webgpu-buffer';
import WebGPURenderPipeline from '../adapter/resources/webgpu-render-pipeline';
import {makeBindGroup} from '../adapter/helpers/make-bind-group';
export type ModelProps = {
id?: string;
pipeline?: RenderPipeline;
vertex?: string | Shader;
fragment?: string | Shader;
topology: PrimitiveTopology;
vertexCount: number;
instanceCount?: number;
// backwards compatibility
vs?: string | Shader;
fs?: string | Shader;
}
topology: PrimitiveTopology;
attributeLayouts?: AttributeBinding[];
vertexCount: number;
instanceCount?: number;
parameters?: RenderPipelineParameters;
attributeBuffers?: Buffer[]; // | Record<string, Buffer>
bindings?: Binding[];
};
const DEFAULT_MODEL_PROPS: Required<ModelProps> = {
id: 'unnamed',
vertex: undefined,
fragment: undefined,
pipeline: undefined,
topology: 'triangle-list',
vertexCount: 0,
instanceCount: 1,
vs: undefined,
fs: undefined,
pipeline: undefined,
topology: 'triangle-list',
attributeLayouts: [],
vertexCount: 0,
instanceCount: 1,
parameters: {},
attributeBuffers: [],
bindings: []
};
export default class Model {
device: WebGPUDevice;
pipeline: WebGPURenderPipeline;
vertex: WebGPUShader;
fragment: WebGPUShader | undefined;
vs: WebGPUShader;
fs: WebGPUShader | undefined;
props: Required<ModelProps>;
_bindGroup: GPUBindGroup;
constructor(device: WebGPUDevice, props: ModelProps) {
this.props = {...DEFAULT_MODEL_PROPS, ...props};
props = this.props;
this.device = device;
// Create the pipeline
if (props.pipeline) {
this.pipeline = cast<WebGPURenderPipeline>(props.pipeline);
} else {
const vertex = props.vertex || props.vs;
const fragment = props.fragment || props.fs;
const vertex = props.vs;
const fragment = props.fs;
assert(vertex);
this.vertex = (typeof vertex === 'string')
? new WebGPUShader(device, {stage: 'vertex', source: vertex})
: cast<WebGPUShader>(vertex);
if (fragment) {
this.fragment = (typeof fragment === 'string')
? new WebGPUShader(device, {stage: 'fragment', source: fragment})
: cast<WebGPUShader>(fragment);
}
this.vs =
typeof vertex === 'string'
? new WebGPUShader(device, {stage: 'vertex', source: vertex})
: cast<WebGPUShader>(vertex);
if (fragment) {
this.fs =
typeof fragment === 'string'
? new WebGPUShader(device, {stage: 'fragment', source: fragment})
: cast<WebGPUShader>(fragment);
}
this.pipeline = device.createRenderPipeline({
vertexShader: this.vertex,
fragmentShader: this.fragment,
topology: props.topology
vertexShader: this.vs,
fragmentShader: this.fs,
topology: props.topology,
parameters: props.parameters,
// Geometry in the vertex shader!
attributeLayouts: props.attributeLayouts
});
}
// Set up the bindings
this._bindGroup = makeBindGroup(this.device.handle, this.pipeline._getBindGroupLayout(), this.props.bindings);
}
draw(renderPass?: GPURenderPassEncoder) {
renderPass = renderPass || this.device.getActiveRenderPass();
renderPass.setPipeline(this.pipeline.handle);
// renderPass.setBindGroup(0, this.uniformBindGroup);
// renderPass.setVertexBuffer(0, this.verticesBuffer);
// Set up attributes
for (let i = 0; i < this.props.attributeBuffers.length; ++i) {
const buffer = cast<WebGPUBuffer>(this.props.attributeBuffers[i]);
const location = this.props.attributeLayouts[i].location;
renderPass.setVertexBuffer(location, buffer.handle);
}
// Set up bindings (uniform buffers, textures etc)
renderPass.setBindGroup(0, this._bindGroup);
renderPass.draw(this.props.vertexCount, this.props.instanceCount, 0, 0); // firstVertex, firstInstance);
}
@ -225,11 +256,8 @@ export default class Model {
});
}
*/
}
/*
private:
void _initializeVertexState(utils::ComboVertexStateDescriptor* descriptor,
@ -444,7 +472,7 @@ export default class Model {
this.userData = {};
this.needsRedraw = true;
// Attributes and buffers
// Attributes and attributeBuffers
// Model manages auto Buffer creation from typed arrays
this._attributes = {}; // All attributes
this.attributes = {}; // User defined attributes
@ -467,7 +495,7 @@ export default class Model {
this.drawMode = props.drawMode !== undefined ? props.drawMode : GL.TRIANGLES;
this.vertexCount = props.vertexCount || 0;
// Track buffers created by setGeometry
// Track attributeBuffers created by setGeometry
this.geometryBuffers = {};
// geometry might have set drawMode and vertexCount

View File

@ -1,12 +1,17 @@
// Initialize any global state
import '@luma.gl/api';
import './init'
// WEBGPU ADAPTER
export {default as WebGPUDevice} from './adapter/webgpu-device';
// WEBGPU CLASSES
export {default as WebGPUBuffer} from './adapter/webgpu-buffer';
export {default as WebGPUTexture} from './adapter/webgpu-texture';
export {default as WebGPUSampler} from './adapter/webgpu-sampler';
export {default as WebGPUShader} from './adapter/webgpu-shader';
// WEBGPU CLASSES (typically not accessed directly)
export {default as WebGPUBuffer} from './adapter/resources/webgpu-buffer';
export {default as WebGPUTexture} from './adapter/resources/webgpu-texture';
export {default as WebGPUSampler} from './adapter/resources/webgpu-sampler';
export {default as WebGPUShader} from './adapter/resources/webgpu-shader';
// WEBGPU ENGINE CLASSES
// WEBGPU ENGINE CLASSES (until we can make the engine module truly platform independent)
export type {ModelProps} from './engine/webgpu-model';
export {default as Model} from './engine/webgpu-model';

View File

@ -0,0 +1,4 @@
import {luma} from '@luma.gl/api';
import WebGPUDevice from './adapter/webgpu-device';
luma.registerDevices([WebGPUDevice]);

View File

@ -102,7 +102,7 @@ const config = {
externals: getExternals(PACKAGE_INFO),
plugins: [
// This is used to define the __VERSION__ constant in core/lib/init.js
// This is used to define the __VERSION__ constant in api/src/init.js
// babel-plugin-version-inline uses the package version from the working directory
// Therefore we need to manually import the correct version from the core
// This is called in prepublishOnly, after lerna bumps the package versions