webgpu-simplified
Version:
A collection of helper functions to simplify the process of building WebGPU applications.
816 lines (815 loc) • 40 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStats = exports.getDatGui = exports.hex2rgb = exports.createCanvasTexture = exports.createImageTexture = exports.getCamera = exports.createNormalMat = exports.combineVpMat = exports.combineMvpMat = exports.createProjectionMat = exports.createViewTransform = exports.createModelMat = exports.createDepthTexture = exports.createMultiSampleTexture = exports.readBufferData = exports.createBindGroup = exports.createBufferWithData = exports.createBuffer = exports.updateVertexBuffers = exports.BufferType = exports.createRenderPassDescriptor = exports.setVertexBuffers = exports.createComputePipelineDescriptor = exports.createRenderPipelineDescriptor = exports.checkWebGPUSupport = exports.initWebGPU = void 0;
var gl_matrix_1 = require("gl-matrix");
var camera = require('3d-view-controls');
var Stats = require("stats.js");
var dat_gui_1 = require("dat.gui");
/**
* This function is used to initialize the WebGPU apps. It returns the IWebGPUInit interface.
*
* @param input - The input argument of the `IWebGPUInitInput` interface type with default members:
*
* `input.format = navigator.gpu.getPreferredCanvasFormat()`
*
* `input.msaa.Count = 1`
* @param deviceDescriptor - Describes a device request. `{}` means the default setting is used
*/
var initWebGPU = function (input, deviceDescriptor) {
if (deviceDescriptor === void 0) { deviceDescriptor = {}; }
return __awaiter(void 0, void 0, void 0, function () {
var adapter, device, context, pixelRatio, size, background;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// set default parameters
input.format = input.format === undefined ? navigator.gpu.getPreferredCanvasFormat() : input.format;
input.msaaCount = input.msaaCount === undefined ? 1 : input.msaaCount;
if (exports.checkWebGPUSupport.includes('does not support WebGPU')) {
throw (exports.checkWebGPUSupport);
}
return [4 /*yield*/, navigator.gpu.requestAdapter()];
case 1:
adapter = _a.sent();
return [4 /*yield*/, adapter.requestDevice(deviceDescriptor)];
case 2:
device = _a.sent();
context = input.canvas.getContext('webgpu');
pixelRatio = window.devicePixelRatio || 1;
input.canvas.width = input.canvas.clientWidth * pixelRatio;
input.canvas.height = input.canvas.clientHeight * pixelRatio;
size = { width: input.canvas.width, height: input.canvas.height };
context.configure({
device: device,
format: input.format,
alphaMode: 'opaque',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
});
background = { r: 0.009, g: 0.0125, b: 0.0164, a: 1.0 };
return [2 /*return*/, { device: device, context: context, format: input.format, size: size, background: background, msaaCount: input.msaaCount }];
}
});
});
};
exports.initWebGPU = initWebGPU;
/** A string variable used to check whether your browser supports WebGPU or not.*/
exports.checkWebGPUSupport = navigator.gpu ? 'Great, your current browser supports WebGPU!' :
"Your current browser does not support WebGPU! Make sure you are on a system \n with WebGPU enabled.";
/**
* This function creates the render pipeline descriptor that will be used to create a render pipeline.
*
* @param input - The `input` argument is a type of the `IRenderPipelineInput` interface with the following default values:
* `input.primitiveType`: `'triangle-list'`, `input.cullMode`: `'none'`, `input.isDepthStencil`: `true`,
* `input.vsEntry`: `'vs_main'`, `input.fsEntry`: `'fs_main'`. If `input.shader` is specified, then
* `input.vsShader = input.shader` and `input.fsShader = input.shader`
*
* @param withFragment - Indicates whether the GPU fragment state should be included or not. Default value is `true`.
* If it is set to `false`, the render pipeline will not produce any color attachment outputs. For example, we do not
* need any color output when rendering shadows, so we can set this parameter to `false` in this case
* @returns The render pipeline descriptor.
*/
var createRenderPipelineDescriptor = function (input, withFragment) {
if (withFragment === void 0) { withFragment = true; }
input.primitiveType = input.primitiveType === undefined ? 'triangle-list' : input.primitiveType;
input.cullMode = input.cullMode === undefined ? 'none' : input.cullMode;
input.isDepthStencil = input.isDepthStencil === undefined ? true : input.isDepthStencil;
input.vsEntry = input.vsEntry === undefined ? 'vs_main' : input.vsEntry;
input.fsEntry = input.fsEntry === undefined ? 'fs_main' : input.fsEntry;
if (input.shader) {
input.vsShader = input.shader;
input.fsShader = input.shader;
}
input.indexFormat = input.indexFormat === undefined ? 'uint32' : input.indexFormat;
var indexFormat = undefined;
if (input.primitiveType.includes('strip')) {
indexFormat = input.indexFormat;
}
var descriptor = {
layout: 'auto',
vertex: {
module: input.init.device.createShaderModule({
code: input.vsShader,
}),
entryPoint: input.vsEntry,
buffers: input.buffers,
},
fragment: withFragment ? {
module: input.init.device.createShaderModule({
code: input.fsShader,
}),
entryPoint: input.fsEntry,
targets: [
{
format: input.init.format
}
],
} : undefined,
primitive: {
topology: input.primitiveType,
stripIndexFormat: indexFormat,
cullMode: input.cullMode,
},
multisample: {
count: input.init.msaaCount,
}
};
if (input.isDepthStencil) {
descriptor.depthStencil = {
format: "depth24plus",
depthWriteEnabled: true,
depthCompare: "less"
};
}
return descriptor;
};
exports.createRenderPipelineDescriptor = createRenderPipelineDescriptor;
/**
* This function create a compute pipeline descriptor that will be used to create a compute pipeline.
* @param device GPU Device
* @param csShader the WGSL compute shader
* @param entry the entry point for teh compute shader
* @returns the compute pipeline descriptor.
*/
var createComputePipelineDescriptor = function (device, csShader, entry) {
if (entry === void 0) { entry = 'cs_main'; }
return {
layout: 'auto',
compute: {
module: device.createShaderModule({
code: csShader,
}),
entryPoint: 'cs_main',
}
};
};
exports.createComputePipelineDescriptor = createComputePipelineDescriptor;
/**
* This function sets the `buffers` attribute of the vertex state in a render pipeline. In this function, the input argument
* `formats` is a GPU vertex-format array. It can be specified as `'float32'`, `'float32x2'`,
* `'float32x3'`, `'float32x4'`, etc., which correspond to the WGSL style in the shader `f32`, `vec2<f32>`,
* `vec3<f32>`, `vec4<f32>`, etc. If the vertex data is stored in a separate buffer for each attribute such as position,
* normal, and UV, you can simply provide only this input argument like `['float32x3', 'float32x3', 'float32x2']` and
* ignore all the other optional arguments. In this case, the `setVertexBuffers` function will automatically
* calculate the `offset`, `arrayStride`, and `shaderLocation` for each vertex attribute. Note that the `shaderLocation`
* is set with an array filled with consecutive numbers like [0, 1, 2], which must match the `@location` attribute specified
* in the vertex shader. Otherwise, you need to manually specify the `shaderLocations` array argument.
*
* On the other hand, if you store the vertex data in a single buffer for all attributes (e.g., position, normal, and uv), you
* will need to provide not only the vertex `formats` array, but also the `offsets` array. Here is an example
* of a single buffer that stores the `position` (`vec3<f32>`), `normal` (`vec3<f32>`), and `uv` (`vec2<f32>`) data.
* The corresponding `arrayStride` will be 12, 12, and 8, and the `offsets` array will be [0, 12, 24].
* In this case, you can set the `buffers` attribute by calling the function like this:
*
* `const bufs = setVertexBuffers(['float32x3', 'float32x3', 'float32x2'], [0, 12, 24]);`
*
* The above example assumes that all the vertex attributes (position, normal, and uv) stored in a
* single buffer are used in the pipeline and vertex shader. What happens if not all the attributes in the buffer are needed.
* For example, the pipeline and shader only need the `position` and `uv` data, but not the `normal` data. In this case,
* in addition to the `formats` and `offsets` arguments, you will also need to specify the `totalArrayStride`
* argument. The `arrayStride` for `position`, `normal`, and `uv` is 12, 12, and 8, respectively, so the
* `totalArrayStride` = 12 + 12 + 8 = 32. Thus, we can create the `buffers` attribute using the following code
*
* `const bufs = setVertexBuffers(['float32x3', 'float32x2'], [0, 24], 32);`
*
* Note that the `offsets` array is set to [0, 24] rather than [0, 12], because the `uv` data starts after `position` and
* `normal` data, while the `normal` data is still stored in the buffer even though it is not used in this example.
*
* @param formats GPU vertex format array with each element specifying the `GPUVertexFormat` of teh attribute.
* @param offsets The offset array that is optional. The offset, in bytes, is counted from the beginning of the element to the data
* for the attribute. Note that the offset must be a multiple of the minimum of 4 and sizeof the `attrib.format`.
* @param totalArrayStride The stride, in bytes, between elements of the array. This is an optional argument.
* @param shaderLocations The numeric location associated with the attribute, such as position, normal, or uv, which will
* correspond with a `@location` attribute declared in the vertex shader. This is an optional argument.
* @returns An array of GPU vertex buffer layout.
*/
var setVertexBuffers = function (formats, offsets, totalArrayStride, shaderLocations) {
if (offsets === void 0) { offsets = []; }
if (totalArrayStride === void 0) { totalArrayStride = 0; }
if (shaderLocations === void 0) { shaderLocations = []; }
var len = formats.length;
var len1 = offsets.length;
var len2 = shaderLocations.length;
var buffers = [];
if (len1 === 0) {
for (var i = 0; i < len; i++) {
var stride = 4 * parseInt(formats[i].split('x')[1]);
var loc = len2 === 0 ? i : shaderLocations[i];
buffers.push({
arrayStride: stride,
attributes: [{
shaderLocation: loc,
format: formats[i],
offset: 0,
}]
});
}
}
else {
var attributes = [];
var strides = 0;
for (var i = 0; i < len1; i++) {
strides += 4 * parseInt(formats[i].split('x')[1]);
var loc = len2 === 0 ? i : shaderLocations[i];
attributes.push({
shaderLocation: loc,
format: formats[i],
offset: offsets[i],
});
}
if (totalArrayStride > 0) {
strides = totalArrayStride;
}
buffers = [{
arrayStride: totalArrayStride,
attributes: attributes
}];
}
return buffers;
};
exports.setVertexBuffers = setVertexBuffers;
/**
* This function creates the render pass descriptor that will be used to create a render pass with various options.
* The returned desciptor will include a depth-stencil attachment if the depth texture view is provided via the input
* interface `IRenderPassInput`. Otherwise, the depth stencil attachment will not be defined. The argument
* `withColorAttachment` indicates whether the descriptor should contain color attachments or not. In addition, you can
* specify the MSAA count parameter.
* @param input The type of interface `IRenderPassInput`
* @param withColorAttachment Indicates whether the descriptor should contain color attachments or not
*/
var createRenderPassDescriptor = function (input, withColorAttachment) {
if (withColorAttachment === void 0) { withColorAttachment = true; }
var colorAttachmentView = input.init.msaaCount > 1 ? input.textureView :
input.init.context.getCurrentTexture().createView();
var colorAttachmentResolveTarget = input.init.msaaCount > 1 ?
input.init.context.getCurrentTexture().createView() : undefined;
var descriptor = {
colorAttachments: withColorAttachment ? [{
view: colorAttachmentView,
resolveTarget: colorAttachmentResolveTarget,
clearValue: input.init.background,
loadOp: 'clear',
storeOp: 'store'
}] : [],
depthStencilAttachment: input.depthView ? {
view: input.depthView,
depthClearValue: 1.0,
depthLoadOp: "clear",
depthStoreOp: "store",
} : undefined,
};
return descriptor;
};
exports.createRenderPassDescriptor = createRenderPassDescriptor;
// #endregion Render pass descriptor **********************************************************
// #region Create, Update GPU Buffers and Bind Group ******************************************
/** The enumeration for specifying the type of a GPU buffer. */
var BufferType;
(function (BufferType) {
/** Uniform buffer */
BufferType[BufferType["Uniform"] = 0] = "Uniform";
/** Vertex buffer */
BufferType[BufferType["Vertex"] = 1] = "Vertex";
/** Index buffer */
BufferType[BufferType["Index"] = 2] = "Index";
/** Storage buffer */
BufferType[BufferType["Storage"] = 3] = "Storage";
/** vertex-Storage buffer */
BufferType[BufferType["VertexStorage"] = 4] = "VertexStorage";
/** Index-Storage buffer */
BufferType[BufferType["IndexStorage"] = 5] = "IndexStorage";
/** Indirect buffer */
BufferType[BufferType["Indirect"] = 6] = "Indirect";
/** Indirect-Storage buffer */
BufferType[BufferType["IndirectStorage"] = 7] = "IndirectStorage";
/** Read buffer */
BufferType[BufferType["Read"] = 8] = "Read";
/** Write buffer */
BufferType[BufferType["Write"] = 9] = "Write";
})(BufferType || (exports.BufferType = BufferType = {}));
/**
* This function updates the vertex buffers when the vertex data is changed by varying some parameters by the user.
* Let's take the UV sphere as an example. A UV sphere can have three parameters: radius, u-segments, and v-segments.
* Varying the radius parameter only changes the data values but not the buffer size, while warying the u- (or v-)
* segments parameter will change both the data values and buffer size. In the former case, we can write the
* new data directly into the original buffers; while in the latter case, we have to destroy the original buffers and recreate
* the new buffers with new buffer size, and then write the new data into the newly created buffers. Here, we check whether the buffer
* size is changed or not by comparing the length of the original data (called `origNumVertices`) with that of the new data.
* @param device GPU device
* @param p Interface of `IPipeline`
* @param data An array of vertex data. Note that this array should include the `index` data. For example, if a render pipeline
* has `position`, `normal`, and `uv`, then this `data` array should defined by
*
* `const data = [dat.positions, dat.normals, dat.uvs, dat.indices];`
*
* Of course, for this data array, you also need to define corresponding vertex buffer array in the render pipeline:
*
* `p.vertexBuffers = [positonBuffer, normalBuffer, uvBuffer, indexBuffer];`
*
* If the data is generated in such a way that the vertex data contains all attributes (`position`, `normal`, `uv` ) and it is
* stored in a single buffer, we can specify the `data` array using the code:
*
* `const data = [dat.vertices, dat.indices];`
*
* and corresponding vertex buffer array:
*
* `p.vertexBuffers = [vertexBuffer, indexBuffer];`
*
* @param origNumVertices The data length of the first element in the original `data` array.
*/
var updateVertexBuffers = function (device, p, data, origNumVertices) {
var len = p.vertexBuffers.length;
if (data[0].length === origNumVertices) {
for (var i = 0; i < len; i++) {
device.queue.writeBuffer(p.vertexBuffers[i], 0, data[i]);
}
}
else {
for (var i = 0; i < len; i++) {
p.vertexBuffers[i].destroy();
}
for (var i = 0; i < len; i++) {
p.vertexBuffers[i] = (0, exports.createBufferWithData)(device, data[i]);
}
}
};
exports.updateVertexBuffers = updateVertexBuffers;
/**
* This function can be used to create vertex, uniform, or storage GPU buffer. The default is a uniform buffer.
* @param device GPU device
* @param bufferSize Buffer size.
* @param bufferType Of the `BufferType` enum.
*/
var createBuffer = function (device, bufferSize, bufferType) {
if (bufferType === void 0) { bufferType = BufferType.Uniform; }
var flag = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
if (bufferType === BufferType.Vertex) {
flag = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
else if (bufferType === BufferType.Index) {
flag = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
else if (bufferType === BufferType.Storage) {
flag = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
else if (bufferType === BufferType.VertexStorage) {
flag = GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.IndexStorage) {
flag = GPUBufferUsage.INDEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.Indirect) {
flag = GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.IndirectStorage) {
flag = GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.Read) {
flag = GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.Write) {
flag = GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
return device.createBuffer({
size: bufferSize,
usage: flag,
});
};
exports.createBuffer = createBuffer;
/**
* This function returns the data type of the input data.
* @param data Can be any data type, such as Float32Array, Float64Array, Uint16Array, Uint32Array, etc.
*/
var getDataType = function (data) { return Object.prototype.toString.call(data).split(/\W/)[2]; };
/**
* This function creats a GPU buffer with data to initialize it. If the input data is a type of `Float32Array`
* or `Float64Array`, it returns a vertex, uniform, or storage buffer specified by the enum `bufferType`. Otherwise,
* if the input data has a `Uint16Array` or `Uint32Array`, this function will return an index buffer.
* @param device GPU device
* @param data Input data that should be one of four data types: `Float32Array`, `Float64Array`, `Uint16Array`, and
* `Uint32Array`
* @param bufferType Type of enum `BufferType`. It is used to specify the type of the returned buffer. The default is
* vertex buffer
*/
var createBufferWithData = function (device, data, bufferType) {
if (bufferType === void 0) { bufferType = BufferType.Vertex; }
var flag = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
if (bufferType === BufferType.Uniform) {
flag = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
else if (bufferType === BufferType.Storage) {
flag = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
else if (bufferType === BufferType.Index) {
flag = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
else if (bufferType === BufferType.VertexStorage) {
flag = GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.IndexStorage) {
flag = GPUBufferUsage.INDEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.Indirect) {
flag = GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.IndirectStorage) {
flag = GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.Read) {
flag = GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST;
}
else if (bufferType === BufferType.Write) {
flag = GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST;
}
var dtype = getDataType(data);
if (bufferType === BufferType.Vertex && dtype.includes('Uint')) {
flag = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
}
var buffer = device.createBuffer({
size: data.byteLength,
usage: flag,
mappedAtCreation: true
});
if (dtype.includes('Uint32')) {
new Uint32Array(buffer.getMappedRange()).set(data);
}
else if (dtype.includes('Uint16')) {
new Uint16Array(buffer.getMappedRange()).set(data);
}
else if (dtype.includes('Float64')) {
new Float64Array(buffer.getMappedRange()).set(data);
}
else {
new Float32Array(buffer.getMappedRange()).set(data);
}
buffer.unmap();
return buffer;
};
exports.createBufferWithData = createBufferWithData;
/**
* This function is used to create a GPU bind group that defines a set of resources to be bound together in a
* group and how the resources are used in shader stages. It accepts GPU device, GPU bind group layout, uniform
* buffer array, and the other GPU binding resource array as its input arguments. If both the buffer and other
* resource arrays have none zero elements, you need to place the buffer array ahead of the other resource array.
* Make sure that the order of buffers and other resources is consistent with the `@group @binding` attributes
* defined in the shader code.
* @param device GPU device
* @param layout GPU bind group layout that defines the interface between a set of resources bound in a GPU bind
* group and their accessibility in shader stages.
* @param buffers The uniform buffer array
* @param otherResources The other resource array, which can include `GPUSampler`, `GPUTextureView`,
* `GPUExternalTexture`, etc.
*/
var createBindGroup = function (device, layout, buffers, otherResources) {
if (buffers === void 0) { buffers = []; }
if (otherResources === void 0) { otherResources = []; }
var entries = [];
var bufLen = buffers.length;
var resLen = otherResources.length;
var len = bufLen + resLen;
for (var i = 0; i < len; i++) {
if (i < bufLen && bufLen > 0) {
entries.push({
binding: i,
resource: {
buffer: buffers[i],
}
});
}
else if (i >= bufLen && resLen > 0) {
entries.push({
binding: i,
resource: otherResources[i - bufLen],
});
}
}
return device.createBindGroup({
layout: layout,
entries: entries,
});
};
exports.createBindGroup = createBindGroup;
/**
* This function is used to read data from a GPU buffer. It can be used for debugging code.
* @param device GPU device
* @param buffer GPU buffer
* @param byteLength the size of the GPU buffer
* @returns data from the GPU buffer
*/
var readBufferData = function (device, buffer, byteLength) { return __awaiter(void 0, void 0, void 0, function () {
var readBuffer, encoder;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
readBuffer = device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
encoder = device.createCommandEncoder();
encoder.copyBufferToBuffer(buffer, 0, readBuffer, 0, byteLength);
device.queue.submit([encoder.finish()]);
return [4 /*yield*/, readBuffer.mapAsync(GPUMapMode.READ)];
case 1:
_a.sent();
return [2 /*return*/, readBuffer.getMappedRange()];
}
});
}); };
exports.readBufferData = readBufferData;
// #endregion Create, Update GPU Buffers and Bind Group ***************************************
// #region Depth and MultiSample Texture ******************************************************
/**
* This function create a GPU texture for MSAA (or sample) count = 4.
* @param init The `IWebGPUInit` interface
*/
var createMultiSampleTexture = function (init) {
var texture = init.device.createTexture({
size: init.size,
format: init.format,
sampleCount: init.msaaCount,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
return texture;
};
exports.createMultiSampleTexture = createMultiSampleTexture;
/**
* This function creates a GPU texture used in the depth stencil attachment in the render pass descriptor.
* @param init The `IWebGPUInit` interface
* @param depthFormat GPU texture format, defaulting to `'depth24plus'`
*/
var createDepthTexture = function (init, depthFormat) {
if (depthFormat === void 0) { depthFormat = 'depth24plus'; }
var depthTexture = init.device.createTexture({
size: init.size,
format: depthFormat,
sampleCount: init.msaaCount,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
return depthTexture;
};
exports.createDepthTexture = createDepthTexture;
/**
* This function creates a model matrix of the `mat4` type.
* @param translation Translation along `x` (`tx`), `y` (`ty`), and `z` (`tz`) directions, which can be specified
* by `[tx, ty, tz]`, defaulting to [0, 0, 0]
* @param rotation Rotation along `x` (`rx`), `y` (`ry`), and `z` (`rz`) axes, which can be specified by
* `[rx, ry, rz]`, defaulting to [0, 0, 0]
* @param scale Scaling along `x` (`sx`), `y` (`sy`), and `z` (`sz`) directions, which can be specified by
* `[sx, sy, sz]`, defaulting to [1, 1, 1]
*/
var createModelMat = function (translation, rotation, scale) {
if (translation === void 0) { translation = [0, 0, 0]; }
if (rotation === void 0) { rotation = [0, 0, 0]; }
if (scale === void 0) { scale = [1, 1, 1]; }
var modelMat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.translate(modelMat, modelMat, translation);
gl_matrix_1.mat4.rotateX(modelMat, modelMat, rotation[0]);
gl_matrix_1.mat4.rotateY(modelMat, modelMat, rotation[1]);
gl_matrix_1.mat4.rotateZ(modelMat, modelMat, rotation[2]);
gl_matrix_1.mat4.scale(modelMat, modelMat, scale);
return modelMat;
};
exports.createModelMat = createModelMat;
/**
* This functions creates a view matrix of the `mat4` type and the camera options. It returns the interface
* `IViewOutput`.
* @param cameraPos Camera position, defaulting to [2, 2, 4]
* @param lookDir Look at direction, defaulting to [0, 0, 0]
* @param upDir Look up direction, defaulting to [0, 1, 0], i.e., the y direction is the look up direction
*/
var createViewTransform = function (cameraPos, lookDir, upDir) {
if (cameraPos === void 0) { cameraPos = [2, 2, 4]; }
if (lookDir === void 0) { lookDir = [0, 0, 0]; }
if (upDir === void 0) { upDir = [0, 1, 0]; }
var viewMat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.lookAt(viewMat, cameraPos, lookDir, upDir);
return {
viewMat: viewMat,
cameraOptions: {
eye: cameraPos,
center: lookDir,
zoomMax: 1000,
zoomSpeed: 2
}
};
};
exports.createViewTransform = createViewTransform;
/**
* This function creates a projection matrix of the `mat4` type.
* @param aspectRatio Aspect ratio, defaulting to 1
*/
var createProjectionMat = function (aspectRatio) {
if (aspectRatio === void 0) { aspectRatio = 1; }
var projectionMat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.perspective(projectionMat, 2 * Math.PI / 5, aspectRatio, 0.1, 1000.0);
return projectionMat;
};
exports.createProjectionMat = createProjectionMat;
/**
* This function creates a model-view-projection matrix of the `mat4` type by combining the model
* matrix, view matrix, and projection matrix together.
* @param modelMat Model matrix
* @param viewMat View matrix
* @param projectMat Projection matrix
*/
var combineMvpMat = function (modelMat, viewMat, projectionMat) {
var mvpMat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.multiply(mvpMat, viewMat, modelMat);
gl_matrix_1.mat4.multiply(mvpMat, projectionMat, mvpMat);
return mvpMat;
};
exports.combineMvpMat = combineMvpMat;
/**
* This function creates a view-projection matrix of the `mat4` type by combining the
* view matrix and projection matrix together.
* @param viewMat View matrix
* @param projectMat Projection matrix
*/
var combineVpMat = function (viewMat, projectionMat) {
var vpMat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.multiply(vpMat, projectionMat, viewMat);
return vpMat;
};
exports.combineVpMat = combineVpMat;
/**
* This function create a normal matrix of the `mat4` type by inverting and transposing a model matrix.
* @param modelMat Model matrix
*/
var createNormalMat = function (modelMat) {
var normalMat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.invert(normalMat, modelMat);
gl_matrix_1.mat4.transpose(normalMat, normalMat);
return normalMat;
};
exports.createNormalMat = createNormalMat;
/**
* This function creates a camera using a `npm` package `3d-view-controls`. The returned easy to use camera
* allows you to interact with graphics objects in the scene using mouse, such as pan, rotate, and zoom in/out
* the objects.
* @param canvas HTML `canvas` element
* @param options Camera options, type of the `ICameraOptions`
*/
var getCamera = function (canvas, options) {
return camera(canvas, options);
};
exports.getCamera = getCamera;
// #endregion Transformations *****************************************************************
// #region texture ****************************************************************************
/**
* This function creates a texture and a sampler from an image file, and returns an object that contains
* attributes `texture` and `sampler`.
* @param device GPU device
* @param imageFile the path of the image file to load
* @param addressModeU (optional) the addressing model for the `u` texture coordinate, defaulting to `'repeat'`
* @param addressModeV (optional) the addressing model for the `v` texture coordinate, defaulting to `'repeat'`
*/
var createImageTexture = function (device, imageFile, addressModeU, addressModeV) {
if (addressModeU === void 0) { addressModeU = 'repeat'; }
if (addressModeV === void 0) { addressModeV = 'repeat'; }
return __awaiter(void 0, void 0, void 0, function () {
var response, img, imageBitmap, sampler, texture;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch(imageFile)];
case 1:
response = _a.sent();
return [4 /*yield*/, response.blob()];
case 2:
img = _a.sent();
return [4 /*yield*/, createImageBitmap(img)];
case 3:
imageBitmap = _a.sent();
sampler = device.createSampler({
minFilter: 'linear',
magFilter: 'linear',
addressModeU: addressModeU,
addressModeV: addressModeV
});
texture = device.createTexture({
size: [imageBitmap.width, imageBitmap.height, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT
});
device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture: texture }, [imageBitmap.width, imageBitmap.height]);
return [2 /*return*/, {
texture: texture,
sampler: sampler
}];
}
});
});
};
exports.createImageTexture = createImageTexture;
/**
* This function creates a texture and a sampler from a 2D canvas, and returns an object that contains
* attributes `texture` and `sampler`.
* @param device GPU device
* @param canvas the HTML canvas element
* @param addressModeU (optional) the addressing model for the `u` texture coordinate, defaulting to `'repeat'`
* @param addressModeV (optional) the addressing model for the `v` texture coordinate, defaulting to `'repeat'`
*/
var createCanvasTexture = function (device, canvas, addressModeU, addressModeV) {
if (addressModeU === void 0) { addressModeU = 'repeat'; }
if (addressModeV === void 0) { addressModeV = 'repeat'; }
return __awaiter(void 0, void 0, void 0, function () {
var sampler, texture;
return __generator(this, function (_a) {
sampler = device.createSampler({
minFilter: 'linear',
magFilter: 'linear',
addressModeU: addressModeU,
addressModeV: addressModeV
});
texture = device.createTexture({
size: { width: canvas.width, height: canvas.height },
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT
});
device.queue.copyExternalImageToTexture({ source: canvas, flipY: true }, { texture: texture }, [canvas.width, canvas.height]);
return [2 /*return*/, {
texture: texture,
sampler: sampler
}];
});
});
};
exports.createCanvasTexture = createCanvasTexture;
// #endregion texture *************************************************************************
// #region utility ****************************************************************************
/**
* This utility function convert `hex` color string to `rgba` color array of the `Float32Array` type.
* @param hex Hex color string
*/
var hex2rgb = function (hex) {
var _a = hex.match(/\w\w/g).map(function (x) { return parseInt(x, 16) / 255.0; }), r = _a[0], g = _a[1], b = _a[2];
return new Float32Array([r, g, b, 1]);
};
exports.hex2rgb = hex2rgb;
/**
* This utility function creates a new `dat-gui` with a specified dom element id. This function is based on the `npm`
* package called `dat.gui`. the `dat.gui` library is a lightweight graphical user interface for changing parameters
* by the user.
* @param guiDomId HTML dom element id, defaulting to `'gui'`
*/
var getDatGui = function (guiDomId) {
if (guiDomId === void 0) { guiDomId = 'gui'; }
var gui = new dat_gui_1.GUI();
document.querySelector('#' + guiDomId).append(gui.domElement);
return gui;
};
exports.getDatGui = getDatGui;
/**
* This utility function creates a new `stats` panel on the scene with a specified dom element id. This function is based on
* the `npm` package called `stats.js`. It can be used to monitor the performance of your apps, such as framerate, rendering
* time, and memory usage.
* @param statsDomId
*/
var getStats = function (statsDomId) {
if (statsDomId === void 0) { statsDomId = 'stats'; }
var stats = new Stats();
stats.dom.style.cssText = 'position:relative;top:0;left:0';
stats.showPanel(1);
document.querySelector('#' + statsDomId).appendChild(stats.dom);
return stats;
};
exports.getStats = getStats;
// #endregion utility *************************************************************************