diff --git a/packages/g-lite/src/Canvas.ts b/packages/g-lite/src/Canvas.ts index 2b23eb6e8..4ffb6c9b4 100644 --- a/packages/g-lite/src/Canvas.ts +++ b/packages/g-lite/src/Canvas.ts @@ -510,28 +510,10 @@ export class Canvas extends EventTarget implements ICanvas { if (this.context.contextService.init) { this.context.contextService.init(); - this.initRenderingService(renderer, firstContentfullPaint); - if (firstContentfullPaint) { - this.requestAnimationFrame(() => { - this.dispatchEvent(new CustomEvent(CanvasEvent.READY)); - }); - if (this.readyPromise) { - this.resolveReadyPromise(); - } - } else { - this.dispatchEvent(new CustomEvent(CanvasEvent.RENDERER_CHANGED)); - } + this.initRenderingService(renderer, firstContentfullPaint, true); } else { this.context.contextService.initAsync().then(() => { this.initRenderingService(renderer, firstContentfullPaint); - if (firstContentfullPaint) { - this.dispatchEvent(new CustomEvent(CanvasEvent.READY)); - if (this.readyPromise) { - this.resolveReadyPromise(); - } - } else { - this.dispatchEvent(new CustomEvent(CanvasEvent.RENDERER_CHANGED)); - } }); } } @@ -539,28 +521,44 @@ export class Canvas extends EventTarget implements ICanvas { private initRenderingService( renderer: IRenderer, firstContentfullPaint = false, + async = false, ) { - this.context.renderingService.init(); - - this.inited = true; + this.context.renderingService.init(() => { + this.inited = true; - if (!firstContentfullPaint) { - this.getRoot().forEach((node) => { - const renderable = (node as Element).renderable; - if (renderable) { - renderable.renderBoundsDirty = true; - renderable.boundsDirty = true; - renderable.dirty = true; + if (firstContentfullPaint) { + if (async) { + this.requestAnimationFrame(() => { + this.dispatchEvent(new CustomEvent(CanvasEvent.READY)); + }); + } else { + this.dispatchEvent(new CustomEvent(CanvasEvent.READY)); } - }); - } + if (this.readyPromise) { + this.resolveReadyPromise(); + } + } else { + this.dispatchEvent(new CustomEvent(CanvasEvent.RENDERER_CHANGED)); + } + + if (!firstContentfullPaint) { + this.getRoot().forEach((node) => { + const renderable = (node as Element).renderable; + if (renderable) { + renderable.renderBoundsDirty = true; + renderable.boundsDirty = true; + renderable.dirty = true; + } + }); + } - // keep current scenegraph unchanged, just trigger mounted event - this.mountChildren(this.getRoot()); + // keep current scenegraph unchanged, just trigger mounted event + this.mountChildren(this.getRoot()); - if (renderer.getConfig().enableAutoRendering) { - this.run(); - } + if (renderer.getConfig().enableAutoRendering) { + this.run(); + } + }); } private loadRendererContainerModule(renderer: IRenderer) { diff --git a/packages/g-lite/src/services/RenderingService.ts b/packages/g-lite/src/services/RenderingService.ts index 84770476c..b702dbe2a 100644 --- a/packages/g-lite/src/services/RenderingService.ts +++ b/packages/g-lite/src/services/RenderingService.ts @@ -9,6 +9,7 @@ import type { CanvasConfig, } from '../types'; import { + AsyncParallelHook, AsyncSeriesWaterfallHook, sortByZIndex, sortedIndex, @@ -72,6 +73,7 @@ export class RenderingService { * called before any frame rendered */ init: new SyncHook<[]>(), + initAsync: new AsyncParallelHook<[]>(), /** * only dirty object which has sth changed will be rendered */ @@ -120,7 +122,7 @@ export class RenderingService { click: new SyncHook<[InteractivePointerEvent]>(), }; - init() { + init(callback: () => void) { const context = { ...this.globalRuntime, ...this.context }; // register rendering plugins @@ -128,7 +130,16 @@ export class RenderingService { plugin.apply(context, runtime); }); this.hooks.init.call(); - this.inited = true; + + if (this.hooks.initAsync.getCallbacksNum() === 0) { + this.inited = true; + callback(); + } else { + this.hooks.initAsync.promise().then(() => { + this.inited = true; + callback(); + }); + } } getStats() { diff --git a/packages/g-lite/src/utils/tapable/AsyncParallelHook.ts b/packages/g-lite/src/utils/tapable/AsyncParallelHook.ts index ceecb3a64..afd84b18c 100644 --- a/packages/g-lite/src/utils/tapable/AsyncParallelHook.ts +++ b/packages/g-lite/src/utils/tapable/AsyncParallelHook.ts @@ -1,6 +1,10 @@ export class AsyncParallelHook { private callbacks: ((...args: T[]) => Promise)[] = []; + getCallbacksNum() { + return this.callbacks.length; + } + tapPromise(options: string, fn: (...args: T[]) => Promise) { this.callbacks.push(fn); } diff --git a/packages/g-plugin-box2d/src/Box2DPlugin.ts b/packages/g-plugin-box2d/src/Box2DPlugin.ts index ff646f60c..098e7b2f6 100644 --- a/packages/g-plugin-box2d/src/Box2DPlugin.ts +++ b/packages/g-plugin-box2d/src/Box2DPlugin.ts @@ -48,7 +48,6 @@ export class Box2DPlugin implements RenderingPlugin { private bodies: Map = new Map(); private fixtures: WeakMap = new WeakMap(); - private pendingDisplayObjects: DisplayObject[] = []; apply(context: RenderingPluginContext) { const { renderingService, renderingContext } = context; @@ -74,13 +73,8 @@ export class Box2DPlugin implements RenderingPlugin { }; const handleMounted = (e: FederatedEvent) => { - const Box2D = this.Box2D; const target = e.target as DisplayObject; - if (Box2D) { - this.addActor(target); - } else { - this.pendingDisplayObjects.push(target); - } + this.addActor(target); }; const handleUnmounted = (e: FederatedEvent) => { @@ -155,7 +149,7 @@ export class Box2DPlugin implements RenderingPlugin { } }; - renderingService.hooks.init.tap(Box2DPlugin.tag, () => { + renderingService.hooks.initAsync.tapPromise(Box2DPlugin.tag, async () => { canvas.addEventListener(ElementEvent.MOUNTED, handleMounted); canvas.addEventListener(ElementEvent.UNMOUNTED, handleUnmounted); canvas.addEventListener( @@ -163,17 +157,14 @@ export class Box2DPlugin implements RenderingPlugin { handleAttributeChanged, ); - (async () => { - this.Box2D = await this.loadBox2D(); + this.Box2D = await this.loadBox2D(); - this.temp = new this.Box2D.b2Vec2(0, 0); - this.temp2 = new this.Box2D.b2Vec2(0, 0); - this.createScene(); - this.handlePendingDisplayObjects(); + this.temp = new this.Box2D.b2Vec2(0, 0); + this.temp2 = new this.Box2D.b2Vec2(0, 0); + this.createScene(); - // do simulation each frame - canvas.addEventListener(CanvasEvent.BEFORE_RENDER, simulate); - })(); + // do simulation each frame + canvas.addEventListener(CanvasEvent.BEFORE_RENDER, simulate); }); renderingService.hooks.destroy.tap(Box2DPlugin.tag, () => { @@ -407,13 +398,6 @@ export class Box2DPlugin implements RenderingPlugin { } } - private handlePendingDisplayObjects() { - this.pendingDisplayObjects.forEach((object) => { - this.addActor(object); - }); - this.pendingDisplayObjects = []; - } - private async loadBox2D(): Promise { const hasSIMD = WebAssembly.validate( new Uint8Array([ diff --git a/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts b/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts index 749143faa..d91e970e6 100644 --- a/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts +++ b/packages/g-plugin-device-renderer/src/RenderGraphPlugin.ts @@ -78,8 +78,6 @@ export class RenderGraphPlugin implements RenderingPlugin { */ private builder: RGGraphBuilder; - private pendingDisplayObjects: DisplayObject[] = []; - private enableCapture: boolean; private captureOptions: Partial; private capturePromise: Promise | undefined; @@ -122,11 +120,7 @@ export class RenderGraphPlugin implements RenderingPlugin { // @ts-ignore object.renderable3D = renderable3D; - if (this.swapChain) { - this.batchManager.add(object); - } else { - this.pendingDisplayObjects.push(object); - } + this.batchManager.add(object); }; const handleUnmounted = (e: FederatedEvent) => { @@ -177,24 +171,28 @@ export class RenderGraphPlugin implements RenderingPlugin { } }; - renderingService.hooks.init.tap(RenderGraphPlugin.tag, () => { - canvas.addEventListener(ElementEvent.MOUNTED, handleMounted); - canvas.addEventListener(ElementEvent.UNMOUNTED, handleUnmounted); - canvas.addEventListener( - ElementEvent.ATTR_MODIFIED, - handleAttributeChanged, - ); - canvas.addEventListener(ElementEvent.BOUNDS_CHANGED, handleBoundsChanged); - this.context.config.renderer.getConfig().enableDirtyRectangleRendering = - false; + renderingService.hooks.initAsync.tapPromise( + RenderGraphPlugin.tag, + async () => { + canvas.addEventListener(ElementEvent.MOUNTED, handleMounted); + canvas.addEventListener(ElementEvent.UNMOUNTED, handleUnmounted); + canvas.addEventListener( + ElementEvent.ATTR_MODIFIED, + handleAttributeChanged, + ); + canvas.addEventListener( + ElementEvent.BOUNDS_CHANGED, + handleBoundsChanged, + ); + this.context.config.renderer.getConfig().enableDirtyRectangleRendering = + false; - const $canvas = - this.context.contextService.getDomElement() as HTMLCanvasElement; + const $canvas = + this.context.contextService.getDomElement() as HTMLCanvasElement; - const { width, height } = this.context.config; - this.context.contextService.resize(width, height); + const { width, height } = this.context.config; + this.context.contextService.resize(width, height); - (async () => { // create swap chain and get device // @ts-ignore this.swapChain = await this.context.deviceContribution.createSwapChain( @@ -213,16 +211,8 @@ export class RenderGraphPlugin implements RenderingPlugin { device: this.device, ...context, }); - - this.pendingDisplayObjects.forEach((object) => { - this.batchManager.add(object); - }); - this.pendingDisplayObjects = []; - - // trigger rerender - this.context.renderingService.dirtify(); - })(); - }); + }, + ); renderingService.hooks.destroy.tap(RenderGraphPlugin.tag, () => { this.renderHelper.destroy(); @@ -243,10 +233,6 @@ export class RenderGraphPlugin implements RenderingPlugin { * build frame graph at the beginning of each frame */ renderingService.hooks.beginFrame.tap(RenderGraphPlugin.tag, () => { - if (!this.swapChain) { - return; - } - const canvas = this.swapChain.getCanvas() as HTMLCanvasElement; const renderInstManager = this.renderHelper.renderInstManager; this.builder = this.renderHelper.renderGraph.newGraphBuilder(); @@ -321,10 +307,6 @@ export class RenderGraphPlugin implements RenderingPlugin { }); renderingService.hooks.endFrame.tap(RenderGraphPlugin.tag, () => { - if (!this.swapChain) { - return; - } - const renderInstManager = this.renderHelper.renderInstManager; // TODO: time for GPU Animation @@ -394,9 +376,6 @@ export class RenderGraphPlugin implements RenderingPlugin { renderInstManager.resetRenderInsts(); - // output to screen - this.swapChain.present(); - // capture here since we don't preserve drawing buffer if (this.enableCapture && this.resolveCapturePromise) { const { type, encoderOptions } = this.captureOptions; diff --git a/packages/g-plugin-device-renderer/src/geometries/BufferGeometry.ts b/packages/g-plugin-device-renderer/src/geometries/BufferGeometry.ts index 5cdb581fb..19df8a414 100644 --- a/packages/g-plugin-device-renderer/src/geometries/BufferGeometry.ts +++ b/packages/g-plugin-device-renderer/src/geometries/BufferGeometry.ts @@ -129,7 +129,7 @@ export class BufferGeometry extends EventEmitter { this.indexBuffer = makeStaticDataBuffer( this.device, - BufferUsage.INDEX | BufferUsage.COPY_DST, + BufferUsage.INDEX, new Uint32Array(ArrayBuffer.isView(indices) ? indices.buffer : indices) .buffer, ); @@ -180,7 +180,7 @@ export class BufferGeometry extends EventEmitter { const buffer = makeStaticDataBuffer( this.device, - BufferUsage.VERTEX | BufferUsage.COPY_DST, + BufferUsage.VERTEX, data.buffer, ); this.vertexBuffers[bufferIndex] = buffer; @@ -222,7 +222,9 @@ export class BufferGeometry extends EventEmitter { if (this.indexBuffer) { this.indexBuffer.setSubData( offset, - ArrayBuffer.isView(indices) ? indices : new Uint32Array(indices), + new Uint8Array( + ArrayBuffer.isView(indices) ? indices : new Uint32Array(indices), + ), ); } return this; diff --git a/packages/g-plugin-device-renderer/src/platform/interfaces.ts b/packages/g-plugin-device-renderer/src/platform/interfaces.ts index 76ad0224c..c74c1ae2d 100644 --- a/packages/g-plugin-device-renderer/src/platform/interfaces.ts +++ b/packages/g-plugin-device-renderer/src/platform/interfaces.ts @@ -29,7 +29,7 @@ export interface Buffer extends ResourceBase { type: ResourceType.Buffer; setSubData: ( dstByteOffset: number, - src: ArrayBufferView, + src: Uint8Array, srcByteOffset?: number, byteLength?: number, ) => void; @@ -336,6 +336,7 @@ export type BufferBindingType = 'uniform' | 'storage' | 'read-only-storage'; export interface BindingLayoutSamplerDescriptor { dimension: TextureDimension; formatKind: SamplerFormatKind; + comparison?: boolean; } export interface BindingLayoutStorageDescriptor { @@ -430,8 +431,10 @@ export interface Color { export interface RenderPassDescriptor { colorAttachment: (RenderTarget | null)[]; + colorAttachmentLevel: number[]; colorClearColor: (Color | 'load')[]; colorResolveTo: (Texture | null)[]; + colorResolveToLevel: number[]; colorStore: boolean[]; depthStencilAttachment: RenderTarget | null; depthStencilResolveTo: Texture | null; @@ -443,9 +446,6 @@ export interface RenderPassDescriptor { occlusionQueryPool: QueryPool | null; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ComputePassDescriptor {} - export interface DeviceLimits { uniformBufferWordAlignment: number; uniformBufferMaxPageWordSize: number; @@ -482,14 +482,6 @@ export interface VendorInfo { export type PlatformFramebuffer = WebGLFramebuffer; -// Viewport in normalized coordinate space, from 0 to 1. -export interface NormalizedViewportCoords { - x: number; - y: number; - w: number; - h: number; -} - export interface SwapChain { // @see https://www.w3.org/TR/webgpu/#canvas-configuration configureSwapChain: ( @@ -500,7 +492,6 @@ export interface SwapChain { getDevice: () => Device; getCanvas: () => HTMLCanvasElement | OffscreenCanvas; getOnscreenTexture: () => Texture; - present: () => void; } export interface RenderPass { @@ -536,11 +527,18 @@ export interface RenderPass { export interface ComputePass { setPipeline: (pipeline: ComputePipeline) => void; - setBindings: (bindingLayoutIndex: number, bindings: Bindings) => void; + setBindings: ( + bindingLayoutIndex: number, + bindings: Bindings, + dynamicByteOffsets: number[], + ) => void; /** * @see https://www.w3.org/TR/webgpu/#compute-pass-encoder-dispatch */ dispatch: (x: number, y?: number, z?: number) => void; + // Debug. + beginDebugGroup: (name: string) => void; + endDebugGroup: () => void; } export enum QueryPoolType { @@ -592,9 +590,10 @@ export interface Device { createQueryPool: (type: QueryPoolType, elemCount: number) => QueryPool; createRenderPass: (renderPassDescriptor: RenderPassDescriptor) => RenderPass; - createComputePass: ( - computePassDescriptor: ComputePassDescriptor, - ) => ComputePass; + createComputePass: () => ComputePass; + + beginFrame(): void; + endFrame(): void; submitPass: (pass: RenderPass | ComputePass) => void; // Render pipeline compilation control. diff --git a/packages/g-plugin-device-renderer/src/platform/utils/hash.ts b/packages/g-plugin-device-renderer/src/platform/utils/hash.ts index 21fc0dcdb..ecfd6f044 100644 --- a/packages/g-plugin-device-renderer/src/platform/utils/hash.ts +++ b/packages/g-plugin-device-renderer/src/platform/utils/hash.ts @@ -33,10 +33,20 @@ export function arrayCopy(a: T[], copyFunc: CopyFunc): T[] { return b; } -function bufferBindingEquals(a: Readonly, b: Readonly): boolean { +function bufferBindingEquals( + a: Readonly, + b: Readonly, +): boolean { return a.buffer === b.buffer && a.wordCount === b.wordCount; } +function bindingLayoutSamplerDescriptorEqual( + a: Readonly, + b: Readonly, +): boolean { + return a.dimension === b.dimension && a.formatKind === b.formatKind; +} + function samplerBindingEquals( a: Readonly, b: Readonly, @@ -52,6 +62,17 @@ export function bindingLayoutDescriptorEqual( ): boolean { if (a.numSamplers !== b.numSamplers) return false; if (a.numUniformBuffers !== b.numUniformBuffers) return false; + if ((a.samplerEntries === undefined) !== (b.samplerEntries === undefined)) + return false; + if ( + a.samplerEntries !== undefined && + !arrayEqual( + a.samplerEntries!, + b.samplerEntries!, + bindingLayoutSamplerDescriptorEqual, + ) + ) + return false; return true; } @@ -60,8 +81,15 @@ export function bindingsDescriptorEquals( b: Readonly, ): boolean { if (a.samplerBindings.length !== b.samplerBindings.length) return false; - if (!arrayEqual(a.samplerBindings, b.samplerBindings, samplerBindingEquals)) return false; - if (!arrayEqual(a.uniformBufferBindings, b.uniformBufferBindings, bufferBindingEquals)) + if (!arrayEqual(a.samplerBindings, b.samplerBindings, samplerBindingEquals)) + return false; + if ( + !arrayEqual( + a.uniformBufferBindings, + b.uniformBufferBindings, + bufferBindingEquals, + ) + ) return false; if (!bindingLayoutEquals(a.bindingLayout, b.bindingLayout)) return false; return true; @@ -83,13 +111,20 @@ function attachmentStateEquals( b: Readonly, ): boolean { if (!channelBlendStateEquals(a.rgbBlendState, b.rgbBlendState)) return false; - if (!channelBlendStateEquals(a.alphaBlendState, b.alphaBlendState)) return false; + if (!channelBlendStateEquals(a.alphaBlendState, b.alphaBlendState)) + return false; if (a.channelWriteMask !== b.channelWriteMask) return false; return true; } -function megaStateDescriptorEquals(a: MegaStateDescriptor, b: MegaStateDescriptor): boolean { - if (!arrayEqual(a.attachmentsState, b.attachmentsState, attachmentStateEquals)) return false; +function megaStateDescriptorEquals( + a: MegaStateDescriptor, + b: MegaStateDescriptor, +): boolean { + if ( + !arrayEqual(a.attachmentsState, b.attachmentsState, attachmentStateEquals) + ) + return false; if (!colorEqual(a.blendConstant, b.blendConstant)) return false; return ( @@ -109,7 +144,10 @@ function bindingLayoutEquals( a: Readonly, b: Readonly, ): boolean { - return a.numSamplers === b.numSamplers && a.numUniformBuffers === b.numUniformBuffers; + return ( + a.numSamplers === b.numSamplers && + a.numUniformBuffers === b.numUniformBuffers + ); } function programEquals(a: Readonly, b: Readonly): boolean { @@ -127,11 +165,21 @@ export function renderPipelineDescriptorEquals( if (a.topology !== b.topology) return false; if (a.inputLayout !== b.inputLayout) return false; if (a.sampleCount !== b.sampleCount) return false; - if (!megaStateDescriptorEquals(a.megaStateDescriptor, b.megaStateDescriptor)) return false; + if (!megaStateDescriptorEquals(a.megaStateDescriptor, b.megaStateDescriptor)) + return false; if (!programEquals(a.program, b.program)) return false; - if (!arrayEqual(a.bindingLayouts, b.bindingLayouts, bindingLayoutEquals)) return false; - if (!arrayEqual(a.colorAttachmentFormats, b.colorAttachmentFormats, formatEquals)) return false; - if (a.depthStencilAttachmentFormat !== b.depthStencilAttachmentFormat) return false; + if (!arrayEqual(a.bindingLayouts, b.bindingLayouts, bindingLayoutEquals)) + return false; + if ( + !arrayEqual( + a.colorAttachmentFormats, + b.colorAttachmentFormats, + formatEquals, + ) + ) + return false; + if (a.depthStencilAttachmentFormat !== b.depthStencilAttachmentFormat) + return false; return true; } @@ -197,7 +245,9 @@ export function samplerDescriptorEquals( ); } -export function samplerBindingCopy(a: Readonly): SamplerBinding { +export function samplerBindingCopy( + a: Readonly, +): SamplerBinding { const sampler = a.sampler; const texture = a.texture; const lateBinding = a.lateBinding; @@ -214,11 +264,21 @@ export function bufferBindingCopy(a: Readonly): BufferBinding { return { buffer, wordCount }; } -export function bindingsDescriptorCopy(a: Readonly): BindingsDescriptor { +export function bindingsDescriptorCopy( + a: Readonly, +): BindingsDescriptor { const bindingLayout = a.bindingLayout; const samplerBindings = arrayCopy(a.samplerBindings, samplerBindingCopy); - const uniformBufferBindings = arrayCopy(a.uniformBufferBindings, bufferBindingCopy); - return { bindingLayout, samplerBindings, uniformBufferBindings, pipeline: a.pipeline }; + const uniformBufferBindings = arrayCopy( + a.uniformBufferBindings, + bufferBindingCopy, + ); + return { + bindingLayout, + samplerBindings, + uniformBufferBindings, + pipeline: a.pipeline, + }; } export function bindingLayoutSamplerDescriptorCopy( @@ -244,7 +304,10 @@ export function bindingLayoutDescriptorCopy( export function renderPipelineDescriptorCopy( a: Readonly, ): RenderPipelineDescriptor { - const bindingLayouts = arrayCopy(a.bindingLayouts, bindingLayoutDescriptorCopy); + const bindingLayouts = arrayCopy( + a.bindingLayouts, + bindingLayoutDescriptorCopy, + ); const inputLayout = a.inputLayout; const program = a.program; const topology = a.topology; @@ -273,7 +336,14 @@ export function vertexAttributeDescriptorCopy( const bufferByteOffset = a.bufferByteOffset; const byteStride = a.byteStride; const divisor = a.divisor; - return { location, format, bufferIndex, bufferByteOffset, byteStride, divisor }; + return { + location, + format, + bufferIndex, + bufferByteOffset, + byteStride, + divisor, + }; } export function inputLayoutBufferDescriptorCopy( @@ -300,5 +370,9 @@ export function inputLayoutDescriptorCopy( inputLayoutBufferDescriptorCopy, ); const indexBufferFormat = a.indexBufferFormat; - return { vertexAttributeDescriptors, vertexBufferDescriptors, indexBufferFormat }; + return { + vertexAttributeDescriptors, + vertexBufferDescriptors, + indexBufferFormat, + }; } diff --git a/packages/g-plugin-device-renderer/src/render/DynamicUniformBuffer.ts b/packages/g-plugin-device-renderer/src/render/DynamicUniformBuffer.ts index 367813611..b6738470c 100644 --- a/packages/g-plugin-device-renderer/src/render/DynamicUniformBuffer.ts +++ b/packages/g-plugin-device-renderer/src/render/DynamicUniformBuffer.ts @@ -109,7 +109,7 @@ export class DynamicUniformBuffer { this.buffer = this.device.createBuffer({ viewOrSize: this.currentBufferWordSize, - usage: BufferUsage.UNIFORM | BufferUsage.COPY_DST, + usage: BufferUsage.UNIFORM, hint: BufferFrequencyHint.Dynamic, }); } diff --git a/packages/g-plugin-device-renderer/src/render/RenderGraph.ts b/packages/g-plugin-device-renderer/src/render/RenderGraph.ts index f33b4637c..544f82a92 100644 --- a/packages/g-plugin-device-renderer/src/render/RenderGraph.ts +++ b/packages/g-plugin-device-renderer/src/render/RenderGraph.ts @@ -1,6 +1,10 @@ import type { Device, RenderTarget, Texture } from '../platform'; import { assert, assertExists, fillArray } from '../platform/utils'; -import type { PassSetupFunc, RGGraphBuilder, RGGraphBuilderDebug } from './interfaces'; +import type { + PassSetupFunc, + RGGraphBuilder, + RGGraphBuilderDebug, +} from './interfaces'; import { RGAttachmentSlot } from './interfaces'; import { RenderGraphPass } from './RenderGraphPass'; import { RGRenderTarget } from './RenderTarget'; @@ -10,6 +14,7 @@ import { SingleSampledTexture } from './SingleSampledTexture'; interface ResolveParam { resolveTo: Texture | null; store: boolean; + level: number; } class GraphImpl { @@ -87,13 +92,18 @@ export class RenderGraph implements RGGraphBuilder { this.currentGraph.passes.push(pass); } - createRenderTargetID(desc: Readonly, debugName: string): number { + createRenderTargetID( + desc: Readonly, + debugName: string, + ): number { this.currentGraph.renderTargetDebugNames.push(debugName); return this.currentGraph.renderTargetDescriptions.push(desc) - 1; } private createResolveTextureID(renderTargetID: number): number { - return this.currentGraph.resolveTextureRenderTargetIDs.push(renderTargetID) - 1; + return ( + this.currentGraph.resolveTextureRenderTargetIDs.push(renderTargetID) - 1 + ); } /** @@ -125,7 +135,9 @@ export class RenderGraph implements RGGraphBuilder { return renderPass.resolveTextureOutputIDs[attachmentSlot]; } - private findPassForResolveRenderTarget(renderTargetID: number): RenderGraphPass { + private findPassForResolveRenderTarget( + renderTargetID: number, + ): RenderGraphPass { // Find the last pass that rendered to this render target, and resolve it now. // If you wanted a previous snapshot copy of it, you should have created a separate, @@ -138,30 +150,48 @@ export class RenderGraph implements RGGraphBuilder { // Check which attachment we're in. This could possibly be explicit from the user, but it's // easy enough to find... - const attachmentSlot: RGAttachmentSlot = renderPass.renderTargetIDs.indexOf(renderTargetID); + const attachmentSlot: RGAttachmentSlot = + renderPass.renderTargetIDs.indexOf(renderTargetID); // Check that the pass isn't resolving its attachment to another texture. Can't do both! - assert(renderPass.resolveTextureOutputExternalTextures[attachmentSlot] === undefined); + assert( + renderPass.resolveTextureOutputExternalTextures[attachmentSlot] === + undefined, + ); return renderPass; } resolveRenderTarget(renderTargetID: number): number { const renderPass = this.findPassForResolveRenderTarget(renderTargetID); - const attachmentSlot: RGAttachmentSlot = renderPass.renderTargetIDs.indexOf(renderTargetID); - return this.resolveRenderTargetPassAttachmentSlot(renderPass, attachmentSlot); + const attachmentSlot: RGAttachmentSlot = + renderPass.renderTargetIDs.indexOf(renderTargetID); + return this.resolveRenderTargetPassAttachmentSlot( + renderPass, + attachmentSlot, + ); } - resolveRenderTargetToExternalTexture(renderTargetID: number, texture: Texture): void { + resolveRenderTargetToExternalTexture( + renderTargetID: number, + texture: Texture, + level = 0, + ): void { const renderPass = this.findPassForResolveRenderTarget(renderTargetID); - const attachmentSlot: RGAttachmentSlot = renderPass.renderTargetIDs.indexOf(renderTargetID); + const attachmentSlot: RGAttachmentSlot = + renderPass.renderTargetIDs.indexOf(renderTargetID); // We shouldn't be resolving to a resolve texture ID in this case. assert(renderPass.resolveTextureOutputIDs[attachmentSlot] === undefined); renderPass.resolveTextureOutputExternalTextures[attachmentSlot] = texture; + renderPass.resolveTextureOutputExternalTextureLevel[attachmentSlot] = level; } - getRenderTargetDescription(renderTargetID: number): Readonly { - return assertExists(this.currentGraph.renderTargetDescriptions[renderTargetID]); + getRenderTargetDescription( + renderTargetID: number, + ): Readonly { + return assertExists( + this.currentGraph.renderTargetDescriptions[renderTargetID], + ); } //#endregion @@ -180,7 +210,8 @@ export class RenderGraph implements RGGraphBuilder { this.renderTargetOutputCount[renderTargetID]++; - if (pass.renderTargetExtraRefs[slot]) this.renderTargetOutputCount[renderTargetID]++; + if (pass.renderTargetExtraRefs[slot]) + this.renderTargetOutputCount[renderTargetID]++; } for (let i = 0; i < pass.resolveTextureInputIDs.length; i++) { @@ -189,7 +220,8 @@ export class RenderGraph implements RGGraphBuilder { this.resolveTextureUseCount[resolveTextureID]++; - const renderTargetID = graph.resolveTextureRenderTargetIDs[resolveTextureID]; + const renderTargetID = + graph.resolveTextureRenderTargetIDs[resolveTextureID]; this.renderTargetResolveCount[renderTargetID]++; } } @@ -205,7 +237,10 @@ export class RenderGraph implements RGGraphBuilder { if (!this.renderTargetAliveForID[renderTargetID]) { const desc = graph.renderTargetDescriptions[renderTargetID]; const newRenderTarget = this.acquireRenderTargetForDescription(desc); - newRenderTarget.setDebugName(this.device, graph.renderTargetDebugNames[renderTargetID]); + newRenderTarget.setDebugName( + this.device, + graph.renderTargetDebugNames[renderTargetID], + ); this.renderTargetAliveForID[renderTargetID] = newRenderTarget; } @@ -218,7 +253,9 @@ export class RenderGraph implements RGGraphBuilder { ): RGRenderTarget | null { if (renderTargetID === undefined) return null; - const renderTarget = assertExists(this.renderTargetAliveForID[renderTargetID]); + const renderTarget = assertExists( + this.renderTargetAliveForID[renderTargetID], + ); if (forOutput) { assert(this.renderTargetOutputCount[renderTargetID] > 0); @@ -246,17 +283,24 @@ export class RenderGraph implements RGGraphBuilder { graph: GraphImpl, resolveTextureID: number, ): Texture { - const renderTargetID = graph.resolveTextureRenderTargetIDs[resolveTextureID]; + const renderTargetID = + graph.resolveTextureRenderTargetIDs[resolveTextureID]; assert(this.resolveTextureUseCount[resolveTextureID] > 0); this.resolveTextureUseCount[resolveTextureID]--; - const renderTarget = assertExists(this.releaseRenderTargetForID(renderTargetID, false)); + const renderTarget = assertExists( + this.releaseRenderTargetForID(renderTargetID, false), + ); - if (this.singleSampledTextureForResolveTextureID[resolveTextureID] !== undefined) { + if ( + this.singleSampledTextureForResolveTextureID[resolveTextureID] !== + undefined + ) { // The resolved texture belonging to this RT is backed by our own single-sampled texture. - const singleSampledTexture = this.singleSampledTextureForResolveTextureID[resolveTextureID]; + const singleSampledTexture = + this.singleSampledTextureForResolveTextureID[resolveTextureID]; if (this.resolveTextureUseCount[resolveTextureID] === 0) { // Release this single-sampled texture back to the pool, if this is the last use of it. @@ -286,6 +330,7 @@ export class RenderGraph implements RGGraphBuilder { let resolveTo: Texture | null = null; let store = false; + let level = 0; if (this.renderTargetOutputCount[renderTargetID] > 1) { // A future pass is going to render to this RT, we need to store the results. @@ -293,53 +338,84 @@ export class RenderGraph implements RGGraphBuilder { } if (hasResolveTextureOutputID) { - assert(graph.resolveTextureRenderTargetIDs[resolveTextureOutputID] === renderTargetID); + assert( + graph.resolveTextureRenderTargetIDs[resolveTextureOutputID] === + renderTargetID, + ); assert(this.resolveTextureUseCount[resolveTextureOutputID] > 0); assert(this.renderTargetOutputCount[renderTargetID] > 0); - const renderTarget = assertExists(this.renderTargetAliveForID[renderTargetID]); + const renderTarget = assertExists( + this.renderTargetAliveForID[renderTargetID], + ); // If we're the last user of this RT, then we don't need to resolve -- the texture itself will be enough. // Note that this isn't exactly an exactly correct algorithm. If we have pass A writing to RenderTargetA, // pass B resolving RenderTargetA to ResolveTextureA, and pass C writing to RenderTargetA, then we don't // strictly need to copy, but in order to determine that at the time of pass A, we'd need a much fancier // schedule than just tracking refcounts... - if (renderTarget.texture !== null && this.renderTargetOutputCount[renderTargetID] === 1) { + if ( + renderTarget.texture !== null && + this.renderTargetOutputCount[renderTargetID] === 1 + ) { resolveTo = null; store = true; } else { - if (!this.singleSampledTextureForResolveTextureID[resolveTextureOutputID]) { - const desc = assertExists(graph.renderTargetDescriptions[renderTargetID]); + if ( + !this.singleSampledTextureForResolveTextureID[resolveTextureOutputID] + ) { + const desc = assertExists( + graph.renderTargetDescriptions[renderTargetID], + ); this.singleSampledTextureForResolveTextureID[resolveTextureOutputID] = this.acquireSingleSampledTextureForDescription(desc); this.device.setResourceName( - this.singleSampledTextureForResolveTextureID[resolveTextureOutputID].texture, + this.singleSampledTextureForResolveTextureID[resolveTextureOutputID] + .texture, renderTarget.debugName + ` (Resolve ${resolveTextureOutputID})`, ); } - resolveTo = this.singleSampledTextureForResolveTextureID[resolveTextureOutputID].texture; + resolveTo = + this.singleSampledTextureForResolveTextureID[resolveTextureOutputID] + .texture; } } else if (hasExternalTexture) { resolveTo = externalTexture; + level = pass.resolveTextureOutputExternalTextureLevel[slot]; } else { resolveTo = null; } - return { resolveTo, store }; + return { resolveTo, store, level }; } private schedulePass(graph: GraphImpl, pass: RenderGraphPass) { - const depthStencilRenderTargetID = pass.renderTargetIDs[RGAttachmentSlot.DepthStencil]; + const depthStencilRenderTargetID = + pass.renderTargetIDs[RGAttachmentSlot.DepthStencil]; - for (let slot = RGAttachmentSlot.Color0; slot <= RGAttachmentSlot.ColorMax; slot++) { + for ( + let slot = RGAttachmentSlot.Color0; + slot <= RGAttachmentSlot.ColorMax; + slot++ + ) { const colorRenderTargetID = pass.renderTargetIDs[slot]; - const colorRenderTarget = this.acquireRenderTargetForID(graph, colorRenderTargetID); + const colorRenderTarget = this.acquireRenderTargetForID( + graph, + colorRenderTargetID, + ); pass.renderTargets[slot] = colorRenderTarget; pass.descriptor.colorAttachment[slot] = colorRenderTarget !== null ? colorRenderTarget.attachment : null; - const { resolveTo, store } = this.determineResolveParam(graph, pass, slot); + pass.descriptor.colorAttachmentLevel[slot] = + pass.renderTargetLevels[slot]; + const { resolveTo, store, level } = this.determineResolveParam( + graph, + pass, + slot, + ); pass.descriptor.colorResolveTo[slot] = resolveTo; + pass.descriptor.colorResolveToLevel[slot] = level; pass.descriptor.colorStore[slot] = store; pass.descriptor.colorClearColor[slot] = colorRenderTarget !== null && colorRenderTarget.needsClear @@ -351,9 +427,12 @@ export class RenderGraph implements RGGraphBuilder { graph, depthStencilRenderTargetID, ); - pass.renderTargets[RGAttachmentSlot.DepthStencil] = depthStencilRenderTarget; + pass.renderTargets[RGAttachmentSlot.DepthStencil] = + depthStencilRenderTarget; pass.descriptor.depthStencilAttachment = - depthStencilRenderTarget !== null ? depthStencilRenderTarget.attachment : null; + depthStencilRenderTarget !== null + ? depthStencilRenderTarget.attachment + : null; const { resolveTo, store } = this.determineResolveParam( graph, pass, @@ -363,11 +442,13 @@ export class RenderGraph implements RGGraphBuilder { pass.descriptor.depthStencilStore = store; pass.descriptor.depthClearValue = depthStencilRenderTarget !== null && depthStencilRenderTarget.needsClear - ? graph.renderTargetDescriptions[depthStencilRenderTargetID].depthClearValue + ? graph.renderTargetDescriptions[depthStencilRenderTargetID] + .depthClearValue : 'load'; pass.descriptor.stencilClearValue = depthStencilRenderTarget !== null && depthStencilRenderTarget.needsClear - ? graph.renderTargetDescriptions[depthStencilRenderTargetID].stencilClearValue + ? graph.renderTargetDescriptions[depthStencilRenderTargetID] + .stencilClearValue : 'load'; let rtWidth = 0; @@ -377,35 +458,32 @@ export class RenderGraph implements RGGraphBuilder { const renderTarget = pass.renderTargets[i]; if (!renderTarget) continue; + const width = renderTarget.width >>> pass.renderTargetLevels[i]; + const height = renderTarget.height >>> pass.renderTargetLevels[i]; + if (rtWidth === 0) { - rtWidth = renderTarget.width; - rtHeight = renderTarget.height; + rtWidth = width; + rtHeight = height; rtSampleCount = renderTarget.sampleCount; } - assert(renderTarget.width === rtWidth); - assert(renderTarget.height === rtHeight); + assert(width === rtWidth); + assert(height === rtHeight); assert(renderTarget.sampleCount === rtSampleCount); renderTarget.needsClear = false; } if (rtWidth > 0 && rtHeight > 0) { - const x = rtWidth * pass.viewport.x; - const y = rtHeight * pass.viewport.y; - const w = rtWidth * pass.viewport.w; - const h = rtHeight * pass.viewport.h; - pass.viewportX = x; - pass.viewportY = y; - pass.viewportW = w; - pass.viewportH = h; + pass.viewportX *= rtWidth; + pass.viewportY *= rtHeight; + pass.viewportW *= rtWidth; + pass.viewportH *= rtHeight; } for (let i = 0; i < pass.resolveTextureInputIDs.length; i++) { const resolveTextureID = pass.resolveTextureInputIDs[i]; - pass.resolveTextureInputTextures[i] = this.acquireResolveTextureInputTextureForID( - graph, - resolveTextureID, - ); + pass.resolveTextureInputTextures[i] = + this.acquireResolveTextureInputTextureForID(graph, resolveTextureID); } for (let i = 0; i < pass.renderTargetIDs.length; i++) @@ -422,22 +500,37 @@ export class RenderGraph implements RGGraphBuilder { assert(this.resolveTextureUseCount.length === 0); // Go through and increment the age of everything in our dead pools to mark that it's old. - for (let i = 0; i < this.renderTargetDeadPool.length; i++) this.renderTargetDeadPool[i].age++; + for (let i = 0; i < this.renderTargetDeadPool.length; i++) + this.renderTargetDeadPool[i].age++; for (let i = 0; i < this.singleSampledTextureDeadPool.length; i++) this.singleSampledTextureDeadPool[i].age++; // Schedule our resources -- first, count up all uses of resources, then hand them out. // Initialize our accumulators. - fillArray(this.renderTargetOutputCount, graph.renderTargetDescriptions.length, 0); - fillArray(this.renderTargetResolveCount, graph.renderTargetDescriptions.length, 0); - fillArray(this.resolveTextureUseCount, graph.resolveTextureRenderTargetIDs.length, 0); + fillArray( + this.renderTargetOutputCount, + graph.renderTargetDescriptions.length, + 0, + ); + fillArray( + this.renderTargetResolveCount, + graph.renderTargetDescriptions.length, + 0, + ); + fillArray( + this.resolveTextureUseCount, + graph.resolveTextureRenderTargetIDs.length, + 0, + ); // Count. - for (let i = 0; i < graph.passes.length; i++) this.scheduleAddUseCount(graph, graph.passes[i]); + for (let i = 0; i < graph.passes.length; i++) + this.scheduleAddUseCount(graph, graph.passes[i]); // Now hand out resources. - for (let i = 0; i < graph.passes.length; i++) this.schedulePass(graph, graph.passes[i]); + for (let i = 0; i < graph.passes.length; i++) + this.schedulePass(graph, graph.passes[i]); // Double-check that all resources were handed out. for (let i = 0; i < this.renderTargetOutputCount.length; i++) @@ -481,7 +574,12 @@ export class RenderGraph implements RGGraphBuilder { const renderPass = this.device.createRenderPass(pass.descriptor); renderPass.beginDebugGroup(pass.debugName); - renderPass.setViewport(pass.viewportX, pass.viewportY, pass.viewportW, pass.viewportH); + renderPass.setViewport( + pass.viewportX, + pass.viewportY, + pass.viewportW, + pass.viewportH, + ); if (pass.execFunc !== null) pass.execFunc(renderPass, this); @@ -495,9 +593,12 @@ export class RenderGraph implements RGGraphBuilder { private execGraph(graph: GraphImpl) { this.scheduleGraph(graph); + + this.device.beginFrame(); graph.passes.forEach((pass) => { this.execPass(pass); }); + this.device.endFrame(); // Clear our transient scope state. this.singleSampledTextureForResolveTextureID.length = 0; } @@ -563,7 +664,11 @@ export class RenderGraph implements RGGraphBuilder { // At the time this is called, we shouldn't have anything alive. for (let i = 0; i < this.renderTargetAliveForID.length; i++) assert(this.renderTargetAliveForID[i] === undefined); - for (let i = 0; i < this.singleSampledTextureForResolveTextureID.length; i++) + for ( + let i = 0; + i < this.singleSampledTextureForResolveTextureID.length; + i++ + ) assert(this.singleSampledTextureForResolveTextureID[i] === undefined); for (let i = 0; i < this.renderTargetDeadPool.length; i++) diff --git a/packages/g-plugin-device-renderer/src/render/RenderGraphPass.ts b/packages/g-plugin-device-renderer/src/render/RenderGraphPass.ts index cc81bee99..379ea5735 100644 --- a/packages/g-plugin-device-renderer/src/render/RenderGraphPass.ts +++ b/packages/g-plugin-device-renderer/src/render/RenderGraphPass.ts @@ -1,9 +1,4 @@ -import type { - NormalizedViewportCoords, - QueryPool, - RenderPassDescriptor, - Texture, -} from '../platform'; +import type { QueryPool, RenderPassDescriptor, Texture } from '../platform'; import { assert } from '../platform/utils'; import type { IRenderGraphPass, @@ -11,23 +6,22 @@ import type { PassPostFunc, RGAttachmentSlot, } from './interfaces'; -import { IdentityViewportCoords } from './interfaces'; import type { RGRenderTarget } from './RenderTarget'; export class RenderGraphPass implements IRenderGraphPass { // RenderTargetAttachmentSlot => renderTargetID renderTargetIDs: number[] = []; + renderTargetLevels: number[] = []; // RenderTargetAttachmentSlot => resolveTextureID resolveTextureOutputIDs: number[] = []; // RenderTargetAttachmentSlot => Texture resolveTextureOutputExternalTextures: Texture[] = []; + resolveTextureOutputExternalTextureLevel: number[] = []; // List of resolveTextureIDs that we have a reference to. resolveTextureInputIDs: number[] = []; // RGAttachmentSlot => refcount. renderTargetExtraRefs: boolean[] = []; - viewport: NormalizedViewportCoords = IdentityViewportCoords; - resolveTextureInputTextures: Texture[] = []; renderTargets: (RGRenderTarget | null)[] = []; @@ -35,7 +29,9 @@ export class RenderGraphPass implements IRenderGraphPass { // Execution state computed by scheduling. descriptor: RenderPassDescriptor = { colorAttachment: [], + colorAttachmentLevel: [], colorResolveTo: [], + colorResolveToLevel: [], colorStore: [], depthStencilAttachment: null, depthStencilResolveTo: null, @@ -48,8 +44,8 @@ export class RenderGraphPass implements IRenderGraphPass { viewportX = 0; viewportY = 0; - viewportW = 0; - viewportH = 0; + viewportW = 1; + viewportH = 1; // Execution callback from user. execFunc: PassExecFunc | null = null; @@ -67,16 +63,21 @@ export class RenderGraphPass implements IRenderGraphPass { this.debugThumbnails[attachmentSlot] = true; } - setViewport(viewport: Readonly): void { - this.viewport = viewport; + setViewport(x: number, y: number, w: number, h: number): void { + this.viewportX = x; + this.viewportY = y; + this.viewportW = w; + this.viewportH = h; } attachRenderTargetID( attachmentSlot: RGAttachmentSlot, renderTargetID: number, + level = 0, ): void { assert(this.renderTargetIDs[attachmentSlot] === undefined); this.renderTargetIDs[attachmentSlot] = renderTargetID; + this.renderTargetLevels[attachmentSlot] = level; } attachResolveTexture(resolveTextureID: number): void { diff --git a/packages/g-plugin-device-renderer/src/render/RenderInst.ts b/packages/g-plugin-device-renderer/src/render/RenderInst.ts index 92e044d52..ca31f9fa4 100644 --- a/packages/g-plugin-device-renderer/src/render/RenderInst.ts +++ b/packages/g-plugin-device-renderer/src/render/RenderInst.ts @@ -26,6 +26,7 @@ import type { RenderCache } from './RenderCache'; import { fillVec4 } from './utils'; export enum RenderInstFlags { + None = 0, Indexed = 1 << 0, AllowSkippingIfPipelineNotReady = 1 << 1, @@ -578,7 +579,6 @@ export class RenderInst { this.dynamicUniformBufferByteOffsets, ); - // if (this.drawInstanceCount > 0) { if (this.drawInstanceCount > 1) { assert(!!(this.flags & RenderInstFlags.Indexed)); passRenderer.drawIndexedInstanced( diff --git a/packages/g-plugin-device-renderer/src/render/interfaces.ts b/packages/g-plugin-device-renderer/src/render/interfaces.ts index a601890e9..97b12d478 100644 --- a/packages/g-plugin-device-renderer/src/render/interfaces.ts +++ b/packages/g-plugin-device-renderer/src/render/interfaces.ts @@ -1,10 +1,4 @@ -import type { - NormalizedViewportCoords, - QueryPool, - RenderPass, - RenderTarget, - Texture, -} from '../platform'; +import type { QueryPool, RenderPass, RenderTarget, Texture } from '../platform'; import type { RGRenderTargetDescription } from './RenderTargetDescription'; export enum RGAttachmentSlot { @@ -22,15 +16,11 @@ export interface RGPassScope { getRenderTargetTexture: (slot: RGAttachmentSlot) => Texture | null; } -export const IdentityViewportCoords: Readonly = { - x: 0, - y: 0, - w: 1, - h: 1, -}; - export type PassSetupFunc = (renderPass: IRenderGraphPass) => void; -export type PassExecFunc = (passRenderer: RenderPass, scope: RGPassScope) => void; +export type PassExecFunc = ( + passRenderer: RenderPass, + scope: RGPassScope, +) => void; export type PassPostFunc = (scope: RGPassScope) => void; export interface IRenderGraphPass { @@ -48,7 +38,10 @@ export interface IRenderGraphPass { * Attach the given renderTargetID to the given attachmentSlot. * This determines which render targets this pass will render to. */ - attachRenderTargetID: (attachmentSlot: RGAttachmentSlot, renderTargetID: number) => void; + attachRenderTargetID: ( + attachmentSlot: RGAttachmentSlot, + renderTargetID: number, + ) => void; /** * Attach the occlusion query pool used by this rendering pass. @@ -56,9 +49,10 @@ export interface IRenderGraphPass { attachOcclusionQueryPool: (queryPool: QueryPool) => void; /** - * Set the viewport used by this rendering pass. + * Set the viewport for the given render pass in *normalized* coordinates (0..1). + * Not required; defaults to full viewport. */ - setViewport: (viewport: Readonly) => void; + setViewport(x: number, y: number, w: number, h: number): void; /** * Attach the resolve texture ID to the given pass. All resolve textures used within the pass @@ -103,7 +97,10 @@ export interface RGGraphBuilder { * use the {@see GfxrPassScope} given to the pass's execution or post callbacks, however * this usage should be rarer than the resolve case. */ - createRenderTargetID: (desc: Readonly, debugName: string) => number; + createRenderTargetID: ( + desc: Readonly, + debugName: string, + ) => number; /** * Resolve the render target in slot {@param attachmentSlot} of pass {@param pass}, and return @@ -141,13 +138,18 @@ export interface RGGraphBuilder { * * Warning: This API might change in the near future. */ - resolveRenderTargetToExternalTexture: (renderTargetID: number, texture: Texture) => void; + resolveRenderTargetToExternalTexture: ( + renderTargetID: number, + texture: Texture, + ) => void; /** * Return the description that a render target was created with. This allows the creator to * not have to pass information to any dependent modules to derive from it. */ - getRenderTargetDescription: (renderTargetID: number) => Readonly; + getRenderTargetDescription: ( + renderTargetID: number, + ) => Readonly; /** * Internal API. @@ -158,6 +160,9 @@ export interface RGGraphBuilder { export interface RGGraphBuilderDebug { getPasses: () => IRenderGraphPass[]; getPassDebugThumbnails: (pass: IRenderGraphPass) => boolean[]; - getPassRenderTargetID: (pass: IRenderGraphPass, slot: RGAttachmentSlot) => number; + getPassRenderTargetID: ( + pass: IRenderGraphPass, + slot: RGAttachmentSlot, + ) => number; getRenderTargetIDDebugName: (renderTargetID: number) => string; } diff --git a/packages/g-plugin-gpgpu/src/Kernel.ts b/packages/g-plugin-gpgpu/src/Kernel.ts index e374db934..0676d8cae 100644 --- a/packages/g-plugin-gpgpu/src/Kernel.ts +++ b/packages/g-plugin-gpgpu/src/Kernel.ts @@ -142,7 +142,7 @@ export class Kernel { dispatchParams = [x, y, z]; } - const computePass = this.device.createComputePass({}); + const computePass = this.device.createComputePass(); computePass.setPipeline(this.computePipeline); const uniforms = this.buffers.filter( @@ -171,7 +171,7 @@ export class Kernel { }); // fixed bind group 0 - computePass.setBindings(0, bindings); + computePass.setBindings(0, bindings, []); computePass.dispatch(...dispatchParams); this.device.submitPass(computePass); } diff --git a/packages/g-plugin-physx/src/PhysXPlugin.ts b/packages/g-plugin-physx/src/PhysXPlugin.ts index 307ad7119..2a961ed9c 100644 --- a/packages/g-plugin-physx/src/PhysXPlugin.ts +++ b/packages/g-plugin-physx/src/PhysXPlugin.ts @@ -26,7 +26,6 @@ export class PhysXPlugin implements RenderingPlugin { private physics: any; private scene: any; private bodies: Map = new Map(); - private pendingDisplayObjects: DisplayObject[] = []; apply(context: RenderingPluginContext) { const { renderingService, renderingContext } = context; @@ -38,46 +37,41 @@ export class PhysXPlugin implements RenderingPlugin { if (PhysX) { this.addActor(target); - } else { - this.pendingDisplayObjects.push(target); } }; - renderingService.hooks.init.tap(PhysXPlugin.tag, () => { + renderingService.hooks.initAsync.tapPromise(PhysXPlugin.tag, async () => { canvas.addEventListener(ElementEvent.MOUNTED, handleMounted); - (async () => { - this.PhysX = (await this.initPhysX()) as any; - this.createScene(); - this.handlePendingDisplayObjects(); - - // do simulation each frame - renderingContext.root.ownerDocument.defaultView.addEventListener( - CanvasEvent.BEFORE_RENDER, - () => { - if (this.scene) { - this.scene.simulate(1 / 60, true); - this.scene.fetchResults(true); - - this.bodies.forEach(({ body, displayObject }) => { - const transform = body.getGlobalPose(); - const { translation, rotation } = transform; - displayObject.setPosition( - translation.x, - translation.y, - translation.z, - ); - displayObject.setRotation( - rotation.x, - rotation.y, - rotation.z, - rotation.w, - ); - }); - } - }, - ); - })(); + this.PhysX = (await this.initPhysX()) as any; + this.createScene(); + + // do simulation each frame + renderingContext.root.ownerDocument.defaultView.addEventListener( + CanvasEvent.BEFORE_RENDER, + () => { + if (this.scene) { + this.scene.simulate(1 / 60, true); + this.scene.fetchResults(true); + + this.bodies.forEach(({ body, displayObject }) => { + const transform = body.getGlobalPose(); + const { translation, rotation } = transform; + displayObject.setPosition( + translation.x, + translation.y, + translation.z, + ); + displayObject.setRotation( + rotation.x, + rotation.y, + rotation.z, + rotation.w, + ); + }); + } + }, + ); }); renderingService.hooks.destroy.tap(PhysXPlugin.tag, () => { @@ -237,11 +231,4 @@ export class PhysXPlugin implements RenderingPlugin { }); } } - - private handlePendingDisplayObjects() { - this.pendingDisplayObjects.forEach((object) => { - this.addActor(object); - }); - this.pendingDisplayObjects = []; - } } diff --git a/packages/g-plugin-webgl-device/src/WebGLDeviceContribution.ts b/packages/g-plugin-webgl-device/src/WebGLDeviceContribution.ts index fb2cb4f17..a50533194 100644 --- a/packages/g-plugin-webgl-device/src/WebGLDeviceContribution.ts +++ b/packages/g-plugin-webgl-device/src/WebGLDeviceContribution.ts @@ -16,7 +16,7 @@ export class WebGLDeviceContribution implements DeviceContribution { // @see https://webglfundamentals.org/webgl/lessons/webgl-and-alpha.html // premultipliedAlpha: true, }; - // this.handleContextEvents($canvas); + this.handleContextEvents($canvas); const { targets } = this.pluginOptions; @@ -45,20 +45,27 @@ export class WebGLDeviceContribution implements DeviceContribution { }); } - // private handleContextEvents($canvas: HTMLCanvasElement) { - // const { onContextLost, onContextRestored, onContextCreationError } = this.pluginOptions; - // // bind context event listeners - // if (onContextCreationError) { - // // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/webglcontextcreationerror_event - // $canvas.addEventListener('webglcontextcreationerror', onContextCreationError, false); - // } - // if (onContextLost) { - // $canvas.addEventListener('webglcontextlost', onContextLost, false); - // } - // if (onContextRestored) { - // $canvas.addEventListener('webglcontextrestored', onContextRestored, false); - // } - - // // TODO: https://github.com/gpuweb/gpuweb/blob/main/design/ErrorHandling.md#fatal-errors-requestadapter-requestdevice-and-devicelost - // } + private handleContextEvents($canvas: HTMLCanvasElement) { + const { onContextLost, onContextRestored, onContextCreationError } = + this.pluginOptions; + // bind context event listeners + if (onContextCreationError) { + // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/webglcontextcreationerror_event + $canvas.addEventListener( + 'webglcontextcreationerror', + onContextCreationError, + false, + ); + } + if (onContextLost) { + $canvas.addEventListener('webglcontextlost', onContextLost, false); + } + if (onContextRestored) { + $canvas.addEventListener( + 'webglcontextrestored', + onContextRestored, + false, + ); + } + } } diff --git a/packages/g-plugin-webgl-device/src/platform/Buffer.ts b/packages/g-plugin-webgl-device/src/platform/Buffer.ts index d051c53fa..0a5ee46be 100644 --- a/packages/g-plugin-webgl-device/src/platform/Buffer.ts +++ b/packages/g-plugin-webgl-device/src/platform/Buffer.ts @@ -118,7 +118,7 @@ export class Buffer_GL extends ResourceBase_GL implements Buffer { setSubData( dstByteOffset: number, - data: ArrayBufferView, + data: Uint8Array, srcByteOffset = 0, byteSize: number = data.byteLength - srcByteOffset, ): void { diff --git a/packages/g-plugin-webgl-device/src/platform/ComputePass.ts b/packages/g-plugin-webgl-device/src/platform/ComputePass.ts index e925adbe4..689753462 100644 --- a/packages/g-plugin-webgl-device/src/platform/ComputePass.ts +++ b/packages/g-plugin-webgl-device/src/platform/ComputePass.ts @@ -1,14 +1,14 @@ import type { Bindings, ComputePass, - ComputePassDescriptor, ComputePipeline, } from '@antv/g-plugin-device-renderer'; // import { assert, assertExists } from '@antv/g-plugin-device-renderer'; // import type { ComputePipeline_GL } from './ComputePipeline'; export class ComputePass_GL implements ComputePass { - descriptor: ComputePassDescriptor; + beginDebugGroup: (name: string) => void; + endDebugGroup: () => void; /** * @see https://www.w3.org/TR/webgpu/#dom-gpucomputepassencoder-dispatch @@ -24,7 +24,7 @@ export class ComputePass_GL implements ComputePass { /** * @see https://www.w3.org/TR/webgpu/#dom-gpucommandencoder-begincomputepass */ - beginComputePass(computePassDescriptor: ComputePassDescriptor): void { + beginComputePass(): void { // assert(this.gpuComputePassEncoder === null); // this.setComputePassDescriptor(computePassDescriptor); // this.gpuComputePassEncoder = this.commandEncoder.beginComputePass( @@ -42,8 +42,4 @@ export class ComputePass_GL implements ComputePass { // const bindings = bindings_ as Bindings_WebGPU; // this.gpuComputePassEncoder.setBindGroup(bindingLayoutIndex, bindings.gpuBindGroup[0]); } - - private setComputePassDescriptor(descriptor: ComputePassDescriptor): void { - this.descriptor = descriptor; - } } diff --git a/packages/g-plugin-webgl-device/src/platform/Device.ts b/packages/g-plugin-webgl-device/src/platform/Device.ts index 31b7cf1ac..464385cea 100644 --- a/packages/g-plugin-webgl-device/src/platform/Device.ts +++ b/packages/g-plugin-webgl-device/src/platform/Device.ts @@ -6,7 +6,6 @@ import type { Buffer, BufferDescriptor, ComputePass, - ComputePassDescriptor, ComputePipeline, ComputePipelineDescriptor, DebugGroup, @@ -155,7 +154,9 @@ export class Device_GL implements SwapChain, Device { // Cached GL driver state private currentColorAttachments: (RenderTarget_GL | null)[] = []; + private currentColorAttachmentLevels: number[] = []; private currentColorResolveTos: (Texture_GL | null)[] = []; + private currentColorResolveToLevels: number[] = []; private currentDepthStencilAttachment: RenderTarget_GL | null; private currentDepthStencilResolveTo: Texture_GL | null = null; private currentSampleCount = -1; @@ -476,25 +477,9 @@ export class Device_GL implements SwapChain, Device { return this.scTexture!; } - present(): void { - // const gl = this.gl; - // // Force alpha to white. - // if (this.currentMegaState.attachmentsState[0].channelWriteMask !== ChannelWriteMask.Alpha) { - // gl.colorMask(false, false, false, true); - // this.currentMegaState.attachmentsState[0].channelWriteMask = ChannelWriteMask.Alpha; - // } - // // TODO: clear depth & stencil - // // @see https://github.com/visgl/luma.gl/blob/30a1039573/modules/webgl/src/classes/clear.ts - // const { r, g, b, a } = OpaqueBlack; - // if (isWebGL2(gl)) { - // gl.clearBufferfv(gl.COLOR, 0, [r, g, b, a]); - // } else { - // gl.clearColor(r, g, b, a); - // gl.clear(gl.COLOR_BUFFER_BIT); - // } - // @see https://stackoverflow.com/questions/2143240/opengl-glflush-vs-glfinish - // gl.flush(); - } + beginFrame(): void {} + + endFrame(): void {} //#endregion //#region Device @@ -751,16 +736,6 @@ export class Device_GL implements SwapChain, Device { } createTexture(descriptor: TextureDescriptor): Texture { - // const { pixelFormat } = descriptor; - // // @see https://blog.tojicode.com/2012/07/using-webgldepthtexture.html - // if ( - // (pixelFormat === Format.D32F || pixelFormat === Format.D24_S8) && - // !isWebGL2(this.gl) && - // !this.WEBGL_depth_texture - // ) { - // return null; - // } - return new Texture_GL({ id: this.getNextUniqueId(), device: this, @@ -853,7 +828,7 @@ export class Device_GL implements SwapChain, Device { }); } - createComputePass(computePassDescriptor: ComputePassDescriptor): ComputePass { + createComputePass(): ComputePass { return new ComputePass_GL(); } @@ -867,12 +842,10 @@ export class Device_GL implements SwapChain, Device { }); } - // createReadback(byteCount: number): Readback { createReadback(): Readback { return new Readback_GL({ id: this.getNextUniqueId(), device: this, - // byteCount, }); } @@ -893,8 +866,10 @@ export class Device_GL implements SwapChain, Device { const { colorAttachment, + colorAttachmentLevel, colorClearColor, colorResolveTo, + colorResolveToLevel, depthStencilAttachment, depthClearValue, stencilClearValue, @@ -905,7 +880,9 @@ export class Device_GL implements SwapChain, Device { this.setRenderPassParametersColor( i, colorAttachment[i] as RenderTarget_GL | null, + colorAttachmentLevel[i], colorResolveTo[i] as Texture_GL | null, + colorResolveToLevel[i], ); } this.setRenderPassParametersDepthStencil( @@ -964,6 +941,7 @@ export class Device_GL implements SwapChain, Device { gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, dst, + 0, ); } @@ -972,6 +950,7 @@ export class Device_GL implements SwapChain, Device { gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, src, + 0, ); gl.blitFramebuffer( @@ -1226,6 +1205,7 @@ export class Device_GL implements SwapChain, Device { framebuffer: GLenum, binding: GLenum, attachment: RenderTarget_GL | Texture_GL | null, + level: number, ): void { const gl = this.gl; @@ -1245,7 +1225,7 @@ export class Device_GL implements SwapChain, Device { binding, GL.TEXTURE_2D, getPlatformTexture((attachment as RenderTarget_GL).texture), - 0, + level, ); } } else if (attachment.type === ResourceType.Texture) { @@ -1255,7 +1235,7 @@ export class Device_GL implements SwapChain, Device { binding, GL.TEXTURE_2D, getPlatformTexture(attachment as Texture_GL), - 0, + level, ); } } @@ -1281,12 +1261,14 @@ export class Device_GL implements SwapChain, Device { framebuffer, gl.DEPTH_STENCIL_ATTACHMENT, attachment, + 0, ); } else { this.bindFramebufferAttachment( framebuffer, gl.DEPTH_ATTACHMENT, attachment, + 0, ); } } else if (depth) { @@ -1294,15 +1276,22 @@ export class Device_GL implements SwapChain, Device { framebuffer, gl.DEPTH_ATTACHMENT, attachment, + 0, + ); + this.bindFramebufferAttachment( + framebuffer, + gl.STENCIL_ATTACHMENT, + null, + 0, ); - this.bindFramebufferAttachment(framebuffer, gl.STENCIL_ATTACHMENT, null); } else if (stencil) { this.bindFramebufferAttachment( framebuffer, gl.STENCIL_ATTACHMENT, attachment, + 0, ); - this.bindFramebufferAttachment(framebuffer, gl.DEPTH_ATTACHMENT, null); + this.bindFramebufferAttachment(framebuffer, gl.DEPTH_ATTACHMENT, null, 0); } } @@ -1397,12 +1386,18 @@ export class Device_GL implements SwapChain, Device { private setRenderPassParametersColor( i: number, colorAttachment: RenderTarget_GL | null, + attachmentLevel: number, colorResolveTo: Texture_GL | null, + resolveToLevel: number, ): void { const gl = this.gl; - if (this.currentColorAttachments[i] !== colorAttachment) { + if ( + this.currentColorAttachments[i] !== colorAttachment || + this.currentColorAttachmentLevels[i] !== attachmentLevel + ) { this.currentColorAttachments[i] = colorAttachment; + this.currentColorAttachmentLevels[i] = attachmentLevel; // disable MRT in WebGL1 if (isWebGL2(gl) || i === 0) { @@ -1411,14 +1406,19 @@ export class Device_GL implements SwapChain, Device { (isWebGL2(gl) ? GL.COLOR_ATTACHMENT0 : GL.COLOR_ATTACHMENT0_WEBGL) + i, colorAttachment, + attachmentLevel, ); } this.resolveColorAttachmentsChanged = true; } - if (this.currentColorResolveTos[i] !== colorResolveTo) { + if ( + this.currentColorResolveTos[i] !== colorResolveTo || + this.currentColorResolveToLevels[i] !== resolveToLevel + ) { this.currentColorResolveTos[i] = colorResolveTo; + this.currentColorResolveToLevels[i] = resolveToLevel; if (colorResolveTo !== null) { this.resolveColorAttachmentsChanged = true; @@ -2117,6 +2117,7 @@ export class Device_GL implements SwapChain, Device { gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorResolveFrom, + this.currentColorAttachmentLevels[i], ); } } @@ -2175,6 +2176,7 @@ export class Device_GL implements SwapChain, Device { isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorResolveFrom, + this.currentColorAttachmentLevels[i], ); } @@ -2405,9 +2407,11 @@ export class Device_GL implements SwapChain, Device { const blitRenderPass = this.createRenderPass({ colorAttachment: [resolveFrom], + colorResolveToLevel: [0], colorResolveTo: [resolveTo], colorClearColor: [TransparentBlack], colorStore: [true], + colorAttachmentLevel: [0], depthStencilAttachment: null, depthStencilResolveTo: null, depthStencilStore: true, diff --git a/packages/g-plugin-webgpu-device/src/WebGPUDeviceContribution.ts b/packages/g-plugin-webgpu-device/src/WebGPUDeviceContribution.ts index e00426263..6c120944a 100644 --- a/packages/g-plugin-webgpu-device/src/WebGPUDeviceContribution.ts +++ b/packages/g-plugin-webgpu-device/src/WebGPUDeviceContribution.ts @@ -32,6 +32,14 @@ export class WebGPUDeviceContribution implements DeviceContribution { ); const device = await adapter.requestDevice({ requiredFeatures }); + // @see https://github.com/gpuweb/gpuweb/blob/main/design/ErrorHandling.md#fatal-errors-requestadapter-requestdevice-and-devicelost + const { onContextLost } = this.pluginOptions; + device.lost.then(() => { + if (onContextLost) { + onContextLost(); + } + }); + if (device === null) return null; const context = $canvas.getContext('webgpu'); @@ -39,7 +47,7 @@ export class WebGPUDeviceContribution implements DeviceContribution { if (!context) return null; try { - await init('/glsl_wgsl_compiler_bg.wasm'); + await init(this.pluginOptions.shaderCompilerPath); } catch (e) {} return new Device_WebGPU(adapter, device, $canvas, context, glsl_compile); } diff --git a/packages/g-plugin-webgpu-device/src/index.ts b/packages/g-plugin-webgpu-device/src/index.ts index 5259902c3..e01cea79b 100644 --- a/packages/g-plugin-webgpu-device/src/index.ts +++ b/packages/g-plugin-webgpu-device/src/index.ts @@ -8,13 +8,12 @@ export class Plugin extends AbstractRendererPlugin { } init(): void { - // @ts-ignore this.context.deviceContribution = new WebGPUDeviceContribution({ + shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', ...this.options, }); } destroy(): void { - // @ts-ignore delete this.context.deviceContribution; } } diff --git a/packages/g-plugin-webgpu-device/src/interfaces.ts b/packages/g-plugin-webgpu-device/src/interfaces.ts index 29dd65e01..6818692ff 100644 --- a/packages/g-plugin-webgpu-device/src/interfaces.ts +++ b/packages/g-plugin-webgpu-device/src/interfaces.ts @@ -1,5 +1,4 @@ export interface WebGPUDeviceOptions { - onContextCreationError: (e: Event) => void; - onContextLost: (e: Event) => void; - onContextRestored: (e: Event) => void; + shaderCompilerPath: string; + onContextLost: () => void; } diff --git a/packages/g-plugin-webgpu-device/src/platform/Bindings.ts b/packages/g-plugin-webgpu-device/src/platform/Bindings.ts index f01dd6e79..5d9859255 100644 --- a/packages/g-plugin-webgpu-device/src/platform/Bindings.ts +++ b/packages/g-plugin-webgpu-device/src/platform/Bindings.ts @@ -13,7 +13,7 @@ import { getPlatformBuffer, getPlatformSampler } from './utils'; import type { BindGroupLayout, IDevice_WebGPU } from './interfaces'; import { ResourceBase_WebGPU } from './ResourceBase'; import type { Texture_WebGPU } from './Texture'; -import type { RenderPipeline_WebGPU } from './RenderPipeline'; +import { ComputePipeline_WebGPU } from './ComputePipeline'; export class Bindings_WebGPU extends ResourceBase_WebGPU implements Bindings { type: ResourceType.Bindings = ResourceType.Bindings; @@ -98,7 +98,7 @@ export class Bindings_WebGPU extends ResourceBase_WebGPU implements Bindings { const sampler = binding.sampler !== null ? binding.sampler - : this.device.fallbackSampler; + : this.device.getFallbackSampler(samplerEntry); const gpuSampler = getPlatformSampler(sampler); gpuBindGroupEntries[1].push({ binding: numBindings++, @@ -108,11 +108,11 @@ export class Bindings_WebGPU extends ResourceBase_WebGPU implements Bindings { this.gpuBindGroup = gpuBindGroupEntries .filter((entries) => entries.length > 0) - .map((gpuBindGroupEntry, i) => + .map((gpuBindGroupEntries, i) => this.device.device.createBindGroup({ - layout: (pipeline as RenderPipeline_WebGPU).getBindGroupLayout(i), // layout: bindGroupLayout.gpuBindGroupLayout[i], - entries: gpuBindGroupEntry, + layout: (pipeline as ComputePipeline_WebGPU).getBindGroupLayout(i), + entries: gpuBindGroupEntries, }), ); this.bindingLayout = descriptor.bindingLayout; diff --git a/packages/g-plugin-webgpu-device/src/platform/Buffer.ts b/packages/g-plugin-webgpu-device/src/platform/Buffer.ts index 72fdc6a61..3e0f96027 100644 --- a/packages/g-plugin-webgpu-device/src/platform/Buffer.ts +++ b/packages/g-plugin-webgpu-device/src/platform/Buffer.ts @@ -2,8 +2,11 @@ import type { Buffer, BufferDescriptor } from '@antv/g-plugin-device-renderer'; import { BufferUsage, ResourceType } from '@antv/g-plugin-device-renderer'; import type { IDevice_WebGPU } from './interfaces'; import { ResourceBase_WebGPU } from './ResourceBase'; +import { translateBufferUsage } from './utils'; -function isView(viewOrSize: ArrayBufferView | number): viewOrSize is ArrayBufferView { +function isView( + viewOrSize: ArrayBufferView | number, +): viewOrSize is ArrayBufferView { return (viewOrSize as ArrayBufferView).byteLength !== undefined; } @@ -32,38 +35,41 @@ export class Buffer_WebGPU extends ResourceBase_WebGPU implements Buffer { super({ id, device }); const { usage, viewOrSize } = descriptor; + const useMapRead = !!(usage & BufferUsage.MAP_READ); - const alignedLength = isView(viewOrSize) - ? (viewOrSize.byteLength + 3) & ~3 - : (viewOrSize + 3) & ~3; // 4 bytes alignments (because of the upload which requires this) + // const alignedLength = isView(viewOrSize) + // ? viewOrSize.byteLength + // : viewOrSize * 4; // 4 bytes alignments (because of the upload which requires this) - this.usage = usage; + this.usage = translateBufferUsage(usage); // Buffer usages (BufferUsage::(MapRead|CopyDst|Storage)) is invalid. If a buffer usage contains BufferUsage::MapRead the only other allowed usage is BufferUsage::CopyDst. // @see https://www.w3.org/TR/webgpu/#dom-gpubufferusage-copy_dst - if (this.usage & BufferUsage.MAP_READ) { + if (useMapRead) { this.usage = BufferUsage.MAP_READ | BufferUsage.COPY_DST; } const mapBuffer = isView(viewOrSize); - this.size = isView(viewOrSize) ? viewOrSize.byteLength : viewOrSize; + this.size = isView(viewOrSize) ? viewOrSize.byteLength : viewOrSize * 4; this.view = isView(viewOrSize) ? viewOrSize : null; this.gpuBuffer = this.device.device.createBuffer({ usage: this.usage, - size: alignedLength, - mappedAtCreation: mapBuffer, + size: this.size, + mappedAtCreation: useMapRead ? mapBuffer : false, }); - if (mapBuffer) { - const arrayBuffer = this.gpuBuffer.getMappedRange(); - // @ts-expect-error - new viewOrSize.constructor(arrayBuffer).set(viewOrSize); - this.gpuBuffer.unmap(); + if (isView(viewOrSize)) { + this.setSubData(0, new Uint8Array(viewOrSize.buffer)); } } - setSubData(dstByteOffset: number, src: ArrayBufferView, srcByteOffset = 0, byteLength = 0): void { + setSubData( + dstByteOffset: number, + src: Uint8Array, + srcByteOffset = 0, + byteLength = 0, + ): void { const buffer = this.gpuBuffer; byteLength = byteLength || src.byteLength; diff --git a/packages/g-plugin-webgpu-device/src/platform/ComputePass.ts b/packages/g-plugin-webgpu-device/src/platform/ComputePass.ts index d99438318..10e60f6c4 100644 --- a/packages/g-plugin-webgpu-device/src/platform/ComputePass.ts +++ b/packages/g-plugin-webgpu-device/src/platform/ComputePass.ts @@ -1,7 +1,6 @@ import type { Bindings, ComputePass, - ComputePassDescriptor, ComputePipeline, } from '@antv/g-plugin-device-renderer'; import { assert, assertExists } from '@antv/g-plugin-device-renderer'; @@ -10,7 +9,6 @@ import type { ComputePipeline_WebGPU } from './ComputePipeline'; export class ComputePass_WebGPU implements ComputePass { commandEncoder: GPUCommandEncoder | null = null; - descriptor: ComputePassDescriptor; private gpuComputePassDescriptor: GPUComputePassDescriptor; private gpuComputePassEncoder: GPUComputePassEncoder | null = null; @@ -32,9 +30,8 @@ export class ComputePass_WebGPU implements ComputePass { /** * @see https://www.w3.org/TR/webgpu/#dom-gpucommandencoder-begincomputepass */ - beginComputePass(computePassDescriptor: ComputePassDescriptor): void { + beginComputePass(): void { assert(this.gpuComputePassEncoder === null); - this.setComputePassDescriptor(computePassDescriptor); this.gpuComputePassEncoder = this.commandEncoder.beginComputePass( this.gpuComputePassDescriptor, ); @@ -46,12 +43,30 @@ export class ComputePass_WebGPU implements ComputePass { this.gpuComputePassEncoder.setPipeline(gpuComputePipeline); } - setBindings(bindingLayoutIndex: number, bindings_: Bindings): void { + setBindings( + bindingLayoutIndex: number, + bindings_: Bindings, + dynamicByteOffsets: number[], + ): void { const bindings = bindings_ as Bindings_WebGPU; - this.gpuComputePassEncoder.setBindGroup(bindingLayoutIndex, bindings.gpuBindGroup[0]); + this.gpuComputePassEncoder.setBindGroup( + bindingLayoutIndex, + bindings.gpuBindGroup[0], + dynamicByteOffsets, + ); } - private setComputePassDescriptor(descriptor: ComputePassDescriptor): void { - this.descriptor = descriptor; + public beginDebugGroup(name: string): void { + // FIREFOX MISSING + if (this.gpuComputePassEncoder!.pushDebugGroup === undefined) return; + + this.gpuComputePassEncoder!.pushDebugGroup(name); + } + + public endDebugGroup(): void { + // FIREFOX MISSING + if (this.gpuComputePassEncoder!.popDebugGroup === undefined) return; + + this.gpuComputePassEncoder!.popDebugGroup(); } } diff --git a/packages/g-plugin-webgpu-device/src/platform/Device.ts b/packages/g-plugin-webgpu-device/src/platform/Device.ts index 8f9391b66..b9c92a3da 100644 --- a/packages/g-plugin-webgpu-device/src/platform/Device.ts +++ b/packages/g-plugin-webgpu-device/src/platform/Device.ts @@ -6,7 +6,6 @@ import type { Buffer, BufferDescriptor, ComputePass, - ComputePassDescriptor, ComputePipeline, ComputePipelineDescriptor, DebugGroup, @@ -53,6 +52,7 @@ import { TextureUsage, ViewportOrigin, WrapMode, + CompareMode, } from '@antv/g-plugin-device-renderer'; import type { glsl_compile as glsl_compile_ } from '../../../../rust/pkg/glsl_wgsl_compiler'; import { Bindings_WebGPU } from './Bindings'; @@ -70,7 +70,6 @@ import type { TextureShared_WebGPU, } from './interfaces'; import { Program_WebGPU } from './Program'; -// import { FullscreenAlphaClear } from './FullscreenAlphaClear'; import { QueryPool_WebGPU } from './QueryPool'; import { Readback_WebGPU } from './Readback'; import { RenderPass_WebGPU } from './RenderPass'; @@ -80,6 +79,7 @@ import { Texture_WebGPU } from './Texture'; import { getFormatBlockSize, isFormatTextureCompressionBC, + translateBindGroupSamplerBinding, translateBindGroupTextureBinding, translateDepthStencilState, translatePrimitiveState, @@ -92,6 +92,7 @@ import { export class Device_WebGPU implements SwapChain, IDevice_WebGPU { private swapChainWidth = 0; private swapChainHeight = 0; + private swapChainFormat: GPUTextureFormat; private swapChainTextureUsage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST; private _resourceUniqueId = 0; @@ -103,17 +104,18 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { bindingLayoutDescriptorEqual, nullHashFunc, ); + // private frameCommandEncoder: GPUCommandEncoder | null = null; + // private queryPoolsSubmitted: QueryPool_WebGPU[] = []; private fallbackTexture2D: Texture_WebGPU; private fallbackTexture2DDepth: Texture_WebGPU; private fallbackTexture2DArray: Texture_WebGPU; private fallbackTexture3D: Texture_WebGPU; private fallbackTextureCube: Texture_WebGPU; - fallbackSampler: Sampler; + fallbackSamplerFiltering: Sampler; + fallbackSamplerComparison: Sampler; private featureTextureCompressionBC = false; - // private fullscreenAlphaClear: FullscreenAlphaClear; - // VendorInfo readonly platformString: string = 'WebGPU'; readonly glslVersion = `#version 440`; @@ -124,7 +126,6 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { readonly supportsSyncPipelineCompilation: boolean = false; readonly supportMRT: boolean = true; - private adapter: GPUAdapter; device: GPUDevice; private canvas: HTMLCanvasElement | OffscreenCanvas; canvasContext: GPUCanvasContext; @@ -137,7 +138,6 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { canvasContext: GPUCanvasContext, glsl_compile: typeof glsl_compile_, ) { - this.adapter = adapter; this.device = device; this.canvas = canvas; this.canvasContext = canvasContext; @@ -164,13 +164,30 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { SamplerFormatKind.Float, ); - this.fallbackSampler = this.createSampler({ - wrapS: WrapMode.Clamp, - wrapT: WrapMode.Clamp, + this.fallbackSamplerFiltering = this.createSampler({ + wrapS: WrapMode.Repeat, + wrapT: WrapMode.Repeat, minFilter: TexFilterMode.Point, magFilter: TexFilterMode.Point, - mipFilter: MipFilterMode.NoMip, + mipFilter: MipFilterMode.Nearest, }); + this.setResourceName( + this.fallbackSamplerFiltering, + 'Fallback Sampler Filtering', + ); + + this.fallbackSamplerComparison = this.createSampler({ + wrapS: WrapMode.Repeat, + wrapT: WrapMode.Repeat, + minFilter: TexFilterMode.Point, + magFilter: TexFilterMode.Point, + mipFilter: MipFilterMode.Nearest, + compareMode: CompareMode.Always, + }); + this.setResourceName( + this.fallbackSamplerFiltering, + 'Fallback Sampler Filtering', + ); // Firefox doesn't support GPUDevice.features yet... if (this.device.features) { @@ -183,7 +200,15 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { console.error(event.error); }; - // this.fullscreenAlphaClear = new FullscreenAlphaClear(this.device); + this.swapChainFormat = navigator.gpu.getPreferredCanvasFormat(); + // @see https://www.w3.org/TR/webgpu/#canvas-configuration + this.canvasContext.configure({ + device: this.device, + format: this.swapChainFormat, + usage: this.swapChainTextureUsage, + // @see https://www.w3.org/TR/webgpu/#enumdef-gpucanvascompositingalphamode + alphaMode: 'opaque', + }); } // SwapChain @@ -192,16 +217,6 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { return; this.swapChainWidth = width; this.swapChainHeight = height; - // @see https://www.w3.org/TR/webgpu/#canvas-configuration - this.canvasContext.configure({ - device: this.device, - format: 'bgra8unorm', - usage: this.swapChainTextureUsage, - // @see https://developer.chrome.com/blog/new-in-chrome-94/#canvas-colorspace - // colorSpace: 'srgb', - // @see https://www.w3.org/TR/webgpu/#enumdef-gpucanvascompositingalphamode - alphaMode: 'premultiplied', - }); } getOnscreenTexture(): Texture { @@ -240,11 +255,24 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { return this.canvas; } - present(): void { - // this.fullscreenAlphaClear.render( - // this.device, - // this.canvasContext.getCurrentTexture().createView(), - // ); + beginFrame(): void { + // assert(this.frameCommandEncoder === null); + // this.frameCommandEncoder = this.device.createCommandEncoder(); + } + + endFrame(): void { + // assert(this.frameCommandEncoder !== null); + // this.device.queue.submit([this.frameCommandEncoder.finish()]); + // this.frameCommandEncoder = null; + // for (let i = 0; i < this.queryPoolsSubmitted.length; i++) { + // const queryPool = this.queryPoolsSubmitted[i]; + // queryPool.cpuBuffer.mapAsync(GPUMapMode.READ).then(() => { + // queryPool.results = new BigUint64Array( + // queryPool.cpuBuffer.getMappedRange(), + // ); + // }); + // } + // this.queryPoolsSubmitted.length = 0; } private getNextUniqueId(): number { @@ -386,6 +414,15 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { } } + getFallbackSampler(samplerEntry: BindingLayoutSamplerDescriptor): Sampler { + const formatKind = samplerEntry.formatKind; + if (formatKind === SamplerFormatKind.Depth && samplerEntry.comparison) { + return this.fallbackSamplerComparison; + } else { + return this.fallbackSamplerFiltering; + } + } + getFallbackTexture(samplerEntry: BindingLayoutSamplerDescriptor): Texture { const dimension = samplerEntry.dimension, formatKind = samplerEntry.formatKind; @@ -528,13 +565,15 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { entries[1].push({ binding: entries[1].length, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, - sampler: { type: 'filtering' }, + sampler: translateBindGroupSamplerBinding(samplerEntry), }); } - const gpuBindGroupLayout = entries.map((entries) => - this.device.createBindGroupLayout({ entries }), - ); + const gpuBindGroupLayout = entries + .map((entries) => + entries.length ? this.device.createBindGroupLayout({ entries }) : null, + ) + .filter((layout) => !!layout); return { gpuBindGroupLayout }; } @@ -549,9 +588,18 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { } _createRenderPipeline(renderPipeline: RenderPipeline_WebGPU, async = false) { - if (renderPipeline.isCreating) return; + // if (this.device.createRenderPipelineAsync === undefined) { + // async = false; + // } + + // // If we're already in the process of creating a the pipeline async, no need to kick the process off again... + // if (async && renderPipeline.isCreatingAsync) { + // return; + // } - if (renderPipeline.gpuRenderPipeline !== null) return; + if (renderPipeline.gpuRenderPipeline !== null) { + return; + } const descriptor = renderPipeline.descriptor; const program = descriptor.program as Program_WebGPU; @@ -578,10 +626,11 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { buffers = (descriptor.inputLayout as InputLayout_WebGPU).buffers; const sampleCount = descriptor.sampleCount; - renderPipeline.isCreating = true; + // renderPipeline.isCreatingAsync = true; - const gpuRenderPipeline: GPURenderPipelineDescriptor = { + const gpuRenderPipelineDescriptor: GPURenderPipelineDescriptor = { layout, + // layout: 'auto', vertex: { ...vertexStage, buffers, @@ -599,11 +648,31 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { // TODO: async creation // @see https://www.w3.org/TR/webgpu/#dom-gpudevice-createrenderpipeline - renderPipeline.gpuRenderPipeline = - this.device.createRenderPipeline(gpuRenderPipeline); + // renderPipeline.gpuRenderPipeline = + // this.device.createRenderPipeline(gpuRenderPipeline); + + // if (renderPipeline.name !== undefined) + // renderPipeline.gpuRenderPipeline.label = renderPipeline.name; + + // if (async) { + // const gpuRenderPipeline = await this.device.createRenderPipelineAsync( + // gpuRenderPipelineDescriptor, + // ); + + // // We might have created a sync pipeline while we were async building; no way to cancel the async + // // pipeline build at this point, so just chuck it out :/ + // if (renderPipeline.gpuRenderPipeline === null) + // renderPipeline.gpuRenderPipeline = gpuRenderPipeline; + // } else { + renderPipeline.gpuRenderPipeline = this.device.createRenderPipeline( + gpuRenderPipelineDescriptor, + ); + // } + + // // if (renderPipeline.ResourceName !== undefined) + // // renderPipeline.gpuRenderPipeline.label = renderPipeline.ResourceName; - if (renderPipeline.name !== undefined) - renderPipeline.gpuRenderPipeline.label = renderPipeline.name; + // renderPipeline.isCreatingAsync = false; } createReadback(): Readback { @@ -628,11 +697,11 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { return pass; } - createComputePass(computePassDescriptor: ComputePassDescriptor): ComputePass { + createComputePass(): ComputePass { let pass = this.computePassPool.pop(); if (pass === undefined) pass = new ComputePass_WebGPU(); pass.commandEncoder = this.device.createCommandEncoder(); - pass.beginComputePass(computePassDescriptor); + pass.beginComputePass(); return pass; } @@ -644,11 +713,17 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { queue.submit([commands]); pass.commandEncoder = null; - if (pass instanceof RenderPass_WebGPU) { - this.renderPassPool.push(pass); - } else { - this.computePassPool.push(pass); - } + // if (pass instanceof RenderPass_WebGPU) { + // pass.finish(); + // this.renderPassPool.push(pass); + + // if (pass.occlusionQueryPool !== null) { + // this.queryPoolsSubmitted.push(pass.occlusionQueryPool); + // } + // } else if (pass instanceof ComputePass_WebGPU) { + // pass.finish(); + // this.computePassPool.push(pass); + // } } copySubTexture2D( diff --git a/packages/g-plugin-webgpu-device/src/platform/FullscreenAlphaClear.ts b/packages/g-plugin-webgpu-device/src/platform/FullscreenAlphaClear.ts deleted file mode 100644 index 760b540a5..000000000 --- a/packages/g-plugin-webgpu-device/src/platform/FullscreenAlphaClear.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Hack for now until browsers implement compositingAlphaMode -// https://bugs.chromium.org/p/chromium/issues/detail?id=1241373 -export class FullscreenAlphaClear { - private shaderModule: GPUShaderModule; - private pipeline: GPURenderPipeline; - - private shaderText = ` -struct VertexOutput { - @builtin(position) pos: vec4; -}; - -@vertex -fn vs( - @builtin(vertex_index) index: u32 -) -> VertexOutput { - var out: VertexOutput; - out.pos.x = select(-1.0, 3.0, index == 1u); - out.pos.y = select(-1.0, 3.0, index == 2u); - out.pos.z = 1.0; - out.pos.w = 1.0; - return out; -} - -struct FragmentOutput { @location(0) color: vec4; }; - -@ stage(fragment) -fn fs() -> FragmentOutput { - return FragmentOutput(vec4(1.0, 0.0, 1.0, 1.0)); -} -`; - - constructor(device: GPUDevice) { - const format: GPUTextureFormat = 'bgra8unorm'; - this.shaderModule = device.createShaderModule({ code: this.shaderText }); - this.pipeline = device.createRenderPipeline({ - vertex: { module: this.shaderModule, entryPoint: 'vs' }, - fragment: { - module: this.shaderModule, - entryPoint: 'fs', - targets: [{ format, writeMask: 0x08 }], - }, - }); - } - - render(device: GPUDevice, onscreenTexture: GPUTextureView): void { - const encoder = device.createCommandEncoder(); - const renderPass = encoder.beginRenderPass({ - colorAttachments: [ - { - view: onscreenTexture, - loadOp: 'load', - loadValue: 'load', - storeOp: 'store', - }, - ], - }); - renderPass.setPipeline(this.pipeline); - renderPass.draw(3); - renderPass.end(); - device.queue.submit([encoder.finish()]); - } -} diff --git a/packages/g-plugin-webgpu-device/src/platform/InputLayout.ts b/packages/g-plugin-webgpu-device/src/platform/InputLayout.ts index 82ba98dbb..695343dfa 100644 --- a/packages/g-plugin-webgpu-device/src/platform/InputLayout.ts +++ b/packages/g-plugin-webgpu-device/src/platform/InputLayout.ts @@ -1,6 +1,8 @@ -import type { InputLayout, InputLayoutDescriptor } from '@antv/g-plugin-device-renderer'; +import type { + InputLayout, + InputLayoutDescriptor, +} from '@antv/g-plugin-device-renderer'; import { assertExists, ResourceType } from '@antv/g-plugin-device-renderer'; -import { isNil } from '@antv/util'; import type { IDevice_WebGPU } from './interfaces'; import { ResourceBase_WebGPU } from './ResourceBase'; import { @@ -9,7 +11,10 @@ import { translateVertexFormat, } from './utils'; -export class InputLayout_WebGPU extends ResourceBase_WebGPU implements InputLayout { +export class InputLayout_WebGPU + extends ResourceBase_WebGPU + implements InputLayout +{ type: ResourceType.InputLayout = ResourceType.InputLayout; buffers: GPUVertexBufferLayout[]; @@ -27,24 +32,28 @@ export class InputLayout_WebGPU extends ResourceBase_WebGPU implements InputLayo super({ id, device }); const buffers: GPUVertexBufferLayout[] = []; - for (let i = 0; i < descriptor.vertexBufferDescriptors.length; i++) { - const b = descriptor.vertexBufferDescriptors[i]; - if (isNil(b)) continue; - const arrayStride = b.byteStride; - const stepMode = translateVertexBufferFrequency(b.frequency); - const attributes: GPUVertexAttribute[] = []; - buffers[i] = { arrayStride, stepMode, attributes }; - } - for (let i = 0; i < descriptor.vertexAttributeDescriptors.length; i++) { const attr = descriptor.vertexAttributeDescriptors[i]; - const b = assertExists(buffers[attr.bufferIndex]); + const attribute: GPUVertexAttribute = { shaderLocation: attr.location, format: translateVertexFormat(attr.format), offset: attr.bufferByteOffset, }; - (b.attributes as GPUVertexAttribute[]).push(attribute); + + if (buffers[attr.bufferIndex] !== undefined) { + (buffers[attr.bufferIndex].attributes as GPUVertexAttribute[]).push( + attribute, + ); + } else { + const b = assertExists( + descriptor.vertexBufferDescriptors[attr.bufferIndex], + ); + const arrayStride = b.byteStride; + const stepMode = translateVertexBufferFrequency(b.frequency); + const attributes: GPUVertexAttribute[] = [attribute]; + buffers[attr.bufferIndex] = { arrayStride, stepMode, attributes }; + } } this.indexFormat = translateIndexFormat(descriptor.indexBufferFormat); diff --git a/packages/g-plugin-webgpu-device/src/platform/Program.ts b/packages/g-plugin-webgpu-device/src/platform/Program.ts index 5d8343a78..863bfff78 100644 --- a/packages/g-plugin-webgpu-device/src/platform/Program.ts +++ b/packages/g-plugin-webgpu-device/src/platform/Program.ts @@ -1,4 +1,7 @@ -import type { Program, ProgramDescriptorSimple } from '@antv/g-plugin-device-renderer'; +import type { + Program, + ProgramDescriptorSimple, +} from '@antv/g-plugin-device-renderer'; import { ResourceType } from '@antv/g-plugin-device-renderer'; import type { Device_WebGPU } from './Device'; import type { IDevice_WebGPU } from './interfaces'; @@ -24,14 +27,23 @@ export class Program_WebGPU extends ResourceBase_WebGPU implements Program { this.descriptor = descriptor; if (descriptor.preprocessedVert) { - this.vertexStage = this.createShaderStage(descriptor.preprocessedVert, 'vertex'); + this.vertexStage = this.createShaderStage( + descriptor.preprocessedVert, + 'vertex', + ); } if (descriptor.preprocessedFrag) { - this.fragmentStage = this.createShaderStage(descriptor.preprocessedFrag, 'fragment'); + this.fragmentStage = this.createShaderStage( + descriptor.preprocessedFrag, + 'fragment', + ); } if (descriptor.preprocessedCompute) { // FIXME: Only support WGSL now - this.computeStage = this.createShaderStage(descriptor.preprocessedCompute, 'compute'); + this.computeStage = this.createShaderStage( + descriptor.preprocessedCompute, + 'compute', + ); } } diff --git a/packages/g-plugin-webgpu-device/src/platform/QueryPool.ts b/packages/g-plugin-webgpu-device/src/platform/QueryPool.ts index 090b611cd..07c86e123 100644 --- a/packages/g-plugin-webgpu-device/src/platform/QueryPool.ts +++ b/packages/g-plugin-webgpu-device/src/platform/QueryPool.ts @@ -8,6 +8,9 @@ export class QueryPool_WebGPU extends ResourceBase_WebGPU implements QueryPool { type: ResourceType.QueryPool = ResourceType.QueryPool; querySet: GPUQuerySet; + resolveBuffer: GPUBuffer; + cpuBuffer: GPUBuffer; + results: BigUint64Array | null; constructor({ id, @@ -28,9 +31,26 @@ export class QueryPool_WebGPU extends ResourceBase_WebGPU implements QueryPool { type: translateQueryPoolType(type), count: elemCount, }); + + this.resolveBuffer = this.device.device.createBuffer({ + size: elemCount * 8, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, + }); + this.cpuBuffer = this.device.device.createBuffer({ + size: elemCount * 8, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + this.results = null; } queryResultOcclusion(dstOffs: number): boolean | null { - return true; + if (this.results === null) return null; + return this.results[dstOffs] !== BigInt(0); + } + + destroy(): void { + this.querySet.destroy(); + this.resolveBuffer.destroy(); + this.cpuBuffer.destroy(); } } diff --git a/packages/g-plugin-webgpu-device/src/platform/RenderPass.ts b/packages/g-plugin-webgpu-device/src/platform/RenderPass.ts index f612c1829..8d8e20d9d 100644 --- a/packages/g-plugin-webgpu-device/src/platform/RenderPass.ts +++ b/packages/g-plugin-webgpu-device/src/platform/RenderPass.ts @@ -54,12 +54,10 @@ export class RenderPass_WebGPU implements RenderPass { this.gfxColorAttachment.length = numColorAttachments; this.gfxColorResolveTo.length = numColorAttachments; for (let i = 0; i < descriptor.colorAttachment.length; i++) { - let colorAttachment: TextureShared_WebGPU | null = descriptor.colorAttachment[ - i - ] as Attachment_WebGPU; - let colorResolveTo: TextureShared_WebGPU | null = descriptor.colorResolveTo[ - i - ] as Texture_WebGPU; + let colorAttachment: TextureShared_WebGPU | null = descriptor + .colorAttachment[i] as Attachment_WebGPU; + let colorResolveTo: TextureShared_WebGPU | null = descriptor + .colorResolveTo[i] as Texture_WebGPU; // Do some dumb juggling... if (colorAttachment === null && colorResolveTo !== null) { @@ -102,11 +100,14 @@ export class RenderPass_WebGPU implements RenderPass { } } - this.gfxDepthStencilAttachment = descriptor.depthStencilAttachment as Attachment_WebGPU; - this.gfxDepthStencilResolveTo = descriptor.depthStencilResolveTo as Texture_WebGPU; + this.gfxDepthStencilAttachment = + descriptor.depthStencilAttachment as Attachment_WebGPU; + this.gfxDepthStencilResolveTo = + descriptor.depthStencilResolveTo as Texture_WebGPU; if (descriptor.depthStencilAttachment !== null) { - const dsAttachment = descriptor.depthStencilAttachment as Attachment_WebGPU; + const dsAttachment = + descriptor.depthStencilAttachment as Attachment_WebGPU; const dstAttachment = this.gpuDepthStencilAttachment; dstAttachment.view = dsAttachment.gpuTextureView; @@ -123,9 +124,14 @@ export class RenderPass_WebGPU implements RenderPass { dstAttachment.stencilLoadOp = 'clear'; dstAttachment.stencilClearValue = descriptor.stencilClearValue; } - dstAttachment.depthStoreOp = descriptor.depthStencilStore ? 'store' : 'discard'; - dstAttachment.stencilStoreOp = descriptor.depthStencilStore ? 'store' : 'discard'; - this.gpuRenderPassDescriptor.depthStencilAttachment = this.gpuDepthStencilAttachment; + dstAttachment.depthStoreOp = descriptor.depthStencilStore + ? 'store' + : 'discard'; + dstAttachment.stencilStoreOp = descriptor.depthStencilStore + ? 'store' + : 'discard'; + this.gpuRenderPassDescriptor.depthStencilAttachment = + this.gpuDepthStencilAttachment; if (this.gfxDepthStencilResolveTo !== null) { dstAttachment.depthStoreOp = 'store'; dstAttachment.stencilStoreOp = 'store'; @@ -143,7 +149,9 @@ export class RenderPass_WebGPU implements RenderPass { beginRenderPass(renderPassDescriptor: RenderPassDescriptor): void { assert(this.gpuRenderPassEncoder === null); this.setRenderPassDescriptor(renderPassDescriptor); - this.gpuRenderPassEncoder = this.commandEncoder.beginRenderPass(this.gpuRenderPassDescriptor); + this.gpuRenderPassEncoder = this.commandEncoder.beginRenderPass( + this.gpuRenderPassDescriptor, + ); } setViewport(x: number, y: number, w: number, h: number): void { @@ -177,18 +185,31 @@ export class RenderPass_WebGPU implements RenderPass { for (let i = 0; i < inputState.vertexBuffers.length; i++) { const b = inputState.vertexBuffers[i]; if (isNil(b)) continue; - this.gpuRenderPassEncoder.setVertexBuffer(i, getPlatformBuffer(b.buffer), b.byteOffset); + this.gpuRenderPassEncoder.setVertexBuffer( + i, + getPlatformBuffer(b.buffer), + b.byteOffset, + ); } } - setBindings(bindingLayoutIndex: number, bindings_: Bindings, dynamicByteOffsets: number[]): void { + setBindings( + bindingLayoutIndex: number, + bindings_: Bindings, + dynamicByteOffsets: number[], + ): void { const bindings = bindings_ as Bindings_WebGPU; this.gpuRenderPassEncoder.setBindGroup( bindingLayoutIndex + 0, bindings.gpuBindGroup[0], dynamicByteOffsets.slice(0, bindings.bindingLayout.numUniformBuffers), ); - this.gpuRenderPassEncoder.setBindGroup(bindingLayoutIndex + 1, bindings.gpuBindGroup[1]); + if (bindings.gpuBindGroup[1]) { + this.gpuRenderPassEncoder.setBindGroup( + bindingLayoutIndex + 1, + bindings.gpuBindGroup[1], + ); + } } setStencilRef(ref: number): void { @@ -203,8 +224,18 @@ export class RenderPass_WebGPU implements RenderPass { this.gpuRenderPassEncoder.drawIndexed(indexCount, 1, firstIndex, 0, 0); } - drawIndexedInstanced(indexCount: number, firstIndex: number, instanceCount: number): void { - this.gpuRenderPassEncoder.drawIndexed(indexCount, instanceCount, firstIndex, 0, 0); + drawIndexedInstanced( + indexCount: number, + firstIndex: number, + instanceCount: number, + ): void { + this.gpuRenderPassEncoder.drawIndexed( + indexCount, + instanceCount, + firstIndex, + 0, + 0, + ); } beginOcclusionQuery(dstOffs: number): void { @@ -247,18 +278,27 @@ export class RenderPass_WebGPU implements RenderPass { } } - if (this.gfxDepthStencilAttachment !== null && this.gfxDepthStencilResolveTo !== null) { + if ( + this.gfxDepthStencilAttachment !== null && + this.gfxDepthStencilResolveTo !== null + ) { if (this.gfxDepthStencilAttachment.sampleCount > 1) { // TODO(jstpierre): MSAA depth resolve (requires shader) } else { - this.copyAttachment(this.gfxDepthStencilResolveTo, this.gfxDepthStencilAttachment); + this.copyAttachment( + this.gfxDepthStencilResolveTo, + this.gfxDepthStencilAttachment, + ); } } return this.commandEncoder.finish(); } - private copyAttachment(dst: TextureShared_WebGPU, src: TextureShared_WebGPU): void { + private copyAttachment( + dst: TextureShared_WebGPU, + src: TextureShared_WebGPU, + ): void { assert(src.sampleCount === 1); const srcCopy: GPUImageCopyTexture = { texture: src.gpuTexture }; const dstCopy: GPUImageCopyTexture = { texture: dst.gpuTexture }; @@ -266,6 +306,10 @@ export class RenderPass_WebGPU implements RenderPass { assert(src.height === dst.height); assert(!!(src.usage & GPUTextureUsage.COPY_SRC)); assert(!!(dst.usage & GPUTextureUsage.COPY_DST)); - this.commandEncoder.copyTextureToTexture(srcCopy, dstCopy, [dst.width, dst.height, 1]); + this.commandEncoder.copyTextureToTexture(srcCopy, dstCopy, [ + dst.width, + dst.height, + 1, + ]); } } diff --git a/packages/g-plugin-webgpu-device/src/platform/RenderPipeline.ts b/packages/g-plugin-webgpu-device/src/platform/RenderPipeline.ts index f9945b70f..2095608d6 100644 --- a/packages/g-plugin-webgpu-device/src/platform/RenderPipeline.ts +++ b/packages/g-plugin-webgpu-device/src/platform/RenderPipeline.ts @@ -13,7 +13,6 @@ export class RenderPipeline_WebGPU type: ResourceType.RenderPipeline = ResourceType.RenderPipeline; descriptor: RenderPipelineDescriptor; - isCreating = false; isCreatingAsync = false; gpuRenderPipeline: GPURenderPipeline | null = null; diff --git a/packages/g-plugin-webgpu-device/src/platform/Sampler.ts b/packages/g-plugin-webgpu-device/src/platform/Sampler.ts index 77954d0f9..11fa0ce53 100644 --- a/packages/g-plugin-webgpu-device/src/platform/Sampler.ts +++ b/packages/g-plugin-webgpu-device/src/platform/Sampler.ts @@ -1,6 +1,19 @@ -import type { Sampler, SamplerDescriptor } from '@antv/g-plugin-device-renderer'; -import { MipFilterMode, ResourceType, TexFilterMode, assert } from '@antv/g-plugin-device-renderer'; -import { translateMinMagFilter, translateMipFilter, translateWrapMode } from './utils'; +import type { + Sampler, + SamplerDescriptor, +} from '@antv/g-plugin-device-renderer'; +import { + MipFilterMode, + ResourceType, + TexFilterMode, + assert, +} from '@antv/g-plugin-device-renderer'; +import { + translateMinMagFilter, + translateMipFilter, + translateWrapMode, + translateCompareMode, +} from './utils'; import type { IDevice_WebGPU } from './interfaces'; import { ResourceBase_WebGPU } from './ResourceBase'; @@ -23,7 +36,9 @@ export class Sampler_WebGPU extends ResourceBase_WebGPU implements Sampler { const lodMinClamp = descriptor.minLOD; const lodMaxClamp = - descriptor.mipFilter === MipFilterMode.NoMip ? descriptor.minLOD : descriptor.maxLOD; + descriptor.mipFilter === MipFilterMode.NoMip + ? descriptor.minLOD + : descriptor.maxLOD; const maxAnisotropy = descriptor.maxAnisotropy ?? 1; if (maxAnisotropy > 1) @@ -42,6 +57,10 @@ export class Sampler_WebGPU extends ResourceBase_WebGPU implements Sampler { minFilter: translateMinMagFilter(descriptor.minFilter), magFilter: translateMinMagFilter(descriptor.magFilter), mipmapFilter: translateMipFilter(descriptor.mipFilter), + compare: + descriptor.compareMode !== undefined + ? translateCompareMode(descriptor.compareMode) + : undefined, maxAnisotropy, }); } diff --git a/packages/g-plugin-webgpu-device/src/platform/interfaces.ts b/packages/g-plugin-webgpu-device/src/platform/interfaces.ts index 730c83d8c..e6a441af4 100644 --- a/packages/g-plugin-webgpu-device/src/platform/interfaces.ts +++ b/packages/g-plugin-webgpu-device/src/platform/interfaces.ts @@ -44,16 +44,20 @@ export interface BindGroupLayout { export interface IDevice_WebGPU extends Device { device: GPUDevice; - fallbackSampler: Sampler; - + getFallbackSampler: (samplerEntry: BindingLayoutSamplerDescriptor) => Sampler; getFallbackTexture: (samplerEntry: BindingLayoutSamplerDescriptor) => Texture; createTextureShared: ( descriptor: TextureSharedDescriptor, texture: TextureShared_WebGPU, skipCreate: boolean, ) => void; - _createRenderPipeline: (renderPipeline: RenderPipeline, async?: boolean) => void; - _createBindGroupLayout: (bindingLayout: BindingLayoutDescriptor) => BindGroupLayout; + _createRenderPipeline: ( + renderPipeline: RenderPipeline, + async?: boolean, + ) => void; + _createBindGroupLayout: ( + bindingLayout: BindingLayoutDescriptor, + ) => BindGroupLayout; // ensureRenderPipeline: (renderPipeline: RenderPipeline) => void; // createBindGroupLayout(bindingLayout: Partial): BindGroupLayout; // createPipelineLayout(bindingLayouts: BindingLayoutDescriptor[]): GPUPipelineLayout; diff --git a/packages/g-plugin-webgpu-device/src/platform/utils.ts b/packages/g-plugin-webgpu-device/src/platform/utils.ts index 086e39bb3..66fd3a79a 100644 --- a/packages/g-plugin-webgpu-device/src/platform/utils.ts +++ b/packages/g-plugin-webgpu-device/src/platform/utils.ts @@ -9,6 +9,7 @@ import type { BindingLayoutSamplerDescriptor, } from '@antv/g-plugin-device-renderer'; import { + BufferUsage, WrapMode, TexFilterMode, MipFilterMode, @@ -82,6 +83,17 @@ export function translateTextureDimension( else throw new Error('whoops'); } +export function translateBufferUsage(usage_: BufferUsage): GPUBufferUsageFlags { + let usage = 0; + if (usage_ & BufferUsage.INDEX) usage |= GPUBufferUsage.INDEX; + if (usage_ & BufferUsage.VERTEX) usage |= GPUBufferUsage.VERTEX; + if (usage_ & BufferUsage.UNIFORM) usage |= GPUBufferUsage.UNIFORM; + if (usage_ & BufferUsage.STORAGE) usage |= GPUBufferUsage.STORAGE; + if (usage_ & BufferUsage.COPY_SRC) usage |= GPUBufferUsage.COPY_SRC; + usage |= GPUBufferUsage.COPY_DST; + return usage; +} + export function translateWrapMode(wrapMode: WrapMode): GPUAddressMode { if (wrapMode === WrapMode.Clamp) return 'clamp-to-edge'; else if (wrapMode === WrapMode.Repeat) return 'repeat'; @@ -108,6 +120,18 @@ function translateSampleType(type: SamplerFormatKind): GPUTextureSampleType { else throw new Error('whoops'); } +export function translateBindGroupSamplerBinding( + sampler: BindingLayoutSamplerDescriptor, +): GPUSamplerBindingLayout { + if (sampler.formatKind === SamplerFormatKind.Depth && sampler.comparison) { + return { type: 'comparison' }; + } else if (sampler.formatKind === SamplerFormatKind.Float) { + return { type: 'filtering' }; + } else { + return { type: 'non-filtering' }; + } +} + function translateViewDimension( dimension: TextureDimension, ): GPUTextureViewDimension { @@ -214,26 +238,45 @@ export function translateBlendMode(mode: BlendMode): GPUBlendOperation { else throw new Error('whoops'); } -export function translateBlendState( - blendState: ChannelBlendState, -): GPUBlendComponent { +function translateBlendComponent(ch: ChannelBlendState): GPUBlendComponent { return { - operation: translateBlendMode(blendState.blendMode), - srcFactor: translateBlendFactor(blendState.blendSrcFactor), - dstFactor: translateBlendFactor(blendState.blendDstFactor), + operation: translateBlendMode(ch.blendMode), + srcFactor: translateBlendFactor(ch.blendSrcFactor), + dstFactor: translateBlendFactor(ch.blendDstFactor), }; } +function blendComponentIsNil(ch: ChannelBlendState): boolean { + return ( + ch.blendMode === BlendMode.Add && + ch.blendSrcFactor === BlendFactor.One && + ch.blendDstFactor === BlendFactor.Zero + ); +} + +function translateBlendState( + attachmentState: AttachmentState, +): GPUBlendState | undefined { + if ( + blendComponentIsNil(attachmentState.rgbBlendState) && + blendComponentIsNil(attachmentState.alphaBlendState) + ) { + return undefined; + } else { + return { + color: translateBlendComponent(attachmentState.rgbBlendState), + alpha: translateBlendComponent(attachmentState.alphaBlendState), + }; + } +} + export function translateColorState( attachmentState: AttachmentState, format: Format, ): GPUColorTargetState { return { format: translateTextureFormat(format), - blend: { - color: translateBlendState(attachmentState.rgbBlendState), - alpha: translateBlendState(attachmentState.alphaBlendState), - }, + blend: translateBlendState(attachmentState), writeMask: attachmentState.channelWriteMask, }; } @@ -298,15 +341,22 @@ export function translateVertexFormat(format: Format): GPUVertexFormat { else if (format === Format.U8_RG) return 'uint8x2'; else if (format === Format.U8_RGB) return 'uint8x4'; else if (format === Format.U8_RGBA) return 'uint8x4'; + else if (format === Format.U8_RG_NORM) return 'unorm8x2'; else if (format === Format.U8_RGBA_NORM) return 'unorm8x4'; else if (format === Format.S8_RGB_NORM) return 'snorm8x4'; else if (format === Format.S8_RGBA_NORM) return 'snorm8x4'; + else if (format === Format.U16_RG_NORM) return 'unorm16x2'; + else if (format === Format.U16_RGBA_NORM) return 'unorm16x4'; + else if (format === Format.S16_RG_NORM) return 'snorm16x2'; + else if (format === Format.S16_RGBA_NORM) return 'snorm16x4'; else if (format === Format.S16_RG) return 'uint16x2'; + else if (format === Format.F16_RG) return 'float16x2'; + else if (format === Format.F16_RGBA) return 'float16x4'; else if (format === Format.F32_R) return 'float32'; else if (format === Format.F32_RG) return 'float32x2'; else if (format === Format.F32_RGB) return 'float32x3'; else if (format === Format.F32_RGBA) return 'float32x4'; - else throw new Error('whoops'); + else throw 'whoops'; } export function isFormatTextureCompressionBC(format: Format): boolean { diff --git a/packages/g-shader-components/billboard.vert b/packages/g-shader-components/billboard.vert index 690a82519..68fe8d8d5 100644 --- a/packages/g-shader-components/billboard.vert +++ b/packages/g-shader-components/billboard.vert @@ -1,7 +1,7 @@ -vec4 mvPosition = u_ViewMatrix * u_ModelMatrix * vec4( 0.0, 0.0, u_ZIndex, 1.0 ); +vec4 mvPosition = u_ViewMatrix * u_ModelMatrix * vec4(0.0, 0.0, u_ZIndex, 1.0); vec2 scale; -scale.x = length( vec3( u_ModelMatrix[ 0 ].x, u_ModelMatrix[ 0 ].y, u_ModelMatrix[ 0 ].z ) ); -scale.y = length( vec3( u_ModelMatrix[ 1 ].x, u_ModelMatrix[ 1 ].y, u_ModelMatrix[ 1 ].z ) ); +scale.x = length(vec3(u_ModelMatrix[0][0], u_ModelMatrix[0][1], u_ModelMatrix[0][2])); +scale.y = length(vec3(u_ModelMatrix[1][0], u_ModelMatrix[1][1], u_ModelMatrix[1][2])); // if (sizeAttenuation < 0.5) { // bool isPerspective = isPerspectiveMatrix( u_ProjectionMatrix ); @@ -12,8 +12,8 @@ vec2 alignedPosition = offset * scale; float rotation = 0.0; vec2 rotatedPosition; -rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y; -rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y; +rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y; +rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y; mvPosition.xy += rotatedPosition; diff --git a/packages/g-webgpu/src/index.ts b/packages/g-webgpu/src/index.ts index f0f049605..39b8d88a3 100644 --- a/packages/g-webgpu/src/index.ts +++ b/packages/g-webgpu/src/index.ts @@ -9,7 +9,9 @@ import { ContextRegisterPlugin } from './ContextRegisterPlugin'; export { DomInteraction, DeviceRenderer, WebGPUDevice, HTMLRenderer }; -type WebGPURendererConfig = RendererConfig; +interface WebGPURendererConfig extends RendererConfig { + shaderCompilerPath: string; +} export class Renderer extends AbstractRenderer { constructor(config?: Partial) { @@ -18,7 +20,11 @@ export class Renderer extends AbstractRenderer { const deviceRendererPlugin = new DeviceRenderer.Plugin(); this.registerPlugin(new ContextRegisterPlugin(deviceRendererPlugin)); this.registerPlugin(new ImageLoader.Plugin()); - this.registerPlugin(new WebGPUDevice.Plugin()); + this.registerPlugin( + new WebGPUDevice.Plugin({ + shaderCompilerPath: config?.shaderCompilerPath, + }), + ); this.registerPlugin(deviceRendererPlugin); this.registerPlugin(new DomInteraction.Plugin()); this.registerPlugin(new HTMLRenderer.Plugin()); diff --git a/rust/pkg/glsl_wgsl_compiler.d.ts b/rust/pkg/glsl_wgsl_compiler.d.ts index a311a63b1..ac1707841 100644 --- a/rust/pkg/glsl_wgsl_compiler.d.ts +++ b/rust/pkg/glsl_wgsl_compiler.d.ts @@ -6,13 +6,29 @@ * @param {boolean} validation_enabled * @returns {string} */ -export function glsl_compile(source: string, stage: string, validation_enabled: boolean): string; +export function glsl_compile( + source: string, + stage: string, + validation_enabled: boolean, +): string; -export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; +export type InitInput = + | RequestInfo + | URL + | Response + | BufferSource + | WebAssembly.Module; export interface InitOutput { readonly memory: WebAssembly.Memory; - readonly glsl_compile: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly glsl_compile: ( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number, + ) => void; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_malloc: (a: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number) => number; @@ -27,4 +43,6 @@ export interface InitOutput { * * @returns {Promise} */ -export default function init(module_or_path?: InitInput | Promise): Promise; +export default function init( + module_or_path?: InitInput | Promise, +): Promise; diff --git a/rust/pkg/glsl_wgsl_compiler.js b/rust/pkg/glsl_wgsl_compiler.js index ed13bd01e..90266d600 100644 --- a/rust/pkg/glsl_wgsl_compiler.js +++ b/rust/pkg/glsl_wgsl_compiler.js @@ -1,12 +1,18 @@ let wasm; -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +let cachedTextDecoder = new TextDecoder('utf-8', { + ignoreBOM: true, + fatal: true, +}); cachedTextDecoder.decode(); let cachegetUint8Memory0 = null; function getUint8Memory0() { - if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + if ( + cachegetUint8Memory0 === null || + cachegetUint8Memory0.buffer !== wasm.memory.buffer + ) { cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); } return cachegetUint8Memory0; @@ -106,7 +112,10 @@ function passStringToWasm0(arg, malloc, realloc) { let cachegetInt32Memory0 = null; function getInt32Memory0() { - if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { + if ( + cachegetInt32Memory0 === null || + cachegetInt32Memory0.buffer !== wasm.memory.buffer + ) { cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); } return cachegetInt32Memory0; @@ -120,9 +129,17 @@ function getInt32Memory0() { export function glsl_compile(source, stage, validation_enabled) { try { const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - var ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var ptr0 = passStringToWasm0( + source, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); var len0 = WASM_VECTOR_LEN; - var ptr1 = passStringToWasm0(stage, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var ptr1 = passStringToWasm0( + stage, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); var len1 = WASM_VECTOR_LEN; wasm.glsl_compile(retptr, ptr0, len0, ptr1, len1, validation_enabled); var r0 = getInt32Memory0()[retptr / 4 + 0]; diff --git a/rust/pkg/glsl_wgsl_compiler_bg.wasm b/rust/pkg/glsl_wgsl_compiler_bg.wasm index 0dbaf186c..8e7f6ae1b 100644 Binary files a/rust/pkg/glsl_wgsl_compiler_bg.wasm and b/rust/pkg/glsl_wgsl_compiler_bg.wasm differ diff --git a/rust/pkg/package.json b/rust/pkg/package.json index 29673f55e..6d209100b 100644 --- a/rust/pkg/package.json +++ b/rust/pkg/package.json @@ -11,5 +11,7 @@ ], "module": "glsl_wgsl_compiler.js", "types": "glsl_wgsl_compiler.d.ts", - "sideEffects": false + "sideEffects": [ + "./snippets/*" + ] } \ No newline at end of file diff --git a/site/examples/3d/3d-basic/demo/meta.json b/site/examples/3d/3d-basic/demo/meta.json index 3d3ada77a..b561a1dc6 100644 --- a/site/examples/3d/3d-basic/demo/meta.json +++ b/site/examples/3d/3d-basic/demo/meta.json @@ -26,6 +26,13 @@ "zh": "Sprite", "en": "Sprite" } + }, + { + "filename": "webgpu.js", + "title": { + "zh": "WebGPU", + "en": "WebGPU" + } } ] } diff --git a/site/examples/3d/3d-basic/demo/webgpu.js b/site/examples/3d/3d-basic/demo/webgpu.js new file mode 100644 index 000000000..7bba3ebea --- /dev/null +++ b/site/examples/3d/3d-basic/demo/webgpu.js @@ -0,0 +1,58 @@ +import { Canvas, CanvasEvent, Circle, Image } from '@antv/g'; +import { Renderer as WebGPURenderer } from '@antv/g-webgpu'; + +import Stats from 'stats.js'; + +const webgpuRenderer = new WebGPURenderer({ + shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', +}); + +// create a canvas +const canvas = new Canvas({ + container: 'container', + width: 600, + height: 500, + renderer: webgpuRenderer, + background: 'red', +}); + +const circle = new Circle({ + style: { + x: 200, + y: 200, + r: 50, + fill: 'green', + cursor: 'pointer', + }, +}); + +const icon = new Image({ + style: { + x: 200, + y: 200, + z: 0, + width: 200, + height: 200, + src: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', + isBillboard: true, + }, +}); + +canvas.addEventListener(CanvasEvent.READY, () => { + canvas.appendChild(circle); +}); + +// stats +const stats = new Stats(); +stats.showPanel(0); +const $stats = stats.dom; +$stats.style.position = 'absolute'; +$stats.style.left = '0px'; +$stats.style.top = '0px'; +const $wrapper = document.getElementById('container'); +$wrapper.appendChild($stats); +canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => { + if (stats) { + stats.update(); + } +}); diff --git a/site/examples/3d/geometry/demo/sphere.js b/site/examples/3d/geometry/demo/sphere.js index 038df56c8..887c63a22 100644 --- a/site/examples/3d/geometry/demo/sphere.js +++ b/site/examples/3d/geometry/demo/sphere.js @@ -124,6 +124,7 @@ const canvas = new Canvas({ if (stats) { stats.update(); } + sphere.setOrigin(0, 0, 0); sphere.rotate(0, 0.2, 0); }); diff --git a/site/examples/3d/geometry/demo/torus.js b/site/examples/3d/geometry/demo/torus.js index 1b5166388..67bb09113 100644 --- a/site/examples/3d/geometry/demo/torus.js +++ b/site/examples/3d/geometry/demo/torus.js @@ -76,6 +76,7 @@ const canvas = new Canvas({ if (stats) { stats.update(); } + torus.setOrigin(0, 0, 0); torus.rotate(0, 0.2, 0); }); diff --git a/site/static/glsl_wgsl_compiler_bg.wasm b/site/static/glsl_wgsl_compiler_bg.wasm index 0dbaf186c..8e7f6ae1b 100644 Binary files a/site/static/glsl_wgsl_compiler_bg.wasm and b/site/static/glsl_wgsl_compiler_bg.wasm differ