mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
feat(webgpu): Add rotating cube example (#1557)
This commit is contained in:
parent
d3051a379f
commit
58dfd02c1e
@ -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() {
|
||||
|
||||
@ -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": {
|
||||
|
||||
166
examples/webgpu/rotating-cube/app.ts
Normal file
166
examples/webgpu/rotating-cube/app.ts
Normal 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;
|
||||
}
|
||||
*/
|
||||
142
examples/webgpu/rotating-cube/cube.ts
Normal file
142
examples/webgpu/rotating-cube/cube.ts
Normal 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,
|
||||
]);
|
||||
7
examples/webgpu/rotating-cube/index.html
Normal file
7
examples/webgpu/rotating-cube/index.html
Normal file
@ -0,0 +1,7 @@
|
||||
<!doctype html>
|
||||
<script type="module">
|
||||
import './app.ts';
|
||||
</script>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
20
examples/webgpu/rotating-cube/package.json
Normal file
20
examples/webgpu/rotating-cube/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
18
examples/webgpu/rotating-cube/vite.config.ts
Normal file
18
examples/webgpu/rotating-cube/vite.config.ts
Normal 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
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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';
|
||||
@ -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';
|
||||
@ -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
|
||||
@ -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;
|
||||
};
|
||||
@ -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[];
|
||||
}
|
||||
};
|
||||
@ -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',
|
||||
173
modules/api/src/adapter/types/types.ts
Normal file
173
modules/api/src/adapter/types/types.ts
Normal 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;
|
||||
};
|
||||
@ -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
35
modules/api/src/init.ts
Normal 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;
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
@ -4,6 +4,7 @@ import Texture, {TextureProps, TextureSupportOptions} from './texture';
|
||||
|
||||
|
||||
export type Texture2DProps = TextureProps & {
|
||||
format?: number;
|
||||
};
|
||||
|
||||
export default class Texture2D extends Texture {
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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]);
|
||||
|
||||
101
modules/webgpu/src/adapter/helpers/accessor-to-format.ts
Normal file
101
modules/webgpu/src/adapter/helpers/accessor-to-format.ts
Normal 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');
|
||||
}
|
||||
*/
|
||||
107
modules/webgpu/src/adapter/helpers/get-vertex-buffer-layout.ts
Normal file
107
modules/webgpu/src/adapter/helpers/get-vertex-buffer-layout.ts
Normal 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;
|
||||
}
|
||||
*/
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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({});
|
||||
}
|
||||
}
|
||||
|
||||
77
modules/webgpu/src/adapter/resources/webgpu-buffer.ts
Normal file
77
modules/webgpu/src/adapter/resources/webgpu-buffer.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
51
modules/webgpu/src/adapter/resources/webgpu-framebuffer.ts
Normal file
51
modules/webgpu/src/adapter/resources/webgpu-framebuffer.ts
Normal 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",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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();
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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';
|
||||
|
||||
4
modules/webgpu/src/init.ts
Normal file
4
modules/webgpu/src/init.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import {luma} from '@luma.gl/api';
|
||||
import WebGPUDevice from './adapter/webgpu-device';
|
||||
|
||||
luma.registerDevices([WebGPUDevice]);
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user