typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
197 lines (196 loc) • 8.19 kB
JavaScript
import { $internal } from "../../shared/symbols.js";
import { isGPUBuffer } from "../../types.js";
import { DRAW_INDEXED_INDIRECT_SIZE, DRAW_INDIRECT_SIZE, resolveIndirectOffset, } from "../pipeline/pipelineUtils.js";
import { isTexture, isTextureView } from "../texture/texture.js";
import { emitRenderDraw, recordBindGroup, RenderDrawState, stampRenderPipeline, } from "../pipeline/drawState.js";
import { isQuerySet } from "../querySet/querySet.js";
import { unwrapAttachmentView, unwrapTimestampWrites, } from "./attachments.js";
// --------------
// Implementation
// --------------
function attachmentAspects(view) {
let format;
let aspect = 'all';
if (isTexture(view)) {
format = view.props.format;
}
else if (isTextureView(view)) {
format = view[$internal].format;
aspect = view[$internal].aspect ?? 'all';
}
if (format === undefined) {
return undefined;
}
return {
hasDepth: format.includes('depth') && aspect !== 'stencil-only',
hasStencil: format.includes('stencil') && aspect !== 'depth-only',
};
}
function withDepthStencilDefaults(attachment, view) {
const rawAttachment = { ...attachment, view };
const aspects = attachmentAspects(attachment.view);
if (aspects === undefined) {
const hasExplicitOps = attachment.depthLoadOp !== undefined ||
attachment.depthStoreOp !== undefined ||
attachment.stencilLoadOp !== undefined ||
attachment.stencilStoreOp !== undefined;
if (hasExplicitOps) {
return rawAttachment;
}
}
const { hasDepth, hasStencil } = aspects ?? { hasDepth: true, hasStencil: false };
if (hasDepth && !attachment.depthReadOnly) {
rawAttachment.depthLoadOp ??= 'clear';
rawAttachment.depthStoreOp ??= 'store';
rawAttachment.depthClearValue ??= 1;
}
if (hasStencil && !attachment.stencilReadOnly) {
rawAttachment.stencilLoadOp ??= 'clear';
rawAttachment.stencilStoreOp ??= 'store';
}
return rawAttachment;
}
export function INTERNAL_beginRenderPass(encoder, descriptor) {
const { rawEncoder, root } = encoder[$internal];
const colorAttachments = descriptor.colorAttachments === undefined
? []
: Array.isArray(descriptor.colorAttachments)
? descriptor.colorAttachments
: [descriptor.colorAttachments];
const rawDescriptor = {
colorAttachments: colorAttachments.map((attachment) => {
if (attachment === null) {
return null;
}
const rawAttachment = {
...attachment,
loadOp: attachment.loadOp ?? 'clear',
storeOp: attachment.storeOp ?? 'store',
view: unwrapAttachmentView(root, attachment.view),
};
if (attachment.resolveTarget !== undefined) {
rawAttachment.resolveTarget = unwrapAttachmentView(root, attachment.resolveTarget);
}
return rawAttachment;
}),
};
if (descriptor.label !== undefined) {
rawDescriptor.label = descriptor.label;
}
if (descriptor.depthStencilAttachment !== undefined) {
rawDescriptor.depthStencilAttachment = withDepthStencilDefaults(descriptor.depthStencilAttachment, unwrapAttachmentView(root, descriptor.depthStencilAttachment.view));
}
if (descriptor.occlusionQuerySet !== undefined) {
rawDescriptor.occlusionQuerySet = isQuerySet(descriptor.occlusionQuerySet)
? root.unwrap(descriptor.occlusionQuerySet)
: descriptor.occlusionQuerySet;
}
if (descriptor.timestampWrites !== undefined) {
rawDescriptor.timestampWrites = unwrapTimestampWrites(root, descriptor.timestampWrites);
}
if (descriptor.maxDrawCount !== undefined) {
rawDescriptor.maxDrawCount = descriptor.maxDrawCount;
}
return new TgpuRenderPassImpl(root, rawEncoder.beginRenderPass(rawDescriptor), encoder);
}
export function INTERNAL_createRenderBundleEncoder(root, descriptor) {
return new TgpuRenderBundleEncoderImpl(root, root.device.createRenderBundleEncoder(descriptor), undefined);
}
export function INTERNAL_adoptRenderCommands(root, rawPass) {
const adopted = typeof rawPass.executeBundles === 'function'
? new TgpuRenderPassImpl(root, rawPass, undefined)
: new TgpuRenderCommandsImpl(root, rawPass, undefined);
adopted[$internal].state.rawAccessed = true;
return adopted;
}
class TgpuRenderCommandsImpl {
[$internal];
resourceType = 'render-bundle-encoder';
#root;
constructor(root, rawPass, owner) {
this.#root = root;
this[$internal] = {
rawPass,
state: new RenderDrawState(),
owner,
appliedVersion: undefined,
};
}
#emit(usesIndexBuffer, emit) {
const internals = this[$internal];
const pipeline = internals.state.currentPipeline;
if (!pipeline) {
throw new Error('Cannot draw without a call to pass.setPipeline');
}
emitRenderDraw(this.#root, internals, pipeline, usesIndexBuffer, emit);
}
setPipeline(pipeline) {
stampRenderPipeline(this[$internal].state, pipeline);
}
setBindGroup(first, bindGroup) {
recordBindGroup(this[$internal].state, first, bindGroup);
}
setVertexBuffer(vertexLayout, buffer, offset, size) {
const { state } = this[$internal];
state.vertexBuffers.set(vertexLayout, { buffer, offset, size });
state.version++;
}
setIndexBuffer(buffer, indexFormat, offset, size) {
const { state } = this[$internal];
state.indexBuffer = { buffer, indexFormat, offsetBytes: offset, sizeBytes: size };
state.version++;
}
draw(vertexCount, instanceCount, firstVertex, firstInstance) {
this.#emit(false, (rawPass) => rawPass.draw(vertexCount, instanceCount, firstVertex, firstInstance));
}
drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance) {
this.#emit(true, (rawPass) => rawPass.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance));
}
drawIndirect(indirectBuffer, indirectOffset) {
const rawBuffer = isGPUBuffer(indirectBuffer) ? indirectBuffer : indirectBuffer.buffer;
const offset = resolveIndirectOffset(indirectBuffer, indirectOffset, DRAW_INDIRECT_SIZE, 'drawIndirect');
this.#emit(false, (rawPass) => rawPass.drawIndirect(rawBuffer, offset));
}
drawIndexedIndirect(indirectBuffer, indirectOffset) {
const rawBuffer = isGPUBuffer(indirectBuffer) ? indirectBuffer : indirectBuffer.buffer;
const offset = resolveIndirectOffset(indirectBuffer, indirectOffset, DRAW_INDEXED_INDIRECT_SIZE, 'drawIndexedIndirect');
this.#emit(true, (rawPass) => rawPass.drawIndexedIndirect(rawBuffer, offset));
}
}
class TgpuRenderBundleEncoderImpl extends TgpuRenderCommandsImpl {
resourceType = 'render-bundle-encoder';
finish(descriptor) {
return this[$internal].rawPass.finish(descriptor);
}
}
class TgpuRenderPassImpl extends TgpuRenderCommandsImpl {
resourceType = 'render-pass';
setViewport(x, y, width, height, minDepth, maxDepth) {
this[$internal].rawPass.setViewport(x, y, width, height, minDepth, maxDepth);
}
setScissorRect(x, y, width, height) {
this[$internal].rawPass.setScissorRect(x, y, width, height);
}
setBlendConstant(color) {
this[$internal].rawPass.setBlendConstant(color);
}
setStencilReference(reference) {
const { state } = this[$internal];
state.stencilReference = reference;
state.version++;
}
beginOcclusionQuery(queryIndex) {
this[$internal].rawPass.beginOcclusionQuery(queryIndex);
}
endOcclusionQuery() {
this[$internal].rawPass.endOcclusionQuery();
}
executeBundles(bundles) {
const internals = this[$internal];
internals.rawPass.executeBundles(bundles);
internals.appliedVersion = undefined;
}
end() {
this[$internal].rawPass.end();
}
}