diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 9b7957a4eeaff881bf9cf9ccf9b39fdbaf2cc5ec..0000000000000000000000000000000000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,32 +0,0 @@ -# To contribute improvements to CI/CD templates, please follow the Development guide at: -# https://docs.gitlab.com/ee/development/cicd/templates.html -# This specific template is located at: -# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/npm.gitlab-ci.yml - -image: node:latest - -cache: - paths: - - node_modules/ - - example/node_modules/ - -test: - stage: test - script: - - npm i -g pnpm - - pnpm i --no-frozen-lockfile - - npm run test - rules: - - if: $CI_MERGE_REQUEST_IID - - if: $CI_COMMIT_TAG - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - -pages: - stage: deploy - script: - - npm run docs - artifacts: - paths: - - public - only: - - master diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index c165d7ff5b2781f2a344b6d1380b9cd6a48b7a70..0000000000000000000000000000000000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,3 +0,0 @@ -tasks: - - init: npm i -g pnpm && pnpm i - command: npm run watch diff --git a/examples/src/webgpu/RenderObjectChanges/index.ts b/examples/src/webgpu/RenderObjectChanges/index.ts index 2c9d14f9a1462c551fac41528d77ae6548f19458..15a71985cdd3e820eccf3ea18cd0e93362f3daf9 100644 --- a/examples/src/webgpu/RenderObjectChanges/index.ts +++ b/examples/src/webgpu/RenderObjectChanges/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, ISubmit } from "@feng3d/render-api"; +import { Submit, RenderObject, reactive } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement) => @@ -9,7 +9,7 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); // 初始化WebGPU - const renderObject: IRenderObject = { // 渲染对象 + const renderObject: RenderObject = { // 渲染对象 pipeline: { // 渲染管线 vertex: { // 顶点着色器 code: ` @@ -33,11 +33,11 @@ const init = async (canvas: HTMLCanvasElement) => position: { data: new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]), format: "float32x2" }, // 顶点坐标数据 }, indices: new Uint16Array([0, 1, 2]), // 顶点索引数据 - uniforms: { color: [1, 0, 0, 0] }, // Uniform 颜色值。 - drawIndexed: { indexCount: 3 }, // 绘制命令 + draw: { __type__: "DrawIndexed", indexCount: 3 }, // 绘制命令 + bindingResources: { color: [1, 0, 0, 0] as any }, // Uniform 颜色值。 }; - const submit: ISubmit = { // 一次GPU提交 + const submit: Submit = { // 一次GPU提交 commandEncoders: [ // 命令编码列表 { passEncoders: [ // 通道编码列表 @@ -48,7 +48,7 @@ const init = async (canvas: HTMLCanvasElement) => clearValue: [0.0, 0.0, 0.0, 1.0], // 渲染前填充颜色 }], }, - renderObjects: [renderObject] + renderPassObjects: [renderObject] }, ] } @@ -65,8 +65,13 @@ const init = async (canvas: HTMLCanvasElement) => window.onclick = () => { + // reactive(renderObject.vertices.position).stepMode = "instance"; + // reactive(renderObject.vertices.position).stepMode = "vertex"; + // reactive(renderObject.vertices.position).data = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -1]); + // reactive(renderObject.vertices.position).format = "float32x3"; + // reactive(renderObject.vertices.position).data = new Float32Array([1.0, 0.5, 1.0, -0.5, -0.5, 1.0, 0.5, -1, 1.0]); // 修改顶点着色器代码 - renderObject.pipeline.vertex.code = ` + reactive(renderObject.pipeline.vertex).code = ` @vertex fn main( @location(0) position: vec2, @@ -78,7 +83,7 @@ const init = async (canvas: HTMLCanvasElement) => `; // 修改片段着色器代码 - renderObject.pipeline.fragment.code = ` + reactive(renderObject.pipeline.fragment).code = ` @binding(0) @group(0) var color : vec4; @fragment fn main() -> @location(0) vec4f { @@ -89,8 +94,8 @@ const init = async (canvas: HTMLCanvasElement) => return col; } `; - // - // renderObject.uniforms.color = [0, 1, 0, 1]; + + reactive(renderObject.bindingResources).color = [0, 1, 0, 1]; }; }; diff --git a/examples/src/webgpu/a-buffer/index.ts b/examples/src/webgpu/a-buffer/index.ts index cc774ca0ca527e3021bde49c69d8a3d62221e338..7ebdb5a5b940c11a46c28b5809f5890c4b4d419a 100644 --- a/examples/src/webgpu/a-buffer/index.ts +++ b/examples/src/webgpu/a-buffer/index.ts @@ -1,5 +1,5 @@ -import { IBufferBinding, IPassEncoder, IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit, ITexture, ITextureView, IVertexAttributes } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { BufferBinding, CanvasContext, PassEncoder, reactive, RenderPass, RenderPassDescriptor, RenderPipeline, Submit, Texture, TextureView, VertexAttributes } from "@feng3d/render-api"; +import { getGBuffer, WebGPU } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; import { mat4, vec3 } from "wgpu-matrix"; @@ -23,7 +23,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const webgpu = await new WebGPU().init(); - const context: IGPUCanvasContext = { + const context: CanvasContext = { canvasId: canvas.id, configuration: { alphaMode: "opaque" } }; @@ -35,7 +35,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; // Create the model vertex buffer - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: new Float32Array(mesh.positions.flat()), format: "float32x3", arrayStride: 12 } }; // Create the model index buffer @@ -49,14 +49,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => 16 * Float32Array.BYTES_PER_ELEMENT + 2 * Uint32Array.BYTES_PER_ELEMENT, 16 ); - const uniforms: IBufferBinding = { + const uniforms: BufferBinding = { bufferView: new Uint8Array(uniformsSize), modelViewProjectionMatrix: undefined, maxStorableFragments: undefined, targetWidth: undefined, }; - const opaquePipeline: IRenderPipeline = { + const opaquePipeline: RenderPipeline = { vertex: { code: opaqueWGSL, }, @@ -73,7 +73,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => label: "opaquePipeline", }; - const translucentPipeline: IRenderPipeline = { + const translucentPipeline: RenderPipeline = { vertex: { code: translucentWGSL, }, @@ -91,7 +91,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => label: "translucentPipeline", }; - const translucentPassDescriptor: IRenderPassDescriptor = { + const translucentPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { loadOp: "load", @@ -102,7 +102,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => label: "translucentPassDescriptor", }; - const compositePipeline: IRenderPipeline = { + const compositePipeline: RenderPipeline = { vertex: { code: compositeWGSL, }, @@ -127,7 +127,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => label: "compositePipeline", }; - const compositePassDescriptor: IRenderPassDescriptor = { + const compositePassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, @@ -167,13 +167,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", label: "depthTexture", }; - const depthTextureView: ITextureView = { + const depthTextureView: TextureView = { label: "depthTextureView", texture: depthTexture, }; @@ -199,7 +199,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const sliceHeight = Math.ceil(canvas.height / numSlices); const linkedListBufferSize = sliceHeight * bytesPerline; - const linkedListBuffer: IBufferBinding = { + const linkedListBuffer: BufferBinding = { bufferView: new Uint8Array(linkedListBufferSize), // data: [{ color: undefined, depth: undefined, next: undefined }] }; @@ -218,7 +218,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // for a given pixel. // * numFragments : u32 // * data : array - const headsBuffer: IBufferBinding = { + const headsBuffer: BufferBinding = { bufferView: new Uint32Array(1 + canvas.width * sliceHeight), numFragments: undefined, data: undefined, @@ -235,7 +235,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => sliceInfo: { sliceStartY: undefined }, }; - const opaquePassDescriptor: IRenderPassDescriptor = { + const opaquePassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, @@ -280,17 +280,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => return viewProjMatrix; } - const passEncoders: IPassEncoder[] = []; + const passEncoders: PassEncoder[] = []; // Draw the opaque objects - const opaquePassEncoder: IRenderPass = { + const opaquePassEncoder: RenderPass = { descriptor: opaquePassDescriptor, - renderObjects: [{ + renderPassObjects: [{ pipeline: opaquePipeline, - uniforms: bindingResources, + bindingResources: bindingResources, vertices, indices, - drawIndexed: { indexCount: mesh.triangles.length * 3, instanceCount: 8 }, + draw: { __type__: "DrawIndexed", indexCount: mesh.triangles.length * 3, instanceCount: 8 }, }] }; passEncoders.push(opaquePassEncoder); @@ -299,9 +299,9 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => { // initialize the heads buffer passEncoders.push({ - __type: "CopyBufferToBuffer", - source: getIGPUBuffer(headsInitBuffer), - destination: getIGPUBuffer(headsBuffer.bufferView), + __type__: "CopyBufferToBuffer", + source: getGBuffer(headsInitBuffer), + destination: getGBuffer(headsBuffer.bufferView), }); const scissorX = 0; @@ -313,60 +313,60 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // Draw the translucent objects - const translucentPassEncoder: IRenderPass = { + const translucentPassEncoder: RenderPass = { descriptor: translucentPassDescriptor, - renderObjects: [ + renderPassObjects: [ // Set the scissor to only process a horizontal slice of the frame { scissorRect: { x: scissorX, y: scissorY, width: scissorWidth, height: scissorHeight }, pipeline: translucentPipeline, - uniforms: { + bindingResources: { ...bindingResources, sliceInfo: sliceInfoBuffer[slice], }, vertices, indices, - drawIndexed: { indexCount: mesh.triangles.length * 3, instanceCount: 8 }, + draw: { __type__: "DrawIndexed", indexCount: mesh.triangles.length * 3, instanceCount: 8 }, } ], }; passEncoders.push(translucentPassEncoder); // Composite the opaque and translucent objects - const compositePassEncoder: IRenderPass + const compositePassEncoder: RenderPass = { descriptor: compositePassDescriptor, - renderObjects: [ + renderPassObjects: [ // Set the scissor to only process a horizontal slice of the frame { scissorRect: { x: scissorX, y: scissorY, width: scissorWidth, height: scissorHeight }, pipeline: compositePipeline, - uniforms: { + bindingResources: { ...bindingResources, sliceInfo: sliceInfoBuffer[slice] }, - drawVertex: { vertexCount: 6 }, + draw: { __type__: "DrawVertex", vertexCount: 6 }, } ] }; passEncoders.push(compositePassEncoder); } - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders, }], }; // update the uniform buffer - uniforms.maxStorableFragments = averageLayersPerFragment * canvas.width * sliceHeight; - uniforms.targetWidth = canvas.width; + reactive(uniforms).maxStorableFragments = averageLayersPerFragment * canvas.width * sliceHeight; + reactive(uniforms).targetWidth = canvas.width; return function doDraw() { // update the uniform buffer { - uniforms.modelViewProjectionMatrix = getCameraViewProjMatrix(); + reactive(uniforms).modelViewProjectionMatrix = getCameraViewProjMatrix(); } webgpu.submit(submit); diff --git a/examples/src/webgpu/animometer/index.ts b/examples/src/webgpu/animometer/index.ts index 1694cfa010203727df82d9cd7e6e18b85af50b9a..879202e5c54db61039e2b296b0c6d3d36885a900 100644 --- a/examples/src/webgpu/animometer/index.ts +++ b/examples/src/webgpu/animometer/index.ts @@ -2,8 +2,8 @@ import { GUI } from "dat.gui"; import animometerWGSL from "./animometer.wgsl"; -import { IRenderObject, IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit } from "@feng3d/render-api"; -import { IGPURenderBundle, WebGPU } from "@feng3d/webgpu"; +import { reactive, RenderObject, RenderPass, RenderPassDescriptor, RenderPipeline, Submit } from "@feng3d/render-api"; +import { RenderBundle, WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement, gui: GUI) => { @@ -33,7 +33,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const vec4Size = 4 * Float32Array.BYTES_PER_ELEMENT; - const pipelineDesc: IRenderPipeline = { + const pipelineDesc: RenderPipeline = { vertex: { code: animometerWGSL, }, @@ -42,10 +42,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, primitive: { frontFace: "ccw", - } + }, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { ...pipelineDesc, }; @@ -56,14 +56,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => 0.1, -0.1, 0, 1, /**/ 0, 0, 1, 1, ]); - const renderObject: IRenderObject = { - pipeline, + const renderObject: RenderObject = { + pipeline: pipeline, + bindingResources: {}, vertices: { position: { data: vertexBuffer, format: "float32x4", offset: 0, arrayStride: 2 * vec4Size }, color: { data: vertexBuffer, format: "float32x4", offset: vec4Size, arrayStride: 2 * vec4Size }, }, - uniforms: {}, - drawVertex: { vertexCount: 3, instanceCount: 1 }, + draw: { __type__: "DrawVertex", vertexCount: 3, instanceCount: 1 }, }; const uniformBytes = 5 * Float32Array.BYTES_PER_ELEMENT; @@ -88,12 +88,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => value: 0, }; - const renderObjects0: IRenderObject[] = []; + const renderObjects0: RenderObject[] = []; for (let i = 0; i < maxTriangles; ++i) { renderObjects0[i] = { ...renderObject, - uniforms: { + bindingResources: { time, uniforms: { bufferView: new Float32Array(uniformBuffer.buffer, i * alignedUniformBytes, 5), @@ -102,7 +102,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; } - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -120,13 +120,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => let startTime: number; const uniformTime = new Float32Array([0]); - const renderBundleObject: IGPURenderBundle = { - __type: "RenderBundle", + const renderBundleObject: RenderBundle = { + __type__: "RenderBundle", renderObjects }; - const renderPasss: IRenderPass[] = []; - const submit: ISubmit = { + const renderPasss: RenderPass[] = []; + const submit: Submit = { commandEncoders: [ { passEncoders: renderPasss, @@ -134,14 +134,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ] }; - const renderBundlesPass = { + const renderBundlesPass: RenderPass = { descriptor: renderPassDescriptor, - renderObjects: [renderBundleObject], + renderPassObjects: [renderBundleObject], }; - const renderPass = { + const renderPass: RenderPass = { descriptor: renderPassDescriptor, - renderObjects, + renderPassObjects: renderObjects, }; return function doDraw(timestamp: number) @@ -152,7 +152,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } uniformTime[0] = (timestamp - startTime) / 1000; - time.value = uniformTime[0]; + reactive(time).value = uniformTime[0]; if (settings.renderBundles) { diff --git a/examples/src/webgpu/atmosphericScatteringSky/index.ts b/examples/src/webgpu/atmosphericScatteringSky/index.ts index 8328949a96158adf6e9d72287553f6c2b05dfa85..a8ee3da32aae41f531526276bf128c29c914031a 100644 --- a/examples/src/webgpu/atmosphericScatteringSky/index.ts +++ b/examples/src/webgpu/atmosphericScatteringSky/index.ts @@ -2,8 +2,8 @@ import { GUI } from "dat.gui"; import atmosphericScatteringSkyWGSL from "./atmosphericScatteringSky.wgsl"; -import { ITexture } from "@feng3d/render-api"; -import { IGPUCanvasContext, IGPUComputeObject, WebGPU } from "@feng3d/webgpu"; +import { CanvasContext, Texture } from "@feng3d/render-api"; +import { ComputeObject, WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement, gui: GUI) => { @@ -11,7 +11,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; - const context: IGPUCanvasContext = { + const context: CanvasContext = { canvasId: canvas.id, configuration: { format: "rgba16float", @@ -21,7 +21,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const webgpu = await new WebGPU().init(); - const framebuffer: ITexture = { + const framebuffer: Texture = { label: "framebuffer", size: [canvas.width, canvas.height], format: "rgba16float", @@ -44,11 +44,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => skyColor: [1, 1, 1, 1], }; - const computeObject0: IGPUComputeObject = { + const computeObject0: ComputeObject = { pipeline: { compute: { code: atmosphericScatteringSkyWGSL } }, - uniforms: { + bindingResources: { uniformBuffer, outTexture: { texture: framebuffer } }, @@ -59,7 +59,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => function frame() { webgpu.submit({ - commandEncoders: [{ passEncoders: [{ __type: "ComputePass", computeObjects: [computeObject0] }] }] + commandEncoders: [{ passEncoders: [{ __type__: "ComputePass", computeObjects: [computeObject0] }] }] }); ++t; diff --git a/examples/src/webgpu/bitonicSort/bitonicDisplay.ts b/examples/src/webgpu/bitonicSort/bitonicDisplay.ts index 05d0b727bffc02d51828f464dacac61356f968cc..d7e796054c61af4743fc498942598b584663091c 100644 --- a/examples/src/webgpu/bitonicSort/bitonicDisplay.ts +++ b/examples/src/webgpu/bitonicSort/bitonicDisplay.ts @@ -1,4 +1,4 @@ -import { IBufferBinding, ICommandEncoder, IRenderPassDescriptor, IUniforms } from "@feng3d/render-api"; +import { BindingResources, BufferBinding, CommandEncoder, reactive, RenderPassDescriptor } from "@feng3d/render-api"; import bitonicDisplay from "./bitonicDisplay.frag.wgsl"; import { Base2DRendererClass } from "./utils"; @@ -12,11 +12,11 @@ export default class BitonicDisplayRenderer extends Base2DRendererClass { switchBindGroup: (name: string) => void; setArguments: (args: BitonicDisplayRenderArgs) => void; - computeBGDescript: IUniforms; + computeBGDescript: BindingResources; constructor( - renderPassDescriptor: IRenderPassDescriptor, - computeBGDescript: IUniforms, + renderPassDescriptor: RenderPassDescriptor, + computeBGDescript: BindingResources, label: string ) { @@ -24,11 +24,11 @@ export default class BitonicDisplayRenderer extends Base2DRendererClass this.renderPassDescriptor = renderPassDescriptor; this.computeBGDescript = computeBGDescript; - const fragment_uniforms: IBufferBinding = { + const fragment_uniforms: BufferBinding = { highlight: undefined, }; - computeBGDescript.fragment_uniforms = fragment_uniforms; + reactive(computeBGDescript).fragment_uniforms = fragment_uniforms; this.pipeline = super.create2DRenderPipeline( label, @@ -37,11 +37,11 @@ export default class BitonicDisplayRenderer extends Base2DRendererClass this.setArguments = (args: BitonicDisplayRenderArgs) => { - fragment_uniforms.highlight = args.highlight; + reactive(fragment_uniforms).highlight = args.highlight; }; } - startRun(commandEncoder: ICommandEncoder, args: BitonicDisplayRenderArgs) + startRun(commandEncoder: CommandEncoder, args: BitonicDisplayRenderArgs) { this.setArguments(args); super.executeRun(commandEncoder, this.renderPassDescriptor, this.pipeline, this.computeBGDescript); diff --git a/examples/src/webgpu/bitonicSort/index.ts b/examples/src/webgpu/bitonicSort/index.ts index 20ff19141a40071b0d3f51e8d9e15b0d9f5572a3..8acafea5752303049e25148425889d4292b0836f 100644 --- a/examples/src/webgpu/bitonicSort/index.ts +++ b/examples/src/webgpu/bitonicSort/index.ts @@ -1,8 +1,6 @@ -import { IBuffer, IBufferBinding, ICommandEncoder, IRenderPassDescriptor, ISubmit, IUniforms } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { getIGPUBuffer, IGPUComputePass, IGPUComputePipeline, IGPUTimestampQuery, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, BufferBinding, CommandEncoder, Buffer, RenderPassDescriptor, Submit, reactive } from "@feng3d/render-api"; +import { ComputePass, ComputePipeline, TimestampQuery, WebGPU, getGBuffer } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; -import Stats from "stats.js"; import atomicToZero from "./atomicToZero.wgsl"; import { NaiveBitonicCompute } from "./bitonicCompute"; @@ -103,32 +101,33 @@ async function init( const maxInvocationsX = webgpu.device.limits.maxComputeWorkgroupSizeX; // Handle timestamp query stuff - const querySet: IGPUTimestampQuery = {}; - watcher.watch(querySet, "elapsedNs", () => - { - // Calculate new step, sort, and average sort times - const newStepTime = querySet.elapsedNs / 1000000; - const newSortTime = settings.sortTime + newStepTime; - // Apply calculated times to settings object as both number and 'ms' appended string - settings.stepTime = newStepTime; - settings.sortTime = newSortTime; - stepTimeController.setValue(`${newStepTime.toFixed(5)}ms`); - sortTimeController.setValue(`${newSortTime.toFixed(5)}ms`); - // Calculate new average sort upon end of final execution step of a full bitonic sort. - if (highestBlockHeight === settings["Total Elements"] * 2) + const querySet: TimestampQuery = { + onQuery: (elapsedNs) => { - // Lock off access to this larger if block..not best architected solution but eh - highestBlockHeight *= 2; - settings.configToCompleteSwapsMap[settings.configKey].time - += newSortTime; - const averageSortTime - = settings.configToCompleteSwapsMap[settings.configKey].time - / settings.configToCompleteSwapsMap[settings.configKey].sorts; - averageSortTimeController.setValue( - `${averageSortTime.toFixed(5)}ms` - ); - } - }, undefined, false); + // Calculate new step, sort, and average sort times + const newStepTime = elapsedNs / 1000000; + const newSortTime = settings.sortTime + newStepTime; + // Apply calculated times to settings object as both number and 'ms' appended string + settings.stepTime = newStepTime; + settings.sortTime = newSortTime; + stepTimeController.setValue(`${newStepTime.toFixed(5)}ms`); + sortTimeController.setValue(`${newSortTime.toFixed(5)}ms`); + // Calculate new average sort upon end of final execution step of a full bitonic sort. + if (highestBlockHeight === settings["Total Elements"] * 2) + { + // Lock off access to this larger if block..not best architected solution but eh + highestBlockHeight *= 2; + settings.configToCompleteSwapsMap[settings.configKey].time + += newSortTime; + const averageSortTime + = settings.configToCompleteSwapsMap[settings.configKey].time + / settings.configToCompleteSwapsMap[settings.configKey].sorts; + averageSortTimeController.setValue( + `${averageSortTime.toFixed(5)}ms` + ); + } + }, + }; const totalElementOptions = []; const maxElements = maxInvocationsX * 32; @@ -256,34 +255,34 @@ async function init( const elementsBufferSize = Float32Array.BYTES_PER_ELEMENT * totalElementOptions[0]; // Initialize input, output, staging buffers - const elementsInputBuffer: IBufferBinding = { + const elementsInputBuffer: BufferBinding = { bufferView: new Uint8Array(elementsBufferSize) }; - const elementsOutputBuffer: IBufferBinding = { + const elementsOutputBuffer: BufferBinding = { bufferView: new Uint8Array(elementsBufferSize) }; - const elementsStagingBuffer: IBuffer = { + const elementsStagingBuffer: Buffer = { size: elementsBufferSize, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, }; // Initialize atomic swap buffer on GPU and CPU. Counts number of swaps actually performed by // compute shader (when value at index x is greater than value at index y) - const atomicSwapsOutputBuffer: IBufferBinding = { + const atomicSwapsOutputBuffer: BufferBinding = { bufferView: new Uint32Array(1) }; - const atomicSwapsStagingBuffer: IBuffer = { + const atomicSwapsStagingBuffer: Buffer = { size: Uint32Array.BYTES_PER_ELEMENT, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, }; // Create uniform buffer for compute shader - const computeUniformsBuffer: IBufferBinding = { + const computeUniformsBuffer: BufferBinding = { // width, height, blockHeight, algo bufferView: new Float32Array(4), }; - const computeBGCluster: IUniforms = { + const computeBGCluster: BindingResources = { input_data: elementsInputBuffer, data: elementsInputBuffer, output_data: elementsOutputBuffer, @@ -291,21 +290,21 @@ async function init( uniforms: computeUniformsBuffer, }; - let computePipeline: IGPUComputePipeline = { + let computePipeline: ComputePipeline = { compute: { code: NaiveBitonicCompute(settings["Workgroup Size"]), }, }; // Simple pipeline that zeros out an atomic value at group 0 binding 3 - const atomicToZeroComputePipeline: IGPUComputePipeline = { + const atomicToZeroComputePipeline: ComputePipeline = { compute: { code: atomicToZero, }, }; // Create bitonic debug renderer - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, // Assigned later @@ -375,13 +374,13 @@ async function init( nextBlockHeightController.setValue(2); // Reset Total Swaps by setting atomic value to 0 - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders: [{ - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ pipeline: atomicToZeroComputePipeline, - uniforms: computeBGCluster, + bindingResources: computeBGCluster, workgroups: { workgroupCountX: 1 }, }] }] @@ -704,10 +703,10 @@ async function init( { // Write elements buffer - let iGPUBuffer = getIGPUBuffer(elementsInputBuffer.bufferView); + let iGPUBuffer = getGBuffer(elementsInputBuffer.bufferView); let writeBuffers = iGPUBuffer.writeBuffers || []; writeBuffers.push({ data: elements }); - iGPUBuffer.writeBuffers = writeBuffers; + reactive(iGPUBuffer).writeBuffers = writeBuffers; const dims = new Float32Array([ settings["Grid Width"], @@ -718,14 +717,14 @@ async function init( settings["Next Swap Span"], ]); - iGPUBuffer = getIGPUBuffer(computeUniformsBuffer.bufferView); + iGPUBuffer = getGBuffer(computeUniformsBuffer.bufferView); writeBuffers = iGPUBuffer.writeBuffers || []; writeBuffers.push({ data: dims }); writeBuffers.push({ bufferOffset: 8, data: stepDetails }); - iGPUBuffer.writeBuffers = writeBuffers; + reactive(iGPUBuffer).writeBuffers = writeBuffers; - const commandEncoder: ICommandEncoder = { passEncoders: [] }; - const submit: ISubmit = { commandEncoders: [commandEncoder] }; + const commandEncoder: CommandEncoder = { passEncoders: [] }; + const submit: Submit = { commandEncoders: [commandEncoder] }; bitonicDisplayRenderer.startRun(commandEncoder, { highlight: settings["Display Mode"] === "Elements" ? 0 : 1, @@ -735,12 +734,14 @@ async function init( && highestBlockHeight < settings["Total Elements"] * 2 ) { - const computePassEncoder: IGPUComputePass = { - __type: "ComputePass", - timestampQuery: querySet, + const computePassEncoder: ComputePass = { + __type__: "ComputePass", + descriptor: { + timestampQuery: querySet, + }, computeObjects: [{ pipeline: computePipeline, - uniforms: computeBGCluster, + bindingResources: computeBGCluster, workgroups: { workgroupCountX: settings["Workgroups Per Step"] }, }] }; @@ -795,16 +796,16 @@ async function init( // Copy GPU accessible buffers to CPU accessible buffers commandEncoder.passEncoders.push( { - __type: "CopyBufferToBuffer", - source: getIGPUBuffer(elementsOutputBuffer.bufferView), + __type__: "CopyBufferToBuffer", + source: getGBuffer(elementsOutputBuffer.bufferView), sourceOffset: 0, destination: elementsStagingBuffer, destinationOffset: 0, size: elementsBufferSize }, { - __type: "CopyBufferToBuffer", - source: getIGPUBuffer(atomicSwapsOutputBuffer.bufferView), + __type__: "CopyBufferToBuffer", + source: getGBuffer(atomicSwapsOutputBuffer.bufferView), sourceOffset: 0, destination: atomicSwapsStagingBuffer, destinationOffset: 0, @@ -838,10 +839,7 @@ async function init( } const canvas = document.getElementById("webgpu") as HTMLCanvasElement; -const stats = new Stats(); const gui = new GUI(); -document.body.appendChild(stats.dom); - init(gui, canvas); diff --git a/examples/src/webgpu/bitonicSort/utils.ts b/examples/src/webgpu/bitonicSort/utils.ts index a9f4d09a0a321617aaf8aa5e6653cfeaf3b752e1..baedffd3a5ac28b909bcbbd0ef87d547fc2d6303 100644 --- a/examples/src/webgpu/bitonicSort/utils.ts +++ b/examples/src/webgpu/bitonicSort/utils.ts @@ -1,4 +1,4 @@ -import { ICommandEncoder, IRenderPass, IRenderPassDescriptor, IRenderPipeline, IUniforms } from "@feng3d/render-api"; +import { BindingResources, CommandEncoder, RenderPass, RenderPassDescriptor, RenderPipeline } from "@feng3d/render-api"; const fullscreenTexturedQuad = ` @@ -39,27 +39,27 @@ export abstract class Base2DRendererClass { abstract switchBindGroup(name: string): void; abstract startRun( - commandEncoder: ICommandEncoder, + commandEncoder: CommandEncoder, ...args: unknown[] ): void; - renderPassDescriptor: IRenderPassDescriptor; - pipeline: IRenderPipeline; + renderPassDescriptor: RenderPassDescriptor; + pipeline: RenderPipeline; bindGroupMap: Record; currentBindGroupName: string; executeRun( - commandEncoder: ICommandEncoder, - renderPassDescriptor: IRenderPassDescriptor, - pipeline: IRenderPipeline, - bindingResources?: IUniforms + commandEncoder: CommandEncoder, + renderPassDescriptor: RenderPassDescriptor, + pipeline: RenderPipeline, + bindingResources?: BindingResources ) { - const passEncoder: IRenderPass = { + const passEncoder: RenderPass = { descriptor: renderPassDescriptor, - renderObjects: [{ - pipeline, - uniforms: bindingResources, - drawVertex: { vertexCount: 6, instanceCount: 1 } + renderPassObjects: [{ + pipeline: pipeline, + bindingResources: bindingResources, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1 }, }], }; commandEncoder.passEncoders.push(passEncoder); @@ -70,7 +70,7 @@ export abstract class Base2DRendererClass code: string, ) { - const renderPipeline: IRenderPipeline = { + const renderPipeline: RenderPipeline = { label: `${label}.pipeline`, vertex: { code: fullscreenTexturedQuad, diff --git a/examples/src/webgpu/blending/index.ts b/examples/src/webgpu/blending/index.ts index e23096c08be06db0ecc677dec9e434f08eb64c0c..0140d04683c7f054b97362a67d1cff03a9eb8ff4 100644 --- a/examples/src/webgpu/blending/index.ts +++ b/examples/src/webgpu/blending/index.ts @@ -2,8 +2,16 @@ import { GUI } from "dat.gui"; import { mat4 } from "wgpu-matrix"; import texturedQuadWGSL from "./texturedQuad.wgsl"; -import { IBlendComponent, IRenderObject, IRenderPassDescriptor, IRenderPassObject, IRenderPipeline, ISampler, ISubmit, ITexture, ITextureView, IUniforms } from "@feng3d/render-api"; -import { IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, BlendComponent, CanvasContext, Color, computed, reactive, RenderObject, RenderPassDescriptor, RenderPassObject, RenderPipeline, Sampler, Submit, Texture, TextureView } from "@feng3d/render-api"; +import { WebGPU } from "@feng3d/webgpu"; + +declare module "@feng3d/render-api" +{ + interface Uniforms + { + matrix?: Float32Array; + } +} const init = async (canvas: HTMLCanvasElement, gui: GUI) => { @@ -93,8 +101,8 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; - const context: IGPUCanvasContext = { canvasId: canvas.id, configuration: {} }; - const canvasTexture: ITextureView = { texture: { context } }; + const context: CanvasContext = { canvasId: canvas.id, configuration: {} }; + const canvasTexture: TextureView = { texture: { context } }; // Get a WebGPU context from the canvas and configure it const webgpu = await new WebGPU().init(); @@ -108,7 +116,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ) { const { flipY, premultipliedAlpha } = options; - const texture: ITexture = { + const texture: Texture = { format: "rgba8unorm", size: [source.width, source.height], sources: [{ image: source, flipY, premultipliedAlpha }] @@ -135,16 +143,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => { premultipliedAlpha: true } ); - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear", }; - type Uniforms = { - matrix: Float32Array; - }; - // function makeUniformBufferAndValues(): Uniforms // { // // offsets to the various uniform values in float32 indices @@ -163,25 +167,25 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const srcUniform = { matrix: new Float32Array(16) }; const dstUniform = { matrix: new Float32Array(16) }; - const srcBindGroupUnpremultipliedAlpha: IUniforms = { + const srcBindGroupUnpremultipliedAlpha: BindingResources = { ourSampler: sampler, ourTexture: { texture: srcTextureUnpremultipliedAlpha }, uni: srcUniform, }; - const dstBindGroupUnpremultipliedAlpha: IUniforms = { + const dstBindGroupUnpremultipliedAlpha: BindingResources = { ourSampler: sampler, ourTexture: { texture: dstTextureUnpremultipliedAlpha }, uni: dstUniform, }; - const srcBindGroupPremultipliedAlpha: IUniforms = { + const srcBindGroupPremultipliedAlpha: BindingResources = { ourSampler: sampler, ourTexture: { texture: srcTexturePremultipliedAlpha }, uni: srcUniform, }; - const dstBindGroupPremultipliedAlpha: IUniforms = { + const dstBindGroupPremultipliedAlpha: BindingResources = { ourSampler: sampler, ourTexture: { texture: dstTexturePremultipliedAlpha }, uni: dstUniform, @@ -203,7 +207,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ]; const clearValue: [red: number, green: number, blue: number, alpha: number] = [0, 0, 0, 0]; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { label: "our basic canvas renderPass", colorAttachments: [ { @@ -324,22 +328,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const kPresets = keysOf(presets); type Preset = (typeof kPresets)[number]; - const color: IBlendComponent = { - operation: "add", - srcFactor: "one", - dstFactor: "one-minus-src", - }; - - const alpha: IBlendComponent = { - operation: "add", - srcFactor: "one", - dstFactor: "one-minus-src", - }; - - const constant = { + const constant = reactive({ color: [1, 0.5, 0.25], alpha: 1, - }; + }); const clear = { color: [0, 0, 0], @@ -378,58 +370,85 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ); } } + const constantColor: Color = [0, 0, 0, 0]; + const srcPipeline: RenderPipeline = { + label: "hardcoded textured quad pipeline", + vertex: { + code: texturedQuadWGSL, + }, + fragment: { + code: texturedQuadWGSL, + targets: [ + { + blend: { + constantColor: constantColor, + color: { + operation: "add", + srcFactor: "one", + dstFactor: "one-minus-src", + }, + alpha: { + operation: "add", + srcFactor: "one", + dstFactor: "one-minus-src", + }, + }, + }, + ], + }, + }; + + const r_color = reactive(srcPipeline.fragment.targets[0].blend.color); + const r_alpha = reactive(srcPipeline.fragment.targets[0].blend.alpha); function applyPreset() { const preset = presets[settings.preset]; - Object.assign(color, preset.color); - Object.assign(alpha, preset.alpha || preset.color); + Object.assign(r_color, preset.color); + Object.assign(r_alpha, preset.alpha || preset.color); } gui .add(settings, "alphaMode", ["opaque", "premultiplied"]) - .name("canvas alphaMode") - .onChange(render); + .name("canvas alphaMode"); gui .add(settings, "textureSet", [ "premultiplied alpha", "un-premultiplied alpha", ]) - .name("texture data") - .onChange(render); + .name("texture data"); gui.add(settings, "preset", [...Object.keys(presets)]).onChange(() => { applyPreset(); - render(); }); const colorFolder = gui.addFolder("color"); colorFolder.open(); - colorFolder.add(color, "operation", operations).onChange(render); - colorFolder.add(color, "srcFactor", factors).onChange(render); - colorFolder.add(color, "dstFactor", factors).onChange(render); + colorFolder.add(r_color, "operation", operations); + colorFolder.add(r_color, "srcFactor", factors); + colorFolder.add(r_color, "dstFactor", factors); const alphaFolder = gui.addFolder("alpha"); alphaFolder.open(); - alphaFolder.add(alpha, "operation", operations).onChange(render); - alphaFolder.add(alpha, "srcFactor", factors).onChange(render); - alphaFolder.add(alpha, "dstFactor", factors).onChange(render); + alphaFolder.add(r_alpha, "operation", operations); + alphaFolder.add(r_alpha, "srcFactor", factors); + alphaFolder.add(r_alpha, "dstFactor", factors); const constantFolder = gui.addFolder("constant"); constantFolder.open(); constantFolder .addColor(new GUIColorHelper(constant.color), "value") .name("color") - .onChange(render); - constantFolder.add(constant, "alpha", 0, 1).onChange(render); + ; + constantFolder.add(constant, "alpha", 0, 1); const clearFolder = gui.addFolder("clear color"); clearFolder.open(); - clearFolder.add(clear, "premultiply").onChange(render); - clearFolder.add(clear, "alpha", 0, 1).onChange(render); - clearFolder.addColor(new GUIColorHelper(clear.color), "value").onChange(render); + clearFolder.add(clear, "premultiply"); + clearFolder.add(clear, "alpha", 0, 1); + clearFolder.addColor(new GUIColorHelper(clear.color), "value"); - const dstPipeline: IRenderPipeline = { + const dstPipeline: RenderPipeline = { label: "hardcoded textured quad pipeline", vertex: { code: texturedQuadWGSL, @@ -439,33 +458,66 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; + function updateUniforms( + uniforms: { matrix: Float32Array; }, + canvas: HTMLCanvasElement, + texture: Texture + ) + { + const projectionMatrix = mat4.ortho( + 0, + canvas.width / devicePixelRatio, + canvas.height / devicePixelRatio, + 0, + -1, + 1 + ); + + mat4.scale( + projectionMatrix, + [texture.size[0], texture.size[1], 1], + uniforms.matrix + ); + + uniforms.matrix = new Float32Array(uniforms.matrix); + } + + + const ro: RenderObject = { + pipeline: dstPipeline, + draw: { __type__: "DrawVertex", vertexCount: 6 }, + }; + + const ro1: RenderObject = { + pipeline: srcPipeline, + draw: { __type__: "DrawVertex", vertexCount: 6 }, + }; + + const renderObjects: RenderPassObject[] = [ + ro, + ro1, + ]; + + const submit: Submit = { + commandEncoders: [{ + passEncoders: [{ + descriptor: renderPassDescriptor, + renderPassObjects: renderObjects + }] + }], + }; + function render() { gui.updateDisplay(); - const srcPipeline: IRenderPipeline = { - label: "hardcoded textured quad pipeline", - vertex: { - code: texturedQuadWGSL, - }, - fragment: { - code: texturedQuadWGSL, - targets: [ - { - blend: { - constantColor: [...constant.color, constant.alpha] as any, - color, - alpha, - }, - }, - ], - }, - }; - const { srcTexture, dstTexture, srcBindGroup, dstBindGroup } = textureSets[settings.textureSet === "premultiplied alpha" ? 0 : 1]; - context.configuration.alphaMode = settings.alphaMode; + reactive(ro).bindingResources = dstBindGroup; + reactive(ro1).bindingResources = srcBindGroup; + + reactive(context.configuration).alphaMode = settings.alphaMode; // Apply the clearValue, pre-multiplying or not it based on the settings. { @@ -477,59 +529,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => clearValue[3] = alpha; } - function updateUniforms( - uniforms: Uniforms, - canvas: HTMLCanvasElement, - texture: ITexture - ) - { - const projectionMatrix = mat4.ortho( - 0, - canvas.width / devicePixelRatio, - canvas.height / devicePixelRatio, - 0, - -1, - 1 - ); + constantColor[0] = constant.color[0]; + constantColor[1] = constant.color[1]; + constantColor[2] = constant.color[2]; + constantColor[3] = constant.alpha; - mat4.scale( - projectionMatrix, - [texture.size[0], texture.size[1], 1], - uniforms.matrix - ); - - uniforms.matrix = new Float32Array(uniforms.matrix); - } updateUniforms(srcUniform, canvas, srcTexture); updateUniforms(dstUniform, canvas, dstTexture); - const ro: IRenderObject = { - pipeline: dstPipeline, - uniforms: dstBindGroup, - drawVertex: { vertexCount: 6 }, - }; - - const ro1: IRenderObject = { - pipeline: srcPipeline, - uniforms: srcBindGroup, - drawVertex: { vertexCount: 6 }, - }; - - const renderObjects: IRenderPassObject[] = [ - ro, - ro1, - ]; - - const submit: ISubmit = { - commandEncoders: [{ - passEncoders: [{ - descriptor: renderPassDescriptor, - renderObjects: renderObjects - }] - }], - }; - webgpu.submit(submit); + + requestAnimationFrame(render); } applyPreset(); diff --git a/examples/src/webgpu/cameras/camera.ts b/examples/src/webgpu/cameras/camera.ts index bfbce41441470573b1490f1c9d08a3d313553b83..ad9d7dace38891f40d17aa251d0bcb06be360de6 100644 --- a/examples/src/webgpu/cameras/camera.ts +++ b/examples/src/webgpu/cameras/camera.ts @@ -5,7 +5,8 @@ import { Mat4, Vec3, Vec4, mat4, vec3 } from "wgpu-matrix"; import Input from "./input"; // Common interface for camera implementations -export default interface Camera { +export default interface Camera +{ // update updates the camera using the user-input and returns the view matrix. update(delta_time: number, input: Input): Mat4; @@ -41,67 +42,67 @@ class CameraBase // Returns the camera matrix get matrix() -{ + { return this.matrix_; } // Assigns `mat` to the camera matrix set matrix(mat: Mat4) -{ + { mat4.copy(mat, this.matrix_); } // Returns the camera view matrix get view() -{ + { return this.view_; } // Assigns `mat` to the camera view set view(mat: Mat4) -{ + { mat4.copy(mat, this.view_); } // Returns column vector 0 of the camera matrix get right() -{ + { return this.right_; } // Assigns `vec` to the first 3 elements of column vector 0 of the camera matrix set right(vec: Vec3) -{ + { vec3.copy(vec, this.right_); } // Returns column vector 1 of the camera matrix get up() -{ + { return this.up_; } // Assigns `vec` to the first 3 elements of column vector 1 of the camera matrix set up(vec: Vec3) -{ + { vec3.copy(vec, this.up_); } // Returns column vector 2 of the camera matrix get back() -{ + { return this.back_; } // Assigns `vec` to the first 3 elements of column vector 2 of the camera matrix set back(vec: Vec3) -{ + { vec3.copy(vec, this.back_); } // Returns column vector 3 of the camera matrix get position() -{ + { return this.position_; } // Assigns `vec` to the first 3 elements of column vector 3 of the camera matrix set position(vec: Vec3) -{ + { vec3.copy(vec, this.position_); } } @@ -130,12 +131,12 @@ export class WASDCamera extends CameraBase implements Camera // Returns velocity vector get velocity() -{ + { return this.velocity_; } // Assigns `vec` to the velocity vector set velocity(vec: Vec3) -{ + { vec3.copy(vec, this.velocity_); } @@ -146,10 +147,10 @@ export class WASDCamera extends CameraBase implements Camera // The initial target of the camera target?: Vec3; }) -{ + { super(); if (options && (options.position || options.target)) -{ + { const position = options.position ?? vec3.create(0, 0, -5); const target = options.target ?? vec3.create(0, 0, 0); const back = vec3.normalize(vec3.sub(position, target)); @@ -160,19 +161,19 @@ export class WASDCamera extends CameraBase implements Camera // Returns the camera matrix get matrix() -{ + { return super.matrix; } // Assigns `mat` to the camera matrix, and recalcuates the camera angles set matrix(mat: Mat4) -{ + { super.matrix = mat; this.recalculateAngles(this.back); } update(deltaTime: number, input: Input): Mat4 -{ + { const sign = (positive: boolean, negative: boolean) => (positive ? 1 : 0) - (negative ? 1 : 0); @@ -216,12 +217,12 @@ export class WASDCamera extends CameraBase implements Camera // Invert the camera matrix to build the view matrix this.view = mat4.invert(this.matrix); -return this.view; + return this.view; } // Recalculates the yaw and pitch values from a directional vector recalculateAngles(dir: Vec3) -{ + { this.yaw = Math.atan2(dir[0], dir[2]); this.pitch = -Math.asin(dir[1]); } @@ -241,12 +242,12 @@ export class ArcballCamera extends CameraBase implements Camera // Returns the rotation axis get axis() -{ + { return this.axis_; } // Assigns `vec` to the rotation axis set axis(vec: Vec3) -{ + { vec3.copy(vec, this.axis_); } @@ -266,10 +267,10 @@ export class ArcballCamera extends CameraBase implements Camera // The initial position of the camera position?: Vec3; }) -{ + { super(); if (options && options.position) -{ + { this.position = options.position; this.distance = vec3.len(this.position); this.back = vec3.normalize(this.position); @@ -280,28 +281,28 @@ export class ArcballCamera extends CameraBase implements Camera // Returns the camera matrix get matrix() -{ + { return super.matrix; } // Assigns `mat` to the camera matrix, and recalcuates the distance set matrix(mat: Mat4) -{ + { super.matrix = mat; this.distance = vec3.len(this.position); } update(deltaTime: number, input: Input): Mat4 -{ + { const epsilon = 0.0000001; if (input.analog.touching) -{ + { // Currently being dragged. this.angularVelocity = 0; } - else -{ + else + { // Dampen any existing angular velocity this.angularVelocity *= Math.pow(1 - this.frictionCoefficient, deltaTime); } @@ -318,7 +319,7 @@ export class ArcballCamera extends CameraBase implements Camera const magnitude = vec3.len(crossProduct); if (magnitude > epsilon) -{ + { // Normalize the crossProduct to get the rotation axis this.axis = vec3.scale(crossProduct, 1 / magnitude); @@ -329,7 +330,7 @@ export class ArcballCamera extends CameraBase implements Camera // The rotation around this.axis to apply to the camera matrix this update const rotationAngle = this.angularVelocity * deltaTime; if (rotationAngle > epsilon) -{ + { // Rotate the matrix around axis // Note: The rotation is not done as a matrix-matrix multiply as the repeated multiplications // will quickly introduce substantial error into the matrix. @@ -340,7 +341,7 @@ export class ArcballCamera extends CameraBase implements Camera // recalculate `this.position` from `this.back` considering zoom if (input.analog.zoom !== 0) -{ + { this.distance *= 1 + input.analog.zoom * this.zoomSpeed; } this.position = vec3.scale(this.back, this.distance); @@ -348,18 +349,18 @@ export class ArcballCamera extends CameraBase implements Camera // Invert the camera matrix to build the view matrix this.view = mat4.invert(this.matrix); -return this.view; + return this.view; } // Assigns `this.right` with the cross product of `this.up` and `this.back` recalcuateRight() -{ + { this.right = vec3.normalize(vec3.cross(this.up, this.back)); } // Assigns `this.up` with the cross product of `this.back` and `this.right` recalcuateUp() -{ + { this.up = vec3.normalize(vec3.cross(this.back, this.right)); } } diff --git a/examples/src/webgpu/cameras/index.ts b/examples/src/webgpu/cameras/index.ts index 796f8921ce11e2935999f2c1ecec7e9a13c2b0ba..fe53162c72f461306664c82880c09bc595e3d32b 100644 --- a/examples/src/webgpu/cameras/index.ts +++ b/examples/src/webgpu/cameras/index.ts @@ -5,7 +5,7 @@ import { ArcballCamera, WASDCamera } from "./camera"; import cubeWGSL from "./cube.wgsl"; import { createInputHandler } from "./input"; -import { IRenderObject, IRenderPassDescriptor, IRenderPipeline, ISampler, ISubmit, ITexture, IVertexAttributes } from "@feng3d/render-api"; +import { reactive, RenderObject, RenderPassDescriptor, RenderPipeline, Sampler, Submit, Texture, VertexAttributes } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement, gui: GUI) => @@ -42,12 +42,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const webgpu = await new WebGPU().init(); // Create a vertex buffer from the cube data. - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: cubeWGSL, }, @@ -64,13 +64,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; // Fetch the image and upload it into a GPUTexture. - let cubeTexture: ITexture; + let cubeTexture: Texture; { const response = await fetch("../../../assets/img/Di-3d.png"); const imageBitmap = await createImageBitmap(await response.blob()); @@ -83,7 +83,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } // Create a sampler with linear filtering for smooth interpolation. - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; @@ -96,14 +96,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => myTexture: { texture: cubeTexture }, }; - const renderObject: IRenderObject = { - pipeline, + const renderObject: RenderObject = { + pipeline: pipeline, + bindingResources: bindingResources, vertices, - uniforms: bindingResources, - drawVertex: { vertexCount: cubeVertexCount }, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -122,11 +122,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPassDescriptor, renderObjects: [renderObject] }, + { descriptor: renderPassDescriptor, renderPassObjects: [renderObject] }, ] } ], @@ -154,7 +154,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => lastFrameMS = now; const modelViewProjection = getModelViewProjectionMatrix(deltaTime); - bindingResources.uniforms.modelViewProjectionMatrix = new Float32Array(modelViewProjection); // 使用 new Float32Array 是因为赋值不同的对象才会触发数据改变重新上传数据到GPU + reactive(bindingResources.uniforms).modelViewProjectionMatrix = new Float32Array(modelViewProjection); // 使用 new Float32Array 是因为赋值不同的对象才会触发数据改变重新上传数据到GPU webgpu.submit(data); diff --git a/examples/src/webgpu/computeBoids/index.ts b/examples/src/webgpu/computeBoids/index.ts index e380a98b6e457f6b39d0a8c775c1d1c31c7b176d..726e8709f91ea1d65d04354d7a160aa340d87777 100644 --- a/examples/src/webgpu/computeBoids/index.ts +++ b/examples/src/webgpu/computeBoids/index.ts @@ -1,5 +1,5 @@ -import { IRenderObject, IRenderPassDescriptor, ISubmit } from "@feng3d/render-api"; -import { IGPUComputeObject, WebGPU } from "@feng3d/webgpu"; +import { RenderObject, RenderPassDescriptor, Submit } from "@feng3d/render-api"; +import { ComputeObject, WebGPU } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; import spriteWGSL from "./sprite.wgsl"; @@ -50,11 +50,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => particleBuffers[i] = initialParticleData.slice(); } - const computeObject0: IGPUComputeObject = { + const computeObject0: ComputeObject = { pipeline: { compute: { code: updateSpritesWGSL } }, - uniforms: { + bindingResources: { params: simParams, particlesA: { bufferView: particleBuffers[0], @@ -66,22 +66,22 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => workgroups: { workgroupCountX: Math.ceil(numParticles / 64) }, }; - const computeObject1: IGPUComputeObject = { + const computeObject1: ComputeObject = { ...computeObject0, - uniforms: { - ...computeObject0.uniforms, + bindingResources: { + ...computeObject0.bindingResources, particlesA: { - ...computeObject0.uniforms.particlesA, + ...computeObject0.bindingResources.particlesA as {}, bufferView: particleBuffers[1], }, particlesB: { - ...computeObject0.uniforms.particlesA, + ...computeObject0.bindingResources.particlesA as {}, bufferView: particleBuffers[0], }, }, }; - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -90,7 +90,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ], }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: spriteWGSL }, fragment: { code: spriteWGSL }, primitive: { @@ -102,11 +102,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => a_particleVel: { data: particleBuffers[0], format: "float32x2", offset: 2 * 4, arrayStride: 4 * 4, stepMode: "instance" }, a_pos: { data: vertexBufferData, format: "float32x2" }, }, - drawVertex: { vertexCount: 3, instanceCount: numParticles } + draw: { __type__: "DrawVertex", vertexCount: 3, instanceCount: numParticles } }; - const renderObject1: IRenderObject = { + const renderObject1: RenderObject = { ...renderObject, + draw: renderObject.draw, vertices: { ...renderObject.vertices, a_particlePos: { @@ -123,12 +124,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => let t = 0; function frame() { - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { __type: "ComputePass", computeObjects: [[computeObject0, computeObject1][t % 2]] }, - { descriptor: renderPass, renderObjects: [[renderObject, renderObject1][(t + 1) % 2]] }, + { __type__: "ComputePass", computeObjects: [[computeObject0, computeObject1][t % 2]] }, + { descriptor: renderPass, renderPassObjects: [[renderObject, renderObject1][(t + 1) % 2]] }, ] } ], diff --git a/examples/src/webgpu/cornell/common.ts b/examples/src/webgpu/cornell/common.ts index 3b42357a31f1b14fc30b3c69aa4da7bc848c7c3f..2fbaad9c8d5bcde774d37d86e19e4c0271719004 100644 --- a/examples/src/webgpu/cornell/common.ts +++ b/examples/src/webgpu/cornell/common.ts @@ -1,5 +1,5 @@ -import { IUniforms } from "@feng3d/render-api"; -import { getIGPUBuffer } from "@feng3d/webgpu"; +import { BindingResources, reactive } from "@feng3d/render-api"; +import { getGBuffer } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; import commonWGSL from "./common.wgsl"; @@ -13,7 +13,7 @@ export default class Common readonly wgsl = commonWGSL; /** The common uniform buffer bind group and layout */ readonly uniforms: { - bindGroup: IUniforms; + bindGroup: BindingResources; }; private readonly uniformBuffer: Uint8Array; @@ -27,7 +27,7 @@ export default class Common + 4 * 16 // inv_mvp + 4 * 4); - const bindGroup: IUniforms = { + const bindGroup: BindingResources = { common_uniforms: { bufferView: this.uniformBuffer, }, @@ -77,7 +77,7 @@ export default class Common uniformDataU32[33] = 0xffffffff * Math.random(); uniformDataU32[34] = 0xffffffff * Math.random(); - getIGPUBuffer(this.uniformBuffer).writeBuffers = [{ data: uniformDataF32 }]; + reactive(getGBuffer(this.uniformBuffer)).writeBuffers = [{ data: uniformDataF32 }]; this.frame++; } diff --git a/examples/src/webgpu/cornell/index.ts b/examples/src/webgpu/cornell/index.ts index 43100bbea45eb248c2a65c43f982d32d5ba4cc22..de7669ce83b89d554865c4dbbb6091e2e24b68bf 100644 --- a/examples/src/webgpu/cornell/index.ts +++ b/examples/src/webgpu/cornell/index.ts @@ -7,8 +7,8 @@ import Raytracer from "./raytracer"; import Scene from "./scene"; import Tonemapper from "./tonemapper"; -import { ICommandEncoder, ISubmit, ITexture } from "@feng3d/render-api"; -import { IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { CanvasContext, CommandEncoder, Submit, Texture } from "@feng3d/render-api"; +import { WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement, gui: GUI) => { @@ -16,7 +16,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const requiredFeatures: GPUFeatureName[] = presentationFormat === "bgra8unorm" ? ["bgra8unorm-storage"] : []; - const context: IGPUCanvasContext = { + const context: CanvasContext = { canvasId: canvas.id, configuration: { format: "rgba16float", @@ -41,7 +41,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const webgpu = await new WebGPU().init(undefined, { requiredFeatures }); - const framebuffer: ITexture = { + const framebuffer: Texture = { label: "framebuffer", size: [canvas.width, canvas.height], format: "rgba16float", @@ -55,13 +55,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const tonemapper = new Tonemapper(common, framebuffer, { context }); // 光栅化渲染 - const rasterizerCommandEncoder: ICommandEncoder = { passEncoders: [] }; + const rasterizerCommandEncoder: CommandEncoder = { passEncoders: [] }; radiosity.encode(rasterizerCommandEncoder); rasterizer.encode(rasterizerCommandEncoder); tonemapper.encode(rasterizerCommandEncoder); // 光线追踪渲染 - const raytracerCommandEncoder: ICommandEncoder = { passEncoders: [] }; + const raytracerCommandEncoder: CommandEncoder = { passEncoders: [] }; radiosity.encode(raytracerCommandEncoder); raytracer.encode(raytracerCommandEncoder); tonemapper.encode(raytracerCommandEncoder); @@ -74,7 +74,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }); radiosity.run(); - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [params.renderer === "rasterizer" ? rasterizerCommandEncoder : raytracerCommandEncoder] }; diff --git a/examples/src/webgpu/cornell/radiosity.ts b/examples/src/webgpu/cornell/radiosity.ts index 99a575ed1bc39e573773a794e2b23c4eb72350d7..bed912c78aeeb7e20ba2ad9370337a9b860cf019 100644 --- a/examples/src/webgpu/cornell/radiosity.ts +++ b/examples/src/webgpu/cornell/radiosity.ts @@ -1,5 +1,5 @@ -import { ICommandEncoder, IPassEncoder, ITexture, IUniforms } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPUComputePipeline } from "@feng3d/webgpu"; +import { BindingResources, CommandEncoder, PassEncoder, reactive, Texture } from "@feng3d/render-api"; +import { ComputePipeline, getGBuffer } from "@feng3d/webgpu"; import Common from "./common"; import radiosityWGSL from "./radiosity.wgsl"; @@ -17,7 +17,7 @@ export default class Radiosity static readonly lightmapHeight = 256; // The output lightmap. - readonly lightmap: ITexture; + readonly lightmap: Texture; // Number of photons emitted per workgroup. // This is equal to the workgroup size (one photon per invocation) @@ -37,9 +37,9 @@ export default class Radiosity private readonly common: Common; private readonly scene: Scene; - private readonly radiosityPipeline: IGPUComputePipeline; - private readonly accumulationToLightmapPipeline: IGPUComputePipeline; - private readonly bindGroup: IUniforms; + private readonly radiosityPipeline: ComputePipeline; + private readonly accumulationToLightmapPipeline: ComputePipeline; + private readonly bindGroup: BindingResources; private readonly accumulationBuffer: Uint8Array; private readonly uniformBuffer: Uint8Array; @@ -109,12 +109,12 @@ export default class Radiosity const lightmapSize = this.lightmap.size; this.passEncoders = [{ - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [ // Dispatch the radiosity workgroups { pipeline: this.radiosityPipeline, - uniforms: { + bindingResources: { ...this.common.uniforms.bindGroup, ...this.bindGroup, }, @@ -123,7 +123,7 @@ export default class Radiosity // Then copy the 'accumulation' data to 'lightmap' { pipeline: this.accumulationToLightmapPipeline, - uniforms: { + bindingResources: { ...this.common.uniforms.bindGroup, ...this.bindGroup, }, @@ -136,9 +136,9 @@ export default class Radiosity ], }]; } - private passEncoders: IPassEncoder[]; + private passEncoders: PassEncoder[]; - encode(commandEncoder: ICommandEncoder) + encode(commandEncoder: CommandEncoder) { this.passEncoders.forEach((v) => { @@ -169,6 +169,6 @@ export default class Radiosity uniformDataF32[4] = this.scene.lightCenter[0]; uniformDataF32[5] = this.scene.lightCenter[1]; uniformDataF32[6] = this.scene.lightCenter[2]; - getIGPUBuffer(this.uniformBuffer).writeBuffers = [{ data: uniformDataF32 }]; + reactive(getGBuffer(this.uniformBuffer)).writeBuffers = [{ data: uniformDataF32 }]; } } diff --git a/examples/src/webgpu/cornell/rasterizer.ts b/examples/src/webgpu/cornell/rasterizer.ts index 99d6dad2a6f8b31d0cc11747a74b06234205a98f..0cf82e6a7d939ad56e26e8e8e2acfbba949c6e80 100644 --- a/examples/src/webgpu/cornell/rasterizer.ts +++ b/examples/src/webgpu/cornell/rasterizer.ts @@ -1,4 +1,4 @@ -import { ICommandEncoder, IRenderPass, IRenderPassDescriptor, IRenderPipeline, ITexture, IUniforms } from "@feng3d/render-api"; +import { BindingResources, CommandEncoder, RenderPass, RenderPassDescriptor, RenderPipeline, Texture } from "@feng3d/render-api"; import Common from "./common"; import Radiosity from "./radiosity"; @@ -12,15 +12,15 @@ export default class Rasterizer { private readonly common: Common; private readonly scene: Scene; - private readonly renderPassDescriptor: IRenderPassDescriptor; - private readonly pipeline: IRenderPipeline; - private readonly bindGroup: IUniforms; + private readonly renderPassDescriptor: RenderPassDescriptor; + private readonly pipeline: RenderPipeline; + private readonly bindGroup: BindingResources; constructor( common: Common, scene: Scene, radiosity: Radiosity, - framebuffer: ITexture, + framebuffer: Texture, ) { this.common = common; @@ -28,7 +28,7 @@ export default class Rasterizer const framebufferSize = framebuffer.size; - const depthTexture: ITexture = { + const depthTexture: Texture = { label: "RasterizerRenderer.depthTexture", size: [framebufferSize[0], framebufferSize[1]], format: "depth24plus", @@ -77,21 +77,21 @@ export default class Rasterizer // this.renderPassEncoder = { descriptor: this.renderPassDescriptor, - renderObjects: [{ + renderPassObjects: [{ pipeline: this.pipeline, - vertices: this.scene.vertexAttributes, - indices: this.scene.indices, - uniforms: { + bindingResources: { ...this.common.uniforms.bindGroup, ...this.bindGroup, }, - drawIndexed: { indexCount: this.scene.indexCount }, + vertices: this.scene.vertexAttributes, + indices: this.scene.indices, + draw: { __type__: "DrawIndexed", indexCount: this.scene.indexCount }, }], }; } - private renderPassEncoder: IRenderPass; + private renderPassEncoder: RenderPass; - encode(commandEncoder: ICommandEncoder) + encode(commandEncoder: CommandEncoder) { commandEncoder.passEncoders.push(this.renderPassEncoder); } diff --git a/examples/src/webgpu/cornell/raytracer.ts b/examples/src/webgpu/cornell/raytracer.ts index 22a0b739c9e72ef7f77d82b5720b5c262cabdec4..1b73e93ff9cfcf42d5c04906f8127461fe0b426b 100644 --- a/examples/src/webgpu/cornell/raytracer.ts +++ b/examples/src/webgpu/cornell/raytracer.ts @@ -1,5 +1,5 @@ -import { ICommandEncoder, IPassEncoder, ITexture, IUniforms } from "@feng3d/render-api"; -import { IGPUComputePipeline } from "@feng3d/webgpu"; +import { BindingResources, CommandEncoder, PassEncoder, Texture } from "@feng3d/render-api"; +import { ComputePipeline } from "@feng3d/webgpu"; import Common from "./common"; import Radiosity from "./radiosity"; @@ -11,9 +11,9 @@ import raytracerWGSL from "./raytracer.wgsl"; export default class Raytracer { private readonly common: Common; - private readonly framebuffer: ITexture; - private readonly pipeline: IGPUComputePipeline; - private readonly bindGroup: IUniforms; + private readonly framebuffer: Texture; + private readonly material: ComputePipeline; + private readonly bindGroup: BindingResources; private readonly kWorkgroupSizeX = 16; private readonly kWorkgroupSizeY = 16; @@ -21,7 +21,7 @@ export default class Raytracer constructor( common: Common, radiosity: Radiosity, - framebuffer: ITexture, + framebuffer: Texture, ) { this.common = common; @@ -39,7 +39,7 @@ export default class Raytracer framebuffer: { texture: framebuffer }, }; - this.pipeline = { + this.material = { label: "raytracerPipeline", compute: { code: raytracerWGSL + common.wgsl, @@ -53,10 +53,10 @@ export default class Raytracer const framebufferSize = this.framebuffer.size; // this.passEncoder = { - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ - pipeline: this.pipeline, - uniforms: { + pipeline: this.material, + bindingResources: { ...this.common.uniforms.bindGroup, ...this.bindGroup, }, @@ -67,9 +67,9 @@ export default class Raytracer }], }; } - private passEncoder: IPassEncoder; + private passEncoder: PassEncoder; - encode(commandEncoder: ICommandEncoder) + encode(commandEncoder: CommandEncoder) { commandEncoder.passEncoders.push(this.passEncoder); } diff --git a/examples/src/webgpu/cornell/scene.ts b/examples/src/webgpu/cornell/scene.ts index 96bc1cf3d36afedfc854ea4895a1069d634ed537..cd445c768997806cd5ea0751f065661f97decdb3 100644 --- a/examples/src/webgpu/cornell/scene.ts +++ b/examples/src/webgpu/cornell/scene.ts @@ -1,4 +1,4 @@ -import { IVertexAttributes } from "@feng3d/render-api"; +import { VertexAttributes } from "@feng3d/render-api"; import { Vec3, vec3 } from "wgpu-matrix"; function reciprocal(v: Vec3) @@ -130,7 +130,7 @@ export default class Scene readonly vertexCount: number; readonly indexCount: number; readonly vertices: Float32Array; - readonly vertexAttributes: IVertexAttributes; + readonly vertexAttributes: VertexAttributes; readonly indices: Uint16Array; readonly quadBuffer: Float32Array; readonly quads = [ @@ -280,7 +280,7 @@ export default class Scene const vertices = vertexData; - const vertexAttributes: IVertexAttributes = { + const vertexAttributes: VertexAttributes = { position: { data: vertices, format: "float32x4", offset: 0 * 4, arrayStride: vertexStride }, uv: { data: vertices, format: "float32x3", offset: 4 * 4, arrayStride: vertexStride }, emissive: { data: vertices, format: "float32x3", offset: 7 * 4, arrayStride: vertexStride }, diff --git a/examples/src/webgpu/cornell/tonemapper.ts b/examples/src/webgpu/cornell/tonemapper.ts index 18004fda757037b5333729ebd5518248514cf3a3..5b8e7ba29b2e422555b0faa54c0f823e21ecd06d 100644 --- a/examples/src/webgpu/cornell/tonemapper.ts +++ b/examples/src/webgpu/cornell/tonemapper.ts @@ -1,5 +1,5 @@ -import { ICommandEncoder, IPassEncoder, ITexture, IUniforms } from "@feng3d/render-api"; -import { IGPUCanvasTexture, IGPUComputePipeline } from "@feng3d/webgpu"; +import { BindingResources, CanvasTexture, CommandEncoder, PassEncoder, Texture } from "@feng3d/render-api"; +import { ComputePipeline } from "@feng3d/webgpu"; import Common from "./common"; import tonemapperWGSL from "./tonemapper.wgsl"; @@ -10,8 +10,8 @@ import tonemapperWGSL from "./tonemapper.wgsl"; */ export default class Tonemapper { - private readonly bindGroup: IUniforms; - private readonly pipeline: IGPUComputePipeline; + private readonly bindGroup: BindingResources; + private readonly material: ComputePipeline; private readonly width: number; private readonly height: number; private readonly kWorkgroupSizeX = 16; @@ -19,8 +19,8 @@ export default class Tonemapper constructor( common: Common, - input: ITexture, - output: IGPUCanvasTexture, + input: Texture, + output: CanvasTexture, ) { const inputSize = input.size; @@ -32,7 +32,7 @@ export default class Tonemapper output: { texture: output }, }; - this.pipeline = { + this.material = { label: "Tonemap.pipeline", compute: { code: tonemapperWGSL.replace("{OUTPUT_FORMAT}", output.context.configuration.format), @@ -45,10 +45,10 @@ export default class Tonemapper // this.passEncoder = { - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ - pipeline: this.pipeline, - uniforms: { + pipeline: this.material, + bindingResources: { ...this.bindGroup, }, workgroups: { @@ -58,9 +58,9 @@ export default class Tonemapper }], }; } - private passEncoder: IPassEncoder; + private passEncoder: PassEncoder; - encode(commandEncoder: ICommandEncoder) + encode(commandEncoder: CommandEncoder) { commandEncoder.passEncoders.push(this.passEncoder); } diff --git a/examples/src/webgpu/cubemap/index.ts b/examples/src/webgpu/cubemap/index.ts index bd1bf860742b7180fbd3d4d8a000106fb93f5a14..c0c318f4009831930be8f26f668f9a41590783ab 100644 --- a/examples/src/webgpu/cubemap/index.ts +++ b/examples/src/webgpu/cubemap/index.ts @@ -1,4 +1,4 @@ -import { IBufferBinding, IRenderObject, IRenderPassDescriptor, ISampler, ISubmit, ITexture, ITextureImageSource } from "@feng3d/render-api"; +import { BufferBinding, RenderPassDescriptor, Sampler, Submit, Texture, TextureImageSource, RenderObject, reactive } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; @@ -16,7 +16,7 @@ const init = async (canvas: HTMLCanvasElement) => // Fetch the 6 separate images for negative/positive x, y, z axis of a cubemap // and upload it into a GPUTexture. - let cubemapTexture: ITexture; + let cubemapTexture: Texture; { // The order of the array layers is [+X, -X, +Y, -Y, +Z, -Z] const imgSrcs = [ @@ -55,7 +55,7 @@ const init = async (canvas: HTMLCanvasElement) => const imageBitmaps = await Promise.all(promises); const textureSource = imageBitmaps.map((v, i) => { - const item: ITextureImageSource = { + const item: TextureImageSource = { image: v, textureOrigin: [0, 0, i] }; @@ -70,7 +70,7 @@ const init = async (canvas: HTMLCanvasElement) => }; } - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; @@ -106,7 +106,7 @@ const init = async (canvas: HTMLCanvasElement) => ); } - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -119,38 +119,38 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: basicVertWGSL }, fragment: { code: sampleCubemapWGSL }, primitive: { cullFace: "none", }, }, - vertices: { - position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, - uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, - }, - uniforms: { + bindingResources: { uniforms: { modelViewProjectionMatrix: new Float32Array(16) }, mySampler: sampler, myTexture: { texture: cubemapTexture }, }, - drawVertex: { vertexCount: cubeVertexCount }, + vertices: { + position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, + uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, + }, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, }; function frame() { updateTransformationMatrix(); - (renderObject.uniforms.uniforms as IBufferBinding).modelViewProjectionMatrix = modelViewProjectionMatrix; + reactive(renderObject.bindingResources.uniforms as BufferBinding).modelViewProjectionMatrix = modelViewProjectionMatrix.subarray(); - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPass, renderObjects: [renderObject] }, + { descriptor: renderPass, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/deferredRendering/index.ts b/examples/src/webgpu/deferredRendering/index.ts index 53692400c211644fd32fa3364c952f0169b20905..75f8eebd55e5c87297899df2c8adc767e7b79d4c 100644 --- a/examples/src/webgpu/deferredRendering/index.ts +++ b/examples/src/webgpu/deferredRendering/index.ts @@ -10,8 +10,8 @@ import lightUpdate from "./lightUpdate.wgsl"; import vertexTextureQuad from "./vertexTextureQuad.wgsl"; import vertexWriteGBuffers from "./vertexWriteGBuffers.wgsl"; -import { IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit, ITexture, ITextureView, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPUComputePass, IGPUComputePipeline, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, RenderPass, RenderPassDescriptor, RenderPipeline, Submit, Texture, TextureView, VertexAttributes, reactive } from "@feng3d/render-api"; +import { ComputePass, ComputePipeline, WebGPU, getGBuffer } from "@feng3d/webgpu"; const kMaxNumLights = 1024; const lightExtentMin = vec3.fromValues(-50, -30, -50); @@ -36,7 +36,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => vertexBuffer.set(mesh.uvs[i], kVertexStride * i + 6); } - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: vertexBuffer, format: "float32x3", offset: 0, arrayStride: Float32Array.BYTES_PER_ELEMENT * 8 }, normal: { data: vertexBuffer, format: "float32x3", offset: Float32Array.BYTES_PER_ELEMENT * 3, arrayStride: Float32Array.BYTES_PER_ELEMENT * 8 }, uv: { data: vertexBuffer, format: "float32x2", offset: Float32Array.BYTES_PER_ELEMENT * 6, arrayStride: Float32Array.BYTES_PER_ELEMENT * 8 }, @@ -51,19 +51,19 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } // GBuffer texture render targets - const gBufferTexture2DFloat32: ITexture = { + const gBufferTexture2DFloat32: Texture = { size: [canvas.width, canvas.height], format: "rgba32float", }; - const gBufferTexture2DFloat16: ITexture = { + const gBufferTexture2DFloat16: Texture = { size: [canvas.width, canvas.height], format: "rgba16float", }; - const gBufferTextureAlbedo: ITexture = { + const gBufferTextureAlbedo: Texture = { size: [canvas.width, canvas.height], format: "bgra8unorm", }; - const gBufferTextureViews: ITextureView[] = [ + const gBufferTextureViews: TextureView[] = [ { texture: gBufferTexture2DFloat32 }, { texture: gBufferTexture2DFloat16 }, { texture: gBufferTextureAlbedo }, @@ -74,7 +74,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => cullMode: "back", }; - const writeGBuffersPipeline: IRenderPipeline = { + const writeGBuffersPipeline: RenderPipeline = { vertex: { code: vertexWriteGBuffers, }, @@ -84,7 +84,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => primitive, }; - const gBuffersDebugViewPipeline: IRenderPipeline = { + const gBuffersDebugViewPipeline: RenderPipeline = { vertex: { code: vertexTextureQuad, }, @@ -97,7 +97,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, primitive, }; - const deferredRenderPipeline: IRenderPipeline = { + const deferredRenderPipeline: RenderPipeline = { vertex: { code: vertexTextureQuad, }, @@ -107,12 +107,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => primitive, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; - const writeGBufferPassDescriptor: IRenderPassDescriptor = { + const writeGBufferPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: gBufferTextureViews[0], @@ -144,7 +144,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const textureQuadPassDescriptor: IRenderPassDescriptor = { + const textureQuadPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -166,13 +166,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => .step(1) .onChange(() => { - if (getIGPUBuffer(configUniformBuffer).writeBuffers) + if (getGBuffer(configUniformBuffer).writeBuffers) { - getIGPUBuffer(configUniformBuffer).writeBuffers.push({ data: new Uint32Array([settings.numLights]) }); + getGBuffer(configUniformBuffer).writeBuffers.push({ data: new Uint32Array([settings.numLights]) }); } else { - getIGPUBuffer(configUniformBuffer).writeBuffers = [{ data: new Uint32Array([settings.numLights]) }]; + reactive(getGBuffer(configUniformBuffer)).writeBuffers = [{ data: new Uint32Array([settings.numLights]) }]; } }); @@ -180,7 +180,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const cameraUniformBuffer = new Uint8Array(4 * 16); - const sceneUniformBindGroup: IUniforms = { + const sceneUniformBindGroup: BindingResources = { uniforms: { bufferView: modelUniformBuffer, }, @@ -189,7 +189,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const gBufferTexturesBindGroup: IUniforms = { + const gBufferTexturesBindGroup: BindingResources = { gBufferPosition: gBufferTextureViews[0], gBufferNormal: gBufferTextureViews[1], gBufferAlbedo: gBufferTextureViews[2], @@ -199,13 +199,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // which could be updated/culled/etc. with a compute shader const extent = vec3.sub(lightExtentMax, lightExtentMin); const lightDataStride = 8; - const bufferSizeInByte = Float32Array.BYTES_PER_ELEMENT * lightDataStride * kMaxNumLights; - const lightsBuffer = new Uint8Array(bufferSizeInByte); // We randomaly populate lights randomly in a box range // And simply move them along y-axis per frame to show they are // dynamic lightings - const lightData = new Float32Array(lightDataStride * kMaxNumLights); + const lightsBuffer = new Float32Array(lightDataStride * kMaxNumLights); const tmpVec4 = vec4.create(); let offset = 0; for (let i = 0; i < kMaxNumLights; i++) @@ -217,29 +215,28 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => tmpVec4[i] = Math.random() * extent[i] + lightExtentMin[i]; } tmpVec4[3] = 1; - lightData.set(tmpVec4, offset); + lightsBuffer.set(tmpVec4, offset); // color tmpVec4[0] = Math.random() * 2; tmpVec4[1] = Math.random() * 2; tmpVec4[2] = Math.random() * 2; // radius tmpVec4[3] = 20.0; - lightData.set(tmpVec4, offset + 4); + lightsBuffer.set(tmpVec4, offset + 4); } - getIGPUBuffer(lightsBuffer).data = lightData; const lightExtentBuffer = new Uint8Array(4 * 8); const lightExtentData = new Float32Array(8); lightExtentData.set(lightExtentMin, 0); lightExtentData.set(lightExtentMax, 4); - getIGPUBuffer(lightExtentBuffer).writeBuffers = [{ data: lightExtentData }]; + reactive(getGBuffer(lightExtentBuffer)).writeBuffers = [{ data: lightExtentData }]; - const lightUpdateComputePipeline: IGPUComputePipeline = { + const lightUpdateComputePipeline: ComputePipeline = { compute: { code: lightUpdate, }, }; - const lightsBufferBindGroup: IUniforms = { + const lightsBufferBindGroup: BindingResources = { lightsBuffer: { bufferView: lightsBuffer, }, @@ -247,7 +244,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => bufferView: configUniformBuffer, }, }; - const lightsBufferComputeBindGroup: IUniforms = { + const lightsBufferComputeBindGroup: BindingResources = { lightsBuffer: { bufferView: lightsBuffer, }, @@ -280,33 +277,33 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const modelMatrix = mat4.translation([0, -45, 0]); const cameraMatrixData = viewProjMatrix as Float32Array; - if (getIGPUBuffer(cameraUniformBuffer).writeBuffers) + if (getGBuffer(cameraUniformBuffer).writeBuffers) { - getIGPUBuffer(cameraUniformBuffer).writeBuffers.push({ data: cameraMatrixData }); + getGBuffer(cameraUniformBuffer).writeBuffers.push({ data: cameraMatrixData }); } else { - getIGPUBuffer(cameraUniformBuffer).writeBuffers = [{ data: cameraMatrixData }]; + reactive(getGBuffer(cameraUniformBuffer)).writeBuffers = [{ data: cameraMatrixData }]; } const modelData = modelMatrix as Float32Array; - if (getIGPUBuffer(modelUniformBuffer).writeBuffers) + if (getGBuffer(modelUniformBuffer).writeBuffers) { - getIGPUBuffer(modelUniformBuffer).writeBuffers.push({ data: modelData }); + getGBuffer(modelUniformBuffer).writeBuffers.push({ data: modelData }); } else { - getIGPUBuffer(modelUniformBuffer).writeBuffers = [{ data: modelData }]; + reactive(getGBuffer(modelUniformBuffer)).writeBuffers = [{ data: modelData }]; } const invertTransposeModelMatrix = mat4.invert(modelMatrix); mat4.transpose(invertTransposeModelMatrix, invertTransposeModelMatrix); const normalModelData = invertTransposeModelMatrix as Float32Array; - if (getIGPUBuffer(modelUniformBuffer).writeBuffers) + if (getGBuffer(modelUniformBuffer).writeBuffers) { - getIGPUBuffer(modelUniformBuffer).writeBuffers.push({ bufferOffset: 64, data: normalModelData }); + getGBuffer(modelUniformBuffer).writeBuffers.push({ bufferOffset: 64, data: normalModelData }); } else { - getIGPUBuffer(modelUniformBuffer).writeBuffers = [{ bufferOffset: 64, data: normalModelData }]; + reactive(getGBuffer(modelUniformBuffer)).writeBuffers = [{ bufferOffset: 64, data: normalModelData }]; } // Rotates the camera around the origin based on time. @@ -325,27 +322,27 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => return viewProjMatrix as Float32Array; } - const passEncoders: (IGPUComputePass | IRenderPass)[] = []; + const passEncoders: (ComputePass | RenderPass)[] = []; passEncoders.push({ descriptor: writeGBufferPassDescriptor, - renderObjects: [ + renderPassObjects: [ { pipeline: writeGBuffersPipeline, - uniforms: { + bindingResources: { ...sceneUniformBindGroup, }, vertices, indices: indexBuffer, - drawIndexed: { indexCount }, + draw: { __type__: "DrawIndexed", indexCount }, }, ] }); passEncoders.push({ - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [ { pipeline: lightUpdateComputePipeline, - uniforms: { + bindingResources: { ...lightsBufferComputeBindGroup, }, workgroups: { workgroupCountX: Math.ceil(kMaxNumLights / 64) }, @@ -353,31 +350,31 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ] }); - const gBuffersPassEncoders: (IGPUComputePass | IRenderPass)[] = passEncoders.concat(); + const gBuffersPassEncoders: (ComputePass | RenderPass)[] = passEncoders.concat(); gBuffersPassEncoders.push({ descriptor: textureQuadPassDescriptor, - renderObjects: [ + renderPassObjects: [ { pipeline: gBuffersDebugViewPipeline, - uniforms: { + bindingResources: { ...gBufferTexturesBindGroup, }, - drawVertex: { vertexCount: 6 }, + draw: { __type__: "DrawVertex", vertexCount: 6 }, }, ] }); passEncoders.push({ descriptor: textureQuadPassDescriptor, - renderObjects: [ + renderPassObjects: [ { pipeline: deferredRenderPipeline, - uniforms: { + bindingResources: { ...gBufferTexturesBindGroup, ...lightsBufferBindGroup, }, - drawVertex: { vertexCount: 6 }, + draw: { __type__: "DrawVertex", vertexCount: 6 }, }, ] }); @@ -385,16 +382,16 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => function frame() { const cameraViewProj = getCameraViewProjMatrix(); - if (getIGPUBuffer(cameraUniformBuffer).writeBuffers) + if (getGBuffer(cameraUniformBuffer).writeBuffers) { - getIGPUBuffer(cameraUniformBuffer).writeBuffers.push({ data: cameraViewProj }); + getGBuffer(cameraUniformBuffer).writeBuffers.push({ data: cameraViewProj }); } else { - getIGPUBuffer(cameraUniformBuffer).writeBuffers = [{ data: cameraViewProj }]; + reactive(getGBuffer(cameraUniformBuffer)).writeBuffers = [{ data: cameraViewProj }]; } - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders: settings.mode === "gBuffers view" ? gBuffersPassEncoders : passEncoders, diff --git a/examples/src/webgpu/fractalCube/index.ts b/examples/src/webgpu/fractalCube/index.ts index f0ab182ff1e90ec3ceb8aafc778b29b2b329d18f..8e36187524789967ed91884f4585182dd747fa9b 100644 --- a/examples/src/webgpu/fractalCube/index.ts +++ b/examples/src/webgpu/fractalCube/index.ts @@ -1,5 +1,5 @@ -import { IBufferBinding, ICopyTextureToTexture, IRenderObject, IRenderPassDescriptor, ISampler, ISubmit, ITexture } from "@feng3d/render-api"; -import { IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { BufferBinding, CanvasContext, CopyTextureToTexture, reactive, RenderObject, RenderPassDescriptor, Sampler, Submit, Texture } from "@feng3d/render-api"; +import { WebGPU } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; import { cubePositionOffset, cubeUVOffset, cubeVertexArray, cubeVertexCount, cubeVertexSize } from "../../meshes/cube"; @@ -18,13 +18,13 @@ const init = async (canvas: HTMLCanvasElement) => // We will copy the frame's rendering results into this texture and // sample it on the next frame. - const cubeTexture: ITexture = { + const cubeTexture: Texture = { size: [canvas.width, canvas.height], format: presentationFormat, }; // Create a sampler with linear filtering for smooth interpolation. - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; @@ -55,14 +55,14 @@ const init = async (canvas: HTMLCanvasElement) => return modelViewProjectionMatrix as Float32Array; } - const context: IGPUCanvasContext = { + const context: CanvasContext = { canvasId: canvas.id, configuration: { usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT } }; - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, @@ -76,7 +76,7 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: basicVertWGSL }, fragment: { code: sampleSelfWGSL }, primitive: { @@ -87,18 +87,18 @@ const init = async (canvas: HTMLCanvasElement) => position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }, - uniforms: { + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, + bindingResources: { uniforms: { modelViewProjectionMatrix: new Float32Array(16) }, mySampler: sampler, myTexture: { texture: cubeTexture }, }, - drawVertex: { vertexCount: cubeVertexCount }, }; - const copyTextureToTexture: ICopyTextureToTexture = { - __type: "CopyTextureToTexture", + const copyTextureToTexture: CopyTextureToTexture = { + __type__: "CopyTextureToTexture", source: { texture: { context } }, destination: { texture: cubeTexture }, copySize: [canvas.width, canvas.height], @@ -108,13 +108,13 @@ const init = async (canvas: HTMLCanvasElement) => { const transformationMatrix = getTransformationMatrix(); - (renderObject.uniforms.uniforms as IBufferBinding).modelViewProjectionMatrix = transformationMatrix; + reactive(renderObject.bindingResources.uniforms as BufferBinding).modelViewProjectionMatrix = transformationMatrix.subarray(); - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPass, renderObjects: [renderObject] }, + { descriptor: renderPass, renderPassObjects: [renderObject] }, copyTextureToTexture, ] } diff --git a/examples/src/webgpu/gameOfLife/index.ts b/examples/src/webgpu/gameOfLife/index.ts index 72970205d36acfbad837d1a8005a9ddad78be26e..fb1989d15f0166f8041fba56463b964f75dab211 100644 --- a/examples/src/webgpu/gameOfLife/index.ts +++ b/examples/src/webgpu/gameOfLife/index.ts @@ -4,8 +4,8 @@ import computeWGSL from "./compute.wgsl"; import fragWGSL from "./frag.wgsl"; import vertWGSL from "./vert.wgsl"; -import { IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { IGPUComputePass, IGPUComputePipeline, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, RenderPass, RenderPassDescriptor, RenderPipeline, Submit, VertexAttributes } from "@feng3d/render-api"; +import { ComputePass, ComputePipeline, WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement, gui: GUI) => { @@ -23,7 +23,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; const squareVertices = new Uint32Array([0, 0, 0, 1, 1, 0, 1, 1]); - const verticesSquareBuffer: IVertexAttributes = { + const verticesSquareBuffer: VertexAttributes = { pos: { data: squareVertices, format: "uint32x2" } }; @@ -40,14 +40,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => let wholeTime = 0; let loopTimes = 0; let buffer0: Uint32Array; - let verticesBuffer0: IVertexAttributes; + let verticesBuffer0: VertexAttributes; let buffer1: Uint32Array; - let verticesBuffer1: IVertexAttributes; + let verticesBuffer1: VertexAttributes; let render: () => void; function resetGameData() { // compute pipeline - const computePipeline: IGPUComputePipeline = { + const computePipeline: ComputePipeline = { compute: { code: computeWGSL, constants: { @@ -74,31 +74,31 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => cell: { data: buffer1, format: "uint32", stepMode: "instance" } }; - const bindGroup0: IUniforms = { + const bindGroup0: BindingResources = { size: { bufferView: sizeBuffer }, current: { bufferView: buffer0 }, next: { bufferView: buffer1 }, }; - const bindGroup1: IUniforms = { + const bindGroup1: BindingResources = { size: { bufferView: sizeBuffer }, current: { bufferView: buffer1 }, next: { bufferView: buffer0 }, }; - const renderPipeline: IRenderPipeline = { - primitive: { - topology: "triangle-strip", - }, + const renderPipeline: RenderPipeline = { vertex: { code: vertWGSL, }, fragment: { code: fragWGSL, }, + primitive: { + topology: "triangle-strip", + }, }; - const uniformBindGroup: IUniforms = { + const uniformBindGroup: BindingResources = { size: { bufferView: sizeBuffer, offset: 0, @@ -106,7 +106,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -114,18 +114,18 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ], }; - const passEncodersArray: (IGPUComputePass | IRenderPass)[][] = []; + const passEncodersArray: (ComputePass | RenderPass)[][] = []; for (let i = 0; i < 2; i++) { - const vertices1: IVertexAttributes = {}; + const vertices1: VertexAttributes = {}; Object.assign(vertices1, i ? verticesBuffer1 : verticesBuffer0, verticesSquareBuffer); passEncodersArray[i] = [ { - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ pipeline: computePipeline, - uniforms: i ? bindGroup1 : bindGroup0, + bindingResources: i ? bindGroup1 : bindGroup0, workgroups: { workgroupCountX: GameOptions.width / GameOptions.workgroupSize, workgroupCountY: GameOptions.height / GameOptions.workgroupSize @@ -134,12 +134,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, { descriptor: renderPass, - renderObjects: [ + renderPassObjects: [ { pipeline: renderPipeline, - uniforms: uniformBindGroup, + bindingResources: uniformBindGroup, vertices: vertices1, - drawVertex: { vertexCount: 4, instanceCount: length }, + draw: { __type__: "DrawVertex", vertexCount: 4, instanceCount: length }, } ], } @@ -149,7 +149,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => loopTimes = 0; render = () => { - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders: passEncodersArray[loopTimes], diff --git a/examples/src/webgpu/helloTriangle/index.ts b/examples/src/webgpu/helloTriangle/index.ts index 808fa7c972ce43b43afe213f97d2f8cda68baa82..153c9abe155e0601093e83b6981af21b0ee006ea 100644 --- a/examples/src/webgpu/helloTriangle/index.ts +++ b/examples/src/webgpu/helloTriangle/index.ts @@ -1,4 +1,4 @@ -import { ISubmit } from "@feng3d/render-api"; +import { Submit } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement) => @@ -9,7 +9,7 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); // 初始化WebGPU - const submit: ISubmit = { // 一次GPU提交 + const submit: Submit = { // 一次GPU提交 commandEncoders: [ // 命令编码列表 { passEncoders: [ // 通道编码列表 @@ -20,7 +20,7 @@ const init = async (canvas: HTMLCanvasElement) => clearValue: [0.0, 0.0, 0.0, 1.0], // 渲染前填充颜色 }], }, - renderObjects: [{ // 渲染对象 + renderPassObjects: [{ // 渲染对象 pipeline: { // 渲染管线 vertex: { // 顶点着色器 code: ` @@ -44,8 +44,8 @@ const init = async (canvas: HTMLCanvasElement) => position: { data: new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]), format: "float32x2" }, // 顶点坐标数据 }, indices: new Uint16Array([0, 1, 2]), // 顶点索引数据 - uniforms: { color: [1, 0, 0, 0] }, // Uniform 颜色值。 - drawIndexed: { indexCount: 3 }, // 绘制命令 + draw: { __type__: "DrawIndexed", indexCount: 3 }, // 绘制命令 + bindingResources: { color: [1, 0, 0, 0] }, // Uniform 颜色值。 }] }, ] diff --git a/examples/src/webgpu/helloTriangleMSAA/index.ts b/examples/src/webgpu/helloTriangleMSAA/index.ts index fa8ab802a414b5dd05927f1307b2e80fff622fdc..0cfd854e505724fc21ce1c8386907ebf67f0875c 100644 --- a/examples/src/webgpu/helloTriangleMSAA/index.ts +++ b/examples/src/webgpu/helloTriangleMSAA/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, IRenderPassDescriptor, ISubmit } from "@feng3d/render-api"; +import { RenderPassDescriptor, Submit, RenderObject } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import redFragWGSL from "../../shaders/red.frag.wgsl"; @@ -12,7 +12,7 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [{ view: { texture: { context: { canvasId: canvas.id } } }, clearValue: [0.0, 0.0, 0.0, 1.0], @@ -20,20 +20,20 @@ const init = async (canvas: HTMLCanvasElement) => sampleCount: 4, // 设置多重采样数量 }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: triangleVertWGSL }, fragment: { code: redFragWGSL }, }, - drawVertex: { vertexCount: 3 }, + draw: { __type__: "DrawVertex", vertexCount: 3 }, }; function frame() { - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPassDescriptor, renderObjects: [renderObject] }, + { descriptor: renderPassDescriptor, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/imageBlur/index.ts b/examples/src/webgpu/imageBlur/index.ts index 57c56fdad2246289d759e42fdbdc346553eb1446..ddcd0211529b24f447bc051550626ab90e329def 100644 --- a/examples/src/webgpu/imageBlur/index.ts +++ b/examples/src/webgpu/imageBlur/index.ts @@ -3,8 +3,8 @@ import { GUI } from "dat.gui"; import fullscreenTexturedQuadWGSL from "../../shaders/fullscreenTexturedQuad.wgsl"; import blurWGSL from "./blur.wgsl"; -import { IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISampler, ISubmit, ITexture, IUniforms } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPUComputePass, IGPUComputePipeline, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, RenderPass, RenderPassDescriptor, RenderPipeline, Sampler, Submit, Texture, reactive } from "@feng3d/render-api"; +import { ComputePass, ComputePipeline, WebGPU, getGBuffer } from "@feng3d/webgpu"; // Contants from the blur.wgsl shader. const tileDim = 128; @@ -18,13 +18,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const webgpu = await new WebGPU().init(); - const blurPipeline: IGPUComputePipeline = { + const blurPipeline: ComputePipeline = { compute: { code: blurWGSL, }, }; - const fullscreenQuadPipeline1: IRenderPipeline = { + const fullscreenQuadPipeline1: RenderPipeline = { vertex: { code: fullscreenTexturedQuadWGSL, }, @@ -33,7 +33,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; @@ -47,18 +47,18 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const imageBitmap = await createImageBitmap(img); const [srcWidth, srcHeight] = [imageBitmap.width, imageBitmap.height]; - const cubeTexture1: ITexture = { + const cubeTexture1: Texture = { size: [imageBitmap.width, imageBitmap.height], format: "rgba8unorm", sources: [{ image: imageBitmap }], }; - const textures: ITexture[] = [0, 1].map(() => + const textures: Texture[] = [0, 1].map(() => ({ size: [srcWidth, srcHeight], format: "rgba8unorm", usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING, - } as ITexture)); + } as Texture)); const buffer0 = new Uint32Array([0]); @@ -66,14 +66,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const blurParamsBuffer = new Uint8Array(8); - const computeConstants: IUniforms = { + const computeConstants: BindingResources = { samp: sampler, params: { bufferView: blurParamsBuffer, }, }; - const computeBindGroup0: IUniforms = { + const computeBindGroup0: BindingResources = { inputTex: { texture: cubeTexture1 }, outputTex: { texture: textures[0] }, flip: { @@ -81,7 +81,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } }; - const computeBindGroup1: IUniforms = { + const computeBindGroup1: BindingResources = { inputTex: { texture: textures[0] }, outputTex: { texture: textures[1] }, flip: { @@ -89,7 +89,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const computeBindGroup2: IUniforms = { + const computeBindGroup2: BindingResources = { inputTex: { texture: textures[1] }, outputTex: { texture: textures[0] }, flip: { @@ -97,7 +97,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const showResultBindGroup1: IUniforms = { + const showResultBindGroup1: BindingResources = { mySampler: sampler, myTexture: { texture: textures[1] }, }; @@ -113,13 +113,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const updateSettings = () => { blockDim = tileDim - (settings.filterSize - 1); - if (getIGPUBuffer(blurParamsBuffer).writeBuffers) + if (getGBuffer(blurParamsBuffer).writeBuffers) { - getIGPUBuffer(blurParamsBuffer).writeBuffers.push({ data: new Uint32Array([settings.filterSize, blockDim]) }); + getGBuffer(blurParamsBuffer).writeBuffers.push({ data: new Uint32Array([settings.filterSize, blockDim]) }); } else { - getIGPUBuffer(blurParamsBuffer).writeBuffers = [{ data: new Uint32Array([settings.filterSize, blockDim]) }]; + reactive(getGBuffer(blurParamsBuffer)).writeBuffers = [{ data: new Uint32Array([settings.filterSize, blockDim]) }]; } needUpdateEncoder = true; }; @@ -131,7 +131,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => updateSettings(); - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -140,17 +140,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ], }; - const gpuRenderPassEncoder: IRenderPass = { + const gpuRenderPassEncoder: RenderPass = { descriptor: renderPassDescriptor, - renderObjects: [{ + renderPassObjects: [{ pipeline: fullscreenQuadPipeline1, - uniforms: showResultBindGroup1, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 0 }, + bindingResources: showResultBindGroup1, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 0 }, }], }; - const gpuComputePassEncoder: IGPUComputePass = { __type: "ComputePass", computeObjects: [] }; - const submit: ISubmit = { + const gpuComputePassEncoder: ComputePass = { __type__: "ComputePass", computeObjects: [] }; + const submit: Submit = { commandEncoders: [ { passEncoders: [ @@ -161,15 +161,15 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ] }; - const bindingResources0: IUniforms = { + const bindingResources0: BindingResources = { ...computeConstants, ...computeBindGroup0, }; - const bindingResources1: IUniforms = { + const bindingResources1: BindingResources = { ...computeConstants, ...computeBindGroup1, }; - const bindingResources2: IUniforms = { + const bindingResources2: BindingResources = { ...computeConstants, ...computeBindGroup2, }; @@ -181,12 +181,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => gpuComputePassEncoder.computeObjects = [ { pipeline: blurPipeline, - uniforms: bindingResources0, + bindingResources: bindingResources0, workgroups: { workgroupCountX: Math.ceil(srcWidth / blockDim), workgroupCountY: Math.ceil(srcHeight / batch[1]) } }, { pipeline: blurPipeline, - uniforms: bindingResources1, + bindingResources: bindingResources1, workgroups: { workgroupCountX: Math.ceil(srcHeight / blockDim), workgroupCountY: Math.ceil(srcWidth / batch[1]) } }, ]; @@ -196,7 +196,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => gpuComputePassEncoder.computeObjects.push( { pipeline: blurPipeline, - uniforms: bindingResources2, + bindingResources: bindingResources2, workgroups: { workgroupCountX: Math.ceil(srcWidth / blockDim), workgroupCountY: Math.ceil(srcHeight / batch[1]) } } ); @@ -204,7 +204,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => gpuComputePassEncoder.computeObjects.push( { pipeline: blurPipeline, - uniforms: bindingResources1, + bindingResources: bindingResources1, workgroups: { workgroupCountX: Math.ceil(srcHeight / blockDim), workgroupCountY: Math.ceil(srcWidth / batch[1]) } } ); diff --git a/examples/src/webgpu/instancedCube/index.ts b/examples/src/webgpu/instancedCube/index.ts index 9f22a3b55a7fac2c773d78aa9039e423f438c2ad..e0d97d46feed6c6ab2beebb211c9057311e38dbb 100644 --- a/examples/src/webgpu/instancedCube/index.ts +++ b/examples/src/webgpu/instancedCube/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, IRenderPassDescriptor, ISubmit } from "@feng3d/render-api"; +import { RenderPassDescriptor, Submit, RenderObject, reactive } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { Mat4, mat4, vec3 } from "wgpu-matrix"; @@ -79,7 +79,8 @@ const init = async (canvas: HTMLCanvasElement) => mat4.multiply(viewMatrix, tmpMat4, tmpMat4); mat4.multiply(projectionMatrix, tmpMat4, tmpMat4); - mvpMatricesData[i] = tmpMat4.slice(); + // Update the matrix data. + reactive(mvpMatricesData)[i] = tmpMat4.slice(); i++; m += matrixFloatCount; @@ -87,7 +88,7 @@ const init = async (canvas: HTMLCanvasElement) => } } - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -101,7 +102,7 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: instancedVertWGSL }, fragment: { code: vertexPositionColorWGSL }, primitive: { @@ -112,19 +113,19 @@ const init = async (canvas: HTMLCanvasElement) => position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }, - uniforms: { + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount, instanceCount: numInstances }, + bindingResources: { uniforms: { modelViewProjectionMatrix: mvpMatricesData }, }, - drawVertex: { vertexCount: cubeVertexCount, instanceCount: numInstances } }; - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPass, renderObjects: [renderObject] }, + { descriptor: renderPass, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/multipleCanvases/index.ts b/examples/src/webgpu/multipleCanvases/index.ts index e9578c3f7e2503dfb2ec07ac66d68615b1582ebf..06872c3d6b2028018c92edbc3af7d7d41ccf0e5c 100644 --- a/examples/src/webgpu/multipleCanvases/index.ts +++ b/examples/src/webgpu/multipleCanvases/index.ts @@ -1,5 +1,5 @@ -import { IPassEncoder, IRenderPassDescriptor, IRenderPipeline, ISubmit, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, CanvasContext, PassEncoder, reactive, RenderPassDescriptor, RenderPipeline, Submit, VertexAttributes } from "@feng3d/render-api"; +import { getGBuffer, WebGPU } from "@feng3d/webgpu"; import { mat3, mat4 } from "wgpu-matrix"; import { modelData } from "./models"; @@ -23,14 +23,14 @@ function createBufferWithData( type Model = { vertices: Float32Array; indices: Uint32Array; - vertexAttributes: IVertexAttributes, + vertexAttributes: VertexAttributes, }; function createVertexAndIndexBuffer( { vertices, indices }: { vertices: Float32Array; indices: Uint32Array } ): Model { - const vertexAttributes: IVertexAttributes = { + const vertexAttributes: VertexAttributes = { position: { data: vertices, format: "float32x3", offset: 0, arrayStride: 6 * 4 }, normal: { data: vertices, format: "float32x3", offset: 3 * 4, arrayStride: 6 * 4 }, }; @@ -114,7 +114,7 @@ const init = async () => `, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { label: "our hardcoded red triangle pipeline", vertex: { ...module, @@ -167,15 +167,15 @@ const init = async () => }); type CanvasInfo = { - context: IGPUCanvasContext; + context: CanvasContext; clearValue: [number, number, number, number]; worldViewProjectionMatrixValue: Float32Array; worldMatrixValue: Float32Array; uniformValues: Float32Array; - bindGroup: IUniforms; + bindGroup: BindingResources; rotation: number; model: Model; - renderPassDescriptor?: IRenderPassDescriptor + renderPassDescriptor?: RenderPassDescriptor }; const outerElem = document.querySelector("#outer"); @@ -204,7 +204,7 @@ const init = async () => // Get a WebGPU context and configure it. canvas.id = canvas.id || `gpuCanvas___${globalThis["gpuCanvasAutoID"] = ~~globalThis["gpuCanvasAutoID"] + 1}`; - const context: IGPUCanvasContext = { canvasId: canvas.id }; + const context: CanvasContext = { canvasId: canvas.id }; // Make a uniform buffer and type array views // for our uniforms. @@ -224,7 +224,7 @@ const init = async () => colorValue.set(randColor()); // Make a bind group for this uniform - const bindGroup: IUniforms = { + const bindGroup: BindingResources = { uni: { bufferView: uniformValues, worldViewProjectionMatrix: undefined, @@ -254,7 +254,7 @@ const init = async () => time *= 0.001; // convert to seconds; // make a command encoder to start encoding commands - const passEncoders: IPassEncoder[] = []; + const passEncoders: PassEncoder[] = []; visibleCanvasSet.forEach((canvas) => { @@ -272,7 +272,7 @@ const init = async () => // Get the current texture from the canvas context and // set it as the texture to render to. - const renderPassDescriptor: IRenderPassDescriptor = canvasInfo.renderPassDescriptor = canvasInfo.renderPassDescriptor || { + const renderPassDescriptor: RenderPassDescriptor = canvasInfo.renderPassDescriptor = canvasInfo.renderPassDescriptor || { label: "our basic canvas renderPass", colorAttachments: [ { @@ -307,27 +307,27 @@ const init = async () => mat3.fromMat4(world, worldMatrixValue); // Upload our uniform values. - const buffer = getIGPUBuffer(uniformValues); + const buffer = getGBuffer(uniformValues); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ data: uniformValues, }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; // make a render pass encoder to encode render specific commands passEncoders.push({ descriptor: renderPassDescriptor, - renderObjects: [{ - pipeline, + renderPassObjects: [{ + pipeline: pipeline, + bindingResources: bindGroup, vertices: vertexAttributes, indices, - uniforms: bindGroup, - drawIndexed: { indexCount: indices.length }, + draw: { __type__: "DrawIndexed", indexCount: indices.length }, }], }); }); - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders, }] diff --git a/examples/src/webgpu/normalMap/index.ts b/examples/src/webgpu/normalMap/index.ts index 148b50331b93e1f01f7a03ee1f38e2a92f9d8f76..dac020d5fe854ad86b3be3018bf8aba022a1d82c 100644 --- a/examples/src/webgpu/normalMap/index.ts +++ b/examples/src/webgpu/normalMap/index.ts @@ -1,4 +1,4 @@ -import { IRenderPassDescriptor, IRenderPipeline, ISampler, ISubmit, ITexture, IUniforms } from "@feng3d/render-api"; +import { RenderPassDescriptor, RenderPipeline, Sampler, Submit, Texture, BindingResources, reactive } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; import { mat4, vec3 } from "wgpu-matrix"; @@ -62,62 +62,62 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; // Create normal mapping resources and pipeline - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; // Fetch the image and upload it into a GPUTexture. - let woodAlbedoTexture: ITexture; + let woodAlbedoTexture: Texture; { const response = await fetch("../../../assets/img/wood_albedo.png"); const imageBitmap = await createImageBitmap(await response.blob()); woodAlbedoTexture = createTextureFromImage(imageBitmap); } - let spiralNormalTexture: ITexture; + let spiralNormalTexture: Texture; { const response = await fetch("../../../assets/img/spiral_normal.png"); const imageBitmap = await createImageBitmap(await response.blob()); spiralNormalTexture = createTextureFromImage(imageBitmap); } - let spiralHeightTexture: ITexture; + let spiralHeightTexture: Texture; { const response = await fetch("../../../assets/img/spiral_height.png"); const imageBitmap = await createImageBitmap(await response.blob()); spiralHeightTexture = createTextureFromImage(imageBitmap); } - let toyboxNormalTexture: ITexture; + let toyboxNormalTexture: Texture; { const response = await fetch("../../../assets/img/toybox_normal.png"); const imageBitmap = await createImageBitmap(await response.blob()); toyboxNormalTexture = createTextureFromImage(imageBitmap); } - let toyboxHeightTexture: ITexture; + let toyboxHeightTexture: Texture; { const response = await fetch("../../../assets/img/toybox_height.png"); const imageBitmap = await createImageBitmap(await response.blob()); toyboxHeightTexture = createTextureFromImage(imageBitmap); } - let brickwallAlbedoTexture: ITexture; + let brickwallAlbedoTexture: Texture; { const response = await fetch("../../../assets/img/brickwall_albedo.png"); const imageBitmap = await createImageBitmap(await response.blob()); brickwallAlbedoTexture = createTextureFromImage(imageBitmap); } - let brickwallNormalTexture: ITexture; + let brickwallNormalTexture: Texture; { const response = await fetch("../../../assets/img/brickwall_normal.png"); const imageBitmap = await createImageBitmap(await response.blob()); brickwallNormalTexture = createTextureFromImage(imageBitmap); } - let brickwallHeightTexture: ITexture; + let brickwallHeightTexture: Texture; { const response = await fetch("../../../assets/img/brickwall_height.png"); const imageBitmap = await createImageBitmap(await response.blob()); @@ -125,12 +125,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } // Create a sampler with linear filtering for smooth interpolation. - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -164,7 +164,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => depthLayers: undefined, }; - const bindingResources: IUniforms = { + const bindingResources: BindingResources = { spaceTransform, mapInfo, // Texture bindGroups and bindGroupLayout @@ -232,7 +232,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } }; - const texturedCubePipeline: IRenderPipeline = create3DRenderPipeline( + const texturedCubePipeline: RenderPipeline = create3DRenderPipeline( "NormalMappingRender", normalMapWGSL, // Position, normal uv tangent bitangent @@ -288,8 +288,8 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const worldViewMatrix = mat4.mul(viewMatrix, getModelMatrix()); const worldViewProjMatrix = mat4.mul(projectionMatrix, worldViewMatrix); - spaceTransform.worldViewMatrix = worldViewMatrix; - spaceTransform.worldViewProjMatrix = worldViewProjMatrix; + reactive(spaceTransform).worldViewMatrix = worldViewMatrix; + reactive(spaceTransform).worldViewProjMatrix = worldViewProjMatrix; // Update mapInfoBuffer const lightPosWS = vec3.create( @@ -312,12 +312,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => mapInfo.depthScale = settings.depthScale; mapInfo.depthLayers = settings.depthLayers; - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders: [{ descriptor: renderPassDescriptor, - renderObjects: [{ + renderPassObjects: [{ pipeline: texturedCubePipeline, + bindingResources: bindingResourcesList[currentSurfaceBindGroup], // * position : float32x3 // * normal : float32x3 // * uv : float32x2 @@ -331,8 +332,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => vert_bitan: { data: box.vertices, offset: 44, format: "float32x3", arrayStride: box.vertexStride }, }, indices: box.indices, - uniforms: bindingResourcesList[currentSurfaceBindGroup], - drawIndexed: { indexCount: box.indices.length }, + draw: { __type__: "DrawIndexed", indexCount: box.indices.length }, }], }] }] @@ -341,7 +341,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => requestAnimationFrame(frame); } - requestAnimationFrame(frame); + frame(); }; const panel = new GUI(); diff --git a/examples/src/webgpu/normalMap/utils.ts b/examples/src/webgpu/normalMap/utils.ts index a75dd6174c404d81aeb0f3bfb4970bb800b34950..ae3865cb7e7123aa02abeaeea8cba796f5446a36 100644 --- a/examples/src/webgpu/normalMap/utils.ts +++ b/examples/src/webgpu/normalMap/utils.ts @@ -1,15 +1,13 @@ -import { IDepthStencilState, IRenderPipeline, ITexture } from "@feng3d/render-api"; +import { DepthStencilState, RenderPipeline, Texture } from "@feng3d/render-api"; export const create3DRenderPipeline = ( label: string, vertexShader: string, fragmentShader: string, depthTest = false, - topology: GPUPrimitiveTopology = "triangle-list", - cullMode: GPUCullMode = "back" ) => { - let depthStencil: IDepthStencilState; + let depthStencil: DepthStencilState; if (depthTest) { depthStencil = { @@ -18,7 +16,7 @@ export const create3DRenderPipeline = ( }; } - const pipelineDescriptor: IRenderPipeline = { + const pipelineDescriptor: RenderPipeline = { label: `${label}.pipeline`, vertex: { code: vertexShader, @@ -27,8 +25,8 @@ export const create3DRenderPipeline = ( code: fragmentShader, }, primitive: { - topology, - cullFace: cullMode, + topology: "triangle-list", + cullFace: "back", }, depthStencil, }; @@ -40,7 +38,7 @@ export const createTextureFromImage = ( bitmap: ImageBitmap ) => { - const texture: ITexture = { + const texture: Texture = { size: [bitmap.width, bitmap.height], format: "rgba8unorm", sources: [{ image: bitmap }] diff --git a/examples/src/webgpu/occlusionQuery/index.ts b/examples/src/webgpu/occlusionQuery/index.ts index f96df9dd3c91cf54e390260319a45b9887b84ee7..bf2c3ffe3a8b724a92137cca5831f5faff829ca9 100644 --- a/examples/src/webgpu/occlusionQuery/index.ts +++ b/examples/src/webgpu/occlusionQuery/index.ts @@ -1,12 +1,10 @@ -import { IBufferBinding, IRenderObject, IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { getIGPUBuffer, IGPUOcclusionQuery, WebGPU } from "@feng3d/webgpu"; +import { BufferBinding, OcclusionQuery, reactive, RenderObject, RenderPass, RenderPassDescriptor, RenderPipeline, Submit } from "@feng3d/render-api"; +import { getGBuffer, WebGPU } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; import { mat4 } from "wgpu-matrix"; import solidColorLitWGSL from "./solidColorLit.wgsl"; - const info = document.querySelector("#info"); const init = async (canvas: HTMLCanvasElement, gui: GUI) => @@ -22,7 +20,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const webgpu = await new WebGPU().init(); - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: solidColorLitWGSL, }, @@ -110,7 +108,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const vertexBuf = vertexData; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -126,26 +124,26 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const renderObject: IRenderObject = { - pipeline, + const renderObject: RenderObject = { + pipeline: pipeline, vertices: { position: { data: vertexBuf, offset: 0, arrayStride: 6 * 4, format: "float32x3" }, normal: { data: vertexBuf, offset: 12, arrayStride: 6 * 4, format: "float32x3" }, }, indices, - uniforms: { + draw: { __type__: "DrawIndexed", indexCount: indices.length }, + bindingResources: { uni: { bufferView: undefined, }, }, - drawIndexed: { indexCount: indices.length }, }; - const renderObjects: IRenderObject[] = objectInfos.map((v) => + const renderObjects: RenderObject[] = objectInfos.map((v) => { - const ro: IRenderObject = { + const ro: RenderObject = { ...renderObject, - uniforms: { + bindingResources: { uni: { bufferView: v.uniformBuffer, }, @@ -155,16 +153,23 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => return ro; }); - const occlusionQueryObjects: IGPUOcclusionQuery[] = renderObjects.map((ro) => - ({ __type: "OcclusionQuery", renderObjects: [ro] })); + const occlusionQueryObjects: OcclusionQuery[] = renderObjects.map((ro) => + ({ __type__: "OcclusionQuery", renderObjects: [ro] })); - const renderPass: IRenderPass = { + const renderPass: RenderPass = { descriptor: renderPassDescriptor, - // renderObjects: renderObjects, - renderObjects: occlusionQueryObjects, + renderPassObjects: occlusionQueryObjects, + onOcclusionQuery(_occlusionQuerys, results) + { + const visible = objectInfos + .filter((_, i) => results[i]) + .map(({ id }) => id) + .join(""); + info.textContent = `visible: ${visible}`; + }, }; - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders: [ @@ -222,23 +227,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => mat4.transpose(mat4.inverse(world), worldInverseTranspose); mat4.multiply(viewProjection, world, worldViewProjection); - const buffer = (renderObjects[i].uniforms.uni as IBufferBinding).bufferView; - getIGPUBuffer(buffer).data = uniformValues.slice(); + const buffer = (renderObjects[i].bindingResources.uni as BufferBinding).bufferView; + reactive(getGBuffer(buffer)).data = uniformValues.subarray(); } ); webgpu.submit(submit); - // 监听查询结果。 - watcher.watch(renderPass, "occlusionQueryResults", () => - { - const visible = objectInfos - .filter((_, i) => renderPass.occlusionQueryResults[i].result.result) - .map(({ id }) => id) - .join(""); - info.textContent = `visible: ${visible}`; - }); - requestAnimationFrame(render); } requestAnimationFrame(render); diff --git a/examples/src/webgpu/particles/index.ts b/examples/src/webgpu/particles/index.ts index b6279e9320beefa0c3b40bd4b1624f1af287734c..c80fcfc80037bfa8216b5537b48d3fb9db527b85 100644 --- a/examples/src/webgpu/particles/index.ts +++ b/examples/src/webgpu/particles/index.ts @@ -7,8 +7,8 @@ import particleWGSL from "./particle.wgsl"; import probabilityMapWGSL from "./probabilityMap.wgsl"; import simulateWGSL from "./simulate.wgsl"; -import { IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit, ITexture, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPUComputePass, IGPUComputePipeline, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, RenderPass, RenderPassDescriptor, RenderPipeline, Submit, Texture, VertexAttributes, reactive } from "@feng3d/render-api"; +import { ComputePass, ComputePipeline, WebGPU, getGBuffer } from "@feng3d/webgpu"; const numParticles = 50000; const particlePositionOffset = 0; @@ -31,12 +31,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const particlesBuffer = new Float32Array(numParticles * particleInstanceByteSize / 4); - const particlesVertices: IVertexAttributes = { + const particlesVertices: VertexAttributes = { position: { data: particlesBuffer, format: "float32x3", offset: particlePositionOffset, arrayStride: particleInstanceByteSize, stepMode: "instance" }, color: { data: particlesBuffer, format: "float32x4", offset: particleColorOffset, arrayStride: particleInstanceByteSize, stepMode: "instance" }, }; - const renderPipeline: IRenderPipeline = { + const renderPipeline: RenderPipeline = { vertex: { code: particleWGSL, }, @@ -59,7 +59,6 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, ], }, - primitive: {}, depthStencil: { depthWriteEnabled: false, @@ -75,13 +74,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => + 0; const uniformBuffer = new Uint8Array(uniformBufferSize); - const uniformBindGroup: IUniforms = { + const uniformBindGroup: BindingResources = { render_params: { bufferView: uniformBuffer, }, }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -104,14 +103,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ]; const quadVertexBuffer = new Float32Array(vertexData); - const quadVertices: IVertexAttributes = { + const quadVertices: VertexAttributes = { quad_pos: { data: quadVertexBuffer, format: "float32x2" } }; // //////////////////////////////////////////////////////////////////////////// // Texture // //////////////////////////////////////////////////////////////////////////// - let texture: ITexture; + let texture: Texture; let textureWidth = 1; let textureHeight = 1; let numMipLevels = 1; @@ -146,12 +145,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // probabilities up to the top 1x1 mip level. // //////////////////////////////////////////////////////////////////////////// { - const probabilityMapImportLevelPipeline: IGPUComputePipeline = { + const probabilityMapImportLevelPipeline: ComputePipeline = { compute: { code: importLevelWGSL, }, }; - const probabilityMapExportLevelPipeline: IGPUComputePipeline = { + const probabilityMapExportLevelPipeline: ComputePipeline = { compute: { code: probabilityMapWGSL, }, @@ -164,11 +163,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const probabilityMapUBOBuffer = new Uint8Array(probabilityMapUBOBufferSize); const bufferA = new Uint8Array(textureWidth * textureHeight * 4); const bufferB = new Uint8Array(textureWidth * textureHeight * 4); - getIGPUBuffer(probabilityMapUBOBuffer).writeBuffers = [{ data: new Int32Array([textureWidth]) }]; + reactive(getGBuffer(probabilityMapUBOBuffer)).writeBuffers = [{ data: new Int32Array([textureWidth]) }]; - const passEncoders: IGPUComputePass[] = []; + const passEncoders: ComputePass[] = []; - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders, @@ -180,7 +179,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => { const levelWidth = textureWidth >> level; const levelHeight = textureHeight >> level; - const probabilityMapBindGroup: IUniforms = { + const probabilityMapBindGroup: BindingResources = { ubo: { bufferView: probabilityMapUBOBuffer }, buf_in: { bufferView: level & 1 ? bufferA : bufferB }, buf_out: { bufferView: level & 1 ? bufferB : bufferA }, @@ -202,10 +201,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => if (level === 0) { passEncoders.push({ - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ pipeline: probabilityMapImportLevelPipeline, - uniforms: { ...probabilityMapBindGroup }, + bindingResources: { ...probabilityMapBindGroup }, workgroups: { workgroupCountX: Math.ceil(levelWidth / 64), workgroupCountY: levelHeight }, }], }); @@ -213,10 +212,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => else { passEncoders.push({ - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ pipeline: probabilityMapExportLevelPipeline, - uniforms: { ...probabilityMapBindGroup }, + bindingResources: { ...probabilityMapBindGroup }, workgroups: { workgroupCountX: Math.ceil(levelWidth / 64), workgroupCountY: levelHeight }, }], }); @@ -246,12 +245,12 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => gui.add(simulationParams, k as any); }); - const computePipeline: IGPUComputePipeline = { + const computePipeline: ComputePipeline = { compute: { code: simulateWGSL, }, }; - const computeBindGroup: IUniforms = { + const computeBindGroup: BindingResources = { sim_params: { bufferView: simulationUBOBuffer, }, @@ -268,9 +267,9 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const view = mat4.create(); const mvp = mat4.create(); - const passEncoders: (IGPUComputePass | IRenderPass)[] = []; + const passEncoders: (ComputePass | RenderPass)[] = []; - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders, @@ -280,27 +279,27 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => passEncoders.push( { - __type: "ComputePass", + __type__: "ComputePass", computeObjects: [{ pipeline: computePipeline, - uniforms: { ...computeBindGroup }, + bindingResources: { ...computeBindGroup }, workgroups: { workgroupCountX: Math.ceil(numParticles / 64) }, }] }, { descriptor: renderPassDescriptor, - renderObjects: [{ + renderPassObjects: [{ pipeline: renderPipeline, - uniforms: { ...uniformBindGroup }, + bindingResources: { ...uniformBindGroup }, vertices: { ...particlesVertices, ...quadVertices }, - drawVertex: { vertexCount: 6, instanceCount: numParticles, firstVertex: 0, firstInstance: 0 }, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: numParticles, firstVertex: 0, firstInstance: 0 }, }], } ); function frame() { - getIGPUBuffer(simulationUBOBuffer).writeBuffers = [{ + reactive(getGBuffer(simulationUBOBuffer)).writeBuffers = [{ data: new Float32Array([ simulationParams.simulate ? simulationParams.deltaTime : 0.0, 0.0, @@ -319,7 +318,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => mat4.multiply(projection, view, mvp); // prettier-ignore - getIGPUBuffer(uniformBuffer).writeBuffers = [{ + reactive(getGBuffer(uniformBuffer)).writeBuffers = [{ data: new Float32Array([ // modelViewProjectionMatrix mvp[0], mvp[1], mvp[2], mvp[3], diff --git a/examples/src/webgpu/points/index.ts b/examples/src/webgpu/points/index.ts index fd37df349cd8b6bc9e3308600c020d39fa02f9ba..a667df96a12b494a65858d44fed5c1ae431a63a1 100644 --- a/examples/src/webgpu/points/index.ts +++ b/examples/src/webgpu/points/index.ts @@ -1,4 +1,4 @@ -import { IRenderPassDescriptor, IRenderPipeline, ISampler, ISubmit, ITexture, IVertexAttributes } from "@feng3d/render-api"; +import { reactive, RenderObject, RenderPassDescriptor, RenderPipeline, Sampler, Submit, Texture, VertexAttributes } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; import { mat4 } from "wgpu-matrix"; @@ -93,7 +93,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => depthCompare: "less", format: depthFormat, }, - } as IRenderPipeline) + } as RenderPipeline) ) ); @@ -103,7 +103,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }); const kNumPoints = vertexData.length / 3; - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: vertexData, format: "float32x3", arrayStride: 12, stepMode: "instance" }, }; @@ -114,8 +114,8 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ctx.textBaseline = "middle"; ctx.fillText("🦋", 32, 32); - const sampler: ISampler = {}; - const texture: ITexture = { + const sampler: Sampler = {}; + const texture: Texture = { size: [ctx.canvas.width, ctx.canvas.height], format: "rgba8unorm", sources: [ @@ -133,7 +133,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => t: { texture }, }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { label: "our basic canvas renderPass", colorAttachments: [ { @@ -161,6 +161,24 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => gui.add(settings, "textured"); gui.add(settings, "size", 0, 80); + const ro: RenderObject = { + pipeline: undefined, + bindingResources: bindingResources, + vertices, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: kNumPoints }, + }; + // + const submit: Submit = { + commandEncoders: [{ + passEncoders: [ + { + descriptor: renderPassDescriptor, + renderPassObjects: [ro] + } + ] + }] + }; + function render(time: number) { // Convert to seconds. @@ -168,10 +186,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const { size, fixedSize, textured } = settings; - const pipeline = pipelines[fixedSize ? 1 : 0][textured ? 1 : 0]; + reactive(ro).pipeline = pipelines[fixedSize ? 1 : 0][textured ? 1 : 0]; // Set the size in the uniform values - bindingResources.uni.size = size; + reactive(bindingResources.uni).size = size; const fov = (90 * Math.PI) / 180; const aspect = canvas.clientWidth / canvas.clientHeight; @@ -186,27 +204,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => mat4.rotateY(viewProjection, time, matrixValue); mat4.rotateX(matrixValue, time * 0.1, matrixValue); // Copy the uniform values to the GPU - bindingResources.uni.matrix = matrixValue; + reactive(bindingResources.uni).matrix = matrixValue; // Update the resolution in the uniform values - bindingResources.uni.resolution = [canvas.width, canvas.height]; - - // - const submit: ISubmit = { - commandEncoders: [{ - passEncoders: [ - { - descriptor: renderPassDescriptor, - renderObjects: [{ - pipeline, - vertices, - uniforms: bindingResources, - drawVertex: { vertexCount: 6, instanceCount: kNumPoints }, - }] - } - ] - }] - }; + reactive(bindingResources.uni).resolution = [canvas.width, canvas.height]; webgpu.submit(submit); diff --git a/examples/src/webgpu/renderBundles/index.ts b/examples/src/webgpu/renderBundles/index.ts index 913623c7485b148d02fb01b4e2504e1d76ffb8f8..899f4fe08f822c22e82f907557a6fcb82203fd67 100644 --- a/examples/src/webgpu/renderBundles/index.ts +++ b/examples/src/webgpu/renderBundles/index.ts @@ -1,5 +1,5 @@ -import { IRenderObject, IRenderPass, IRenderPassDescriptor, IRenderPassObject, IRenderPipeline, ISampler, ISubmit, ITexture, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { IGPUCanvasContext, IGPURenderBundle, WebGPU, getIGPUBuffer } from "@feng3d/webgpu"; +import { BindingResources, CanvasContext, RenderObject, RenderPass, RenderPassDescriptor, RenderPassObject, RenderPipeline, Sampler, Submit, Texture, VertexAttributes, reactive } from "@feng3d/render-api"; +import { RenderBundle, WebGPU, getGBuffer } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; import Stats from "stats-js"; @@ -11,11 +11,11 @@ import meshWGSL from "./mesh.wgsl"; interface Renderable { - renderObject?: IRenderObject; - vertexAttributes: IVertexAttributes; + renderObject?: RenderObject; + vertexAttributes: VertexAttributes; indices: Uint16Array; indexCount: number; - bindGroup?: IUniforms; + bindGroup?: BindingResources; } const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => @@ -24,7 +24,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => useRenderBundles: true, asteroidCount: 5000, }; - gui.add(settings, "useRenderBundles"); + gui.add(settings, "useRenderBundles").onChange(onUseRenderBundlesChanged); gui.add(settings, "asteroidCount", 1000, 10000, 1000).onChange(() => { // If the content of the scene changes the render bundle must be recreated. @@ -37,11 +37,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => canvas.height = canvas.clientHeight * devicePixelRatio; const webgpu = await new WebGPU().init(); - const context: IGPUCanvasContext = { + const context: CanvasContext = { canvasId: canvas.id, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: meshWGSL, }, @@ -54,12 +54,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => // pointing toward the camera. cullFace: "back", }, - // Enable depth testing so that the fragment closest to the camera // is rendered in front. }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; @@ -68,7 +67,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => const uniformBuffer = new Uint8Array(uniformBufferSize); // Fetch the images and upload them into a GPUTexture. - let planetTexture: ITexture; + let planetTexture: Texture; { const response = await fetch( new URL("../../../assets/img/saturn.jpg", import.meta.url).toString() @@ -82,7 +81,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => }; } - let moonTexture: ITexture; + let moonTexture: Texture; { const response = await fetch( new URL("../../../assets/img/moon.jpg", import.meta.url).toString() @@ -96,7 +95,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => }; } - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; @@ -119,7 +118,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => // Create a vertex buffer from the sphere data. const vertices = sphereMesh.vertices; - const vertexAttributes: IVertexAttributes = { + const vertexAttributes: VertexAttributes = { position: { data: vertices, format: "float32x3", offset: SphereLayout.positionsOffset, arrayStride: SphereLayout.vertexStride }, normal: { data: vertices, format: "float32x3", offset: SphereLayout.normalOffset, arrayStride: SphereLayout.vertexStride }, uv: { data: vertices, format: "float32x2", offset: SphereLayout.uvOffset, arrayStride: SphereLayout.vertexStride }, @@ -135,13 +134,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => } function createSphereBindGroup1( - texture: ITexture, + texture: Texture, transform: Float32Array - ): IUniforms + ): BindingResources { const uniformBuffer = new Float32Array(transform); - const bindGroup: IUniforms = { + const bindGroup: BindingResources = { modelMatrix: { bufferView: uniformBuffer, }, @@ -192,7 +191,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => } ensureEnoughAsteroids(); - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, @@ -218,7 +217,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => ); const modelViewProjectionMatrix = mat4.create(); - const frameBindGroup: IUniforms = { + const frameBindGroup: BindingResources = { uniforms: { bufferView: uniformBuffer, }, @@ -244,7 +243,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => // same code both to render the scene normally and to build the render bundle. function renderScene() { - const ros: IRenderObject[] = []; + const ros: RenderObject[] = []; // Loop through every renderable object and draw them individually. // (Because many of these meshes are repeated, with only the transforms // differing, instancing would be highly effective here. This sample @@ -257,11 +256,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => if (!renderable.renderObject) { renderable.renderObject = { - pipeline, - uniforms: { ...frameBindGroup, ...renderable.bindGroup }, + pipeline: pipeline, + bindingResources: { ...frameBindGroup, ...renderable.bindGroup }, vertices: renderable.vertexAttributes, indices: renderable.indices, - drawIndexed: { indexCount: renderable.indexCount }, + draw: { __type__: "DrawIndexed", indexCount: renderable.indexCount }, }; } @@ -276,6 +275,19 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => return ros; } + const renderPass: RenderPass = { + descriptor: renderPassDescriptor, + renderPassObjects: [], + }; + + const submit: Submit = { + commandEncoders: [ + { + passEncoders: [renderPass], + } + ] + }; + // The render bundle can be encoded once and re-used as many times as needed. // Because it encodes all of the commands needed to render at the GPU level, // those commands will not need to execute the associated JavaScript code upon @@ -287,57 +299,46 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI, stats) => // textures used. Cases where the executed commands differ from frame-to-frame, // such as when using frustrum or occlusion culling, will not benefit from // using render bundles as much. - let renderBundle: IGPURenderBundle = { - __type: "RenderBundle", - renderObjects: [], + let renderBundle: RenderBundle = { + __type__: "RenderBundle", + renderObjects: renderScene(), }; - renderBundle.renderObjects = renderScene(); function updateRenderBundle() { - const renderBundleEncoder: IGPURenderBundle = { - __type: "RenderBundle", - renderObjects: [], + const renderBundleEncoder: RenderBundle = { + __type__: "RenderBundle", + renderObjects: renderScene(), }; - renderBundleEncoder.renderObjects = renderScene(); renderBundle = renderBundleEncoder; + onUseRenderBundlesChanged(); } updateRenderBundle(); - function frame() + function onUseRenderBundlesChanged() { - stats.begin(); - - const transformationMatrix = getTransformationMatrix(); - - getIGPUBuffer(uniformBuffer).writeBuffers = [{ data: transformationMatrix }]; - - let renderObjects: IRenderPassObject[] = []; if (settings.useRenderBundles) { // Executing a bundle is equivalent to calling all of the commands encoded // in the render bundle as part of the current render pass. - renderObjects.push(renderBundle); + reactive(renderPass).renderPassObjects = [renderBundle]; } else { // Alternatively, the same render commands can be encoded manually, which // can take longer since each command needs to be interpreted by the // JavaScript virtual machine and re-validated each time. - renderObjects = renderBundle.renderObjects as any; + reactive(renderPass).renderPassObjects = renderBundle.renderObjects; } + } + onUseRenderBundlesChanged(); - const renderPass: IRenderPass = { - descriptor: renderPassDescriptor, - renderObjects, - }; + function frame() + { + stats.begin(); - const submit: ISubmit = { - commandEncoders: [ - { - passEncoders: [renderPass], - } - ] - }; + const transformationMatrix = getTransformationMatrix(); + + reactive(getGBuffer(uniformBuffer)).writeBuffers = [{ data: transformationMatrix }]; webgpu.submit(submit); diff --git a/examples/src/webgpu/resizeCanvas/index.ts b/examples/src/webgpu/resizeCanvas/index.ts index a5f49101a3f256ef46c302e4a5dc5d4eabd1578f..868398fdaa13d35c0cdd6584bd0c3283c29259f2 100644 --- a/examples/src/webgpu/resizeCanvas/index.ts +++ b/examples/src/webgpu/resizeCanvas/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, IRenderPassDescriptor, ISubmit } from "@feng3d/render-api"; +import { RenderObject, RenderPassDescriptor, Submit, reactive } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import redFragWGSL from "../../shaders/red.frag.wgsl"; @@ -10,7 +10,7 @@ const init = async (canvas: HTMLCanvasElement) => { const webgpu = await new WebGPU().init(); - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [{ view: { texture: { context: { canvasId: canvas.id } } }, clearValue: [0.0, 0.0, 0.0, 1.0], @@ -18,11 +18,11 @@ const init = async (canvas: HTMLCanvasElement) => sampleCount: 4, // 设置多重采样数量 }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: triangleVertWGSL }, fragment: { code: redFragWGSL }, }, - drawVertex: { vertexCount: 3 }, + draw: { __type__: "DrawVertex", vertexCount: 3 }, }; canvas.classList.add(styles.animatedCanvasSize); @@ -32,13 +32,13 @@ const init = async (canvas: HTMLCanvasElement) => // 画布尺寸发生变化时更改渲染通道附件尺寸。 const currentWidth = canvas.clientWidth * devicePixelRatio; const currentHeight = canvas.clientHeight * devicePixelRatio; - renderPassDescriptor.attachmentSize = { width: currentWidth, height: currentHeight }; + reactive(renderPassDescriptor).attachmentSize = { width: currentWidth, height: currentHeight }; - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPassDescriptor, renderObjects: [renderObject] }, + { descriptor: renderPassDescriptor, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/resizeObserverHDDPI/index.ts b/examples/src/webgpu/resizeObserverHDDPI/index.ts index 84c26999b476f402770a3b48487b44f5acab2db9..02ec7e36672979cae84bafb1073cbab7c59ccc90 100644 --- a/examples/src/webgpu/resizeObserverHDDPI/index.ts +++ b/examples/src/webgpu/resizeObserverHDDPI/index.ts @@ -1,14 +1,14 @@ import { GUI } from "dat.gui"; import checkerWGSL from "./checker.wgsl"; -import { IRenderPassDescriptor, IRenderPipeline, ISubmit, IUniforms } from "@feng3d/render-api"; +import { BindingResources, reactive, RenderPassDescriptor, RenderPipeline, Submit } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement) => { const webgpu = await new WebGPU().init(); - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: checkerWGSL }, fragment: { code: checkerWGSL, @@ -21,7 +21,7 @@ const init = async (canvas: HTMLCanvasElement) => size: undefined, }; - const bindGroup: IUniforms = { + const bindGroup: BindingResources = { uni, }; @@ -36,7 +36,7 @@ const init = async (canvas: HTMLCanvasElement) => { document.exitFullscreen(); } - else + else { document.body.requestFullscreen(); } @@ -72,45 +72,45 @@ const init = async (canvas: HTMLCanvasElement) => willReadFrequently: true, }); -return function (color: string) + return function (color: string) { ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = color; ctx.fillRect(0, 0, 1, 1); -return [...ctx.getImageData(0, 0, 1, 1).data].map((v) => v / 255); + return [...ctx.getImageData(0, 0, 1, 1).data].map((v) => v / 255); }; })(); - function frame() - { - uni.color0 = cssColorToRGBA(settings.color0); - uni.color1 = cssColorToRGBA(settings.color1); - uni.size = settings.size; - - const renderPassDescriptor: IRenderPassDescriptor = { - colorAttachments: [ - { - view: { texture: { context: { canvasId: canvas.id } } }, - clearValue: [0.2, 0.2, 0.2, 1.0], - loadOp: "clear", - storeOp: "store", - }, - ], - }; + const renderPassDescriptor: RenderPassDescriptor = { + colorAttachments: [ + { + view: { texture: { context: { canvasId: canvas.id } } }, + clearValue: [0.2, 0.2, 0.2, 1.0], + loadOp: "clear", + storeOp: "store", + }, + ], + }; - const submit: ISubmit = { - commandEncoders: [{ - passEncoders: [{ - descriptor: renderPassDescriptor, - renderObjects: [{ - pipeline, - uniforms: bindGroup, - drawVertex: { vertexCount: 3 }, - }] + const submit: Submit = { + commandEncoders: [{ + passEncoders: [{ + descriptor: renderPassDescriptor, + renderPassObjects: [{ + pipeline: pipeline, + bindingResources: bindGroup, + draw: { __type__: "DrawVertex", vertexCount: 3 }, }] }] - }; + }] + }; + function frame() + { + reactive(uni).color0 = cssColorToRGBA(settings.color0); + reactive(uni).color1 = cssColorToRGBA(settings.color1); + reactive(uni).size = settings.size; + webgpu.submit(submit); } @@ -124,11 +124,11 @@ return [...ctx.getImageData(0, 0, 1, 1).data].map((v) => v / 255); height: entry.devicePixelContentBoxSize[0].blockSize, }; } - // These values not correct but they're as close as you can get in Safari - return { - width: entry.contentBoxSize[0].inlineSize * devicePixelRatio, - height: entry.contentBoxSize[0].blockSize * devicePixelRatio, - }; + // These values not correct but they're as close as you can get in Safari + return { + width: entry.contentBoxSize[0].inlineSize * devicePixelRatio, + height: entry.contentBoxSize[0].blockSize * devicePixelRatio, + }; } const { maxTextureDimension2D } = webgpu.device.limits; diff --git a/examples/src/webgpu/reversedZ/index.ts b/examples/src/webgpu/reversedZ/index.ts index 7004551a7a4af8c559411715bd1a5347d135e89d..82e3085bb9b6a9becfae165c587e1a7c74260d23 100644 --- a/examples/src/webgpu/reversedZ/index.ts +++ b/examples/src/webgpu/reversedZ/index.ts @@ -10,8 +10,8 @@ import vertexDepthPrePassWGSL from "./vertexDepthPrePass.wgsl"; import vertexPrecisionErrorPassWGSL from "./vertexPrecisionErrorPass.wgsl"; import vertexTextureQuadWGSL from "./vertexTextureQuad.wgsl"; -import { IRenderPass, IRenderPassDescriptor, IRenderPipeline, ISubmit, ITexture, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, CanvasContext, reactive, RenderPass, RenderPassDescriptor, RenderPipeline, Submit, Texture, VertexAttributes } from "@feng3d/render-api"; +import { WebGPU } from "@feng3d/webgpu"; // Two planes close to each other for depth precision test const geometryVertexSize = 4 * 8; // Byte size of one geometry vertex. @@ -74,11 +74,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; - const context: IGPUCanvasContext = { canvasId: canvas.id }; + const context: CanvasContext = { canvasId: canvas.id }; const webgpu = await new WebGPU().init(); - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: geometryVertexArray, format: "float32x4", offset: geometryPositionOffset, arrayStride: geometryVertexSize }, color: { data: geometryVertexArray, format: "float32x4", offset: geometryColorOffset, arrayStride: geometryVertexSize }, }; @@ -87,7 +87,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // depthPrePass is used to render scene to the depth texture // this is not needed if you just want to use reversed z to render a scene - const depthPrePassRenderPipelineDescriptorBase: IRenderPipeline = { + const depthPrePassRenderPipelineDescriptorBase: RenderPipeline = { vertex: { code: vertexDepthPrePassWGSL, }, @@ -98,7 +98,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // we need the depthCompare to fit the depth buffer mode we are using. // this is the same for other passes - const depthPrePassPipelines: IRenderPipeline[] = []; + const depthPrePassPipelines: RenderPipeline[] = []; depthPrePassPipelines[DepthBufferMode.Default] = { ...depthPrePassRenderPipelineDescriptorBase, depthStencil: { @@ -116,7 +116,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // precisionPass is to draw precision error as color of depth value stored in depth buffer // compared to that directly calcualated in the shader - const precisionPassRenderPipelineDescriptorBase: IRenderPipeline = { + const precisionPassRenderPipelineDescriptorBase: RenderPipeline = { vertex: { code: vertexPrecisionErrorPassWGSL, }, @@ -128,7 +128,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const precisionPassPipelines: IRenderPipeline[] = []; + const precisionPassPipelines: RenderPipeline[] = []; precisionPassPipelines[DepthBufferMode.Default] = { ...precisionPassRenderPipelineDescriptorBase, depthStencil: { @@ -145,7 +145,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; // colorPass is the regular render pass to render the scene - const colorPassRenderPipelineDescriptorBase: IRenderPipeline = { + const colorPassRenderPipelineDescriptorBase: RenderPipeline = { vertex: { code: vertexWGSL, }, @@ -158,7 +158,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; // - const colorPassPipelines: IRenderPipeline[] = []; + const colorPassPipelines: RenderPipeline[] = []; colorPassPipelines[DepthBufferMode.Default] = { ...colorPassRenderPipelineDescriptorBase, depthStencil: { @@ -177,7 +177,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // textureQuadPass is draw a full screen quad of depth texture // to see the difference of depth value using reversed z compared to default depth buffer usage // 0.0 will be the furthest and 1.0 will be the closest - const textureQuadPassPipline: IRenderPipeline = { + const textureQuadPassPipline: RenderPipeline = { vertex: { code: vertexTextureQuadWGSL, }, @@ -186,17 +186,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: depthBufferFormat, }; - const defaultDepthTexture: ITexture = { + const defaultDepthTexture: Texture = { size: [canvas.width, canvas.height], format: depthBufferFormat, }; - const depthPrePassDescriptor: IRenderPassDescriptor = { + const depthPrePassDescriptor: RenderPassDescriptor = { colorAttachments: [], depthStencilAttachment: { view: { texture: depthTexture }, @@ -211,7 +211,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // the scene twice using different depth buffer mode on splitted viewport // of the same canvas // see the difference of the loadOp of the colorAttachments - const drawPassDescriptor: IRenderPassDescriptor = { + const drawPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, @@ -227,7 +227,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const drawPassLoadDescriptor: IRenderPassDescriptor = { + const drawPassLoadDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, @@ -244,7 +244,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; const drawPassDescriptors = [drawPassDescriptor, drawPassLoadDescriptor]; - const textureQuadPassDescriptor: IRenderPassDescriptor = { + const textureQuadPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -253,7 +253,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, ], }; - const textureQuadPassLoadDescriptor: IRenderPassDescriptor = { + const textureQuadPassLoadDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -268,7 +268,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => textureQuadPassLoadDescriptor, ]; - const depthTextureBindGroup: IUniforms = { + const depthTextureBindGroup: BindingResources = { depthTexture: { texture: depthTexture }, }; @@ -310,7 +310,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => viewProjectionMatrix: cameraMatrixReversedDepthBuffer, }; - const uniformBindGroups: IUniforms[] = [ + const uniformBindGroups: BindingResources[] = [ { uniforms, camera: camera0, @@ -351,7 +351,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => (Math.PI / 180) * 30, tmpMat4 ); - mvpMatricesData[i] = tmpMat4.slice(); + reactive(mvpMatricesData)[i] = tmpMat4.slice(); } } @@ -360,7 +360,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; gui.add(settings, "mode", ["color", "precision-error", "depth-texture"]).onChange(updateSubmit); - const colorPassEncoders: IRenderPass[] = []; + const colorPassEncoders: RenderPass[] = []; for (const m of depthBufferModes) { @@ -372,18 +372,18 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => depthClearValue: depthClearValues[m], } }, - renderObjects: [ + renderPassObjects: [ { viewport: { isYup: false, x: (canvas.width * m) / 2, y: 0, width: canvas.width / 2, height: canvas.height, minDepth: 0, maxDepth: 1 }, pipeline: colorPassPipelines[m], - uniforms: { ...uniformBindGroups[m] }, + bindingResources: { ...uniformBindGroups[m] }, vertices, - drawVertex: { vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, + draw: { __type__: "DrawVertex", vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, }] }); } - const precisionErrorPassEncoders: IRenderPass[] = []; + const precisionErrorPassEncoders: RenderPass[] = []; for (const m of depthBufferModes) { precisionErrorPassEncoders.push({ @@ -394,13 +394,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => depthClearValue: depthClearValues[m], } }, - renderObjects: [ + renderPassObjects: [ { viewport: { isYup: false, x: (canvas.width * m) / 2, y: 0, width: canvas.width / 2, height: canvas.height, minDepth: 0, maxDepth: 1 }, pipeline: depthPrePassPipelines[m], - uniforms: { ...uniformBindGroups[m] }, + bindingResources: { ...uniformBindGroups[m] }, vertices, - drawVertex: { vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, + draw: { __type__: "DrawVertex", vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, }] }); precisionErrorPassEncoders.push({ @@ -411,18 +411,18 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => depthClearValue: depthClearValues[m], } }, - renderObjects: [ + renderPassObjects: [ { viewport: { isYup: false, x: (canvas.width * m) / 2, y: 0, width: canvas.width / 2, height: canvas.height, minDepth: 0, maxDepth: 1 }, pipeline: precisionPassPipelines[m], - uniforms: { ...uniformBindGroups[m], ...depthTextureBindGroup }, + bindingResources: { ...uniformBindGroups[m], ...depthTextureBindGroup }, vertices, - drawVertex: { vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, + draw: { __type__: "DrawVertex", vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, }] }); } - const depthBufferPassEncoders: IRenderPass[] = []; + const depthBufferPassEncoders: RenderPass[] = []; for (const m of depthBufferModes) { depthBufferPassEncoders.push({ @@ -433,32 +433,32 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => depthClearValue: depthClearValues[m], } }, - renderObjects: [ + renderPassObjects: [ { viewport: { isYup: false, x: (canvas.width * m) / 2, y: 0, width: canvas.width / 2, height: canvas.height, minDepth: 0, maxDepth: 1 }, pipeline: depthPrePassPipelines[m], - uniforms: { ...uniformBindGroups[m] }, + bindingResources: { ...uniformBindGroups[m] }, vertices, - drawVertex: { vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, + draw: { __type__: "DrawVertex", vertexCount: geometryDrawCount, instanceCount: numInstances, firstVertex: 0, firstInstance: 0 }, }] }); depthBufferPassEncoders.push({ descriptor: textureQuadPassDescriptors1[m], - renderObjects: [ + renderPassObjects: [ { viewport: { isYup: false, x: (canvas.width * m) / 2, y: 0, width: canvas.width / 2, height: canvas.height, minDepth: 0, maxDepth: 1 }, pipeline: textureQuadPassPipline, - uniforms: { ...depthTextureBindGroup }, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 0 }, + bindingResources: { ...depthTextureBindGroup }, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 0 }, }] }); } - let submit: ISubmit; + let submit: Submit; function updateSubmit() { - let passEncoders: IRenderPass[]; + let passEncoders: RenderPass[]; if (settings.mode === "color") { diff --git a/examples/src/webgpu/rotatingCube/index.ts b/examples/src/webgpu/rotatingCube/index.ts index bd2fff0ae94e24fc42ed0bed2c08c6f83ef9e007..806c0bb1643e2d947460b9b1a23e11053868c1cd 100644 --- a/examples/src/webgpu/rotatingCube/index.ts +++ b/examples/src/webgpu/rotatingCube/index.ts @@ -1,4 +1,4 @@ -import { IBufferBinding, IRenderObject, IRenderPassDescriptor, ISubmit } from "@feng3d/render-api"; +import { BufferBinding, reactive, RenderObject, RenderPassDescriptor, Submit } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; @@ -15,7 +15,7 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -29,11 +29,11 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const uniforms: IBufferBinding = { + const uniforms: BufferBinding = { modelViewProjectionMatrix: new Float32Array(16) }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: basicVertWGSL }, fragment: { code: vertexPositionColorWGSL }, primitive: { @@ -44,10 +44,10 @@ const init = async (canvas: HTMLCanvasElement) => position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }, - uniforms: { + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, + bindingResources: { uniforms, }, - drawVertex: { vertexCount: cubeVertexCount }, }; const aspect = canvas.width / canvas.height; @@ -80,13 +80,14 @@ const init = async (canvas: HTMLCanvasElement) => { const transformationMatrix = getTransformationMatrix(); - uniforms.modelViewProjectionMatrix = new Float32Array(transformationMatrix); // 使用 new Float32Array 是因为赋值不同的对象才会触发数据改变重新上传数据到GPU + // 更新uniforms + reactive(uniforms).modelViewProjectionMatrix = transformationMatrix.subarray(); - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPass, renderObjects: [renderObject] }, + { descriptor: renderPass, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/samplerParameters/index.ts b/examples/src/webgpu/samplerParameters/index.ts index db9a6f9663cf0dbb2c7991852a86fc5d7cd88e9d..65d4fec1c16f74188f62d344c99620d698ff54f9 100644 --- a/examples/src/webgpu/samplerParameters/index.ts +++ b/examples/src/webgpu/samplerParameters/index.ts @@ -1,8 +1,8 @@ import { GUI } from "dat.gui"; import { mat4 } from "wgpu-matrix"; -import { IRenderPassDescriptor, IRenderPassObject, IRenderPipeline, ISampler, ISubmit, ITexture, ITextureSource, IUniforms } from "@feng3d/render-api"; -import { getIGPUBuffer, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, reactive, RenderPassDescriptor, RenderPassObject, RenderPipeline, Sampler, Submit, Texture, TextureSource } from "@feng3d/render-api"; +import { getGBuffer, WebGPU } from "@feng3d/webgpu"; import showTextureWGSL from "./showTexture.wgsl"; import texturedSquareWGSL from "./texturedSquare.wgsl"; @@ -51,13 +51,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => Number(config.highlightFlange), ]); - if (getIGPUBuffer(bufConfig).writeBuffers) + if (getGBuffer(bufConfig).writeBuffers) { - getIGPUBuffer(bufConfig).writeBuffers.push({ bufferOffset: 64, data }); + getGBuffer(bufConfig).writeBuffers.push({ bufferOffset: 64, data }); } else { - getIGPUBuffer(bufConfig).writeBuffers = [{ bufferOffset: 64, data }]; + reactive(getGBuffer(bufConfig)).writeBuffers = [{ bufferOffset: 64, data }]; } }; @@ -71,50 +71,44 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => lodMaxClamp: 4, maxAnisotropy: 1, } as const; - const samplerDescriptor: GPUSamplerDescriptor = { ...kInitSamplerDescriptor }; + + const samplerDescriptor: Sampler = { ...kInitSamplerDescriptor }; + const r_samplerDescriptor: Sampler = reactive(samplerDescriptor); { const buttons = { initial() { Object.assign(config, kInitConfig); - Object.assign(samplerDescriptor, kInitSamplerDescriptor); + Object.assign(r_samplerDescriptor, kInitSamplerDescriptor); gui.updateDisplay(); - - updateSamplerResources(); }, checkerboard() { Object.assign(config, { flangeLogSize: 10 }); - Object.assign(samplerDescriptor, { + Object.assign(r_samplerDescriptor, { addressModeU: "repeat", addressModeV: "repeat", }); gui.updateDisplay(); - - updateSamplerResources(); }, smooth() { - Object.assign(samplerDescriptor, { + Object.assign(r_samplerDescriptor, { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear", }); gui.updateDisplay(); - - updateSamplerResources(); }, crunchy() { - Object.assign(samplerDescriptor, { + Object.assign(r_samplerDescriptor, { magFilter: "nearest", minFilter: "nearest", mipmapFilter: "nearest", }); gui.updateDisplay(); - - updateSamplerResources(); }, }; const presets = gui.addFolder("Presets"); @@ -136,28 +130,24 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => folder.open(); const kAddressModes = ["clamp-to-edge", "repeat", "mirror-repeat"]; - folder.add(samplerDescriptor, "addressModeU", kAddressModes).onChange(updateSamplerResources); - folder.add(samplerDescriptor, "addressModeV", kAddressModes).onChange(updateSamplerResources); + folder.add(r_samplerDescriptor, "addressModeU", kAddressModes); + folder.add(r_samplerDescriptor, "addressModeV", kAddressModes); const kFilterModes = ["nearest", "linear"]; - folder.add(samplerDescriptor, "magFilter", kFilterModes).onChange(updateSamplerResources); - folder.add(samplerDescriptor, "minFilter", kFilterModes).onChange(updateSamplerResources); + folder.add(r_samplerDescriptor, "magFilter", kFilterModes); + folder.add(r_samplerDescriptor, "minFilter", kFilterModes); const kMipmapFilterModes = ["nearest", "linear"] as const; - folder.add(samplerDescriptor, "mipmapFilter", kMipmapFilterModes).onChange(updateSamplerResources); + folder.add(r_samplerDescriptor, "mipmapFilter", kMipmapFilterModes); - const ctlMin = folder.add(samplerDescriptor, "lodMinClamp", 0, 4, 0.1); - const ctlMax = folder.add(samplerDescriptor, "lodMaxClamp", 0, 4, 0.1); + const ctlMin = folder.add(r_samplerDescriptor, "lodMinClamp", 0, 4, 0.1); + const ctlMax = folder.add(r_samplerDescriptor, "lodMaxClamp", 0, 4, 0.1); ctlMin.onChange((value: number) => { - if (samplerDescriptor.lodMaxClamp < value) ctlMax.setValue(value); - - updateSamplerResources(); + if (r_samplerDescriptor.lodMaxClamp < value) ctlMax.setValue(value); }); ctlMax.onChange((value: number) => { - if (samplerDescriptor.lodMinClamp > value) ctlMin.setValue(value); - - updateSamplerResources(); + if (r_samplerDescriptor.lodMinClamp > value) ctlMin.setValue(value); }); { @@ -166,29 +156,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ); folder2.open(); const kMaxAnisotropy = 16; - folder2.add(samplerDescriptor, "maxAnisotropy", 1, kMaxAnisotropy, 1).onChange(updateSamplerResources); + folder2.add(r_samplerDescriptor, "maxAnisotropy", 1, kMaxAnisotropy, 1); } } } - /** - * 更新采样资源 - */ - function updateSamplerResources() - { - const sampler: ISampler = { - ...samplerDescriptor, - maxAnisotropy: - samplerDescriptor.minFilter === "linear" - && samplerDescriptor.magFilter === "linear" - && samplerDescriptor.mipmapFilter === "linear" - ? samplerDescriptor.maxAnisotropy - : 1, - }; - - bindingResources0.samp = sampler; - } - // // Canvas setup // @@ -229,19 +201,13 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const kTextureMipLevels = 4; const kTextureBaseSize = 16; - const checkerboard: ITexture = { - format: "rgba8unorm", - size: [kTextureBaseSize, kTextureBaseSize], - mipLevelCount: 4, - }; - const kColorForLevel = [ [255, 255, 255, 255], [30, 136, 229, 255], // blue [255, 193, 7, 255], // yellow [216, 27, 96, 255], // pink ]; - const writeTextures: ITextureSource[] = []; + const writeTextures: TextureSource[] = []; for (let mipLevel = 0; mipLevel < kTextureMipLevels; ++mipLevel) { const size = 2 ** (kTextureMipLevels - mipLevel); // 16, 8, 4, 2 @@ -257,20 +223,25 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } } writeTextures.push({ - __type: "TextureDataSource", + __type__: "TextureDataSource", mipLevel, data, dataLayout: { width: size }, size: [size, size] }); } - checkerboard.sources = writeTextures; + const checkerboard: Texture = { + format: "rgba8unorm", + size: [kTextureBaseSize, kTextureBaseSize], + mipLevelCount: 4, + sources: writeTextures + }; // // "Debug" view of the actual texture contents // - const showTexturePipeline: IRenderPipeline = { + const showTexturePipeline: RenderPipeline = { vertex: { code: showTextureWGSL }, fragment: { code: showTextureWGSL } }; @@ -278,7 +249,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // Pipeline for drawing the test squares // - const texturedSquarePipeline: IRenderPipeline = { + const texturedSquarePipeline: RenderPipeline = { vertex: { code: texturedSquareWGSL, constants: { kTextureBaseSize, kViewportSize } }, fragment: { code: texturedSquareWGSL }, }; @@ -289,11 +260,11 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => [0, 0, -kCameraDist] ); const bufConfig = new Uint8Array(128); - getIGPUBuffer(bufConfig).writeBuffers = [{ data: viewProj }]; + reactive(getGBuffer(bufConfig)).writeBuffers = [{ data: viewProj }]; const bufMatrices = kMatrices; - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -302,17 +273,15 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => ], }; - const renderObjects: IRenderPassObject[] = []; + const renderObjects: RenderPassObject[] = []; - const bindingResources0: IUniforms = { + const bindingResources0: BindingResources = { config: { bufferView: bufConfig }, matrices: { bufferView: bufMatrices }, - samp: null, // 帧更新中设置 + samp: samplerDescriptor, // 帧更新中设置 tex: { texture: checkerboard }, }; - updateSamplerResources(); - for (let i = 0; i < kViewportGridSize ** 2 - 1; ++i) { const vpX = kViewportGridStride * (i % kViewportGridSize) + 1; @@ -320,58 +289,58 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => renderObjects.push( { - viewport: {isYup: false, x: vpX, y: vpY, width: kViewportSize, height: kViewportSize, minDepth: 0, maxDepth: 1 }, + viewport: { isYup: false, x: vpX, y: vpY, width: kViewportSize, height: kViewportSize, minDepth: 0, maxDepth: 1 }, pipeline: texturedSquarePipeline, - uniforms: bindingResources0, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: i } + bindingResources: bindingResources0, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: i } } ); } - const bindingResources1: IUniforms = { + const bindingResources1: BindingResources = { tex: { texture: checkerboard }, }; const kLastViewport = (kViewportGridSize - 1) * kViewportGridStride + 1; renderObjects.push( { - viewport: {isYup: false, x: kLastViewport, y: kLastViewport, width: 32, height: 32, minDepth: 0, maxDepth: 1 }, + viewport: { isYup: false, x: kLastViewport, y: kLastViewport, width: 32, height: 32, minDepth: 0, maxDepth: 1 }, pipeline: showTexturePipeline, - uniforms: bindingResources1, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 0 } + bindingResources: bindingResources1, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 0 } } ); renderObjects.push( { - viewport: {isYup: false, x: kLastViewport + 32, y: kLastViewport, width: 16, height: 16, minDepth: 0, maxDepth: 1 }, + viewport: { isYup: false, x: kLastViewport + 32, y: kLastViewport, width: 16, height: 16, minDepth: 0, maxDepth: 1 }, pipeline: showTexturePipeline, - uniforms: bindingResources1, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 1 } + bindingResources: bindingResources1, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 1 } } ); renderObjects.push( { - viewport: {isYup: false, x: kLastViewport + 32, y: kLastViewport + 16, width: 8, height: 8, minDepth: 0, maxDepth: 1 }, + viewport: { isYup: false, x: kLastViewport + 32, y: kLastViewport + 16, width: 8, height: 8, minDepth: 0, maxDepth: 1 }, pipeline: showTexturePipeline, - uniforms: bindingResources1, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 3 } + bindingResources: bindingResources1, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 3 } } ); renderObjects.push( { - viewport: {isYup: false, x: kLastViewport + 32, y: kLastViewport + 24, width: 4, height: 4, minDepth: 0, maxDepth: 1 }, + viewport: { isYup: false, x: kLastViewport + 32, y: kLastViewport + 24, width: 4, height: 4, minDepth: 0, maxDepth: 1 }, pipeline: showTexturePipeline, - uniforms: bindingResources1, - drawVertex: { vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 2 } + bindingResources: bindingResources1, + draw: { __type__: "DrawVertex", vertexCount: 6, instanceCount: 1, firstVertex: 0, firstInstance: 2 } } ); - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders: [ { descriptor: renderPass, - renderObjects, + renderPassObjects: renderObjects, } ] } diff --git a/examples/src/webgpu/shadowMapping/index.ts b/examples/src/webgpu/shadowMapping/index.ts index 95c68d5e57305fa2dea213441f65e0943d7c0d21..9d2d93c4fdc68750276bae2456bb5a1062814eb0 100644 --- a/examples/src/webgpu/shadowMapping/index.ts +++ b/examples/src/webgpu/shadowMapping/index.ts @@ -1,3 +1,5 @@ +import { BindingResources, RenderPassDescriptor, RenderPipeline, Submit, Texture, VertexAttributes, reactive } from "@feng3d/render-api"; +import { WebGPU, getGBuffer } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; import { mesh } from "../../meshes/stanfordDragon"; @@ -6,9 +8,6 @@ import fragmentWGSL from "./fragment.wgsl"; import vertexWGSL from "./vertex.wgsl"; import vertexShadowWGSL from "./vertexShadow.wgsl"; -import { IRenderPassDescriptor, IRenderPipeline, ISubmit, ITexture, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { WebGPU, getIGPUBuffer } from "@feng3d/webgpu"; - const shadowDepthTextureSize = 1024; const init = async (canvas: HTMLCanvasElement) => @@ -28,7 +27,7 @@ const init = async (canvas: HTMLCanvasElement) => vertexBuffer.set(mesh.normals[i], 6 * i + 3); } - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: vertexBuffer, format: "float32x3", offset: 0, arrayStride: Float32Array.BYTES_PER_ELEMENT * 6 }, normal: { data: vertexBuffer, format: "float32x3", offset: Float32Array.BYTES_PER_ELEMENT * 3, arrayStride: Float32Array.BYTES_PER_ELEMENT * 6 }, }; @@ -42,7 +41,7 @@ const init = async (canvas: HTMLCanvasElement) => } // Create the depth texture for rendering/sampling the shadow map. - const shadowDepthTexture: ITexture = { + const shadowDepthTexture: Texture = { size: [shadowDepthTextureSize, shadowDepthTextureSize, 1], format: "depth32float", }; @@ -54,21 +53,21 @@ const init = async (canvas: HTMLCanvasElement) => cullMode: "back", }; - const shadowPipeline: IRenderPipeline = { + const shadowPipeline: RenderPipeline = { vertex: { code: vertexShadowWGSL, }, + primitive, depthStencil: { depthWriteEnabled: true, depthCompare: "less", }, - primitive, }; // Create a bind group layout which holds the scene uniforms and // the texture+sampler for depth. We create it manually because the WebPU // implementation doesn't infer this from the shader (yet). - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: vertexWGSL, }, @@ -78,19 +77,19 @@ const init = async (canvas: HTMLCanvasElement) => shadowDepthTextureSize, }, }, + primitive, depthStencil: { depthWriteEnabled: true, depthCompare: "less", }, - primitive, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus-stencil8", }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -117,13 +116,13 @@ const init = async (canvas: HTMLCanvasElement) => // Rounded to the nearest multiple of 16. const sceneUniformBuffer = new Uint8Array(2 * 4 * 16 + 4 * 4); - const sceneBindGroupForShadow: IUniforms = { + const sceneBindGroupForShadow: BindingResources = { scene: { bufferView: sceneUniformBuffer, }, }; - const sceneBindGroupForRender: IUniforms = { + const sceneBindGroupForRender: BindingResources = { scene: { bufferView: sceneUniformBuffer, }, @@ -133,7 +132,7 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const modelBindGroup: IUniforms = { + const modelBindGroup: BindingResources = { model: { bufferView: modelUniformBuffer, }, @@ -176,14 +175,14 @@ const init = async (canvas: HTMLCanvasElement) => const lightMatrixData = lightViewProjMatrix as Float32Array; const cameraMatrixData = viewProjMatrix as Float32Array; const lightData = lightPosition as Float32Array; - getIGPUBuffer(sceneUniformBuffer).writeBuffers = [ + reactive(getGBuffer(sceneUniformBuffer)).writeBuffers = [ { bufferOffset: 0, data: lightMatrixData }, { bufferOffset: 64, data: cameraMatrixData }, { bufferOffset: 128, data: lightData }, ]; const modelData = modelMatrix as Float32Array; - getIGPUBuffer(modelUniformBuffer).writeBuffers = [{ data: modelData }]; + reactive(getGBuffer(modelUniformBuffer)).writeBuffers = [{ data: modelData }]; } // Rotates the camera around the origin based on time. @@ -202,7 +201,7 @@ const init = async (canvas: HTMLCanvasElement) => return viewProjMatrix as Float32Array; } - const shadowPassDescriptor: IRenderPassDescriptor = { + const shadowPassDescriptor: RenderPassDescriptor = { colorAttachments: [], depthStencilAttachment: { view: { texture: shadowDepthTexture }, @@ -213,37 +212,37 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders: [ { descriptor: shadowPassDescriptor, - renderObjects: [ + renderPassObjects: [ { pipeline: shadowPipeline, - uniforms: { + bindingResources: { ...sceneBindGroupForShadow, ...modelBindGroup, }, vertices, indices: indexBuffer, - drawIndexed: { indexCount }, + draw: { __type__: "DrawIndexed", indexCount }, }, ] }, { descriptor: renderPassDescriptor, - renderObjects: [ + renderPassObjects: [ { - pipeline, - uniforms: { + pipeline: pipeline, + bindingResources: { ...sceneBindGroupForRender, ...modelBindGroup, }, vertices, indices: indexBuffer, - drawIndexed: { indexCount }, + draw: { __type__: "DrawIndexed", indexCount }, } ], } @@ -255,9 +254,9 @@ const init = async (canvas: HTMLCanvasElement) => function frame() { const cameraViewProj = getCameraViewProjMatrix(); - const writeBuffers = getIGPUBuffer(sceneUniformBuffer).writeBuffers || []; + const writeBuffers = getGBuffer(sceneUniformBuffer).writeBuffers || []; writeBuffers.push({ bufferOffset: 64, data: cameraViewProj }); - getIGPUBuffer(sceneUniformBuffer).writeBuffers = writeBuffers; + reactive(getGBuffer(sceneUniformBuffer)).writeBuffers = writeBuffers; webgpu.submit(submit); diff --git a/examples/src/webgpu/skinnedMesh/glbUtils.ts b/examples/src/webgpu/skinnedMesh/glbUtils.ts index 304b90d7fb687d1cbdfd91ff5aa6f0ee9f530bc9..535df79d37cc68ffed9ec67121caddb3ffe10682 100644 --- a/examples/src/webgpu/skinnedMesh/glbUtils.ts +++ b/examples/src/webgpu/skinnedMesh/glbUtils.ts @@ -1,8 +1,8 @@ import { Mat4, mat4, Quatn, Vec3n } from "wgpu-matrix"; import { Accessor, BufferView, GlTf, Scene } from "./gltf"; -import { IBuffer, IDrawIndexed, IDrawVertex, IFragmentState, IPrimitiveState, IRenderObject, IRenderPipeline, IUniforms, IVertexAttributes, IVertexState } from "@feng3d/render-api"; -import { getIGPUBuffer, gpuVertexFormatMap } from "@feng3d/webgpu"; +import { BindingResources, FragmentState, Buffer, IDraw, PrimitiveState, reactive, RenderObject, RenderPipeline, VertexAttributes, vertexFormatMap, VertexState } from "@feng3d/render-api"; +import { getGBuffer } from "@feng3d/webgpu"; //NOTE: GLTF code is not generally extensible to all gltf models // Modified from Will Usher code found at this link https://www.willusher.io/graphics/2023/05/16/0-to-gltf-first-mesh @@ -245,7 +245,7 @@ export class GLTFBufferView byteStride: number; view: Uint8Array; needsUpload: boolean; - gpuBuffer: IBuffer; + gpuBuffer: Buffer; usage: number; constructor(buffer: GLTFBuffer, view: BufferView) { @@ -285,7 +285,7 @@ export class GLTFBufferView upload() { // Note: must align to 4 byte size when mapped at creation is true - const buf: IBuffer = { + const buf: Buffer = { size: alignTo(this.view.byteLength, 4), usage: this.usage, data: this.view, @@ -342,10 +342,10 @@ interface AttributeMapInterface export class GLTFPrimitive { topology: GLTFRenderMode; - renderPipeline: IRenderPipeline; + renderPipeline: RenderPipeline; private attributeMap: AttributeMapInterface; private attributes: string[] = []; - vertices: IVertexAttributes; + vertices: VertexAttributes; indices: Uint16Array | Uint32Array; constructor( topology: GLTFRenderMode, @@ -369,7 +369,7 @@ export class GLTFPrimitive const vertexFormat: GPUVertexFormat = this.attributeMap[attr].vertexType; const attrString = attr.toLowerCase().replace(/_0$/, ""); - const Cls = gpuVertexFormatMap[vertexFormat].typedArrayConstructor; + const Cls = vertexFormatMap[vertexFormat].typedArrayConstructor; const data = new Cls(view.buffer, view.byteOffset, view.byteLength / Cls.BYTES_PER_ELEMENT); @@ -415,33 +415,22 @@ export class GLTFPrimitive ); VertexInputShaderString += "}"; - const vertexState: IVertexState = { + const vertexState: VertexState = { // Shader stage info code: VertexInputShaderString + vertexShader, }; - const fragmentState: IFragmentState = { + const fragmentState: FragmentState = { // Shader info code: VertexInputShaderString + fragmentShader, // Output render target info // targets: [{ format: colorFormat }], }; - // Our loader only supports triangle lists and strips, so by default we set - // the primitive topology to triangle list, and check if it's instead a triangle strip - let primitive: IPrimitiveState = { topology: "triangle-list" }; - if (this.topology == GLTFRenderMode.TRIANGLE_STRIP) - { - primitive = { - topology: "triangle-strip", - }; - } - - const rpDescript: IRenderPipeline = { + const rpDescript: RenderPipeline = { label: `${label}.pipeline`, vertex: vertexState, fragment: fragmentState, - primitive, depthStencil: { // format: depthFormat, depthWriteEnabled: true, @@ -452,31 +441,40 @@ export class GLTFPrimitive this.renderPipeline = rpDescript; } - render(renderObjects: IRenderObject[], bindingResources: IUniforms) + render(renderObjects: RenderObject[], bindingResources: BindingResources) { - let drawIndexed: IDrawIndexed; - let drawVertex: IDrawVertex; + let draw: IDraw; if (this.indices) { - drawIndexed = { indexCount: this.indices.length }; + draw = { __type__: "DrawIndexed", indexCount: this.indices.length }; } else { const vertexAttribute = this.vertices[Object.keys(this.vertices)[0]]; - const vertexCount = vertexAttribute.data.byteLength / gpuVertexFormatMap[vertexAttribute.format].byteSize; + const vertexCount = vertexAttribute.data.byteLength / vertexFormatMap[vertexAttribute.format].byteSize; - drawVertex = { vertexCount }; + draw = { __type__: "DrawVertex", vertexCount }; + } + + // Our loader only supports triangle lists and strips, so by default we set + // the primitive topology to triangle list, and check if it's instead a triangle strip + let primitive: PrimitiveState = { topology: "triangle-list" }; + if (this.topology == GLTFRenderMode.TRIANGLE_STRIP) + { + primitive = { + topology: "triangle-strip", + }; } + reactive(this.renderPipeline).primitive = primitive; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: this.renderPipeline, - uniforms: bindingResources, + bindingResources: bindingResources, //if skin do something with bone bind group vertices: this.vertices, indices: this.indices, - drawVertex, - drawIndexed, + draw, }; renderObjects.push(renderObject); } @@ -509,7 +507,7 @@ export class GLTFMesh } } - render(renderObjects: IRenderObject[], bindingResources: IUniforms) + render(renderObjects: RenderObject[], bindingResources: BindingResources) { // We take a pretty simple approach to start. Just loop through all the primitives and // call their individual draw methods @@ -664,7 +662,7 @@ export class GLTFNode } renderDrawables( - renderObjects: IRenderObject[], bindingResources: IUniforms + renderObjects: RenderObject[], bindingResources: BindingResources ) { if (this.drawables !== undefined) @@ -733,7 +731,7 @@ export class GLTFSkin // [5, 2, 3] means our joint info is at nodes 5, 2, and 3 joints: number[]; // Bind Group for this skin's uniform buffer - skinBindGroup: IUniforms; + skinBindGroup: BindingResources; // Static bindGroupLayout shared across all skins // In a larger shader with more properties, certain bind groups // would likely have to be combined due to device limitations in the number of bind groups @@ -782,7 +780,7 @@ export class GLTFSkin const globalWorldInverse = mat4.inverse( nodes[currentNodeIndex].worldMatrix ); - const gpuBuffer = getIGPUBuffer(this.jointMatricesUniformBuffer); + const gpuBuffer = getGBuffer(this.jointMatricesUniformBuffer); const writeBuffers = gpuBuffer.writeBuffers || []; for (let j = 0; j < this.joints.length; j++) @@ -800,7 +798,7 @@ export class GLTFSkin }); } - gpuBuffer.writeBuffers = writeBuffers; + reactive(gpuBuffer).writeBuffers = writeBuffers; } } diff --git a/examples/src/webgpu/skinnedMesh/gridUtils.ts b/examples/src/webgpu/skinnedMesh/gridUtils.ts index 7430adb044206b4356578d8e4f4f5f93806ae10d..5fa4a07cfd4042377683f814ea7172d6e121c1fb 100644 --- a/examples/src/webgpu/skinnedMesh/gridUtils.ts +++ b/examples/src/webgpu/skinnedMesh/gridUtils.ts @@ -1,4 +1,4 @@ -import { IRenderPipeline, IVertexAttributes } from "@feng3d/render-api"; +import { RenderPipeline, VertexAttributes } from "@feng3d/render-api"; import { gridIndices, gridJoints, gridVertices, gridWeights } from "./gridData"; @@ -7,7 +7,7 @@ export const createSkinnedGridBuffers = () => { // Utility function that creates GPUBuffers from data - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { vert_pos: { data: gridVertices, format: "float32x2" }, joints: { data: gridJoints, format: "uint32x4" }, weights: { data: gridWeights, format: "float32x4" }, @@ -24,7 +24,7 @@ export const createSkinnedGridRenderPipeline = ( fragmentShader: string, ) => { - const pipeline: IRenderPipeline = { + const material: RenderPipeline = { label: "SkinnedGridRenderer", vertex: { code: vertexShader, @@ -37,5 +37,5 @@ export const createSkinnedGridRenderPipeline = ( }, }; -return pipeline; + return material; }; diff --git a/examples/src/webgpu/skinnedMesh/index.ts b/examples/src/webgpu/skinnedMesh/index.ts index e48f69c30f64615bfcf8ccb99a5e70e8d17d5cf0..26b82bec5e75100567a46481be51bc7aab254584 100644 --- a/examples/src/webgpu/skinnedMesh/index.ts +++ b/examples/src/webgpu/skinnedMesh/index.ts @@ -7,8 +7,8 @@ import gridWGSL from "./grid.wgsl"; import { gridIndices } from "./gridData"; import { createSkinnedGridBuffers, createSkinnedGridRenderPipeline } from "./gridUtils"; -import { IPassEncoder, IRenderObject, IRenderPass, IRenderPassDescriptor, ITexture, IUniforms } from "@feng3d/render-api"; -import { getIGPUBuffer, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, PassEncoder, reactive, RenderObject, RenderPass, RenderPassDescriptor, Submit, Texture } from "@feng3d/render-api"; +import { getGBuffer, WebGPU } from "@feng3d/webgpu"; const init = async (canvas: HTMLCanvasElement, gui: GUI) => { @@ -147,14 +147,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => .add(settings, "renderMode", ["NORMAL", "JOINTS", "WEIGHTS"]) .onChange(() => { - const buffer = getIGPUBuffer(generalUniformsBuffer); + const buffer = getGBuffer(generalUniformsBuffer); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ data: new Uint32Array([RenderMode[settings.renderMode]]), }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; }); // Determine whether the mesh is static or whether skinning is activated gui.add(settings, "skinMode", ["ON", "OFF"]).onChange(() => @@ -174,26 +174,26 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => settings.cameraZ = -14.6; } } - const buffer = getIGPUBuffer(generalUniformsBuffer); + const buffer = getGBuffer(generalUniformsBuffer); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ bufferOffset: 4, data: new Uint32Array([SkinMode[settings.skinMode]]), }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; }); const animFolder = gui.addFolder("Animation Settings"); animFolder.add(settings, "angle", 0.05, 0.5).step(0.05); animFolder.add(settings, "speed", 10, 100).step(10); - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; const cameraBuffer = new Float32Array(48); - const cameraBGCluster: IUniforms = { + const cameraBGCluster: BindingResources = { camera_uniforms: { bufferView: cameraBuffer, } @@ -201,7 +201,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const generalUniformsBuffer = new Uint32Array(2); - const generalUniformsBGCLuster: IUniforms = { + const generalUniformsBGCLuster: BindingResources = { general_uniforms: { bufferView: generalUniformsBuffer, }, @@ -227,7 +227,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // Buffer for our uniforms, joints, and inverse bind matrices const skinnedGridJointUniformBuffer = new Uint8Array(MAT4X4_BYTES * 5); const skinnedGridInverseBindUniformBuffer = new Uint8Array(MAT4X4_BYTES * 5); - const skinnedGridBoneBGCluster: IUniforms = { + const skinnedGridBoneBGCluster: BindingResources = { joint_matrices: { bufferView: skinnedGridJointUniformBuffer }, inverse_bind_matrices: { bufferView: skinnedGridInverseBindUniformBuffer }, }; @@ -303,7 +303,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } // Pass Descriptor for GLTFs - const gltfRenderPassDescriptor: IRenderPassDescriptor = { + const gltfRenderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, // Assigned later @@ -322,7 +322,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }; // Pass descriptor for grid with no depth testing - const skinnedGridRenderPassDescriptor: IRenderPassDescriptor = { + const skinnedGridRenderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, // Assigned later @@ -374,7 +374,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // Create bones of the skinned grid and write the inverse bind positions to // the skinned grid's inverse bind matrix array - const buffer = getIGPUBuffer(skinnedGridInverseBindUniformBuffer); + const buffer = getGBuffer(skinnedGridInverseBindUniformBuffer); const writeBuffers = buffer.writeBuffers || []; const gridBoneCollection = createBoneCollection(5); for (let i = 0; i < gridBoneCollection.bindPosesInv.length; i++) @@ -384,7 +384,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => data: gridBoneCollection.bindPosesInv[i] }); } - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; // A map that maps a joint index to the original matrix transformation of a bone const origMatrices = new Map(); @@ -424,6 +424,51 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } }; + const passEncoders: PassEncoder[] = []; + const submit: Submit = { commandEncoders: [{ passEncoders }] }; + + const whaleRenderPass = (() => + { + const renderObjects: RenderObject[] = []; + const bindingResources: BindingResources = { + ...cameraBGCluster, + ...generalUniformsBGCLuster, + }; + for (const scene of whaleScene.scenes) + { + scene.root.renderDrawables(renderObjects, bindingResources); + } + const passEncoder: RenderPass = { + descriptor: gltfRenderPassDescriptor, + renderPassObjects: renderObjects + }; + return passEncoder; + })(); + const skinnedGridRenderPass = (() => + { + // Our skinned grid isn't checking for depth, so we pass it + // a separate render descriptor that does not take in a depth texture + // Pass in vertex and index buffers generated from our static skinned grid + // data at ./gridData.ts + const renderObject: RenderObject = { + pipeline: skinnedGridPipeline, + bindingResources: { + ...cameraBGCluster, + ...generalUniformsBGCLuster, + ...skinnedGridBoneBGCluster, + }, + vertices: skinnedGridVertexBuffers.vertices, + indices: skinnedGridVertexBuffers.indices, + draw: { __type__: "DrawIndexed", indexCount: gridIndices.length }, + }; + // + const passEncoder: RenderPass = { + descriptor: gltfRenderPassDescriptor, + renderPassObjects: [renderObject], + }; + return passEncoder; + })(); + function frame() { // Calculate camera matrices @@ -438,7 +483,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => animSkinnedGrid(gridBoneCollection.transforms, angle); // Write to mvp to camera buffer - const buffer = getIGPUBuffer(cameraBuffer); + const buffer = getGBuffer(cameraBuffer); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ bufferOffset: 0, @@ -458,10 +503,10 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => dataOffset: modelMatrix.byteOffset, size: modelMatrix.byteLength }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; // Write to skinned grid bone uniform buffer - const buffer0 = getIGPUBuffer(skinnedGridJointUniformBuffer); + const buffer0 = getGBuffer(skinnedGridJointUniformBuffer); const writeBuffers0 = buffer0.writeBuffers || []; for (let i = 0; i < gridBoneCollection.transforms.length; i++) { @@ -470,7 +515,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => data: gridBoneCollection.transforms[i] }); } - buffer0.writeBuffers = writeBuffers0; + reactive(buffer0).writeBuffers = writeBuffers0; // Update node matrixes for (const scene of whaleScene.scenes) @@ -483,50 +528,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // Node 6 should be the only node with a drawable mesh so hopefully this works fine whaleScene.skins[0].update(6, whaleScene.nodes); - const passEncoders: IPassEncoder[] = []; - + passEncoders.length = 0; if (settings.object === "Whale") { - const renderObjects: IRenderObject[] = []; - const bindingResources: IUniforms = { - ...cameraBGCluster, - ...generalUniformsBGCLuster, - }; - for (const scene of whaleScene.scenes) - { - scene.root.renderDrawables(renderObjects, bindingResources); - } - const passEncoder: IRenderPass = { - descriptor: gltfRenderPassDescriptor, - renderObjects - }; - passEncoders.push(passEncoder); + passEncoders.push(whaleRenderPass); } else { - // Our skinned grid isn't checking for depth, so we pass it - // a separate render descriptor that does not take in a depth texture - // Pass in vertex and index buffers generated from our static skinned grid - // data at ./gridData.ts - const renderObject: IRenderObject = { - pipeline: skinnedGridPipeline, - uniforms: { - ...cameraBGCluster, - ...generalUniformsBGCLuster, - ...skinnedGridBoneBGCluster, - }, - vertices: skinnedGridVertexBuffers.vertices, - indices: skinnedGridVertexBuffers.indices, - drawIndexed: { indexCount: gridIndices.length }, - }; - // - passEncoders.push({ - descriptor: skinnedGridRenderPassDescriptor, - renderObjects: [renderObject], - }); + passEncoders.push(skinnedGridRenderPass); } - webgpu.submit({ commandEncoders: [{ passEncoders }] }); + webgpu.submit(submit); requestAnimationFrame(frame); } diff --git a/examples/src/webgpu/textRenderingMsdf/index.ts b/examples/src/webgpu/textRenderingMsdf/index.ts index b3d65239ffb7e559733599a71d3d20b3a8a47220..3b4c3d2023438e16d246ffef7a1d44b3f69542bd 100644 --- a/examples/src/webgpu/textRenderingMsdf/index.ts +++ b/examples/src/webgpu/textRenderingMsdf/index.ts @@ -1,7 +1,7 @@ import { mat4, vec3 } from "wgpu-matrix"; -import { IRenderPassDescriptor, IRenderPassObject, IRenderPipeline, ISubmit, ITexture, IUniforms, IVertexAttributes } from "@feng3d/render-api"; -import { getIGPUBuffer, WebGPU } from "@feng3d/webgpu"; +import { BindingResources, reactive, RenderPassDescriptor, RenderPassObject, RenderPipeline, Submit, Texture, VertexAttributes } from "@feng3d/render-api"; +import { getGBuffer, WebGPU } from "@feng3d/webgpu"; import basicVertWGSL from "../../shaders/basic.vert.wgsl"; import vertexPositionColorWGSL from "../../shaders/vertexPositionColor.frag.wgsl"; @@ -9,7 +9,6 @@ import vertexPositionColorWGSL from "../../shaders/vertexPositionColor.frag.wgsl import { cubePositionOffset, cubeUVOffset, cubeVertexArray, cubeVertexCount, cubeVertexSize } from "../../meshes/cube"; import { MsdfTextRenderer } from "./msdfText"; - const init = async (canvas: HTMLCanvasElement) => { const devicePixelRatio = window.devicePixelRatio || 1; @@ -136,12 +135,12 @@ setBlendConstant().`, ]; // Create a vertex buffer from the cube data. - const verticesBuffer: IVertexAttributes = { + const verticesBuffer: VertexAttributes = { position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: basicVertWGSL, }, @@ -154,7 +153,6 @@ setBlendConstant().`, // pointing toward the camera. cullFace: "back", }, - // Enable depth testing so that the fragment closest to the camera // is rendered in front. depthStencil: { @@ -163,18 +161,18 @@ setBlendConstant().`, }, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: depthFormat, }; const uniformBuffer = new Float32Array(16); - const uniformBindGroup: IUniforms = { + const uniformBindGroup: BindingResources = { uniforms: { bufferView: uniformBuffer }, }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, // Assigned later @@ -248,31 +246,31 @@ setBlendConstant().`, { const transformationMatrix = getTransformationMatrix(); - const buffer = getIGPUBuffer(uniformBuffer); + const buffer = getGBuffer(uniformBuffer); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ data: transformationMatrix.buffer, dataOffset: transformationMatrix.byteOffset, size: transformationMatrix.byteLength }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; - const renderObjects: IRenderPassObject[] = []; + const renderObjects: RenderPassObject[] = []; renderObjects.push({ - pipeline, - uniforms: uniformBindGroup, + pipeline: pipeline, + bindingResources: uniformBindGroup, vertices: verticesBuffer, - drawVertex: { vertexCount: cubeVertexCount, instanceCount: 1 }, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount, instanceCount: 1 }, }); textRenderer.render(renderObjects, ...text); - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders: [{ descriptor: renderPassDescriptor, - renderObjects, + renderPassObjects: renderObjects, }] }] }; diff --git a/examples/src/webgpu/textRenderingMsdf/msdfText.ts b/examples/src/webgpu/textRenderingMsdf/msdfText.ts index a84fb44a131f9d0a691b81f0079ce02ddd6a5689..0e4be94bf6e197c8a3c75cd2918fd945ffb64254 100644 --- a/examples/src/webgpu/textRenderingMsdf/msdfText.ts +++ b/examples/src/webgpu/textRenderingMsdf/msdfText.ts @@ -2,8 +2,8 @@ import { mat4, Mat4 } from "wgpu-matrix"; import msdfTextWGSL from "./msdfText.wgsl"; -import { IRenderPassObject, IRenderPipeline, ISampler, ITexture, IUniforms } from "@feng3d/render-api"; -import { getIGPUBuffer, IGPURenderBundle } from "@feng3d/webgpu"; +import { BindingResources, reactive, RenderPassObject, RenderPipeline, Sampler, Texture } from "@feng3d/render-api"; +import { getGBuffer, RenderBundle } from "@feng3d/webgpu"; // The kerning map stores a spare map of character ID pairs with an associated // X offset that should be applied to the character spacing when the second @@ -32,8 +32,8 @@ export class MsdfFont charCount: number; defaultChar: MsdfChar; constructor( - public pipeline: IRenderPipeline, - public bindGroup: IUniforms, + public material: RenderPipeline, + public bindGroup: BindingResources, public lineHeight: number, public chars: { [x: number]: MsdfChar }, public kernings: KerningMap @@ -87,7 +87,7 @@ export class MsdfText private bufferArrayDirty = true; constructor( - private renderBundle: IGPURenderBundle, + private renderBundle: RenderBundle, public measurements: MsdfTextMeasurements, public font: MsdfFont, public textBuffer: Float32Array @@ -104,7 +104,7 @@ export class MsdfText if (this.bufferArrayDirty) { this.bufferArrayDirty = false; - const buffer = getIGPUBuffer(this.textBuffer); + const buffer = getGBuffer(this.textBuffer); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ bufferOffset: 0, @@ -112,7 +112,7 @@ export class MsdfText dataOffset: 0, size: this.bufferArray.length }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; } return this.renderBundle; @@ -149,8 +149,8 @@ export interface MsdfTextFormattingOptions export class MsdfTextRenderer { - pipelinePromise: IRenderPipeline; - sampler: ISampler; + pipelinePromise: RenderPipeline; + sampler: Sampler; cameraUniformBuffer: Float32Array = new Float32Array(16 * 2); @@ -204,7 +204,7 @@ export class MsdfTextRenderer const response = await fetch(url); const imageBitmap = await createImageBitmap(await response.blob()); - const texture: ITexture = { + const texture: Texture = { size: [imageBitmap.width, imageBitmap.height], label: `MSDF font texture ${url}`, format: "rgba8unorm", @@ -224,7 +224,7 @@ export class MsdfTextRenderer const i = fontJsonUrl.lastIndexOf("/"); const baseUrl = i !== -1 ? fontJsonUrl.substring(0, i + 1) : undefined; - const pagePromises: Promise[] = []; + const pagePromises: Promise[] = []; for (const pageUrl of json.pages) { pagePromises.push(this.loadTexture(baseUrl + pageUrl)); @@ -256,7 +256,7 @@ export class MsdfTextRenderer const pageTextures = await Promise.all(pagePromises); - const bindGroup: IUniforms = { + const bindGroup: BindingResources = { fontTexture: { texture: pageTextures[0] }, fontSampler: this.sampler, chars: { bufferView: charsArray } @@ -333,21 +333,21 @@ export class MsdfTextRenderer ); } - const bindGroup: IUniforms = { + const bindGroup: BindingResources = { camera: { bufferView: this.cameraUniformBuffer }, text: { bufferView: textBuffer }, }; - const renderBundle: IGPURenderBundle = { - __type: "RenderBundle", + const renderBundle: RenderBundle = { + __type__: "RenderBundle", renderObjects: [ { - pipeline: font.pipeline, - uniforms: { + pipeline: font.material, + bindingResources: { ...font.bindGroup, ...bindGroup, }, - drawVertex: { vertexCount: 4, instanceCount: measurements.printedCharCount }, + draw: { __type__: "DrawVertex", vertexCount: 4, instanceCount: measurements.printedCharCount }, } ], }; @@ -436,15 +436,15 @@ export class MsdfTextRenderer this.cameraUniformBuffer.set(projection, 0); this.cameraUniformBuffer.set(view, 16); - const buffer = getIGPUBuffer(this.cameraUniformBuffer); + const buffer = getGBuffer(this.cameraUniformBuffer); const writeBuffers = buffer.writeBuffers || []; writeBuffers.push({ data: this.cameraUniformBuffer, }); - buffer.writeBuffers = writeBuffers; + reactive(buffer).writeBuffers = writeBuffers; } - render(renderObjects: IRenderPassObject[], ...text: MsdfText[]) + render(renderObjects: RenderPassObject[], ...text: MsdfText[]) { const renderBundles = text.map((t) => t.getRenderBundle()); diff --git a/examples/src/webgpu/texturedCube/index.ts b/examples/src/webgpu/texturedCube/index.ts index f3132c5a43a7cdd6214d8f7f25f6124c24114402..4f942020396ed50b8067754a8f66817e8fa3f0fc 100644 --- a/examples/src/webgpu/texturedCube/index.ts +++ b/examples/src/webgpu/texturedCube/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, IRenderPassDescriptor, ISampler, ISubmit, ITexture } from "@feng3d/render-api"; +import { reactive, RenderObject, RenderPassDescriptor, Sampler, Submit, Texture } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; @@ -22,19 +22,19 @@ const init = async (canvas: HTMLCanvasElement) => ).toString(); await img.decode(); const imageBitmap = await createImageBitmap(img); - const cubeTexture: ITexture = { + const cubeTexture: Texture = { size: [imageBitmap.width, imageBitmap.height], format: "rgba8unorm", sources: [{ image: imageBitmap }], }; // Create a sampler with linear filtering for smooth interpolation. - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -52,23 +52,23 @@ const init = async (canvas: HTMLCanvasElement) => modelViewProjectionMatrix: new Float32Array(16) }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: basicVertWGSL }, fragment: { code: sampleTextureMixColorWGSL }, primitive: { cullFace: "back", }, }, - vertices: { - position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, - uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, - }, - uniforms: { + bindingResources: { uniforms, mySampler: sampler, myTexture: { texture: cubeTexture }, }, - drawVertex: { vertexCount: cubeVertexCount }, + vertices: { + position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, + uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, + }, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, }; const aspect = canvas.width / canvas.height; @@ -101,13 +101,14 @@ const init = async (canvas: HTMLCanvasElement) => { const transformationMatrix = getTransformationMatrix(); - uniforms.modelViewProjectionMatrix = transformationMatrix.slice(); // 使用 new Float32Array 是因为赋值不同的对象才会触发数据改变重新上传数据到GPU + // 重新设置uniforms + reactive(uniforms).modelViewProjectionMatrix = transformationMatrix.subarray(); - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPass, renderObjects: [renderObject] }, + { descriptor: renderPass, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/timestampQuery/PerfCounter.ts b/examples/src/webgpu/timestampQuery/PerfCounter.ts index 475646213351250ea65a07d7f6d12cddd5a97b5d..1500ccd9d2a20d5843d247fa29d3d36a6ce2ed43 100644 --- a/examples/src/webgpu/timestampQuery/PerfCounter.ts +++ b/examples/src/webgpu/timestampQuery/PerfCounter.ts @@ -6,30 +6,30 @@ export default class PerfCounter accumulatedSq: number; constructor() -{ + { this.sampleCount = 0; this.accumulated = 0; this.accumulatedSq = 0; } addSample(value: number) -{ + { this.sampleCount += 1; this.accumulated += value; this.accumulatedSq += value * value; } getAverage(): number -{ + { return this.sampleCount === 0 ? 0 : this.accumulated / this.sampleCount; } getStddev(): number -{ + { if (this.sampleCount === 0) return 0; const avg = this.getAverage(); const variance = this.accumulatedSq / this.sampleCount - avg * avg; -return Math.sqrt(Math.max(0.0, variance)); + return Math.sqrt(Math.max(0.0, variance)); } } diff --git a/examples/src/webgpu/timestampQuery/index.ts b/examples/src/webgpu/timestampQuery/index.ts index 74888274c6e77302a08fc4c531b0af90ab28021f..0d7da38327f0e1f450572448381d5f6ee7830b74 100644 --- a/examples/src/webgpu/timestampQuery/index.ts +++ b/examples/src/webgpu/timestampQuery/index.ts @@ -1,5 +1,5 @@ -import { IRenderObject, IRenderPassDescriptor, IRenderPipeline, ISubmit, ITexture, IVertexAttributes } from "@feng3d/render-api"; -import { IGPUCanvasContext, IGPUTimestampQuery, WebGPU } from "@feng3d/webgpu"; +import { CanvasContext, reactive, RenderObject, RenderPassDescriptor, RenderPipeline, Submit, Texture, VertexAttributes } from "@feng3d/render-api"; +import { TimestampQuery, WebGPU } from "@feng3d/webgpu"; import { mat4, vec3 } from "wgpu-matrix"; @@ -8,39 +8,34 @@ import { cubePositionOffset, cubeUVOffset, cubeVertexArray, cubeVertexCount, cub import basicVertWGSL from "../../shaders/basic.vert.wgsl"; import fragmentWGSL from "../../shaders/black.frag.wgsl"; -import { watcher } from "@feng3d/watcher"; import PerfCounter from "./PerfCounter"; const init = async (canvas: HTMLCanvasElement) => { + const renderPassDurationCounter = new PerfCounter(); // GPU-side timer and the CPU-side counter where we accumulate statistics: // NB: Look for 'timestampQueryManager' in this file to locate parts of this // snippets that are related to timestamps. Most of the logic is in // TimestampQueryManager.ts. - const timestampQuery: IGPUTimestampQuery = {}; - // const timestampQueryManager = new TimestampQueryManager(device); - const renderPassDurationCounter = new PerfCounter(); - - watcher.watch(timestampQuery, "isSupports", () => - { - if (!timestampQuery.isSupports) + const timestampQuery: TimestampQuery = { + onSupports: (isSupports: boolean) => + { + if (!isSupports) + { + perfDisplay.innerHTML = "Timestamp queries are not supported"; + } + }, + onQuery: (elapsedNs: number) => { - perfDisplay.innerHTML = "Timestamp queries are not supported"; + // Show the last successfully downloaded elapsed time. + // Convert from nanoseconds to milliseconds: + const elapsedMs = Number(elapsedNs) * 1e-6; + renderPassDurationCounter.addSample(elapsedMs); + perfDisplay.innerHTML = `Render Pass duration: ${renderPassDurationCounter + .getAverage() + .toFixed(3)} ms ± ${renderPassDurationCounter.getStddev().toFixed(3)} ms`; } - }); - - // 监听结果。 - watcher.watch(timestampQuery, "elapsedNs", () => - { - // Show the last successfully downloaded elapsed time. - const elapsedNs = timestampQuery.elapsedNs; - // Convert from nanoseconds to milliseconds: - const elapsedMs = Number(elapsedNs) * 1e-6; - renderPassDurationCounter.addSample(elapsedMs); - perfDisplay.innerHTML = `Render Pass duration: ${renderPassDurationCounter - .getAverage() - .toFixed(3)} ms ± ${renderPassDurationCounter.getStddev().toFixed(3)} ms`; - }); + }; // const devicePixelRatio = window.devicePixelRatio || 1; @@ -49,19 +44,19 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); // - const context: IGPUCanvasContext = { canvasId: canvas.id }; + const context: CanvasContext = { canvasId: canvas.id }; const perfDisplay = document.querySelector("#info pre"); // Create a vertex buffer from the cube data. - const vertices: IVertexAttributes = { + const vertices: VertexAttributes = { position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }; const uniforms = { modelViewProjectionMatrix: null }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: basicVertWGSL, }, @@ -76,7 +71,6 @@ const init = async (canvas: HTMLCanvasElement) => // pointing toward the camera. cullFace: "back", }, - // Enable depth testing so that the fragment closest to the camera // is rendered in front. depthStencil: { @@ -85,12 +79,12 @@ const init = async (canvas: HTMLCanvasElement) => }, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, // Assigned later @@ -107,25 +101,25 @@ const init = async (canvas: HTMLCanvasElement) => depthLoadOp: "clear", depthStoreOp: "store", }, + // 开启时间戳查询 + timestampQuery, }; - const renderObject: IRenderObject = { - pipeline, - vertices, - uniforms: { + const renderObject: RenderObject = { + pipeline: pipeline, + bindingResources: { uniforms, }, - drawVertex: { vertexCount: cubeVertexCount }, + vertices, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, }; - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [ { passEncoders: [ { - descriptor: renderPassDescriptor, renderObjects: [renderObject], - // 开启时间戳查询 - timestampQuery, + descriptor: renderPassDescriptor, renderPassObjects: [renderObject], }, ] } @@ -156,7 +150,7 @@ const init = async (canvas: HTMLCanvasElement) => function frame() { const transformationMatrix = getTransformationMatrix(); - uniforms.modelViewProjectionMatrix = new Float32Array(transformationMatrix); + reactive(uniforms).modelViewProjectionMatrix = transformationMatrix.subarray(); webgpu.submit(submit); diff --git a/examples/src/webgpu/transparentCanvas/index.ts b/examples/src/webgpu/transparentCanvas/index.ts index c8284a4693913ad1df861535976fc0e1a2c0011f..ec18ec0d1a267627788d19f3a885b5eca9014497 100644 --- a/examples/src/webgpu/transparentCanvas/index.ts +++ b/examples/src/webgpu/transparentCanvas/index.ts @@ -1,7 +1,7 @@ import { mat4, vec3 } from "wgpu-matrix"; -import { IRenderPassDescriptor, IRenderPipeline, ITexture, IVertexAttributes } from "@feng3d/render-api"; -import { IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { CanvasContext, reactive, RenderPassDescriptor, RenderPipeline, Submit, Texture, VertexAttributes } from "@feng3d/render-api"; +import { WebGPU } from "@feng3d/webgpu"; import { cubePositionOffset, cubeUVOffset, cubeVertexArray, cubeVertexCount, cubeVertexSize } from "../../meshes/cube"; @@ -16,7 +16,7 @@ const init = async (canvas: HTMLCanvasElement) => canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; - const context: IGPUCanvasContext = { + const context: CanvasContext = { canvasId: canvas.id, configuration: { // The canvas alphaMode defaults to 'opaque', use 'premultiplied' for transparency. @@ -25,12 +25,12 @@ const init = async (canvas: HTMLCanvasElement) => }; // Create a vertex buffer from the cube data. - const verticesBuffer: IVertexAttributes = { + const verticesBuffer: VertexAttributes = { position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: basicVertWGSL, }, @@ -41,14 +41,13 @@ const init = async (canvas: HTMLCanvasElement) => topology: "triangle-list", cullFace: "back", }, - depthStencil: { depthWriteEnabled: true, depthCompare: "less", }, }; - const depthTexture: ITexture = { + const depthTexture: Texture = { size: [canvas.width, canvas.height], format: "depth24plus", }; @@ -57,7 +56,7 @@ const init = async (canvas: HTMLCanvasElement) => uniforms: { modelViewProjectionMatrix: undefined } }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, // Assigned later @@ -97,23 +96,25 @@ const init = async (canvas: HTMLCanvasElement) => return modelViewProjectionMatrix; } - function frame() - { - uniformBindGroup.uniforms.modelViewProjectionMatrix = getTransformationMatrix().slice(); - - webgpu.submit({ - commandEncoders: [{ - passEncoders: [{ - descriptor: renderPassDescriptor, - renderObjects: [{ - pipeline, - uniforms: uniformBindGroup, - vertices: verticesBuffer, - drawVertex: { vertexCount: cubeVertexCount }, - }] + const submit: Submit = { + commandEncoders: [{ + passEncoders: [{ + descriptor: renderPassDescriptor, + renderPassObjects: [{ + pipeline: pipeline, + bindingResources: uniformBindGroup, + vertices: verticesBuffer, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, }] }] - }); + }] + } + + function frame() + { + reactive(uniformBindGroup.uniforms).modelViewProjectionMatrix = getTransformationMatrix().slice(); + + webgpu.submit(submit); requestAnimationFrame(frame); } diff --git a/examples/src/webgpu/twoCubes/index.ts b/examples/src/webgpu/twoCubes/index.ts index 60dea7492cc94814188166eb4654d2d536231a06..77ce7a6c181fd4fb8eba0d5cd324c312c733bf4c 100644 --- a/examples/src/webgpu/twoCubes/index.ts +++ b/examples/src/webgpu/twoCubes/index.ts @@ -1,6 +1,6 @@ import { mat4, vec3 } from "wgpu-matrix"; -import { IBufferBinding, IRenderObject, IRenderPassDescriptor, ISubmit } from "@feng3d/render-api"; +import { BufferBinding, reactive, RenderObject, RenderPassDescriptor, Submit } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { cubePositionOffset, cubeUVOffset, cubeVertexArray, cubeVertexCount, cubeVertexSize } from "../../meshes/cube"; @@ -17,7 +17,7 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -37,36 +37,36 @@ const init = async (canvas: HTMLCanvasElement) => const uniformBuffer = new ArrayBuffer(uniformBufferSize); - const uniforms: IBufferBinding = { + const uniforms: BufferBinding = { bufferView: new Uint8Array(uniformBuffer, 0, matrixSize), modelViewProjectionMatrix: null, // 在帧循环中设置 }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: basicVertWGSL }, fragment: { code: vertexPositionColorWGSL }, primitive: { cullFace: "back", }, }, + bindingResources: { + uniforms, + }, vertices: { position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }, - uniforms: { - uniforms, - }, - drawVertex: { vertexCount: cubeVertexCount }, + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount }, }; - const uniforms1: IBufferBinding = { + const uniforms1: BufferBinding = { bufferView: new Uint8Array(uniformBuffer, offset, matrixSize), modelViewProjectionMatrix: null, // 在帧循环中设置 }; - const renderObject1: IRenderObject = { + const renderObject1: RenderObject = { ...renderObject, - uniforms: { + bindingResources: { uniforms: uniforms1, }, }; @@ -119,11 +119,11 @@ const init = async (canvas: HTMLCanvasElement) => ); } - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPassDescriptor, renderObjects: [renderObject, renderObject1] }, + { descriptor: renderPassDescriptor, renderPassObjects: [renderObject, renderObject1] }, ] } ], @@ -133,8 +133,9 @@ const init = async (canvas: HTMLCanvasElement) => { updateTransformationMatrix(); - uniforms.modelViewProjectionMatrix = modelViewProjectionMatrix1; - uniforms1.modelViewProjectionMatrix = modelViewProjectionMatrix2; + // 使用 subarray 是因为赋值不同的对象才会触发数据改变重新上传数据到GPU + reactive(uniforms).modelViewProjectionMatrix = modelViewProjectionMatrix1.subarray(); + reactive(uniforms1).modelViewProjectionMatrix = modelViewProjectionMatrix2.subarray(); webgpu.submit(data); diff --git a/examples/src/webgpu/videoUploading/index.ts b/examples/src/webgpu/videoUploading/index.ts index aa46a83bd9a25724b72ea6fda808da19d0091b9e..d8ca883b6131f87328afbcbb3b4e7f252b83e1a0 100644 --- a/examples/src/webgpu/videoUploading/index.ts +++ b/examples/src/webgpu/videoUploading/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, IRenderPassDescriptor, ISampler, ISubmit } from "@feng3d/render-api"; +import { RenderPassDescriptor, Sampler, Submit, RenderObject } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import fullscreenTexturedQuadWGSL from "../../shaders/fullscreenTexturedQuad.wgsl"; @@ -23,12 +23,12 @@ const init = async (canvas: HTMLCanvasElement) => const webgpu = await new WebGPU().init(); - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", }; - const renderPass: IRenderPassDescriptor = { + const renderPass: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, @@ -37,26 +37,26 @@ const init = async (canvas: HTMLCanvasElement) => ], }; - const renderObject: IRenderObject = { + const renderObject: RenderObject = { pipeline: { vertex: { code: fullscreenTexturedQuadWGSL }, fragment: { code: sampleExternalTextureWGSL }, }, - uniforms: { + bindingResources: { mySampler: sampler, myTexture: { source: video, }, }, - drawVertex: { vertexCount: 6 }, + draw: { __type__: "DrawVertex", vertexCount: 6 }, }; function frame() { - const data: ISubmit = { + const data: Submit = { commandEncoders: [ { passEncoders: [ - { descriptor: renderPass, renderObjects: [renderObject] }, + { descriptor: renderPass, renderPassObjects: [renderObject] }, ] } ], diff --git a/examples/src/webgpu/volumeRenderingTexture3D/index.ts b/examples/src/webgpu/volumeRenderingTexture3D/index.ts index b86fc57233a0fba50d6d2acd8f564b930c18f39c..3b01da185f18b1bd54a5257e46113a2b419f73cf 100644 --- a/examples/src/webgpu/volumeRenderingTexture3D/index.ts +++ b/examples/src/webgpu/volumeRenderingTexture3D/index.ts @@ -2,7 +2,7 @@ import { GUI } from "dat.gui"; import { mat4 } from "wgpu-matrix"; import volumeWGSL from "./volume.wgsl"; -import { IRenderPassDescriptor, IRenderPipeline, ISampler, ISubmit, ITexture, IUniforms } from "@feng3d/render-api"; +import { BindingResources, reactive, RenderPassDescriptor, RenderPipeline, Sampler, Submit, Texture } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; const gui = new GUI(); @@ -28,7 +28,7 @@ const init = async (canvas: HTMLCanvasElement) => const sampleCount = 4; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: volumeWGSL, }, @@ -46,7 +46,7 @@ const init = async (canvas: HTMLCanvasElement) => }; // Fetch the image and upload it into a GPUTexture. - let volumeTexture: ITexture; + let volumeTexture: Texture; { const width = 180; const height = 216; @@ -79,7 +79,7 @@ const init = async (canvas: HTMLCanvasElement) => size: [width, height, depth], format, sources: [{ - __type: "TextureDataSource", + __type__: "TextureDataSource", data: byteArray, dataLayout: { width, height }, size: [width, height, depth], @@ -88,20 +88,20 @@ const init = async (canvas: HTMLCanvasElement) => } // Create a sampler with linear filtering for smooth interpolation. - const sampler: ISampler = { + const sampler: Sampler = { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear", maxAnisotropy: 16, }; - const uniformBindGroup: IUniforms = { + const uniformBindGroup: BindingResources = { uniforms: uniformBuffer, mySampler: sampler, myTexture: { texture: volumeTexture }, }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context: { canvasId: canvas.id } } }, // Assigned later @@ -145,6 +145,19 @@ const init = async (canvas: HTMLCanvasElement) => let lastFrameMS = Date.now(); + const submit: Submit = { + commandEncoders: [{ + passEncoders: [{ + descriptor: renderPassDescriptor, + renderPassObjects: [{ + pipeline, + bindingResources: uniformBindGroup, + draw: { __type__: "DrawVertex", vertexCount: 3 }, + }], + }] + }] + }; + function frame() { const now = Date.now(); @@ -154,20 +167,8 @@ const init = async (canvas: HTMLCanvasElement) => const inverseModelViewProjection = getInverseModelViewProjectionMatrix(deltaTime); - uniformBuffer.inverseModelViewProjectionMatrix = inverseModelViewProjection; - - const submit: ISubmit = { - commandEncoders: [{ - passEncoders: [{ - descriptor: renderPassDescriptor, - renderObjects: [{ - pipeline, - uniforms: uniformBindGroup, - drawVertex: { vertexCount: 3 }, - }], - }] - }] - }; + reactive(uniformBuffer).inverseModelViewProjectionMatrix = inverseModelViewProjection; + webgpu.submit(submit); requestAnimationFrame(frame); diff --git a/examples/src/webgpu/wireframe/index.ts b/examples/src/webgpu/wireframe/index.ts index b0156f7c16f0db8ae263dbcdd082bd8d6cc2551a..adf60a1d5bd92661985906c040297dd2ce319b98 100644 --- a/examples/src/webgpu/wireframe/index.ts +++ b/examples/src/webgpu/wireframe/index.ts @@ -1,4 +1,4 @@ -import { IRenderObject, IRenderPassDescriptor, IRenderPipeline, ISubmit, IUniforms, IVertexAttributes } from "@feng3d/render-api"; +import { BindingResources, reactive, RenderObject, RenderPassDescriptor, RenderPipeline, Submit, VertexAttributes } from "@feng3d/render-api"; import { WebGPU } from "@feng3d/webgpu"; import { GUI } from "dat.gui"; @@ -30,7 +30,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => type Model = { vertices: Float32Array; indices: Uint32Array; - vertexAttributes: IVertexAttributes + vertexAttributes: VertexAttributes }; const models = Object.values(modelData).map((v) => @@ -47,7 +47,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => return model; }); - let litPipeline: IRenderPipeline; + let litPipeline: RenderPipeline; function rebuildLitPipeline() { litPipeline = { @@ -75,7 +75,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => } rebuildLitPipeline(); - const wireframePipeline: IRenderPipeline = { + const wireframePipeline: RenderPipeline = { label: "wireframe pipeline", vertex: { code: wireframeWGSL, @@ -94,7 +94,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }, }; - const barycentricCoordinatesBasedWireframePipeline: IRenderPipeline = { + const barycentricCoordinatesBasedWireframePipeline: RenderPipeline = { label: "barycentric coordinates based wireframe pipeline", vertex: { code: wireframeWGSL, @@ -140,8 +140,8 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => thickness: number; alphaThreshold: number; }; - litBindGroup: IUniforms; - wireframeBindGroups: IUniforms[]; + litBindGroup: BindingResources; + wireframeBindGroups: BindingResources[]; model: Model; }; @@ -164,7 +164,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const model = randElement(models); // Make a bind group for this uniform - const litBindGroup: IUniforms = { + const litBindGroup: BindingResources = { uni: uniformBuffer, }; @@ -186,14 +186,14 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => // We're creating 2 bindGroups, one for each pipeline. // We could create just one since they are identical. To do // so we'd have to manually create a bindGroupLayout. - const wireframeBindGroup: IUniforms = { + const wireframeBindGroup: BindingResources = { uni: uniformBuffer, positions: { bufferView: model.vertices }, indices: { bufferView: model.indices }, line: lineUniformBuffer, }; - const barycentricCoordinatesBasedWireframeBindGroup: IUniforms = { + const barycentricCoordinatesBasedWireframeBindGroup: BindingResources = { uni: uniformBuffer, positions: { bufferView: model.vertices }, indices: { bufferView: model.indices }, @@ -213,7 +213,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => }); } - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { label: "our basic canvas renderPass", colorAttachments: [ { @@ -290,7 +290,7 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => const viewProjection = mat4.multiply(projection, view); - const renderObjects: IRenderObject[] = []; + const renderObjects: RenderObject[] = []; objectInfos.forEach( ( @@ -321,17 +321,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => mat4.multiply(viewProjection, world, worldViewProjectionMatrixValue); // Upload our uniform values. - uniformBuffer.worldViewProjectionMatrix = worldViewProjectionMatrixValue; - uniformBuffer.worldMatrix = world; + reactive(uniformBuffer).worldViewProjectionMatrix = worldViewProjectionMatrixValue.subarray(); + reactive(uniformBuffer).worldMatrix = world; if (settings.models) { renderObjects.push({ pipeline: litPipeline, + bindingResources: litBindGroup, vertices: vertexAttributes, indices, - uniforms: litBindGroup, - drawIndexed: { indexCount: indices.length }, + draw: { __type__: "DrawIndexed", indexCount: indices.length }, }); } } @@ -350,17 +350,17 @@ const init = async (canvas: HTMLCanvasElement, gui: GUI) => { renderObjects.push({ pipeline, - uniforms: wireframeBindGroups[bindGroupNdx], - drawVertex: { vertexCount: indices.length * countMult }, + bindingResources: wireframeBindGroups[bindGroupNdx], + draw: { __type__: "DrawVertex", vertexCount: indices.length * countMult }, }); }); } - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders: [{ descriptor: renderPassDescriptor, - renderObjects, + renderPassObjects: renderObjects, }] }] }; diff --git a/examples/src/webgpu/worker/worker.ts b/examples/src/webgpu/worker/worker.ts index 4396556d4fe6303f0a77b1362447afcb46d1e2c9..e7209bb93245b80a3bd3aa3f70d52a4ddb151033 100644 --- a/examples/src/webgpu/worker/worker.ts +++ b/examples/src/webgpu/worker/worker.ts @@ -1,7 +1,7 @@ import { mat4, vec3 } from "wgpu-matrix"; -import { IRenderPassDescriptor, IRenderPipeline, ISubmit, IVertexAttributes } from "@feng3d/render-api"; -import { getOffscreenCanvasId, IGPUCanvasContext, WebGPU } from "@feng3d/webgpu"; +import { CanvasContext, reactive, RenderPassDescriptor, RenderPipeline, Submit, VertexAttributes } from "@feng3d/render-api"; +import { WebGPU } from "@feng3d/webgpu"; import { cubePositionOffset, cubeUVOffset, cubeVertexArray, cubeVertexCount, cubeVertexSize } from "../../meshes/cube"; @@ -70,15 +70,15 @@ self.addEventListener("message", (ev) => async function init(canvas: OffscreenCanvas) { const webgpu = await new WebGPU().init(); - const context: IGPUCanvasContext = { canvasId: getOffscreenCanvasId(canvas) }; + const context: CanvasContext = { canvasId: canvas }; // Create a vertex buffer from the cube data. - const verticesBuffer: IVertexAttributes = { + const verticesBuffer: VertexAttributes = { position: { data: cubeVertexArray, format: "float32x4", offset: cubePositionOffset, arrayStride: cubeVertexSize }, uv: { data: cubeVertexArray, format: "float32x2", offset: cubeUVOffset, arrayStride: cubeVertexSize }, }; - const pipeline: IRenderPipeline = { + const pipeline: RenderPipeline = { vertex: { code: basicVertWGSL, }, @@ -92,8 +92,8 @@ async function init(canvas: OffscreenCanvas) // Faces pointing away from the camera will be occluded by faces // pointing toward the camera. cullFace: "back", + frontFace: "ccw", }, - // Enable depth testing so that the fragment closest to the camera // is rendered in front. depthStencil: { @@ -106,7 +106,7 @@ async function init(canvas: OffscreenCanvas) uniforms: { modelViewProjectionMatrix: undefined }, }; - const renderPassDescriptor: IRenderPassDescriptor = { + const renderPassDescriptor: RenderPassDescriptor = { colorAttachments: [ { view: { texture: { context } }, // Assigned later @@ -149,15 +149,15 @@ async function init(canvas: OffscreenCanvas) return modelViewProjectionMatrix; } - const submit: ISubmit = { + const submit: Submit = { commandEncoders: [{ passEncoders: [{ descriptor: renderPassDescriptor, - renderObjects: [{ + renderPassObjects: [{ pipeline, - uniforms: uniformBindGroup, + bindingResources: uniformBindGroup, vertices: verticesBuffer, - drawVertex: { vertexCount: cubeVertexCount } + draw: { __type__: "DrawVertex", vertexCount: cubeVertexCount } }] }] }] @@ -166,7 +166,7 @@ async function init(canvas: OffscreenCanvas) function frame() { const transformationMatrix = getTransformationMatrix(); - uniformBindGroup.uniforms.modelViewProjectionMatrix = transformationMatrix.slice(); + reactive(uniformBindGroup.uniforms).modelViewProjectionMatrix = transformationMatrix.slice(); webgpu.submit(submit); diff --git a/examples/vite.config.js b/examples/vite.config.js index 9ca156017fb9f1fe78f77b1254b8ac9e966ddd23..27979c34d0c42fc8404101db57ea8fb4343b42b7 100644 --- a/examples/vite.config.js +++ b/examples/vite.config.js @@ -4,6 +4,9 @@ import { resolve } from "path"; import { defineConfig } from "vite"; export default defineConfig({ + define: { + __DEV__: process.env.NODE_ENV === 'development' ? true : false + }, publicDir: "resources", build: { rollupOptions: { diff --git a/package.json b/package.json index 330d8e84cfd1297a0dc172c885bf961f9f534a8d..703e27f9f78f4ab5c6baf27ccafda0b195accf67 100644 --- a/package.json +++ b/package.json @@ -58,11 +58,9 @@ "vitest": "^0.32.2" }, "dependencies": { - "@feng3d/render-api": "0.0.2", "@feng3d/event": "^0.8.4", - "@feng3d/watcher": "^0.8.9", + "@feng3d/render-api": "0.0.2", "@webgpu/types": "0.1.49", - "stats.js": "^0.17.0", "wgsl_reflect": "^1.0.16" } } diff --git a/src/WebGPU.ts b/src/WebGPU.ts index a48a965ff9ec95cbbbeff284a5167f3a36887a4b..25ff4bf0ef3471db5db7b83d7be518c6208f6ff0 100644 --- a/src/WebGPU.ts +++ b/src/WebGPU.ts @@ -1,168 +1,12 @@ -import { IBuffer, ISubmit, ITextureLike } from "@feng3d/render-api"; -import { getGPUBuffer } from "./caches/getGPUBuffer"; -import { getGPUTexture } from "./caches/getGPUTexture"; -import { getIGPUTextureLikeSize } from "./caches/getIGPUTextureSize"; -import { IGPUReadPixels } from "./data/IGPUReadPixels"; -import { RunWebGPU } from "./runs/RunWebGPU"; -import { RunWebGPUCommandCache } from "./runs/RunWebGPUCommandCache"; -import { copyDepthTexture } from "./utils/copyDepthTexture"; -import { quitIfWebGPUNotAvailable } from "./utils/quitIfWebGPUNotAvailable"; -import { readPixels } from "./utils/readPixels"; -import { textureInvertYPremultiplyAlpha } from "./utils/textureInvertYPremultiplyAlpha"; +import { WebGPUBase } from "./WebGPUBase"; /** * WebGPU 对象。 * * 提供 `WebGPU` 操作入口 {@link WebGPU.submit}。 */ -export class WebGPU +// export class WebGPU extends WebGPUCache +export class WebGPU extends WebGPUBase { - private _runWebGPU: RunWebGPU = new RunWebGPUCommandCache(); - // private _runWebGPU: RunWebGPU = new RunWebGPU(); - /** - * 初始化 WebGPU 获取 GPUDevice 。 - */ - async init(options?: GPURequestAdapterOptions, descriptor?: GPUDeviceDescriptor) - { - const adapter = await navigator.gpu?.requestAdapter(options); - // 获取支持的特性 - const features: GPUFeatureName[] = []; - adapter?.features.forEach((v) => { features.push(v as any); }); - // 判断请求的特性是否被支持 - const requiredFeatures = Array.from(descriptor?.requiredFeatures || []); - if (requiredFeatures.length > 0) - { - for (let i = requiredFeatures.length - 1; i >= 0; i--) - { - if (features.indexOf(requiredFeatures[i]) === -1) - { - console.error(`当前 GPUAdapter 不支持特性 ${requiredFeatures[i]}!`); - requiredFeatures.splice(i, 1); - } - } - descriptor.requiredFeatures = requiredFeatures; - } - // 默认开启当前本机支持的所有WebGPU特性。 - descriptor = descriptor || {}; - descriptor.requiredFeatures = (descriptor.requiredFeatures || features) as any; - // - const device = await adapter?.requestDevice(descriptor); - quitIfWebGPUNotAvailable(adapter, device); - - device?.lost.then(async (info) => - { - console.error(`WebGPU device was lost: ${info.message}`); - - // 'reason' will be 'destroyed' if we intentionally destroy the device. - if (info.reason !== "destroyed") - { - // try again - await this.init(options, descriptor); - } - }); - - this.device = device; - - return this; - } - - public device: GPUDevice; - - /** - * 提交 GPU 。 - * - * @param submit 一次 GPU 提交内容。 - * - * @see GPUQueue.submit - */ - submit(submit: ISubmit) - { - this._runWebGPU.runSubmit(this.device, submit); - } - - /** - * 销毁纹理。 - * - * @param texture 需要被销毁的纹理。 - */ - destoryTexture(texture: ITextureLike) - { - getGPUTexture(this.device, texture, false)?.destroy(); - } - - /** - * 操作纹理进行Y轴翻转或进行预乘Alpha。 - * - * @param texture 被操作的纹理。 - * @param invertY 是否Y轴翻转 - * @param premultiplyAlpha 是否预乘Alpha。 - */ - textureInvertYPremultiplyAlpha(texture: ITextureLike, options: { invertY?: boolean, premultiplyAlpha?: boolean }) - { - const gpuTexture = getGPUTexture(this.device, texture); - - textureInvertYPremultiplyAlpha(this.device, gpuTexture, options); - } - - /** - * 拷贝 深度纹理到 普通纹理。 - * - * @param device GPU设备。 - * @param sourceTexture 源纹理。 - * @param targetTexture 目标纹理。 - */ - copyDepthTexture(sourceTexture: ITextureLike, targetTexture: ITextureLike) - { - const gpuSourceTexture = getGPUTexture(this.device, sourceTexture); - const gpuTargetTexture = getGPUTexture(this.device, targetTexture); - - copyDepthTexture(this.device, gpuSourceTexture, gpuTargetTexture); - } - - /** - * 从 GPU纹理 上读取数据。 - * - * @param gpuReadPixels - * - * @returns 读取到的数据。 - */ - async readPixels(gpuReadPixels: IGPUReadPixels) - { - const gpuTexture = getGPUTexture(this.device, gpuReadPixels.texture, false); - - const result = await readPixels(this.device, { - ...gpuReadPixels, - texture: gpuTexture, - }); - - gpuReadPixels.result = result; - - return result; - } - - /** - * 从GPU缓冲区读取数据到CPU。 - * - * @param buffer GPU缓冲区。 - * @param offset 读取位置。 - * @param size 读取字节数量。 - * @returns CPU数据缓冲区。 - */ - async readBuffer(buffer: IBuffer, offset?: GPUSize64, size?: GPUSize64) - { - const gpuBuffer = getGPUBuffer(this.device, buffer); - await gpuBuffer.mapAsync(GPUMapMode.READ); - - const result = gpuBuffer.getMappedRange(offset, size).slice(0); - - gpuBuffer.unmap(); - - return result; - } - - getGPUTextureSize(input: ITextureLike) - { - return getIGPUTextureLikeSize(input); - } } diff --git a/src/WebGPUBase.ts b/src/WebGPUBase.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b22bb6409e1fd33756ac742dd3bec20e2b8f1c7 --- /dev/null +++ b/src/WebGPUBase.ts @@ -0,0 +1,742 @@ +import { BlendState, Buffer, ChainMap, CommandEncoder, computed, Computed, CopyBufferToBuffer, CopyTextureToTexture, DepthStencilState, effect, OcclusionQuery, reactive, ReadPixels, RenderObject, RenderPass, RenderPassObject, RenderPipeline, Submit, TextureLike, UnReadonly } from "@feng3d/render-api"; + +import { getGPUBindGroup } from "./caches/getGPUBindGroup"; +import { getGPUBuffer } from "./caches/getGPUBuffer"; +import { getGPUComputePassDescriptor } from "./caches/getGPUComputePassDescriptor"; +import { getGPUComputePipeline } from "./caches/getGPUComputePipeline"; +import { getGPUPipelineLayout } from "./caches/getGPUPipelineLayout"; +import { getGPURenderPassDescriptor } from "./caches/getGPURenderPassDescriptor"; +import { getGPURenderPassFormat } from "./caches/getGPURenderPassFormat"; +import { getGPURenderPipeline } from "./caches/getGPURenderPipeline"; +import { getGPUTexture } from "./caches/getGPUTexture"; +import { getGBuffer } from "./caches/getIGPUBuffer"; +import { getNVertexBuffers } from "./caches/getNGPUVertexBuffers"; +import { ComputeObject } from "./data/ComputeObject"; +import { ComputePass } from "./data/ComputePass"; +import "./data/polyfills/RenderObject"; +import "./data/polyfills/RenderPass"; +import { RenderBundle } from "./data/RenderBundle"; +import { CommandEncoderCommand, CommandType, ComputeObjectCommand, ComputePassCommand, CopyBufferToBufferCommand, CopyTextureToTextureCommand, OcclusionQueryCache, RenderBundleCommand, RenderObjectCache, RenderPassCommand, RenderPassObjectCommand, SubmitCommand } from "./internal/RenderObjectCache"; +import { RenderPassFormat } from "./internal/RenderPassFormat"; +import { copyDepthTexture } from "./utils/copyDepthTexture"; +import { getGPUDevice } from "./utils/getGPUDevice"; +import { readPixels } from "./utils/readPixels"; +import { textureInvertYPremultiplyAlpha } from "./utils/textureInvertYPremultiplyAlpha"; + +declare global +{ + interface GPUCommandEncoder + { + /** + * 创建时由引擎设置。 + */ + device: GPUDevice; + } + + interface GPURenderPassEncoder + { + /** + * 创建时由引擎设置。 + */ + device: GPUDevice; + } +} + +/** + * WebGPU 基础类 + */ +export class WebGPUBase +{ + /** + * 初始化 WebGPU 获取 GPUDevice 。 + */ + async init(options?: GPURequestAdapterOptions, descriptor?: GPUDeviceDescriptor) + { + this.device = await getGPUDevice(options, descriptor); + // + this.device?.lost.then(async (info) => + { + console.error(`WebGPU device was lost: ${info.message}`); + + // 'reason' will be 'destroyed' if we intentionally destroy the device. + if (info.reason !== "destroyed") + { + // try again + this.device = await getGPUDevice(options, descriptor); + } + }); + + return this; + } + + get device() + { + return this._device; + } + set device(v) + { + this._device = v; + } + protected _device: GPUDevice; + + constructor(device?: GPUDevice) + { + this.device = device; + } + + submit(submit: Submit) + { + const device = this._device; + + const submitCommand = new SubmitCommand(); + + // + submitCommand.commandBuffers = submit.commandEncoders.map((v) => + { + const commandBuffer = this.runCommandEncoder(v); + + return commandBuffer; + }); + + submitCommand.run(device); + } + + destoryTexture(texture: TextureLike) + { + getGPUTexture(this._device, texture, false)?.destroy(); + } + + textureInvertYPremultiplyAlpha(texture: TextureLike, options: { invertY?: boolean, premultiplyAlpha?: boolean }) + { + const device = this._device; + const gpuTexture = getGPUTexture(device, texture); + + textureInvertYPremultiplyAlpha(device, gpuTexture, options); + } + + copyDepthTexture(sourceTexture: TextureLike, targetTexture: TextureLike) + { + const device = this._device; + const gpuSourceTexture = getGPUTexture(device, sourceTexture); + const gpuTargetTexture = getGPUTexture(device, targetTexture); + + copyDepthTexture(device, gpuSourceTexture, gpuTargetTexture); + } + + async readPixels(gpuReadPixels: ReadPixels) + { + const device = this._device; + const gpuTexture = getGPUTexture(device, gpuReadPixels.texture, false); + + const result = await readPixels(device, { + ...gpuReadPixels, + texture: gpuTexture, + }); + + gpuReadPixels.result = result; + + return result; + } + + async readBuffer(buffer: Buffer, offset?: GPUSize64, size?: GPUSize64) + { + const device = this._device; + const gpuBuffer = getGPUBuffer(device, buffer); + await gpuBuffer.mapAsync(GPUMapMode.READ); + + const result = gpuBuffer.getMappedRange(offset, size).slice(0); + + gpuBuffer.unmap(); + + return result; + } + + protected runCommandEncoder(commandEncoder: CommandEncoder) + { + const commandEncoderCommand = new CommandEncoderCommand(); + + commandEncoderCommand.passEncoders = commandEncoder.passEncoders.map((passEncoder) => + { + if (!passEncoder.__type__) + { + return this.runRenderPass(passEncoder as RenderPass); + } + else if (passEncoder.__type__ === "RenderPass") + { + return this.runRenderPass(passEncoder); + } + else if (passEncoder.__type__ === "ComputePass") + { + return this.runComputePass(passEncoder); + } + else if (passEncoder.__type__ === "CopyTextureToTexture") + { + return this.runCopyTextureToTexture(passEncoder); + } + else if (passEncoder.__type__ === "CopyBufferToBuffer") + { + return this.runCopyBufferToBuffer(passEncoder); + } + else + { + console.error(`未处理 passEncoder ${passEncoder}`); + } + }); + + return commandEncoderCommand; + } + + protected runRenderPass(renderPass: RenderPass) + { + const device = this._device; + let renderPassCommand = this._renderPassCommandMap.get(renderPass); + if (renderPassCommand) return renderPassCommand; + + renderPassCommand = new RenderPassCommand(); + + effect(() => + { + const r_renderPass = reactive(renderPass); + r_renderPass.renderPassObjects; + r_renderPass.descriptor; + + const { descriptor, renderPassObjects } = renderPass; + renderPassCommand.renderPassDescriptor = getGPURenderPassDescriptor(device, renderPass); + + const renderPassFormat = getGPURenderPassFormat(descriptor); + + const renderPassObjectCommands = this.runRenderPassObjects(renderPassFormat, renderPassObjects); + const commands: CommandType[] = []; + const state = new RenderObjectCache(); + renderPassObjectCommands?.forEach((command) => + { + command.run(device, commands, state); + }); + renderPassCommand.commands = commands; + }); + + this._renderPassCommandMap.set(renderPass, renderPassCommand); + + return renderPassCommand; + } + + private runRenderPassObjects(renderPassFormat: RenderPassFormat, renderPassObjects: readonly RenderPassObject[]) + { + const renderPassObjectCommandsKey: RenderPassObjectCommandsKey = [renderPassObjects, renderPassFormat]; + let result = this._renderPassObjectCommandsMap.get(renderPassObjectCommandsKey); + if (result) return result.value; + + result = computed(() => + { + let queryIndex = 0; + const renderPassObjectCommands: RenderPassObjectCommand[] = renderPassObjects?.map((element) => + { + if (!element.__type__) + { + return this.runRenderObject(renderPassFormat, element as RenderObject); + } + if (element.__type__ === "RenderObject") + { + return this.runRenderObject(renderPassFormat, element); + } + if (element.__type__ === "RenderBundle") + { + return this.runRenderBundle(renderPassFormat, element); + } + if (element.__type__ === "OcclusionQuery") + { + const occlusionQueryCache = this.runRenderOcclusionQueryObject(renderPassFormat, element); + occlusionQueryCache.queryIndex = queryIndex++; + return occlusionQueryCache; + } + else + { + throw `未处理 ${(element as RenderPassObject).__type__} 类型的渲染通道对象!`; + } + }); + + return renderPassObjectCommands; + }); + + this._renderPassObjectCommandsMap.set(renderPassObjectCommandsKey, result); + + return result.value; + } + + /** + * 执行计算通道。 + * + * @param device GPU设备。 + * @param commandEncoder 命令编码器。 + * @param computePass 计算通道。 + */ + protected runComputePass(computePass: ComputePass) + { + const computePassCommand = new ComputePassCommand(); + + computePassCommand.descriptor = getGPUComputePassDescriptor(this._device, computePass); + computePassCommand.computeObjectCommands = this.runComputeObjects(computePass.computeObjects); + + return computePassCommand; + } + + protected runComputeObjects(computeObjects: ComputeObject[]) + { + return computeObjects.map((computeObject) => this.runComputeObject(computeObject)); + } + + protected runCopyTextureToTexture(copyTextureToTexture: CopyTextureToTexture) + { + const device = this._device; + + const copyTextureToTextureCommand = new CopyTextureToTextureCommand(); + + const sourceTexture = getGPUTexture(device, copyTextureToTexture.source.texture); + const destinationTexture = getGPUTexture(device, copyTextureToTexture.destination.texture); + + copyTextureToTextureCommand.source = { + ...copyTextureToTexture.source, + texture: sourceTexture, + }; + + copyTextureToTextureCommand.destination = { + ...copyTextureToTexture.destination, + texture: destinationTexture, + }; + + copyTextureToTextureCommand.copySize = copyTextureToTexture.copySize; + + return copyTextureToTextureCommand; + } + + protected runCopyBufferToBuffer(copyBufferToBuffer: CopyBufferToBuffer) + { + const device = this._device; + + const copyBufferToBufferCommand = new CopyBufferToBufferCommand(); + + copyBufferToBufferCommand.source = getGPUBuffer(device, copyBufferToBuffer.source); + copyBufferToBufferCommand.sourceOffset = copyBufferToBuffer.sourceOffset ?? 0; + copyBufferToBufferCommand.destination = getGPUBuffer(device, copyBufferToBuffer.destination); + copyBufferToBufferCommand.destinationOffset = copyBufferToBuffer.destinationOffset ?? 0; + copyBufferToBufferCommand.size = copyBufferToBuffer.size ?? copyBufferToBuffer.source.size; + + return copyBufferToBufferCommand; + } + + protected runRenderOcclusionQueryObject(renderPassFormat: RenderPassFormat, renderOcclusionQueryObject: OcclusionQuery) + { + const occlusionQueryCache = new OcclusionQueryCache(); + + occlusionQueryCache.renderObjectCaches = this.runRenderObjects(renderPassFormat, renderOcclusionQueryObject.renderObjects); + + return occlusionQueryCache; + } + + private runRenderBundle(renderPassFormat: RenderPassFormat, renderBundleObject: RenderBundle) + { + const gpuRenderBundleKey: GPURenderBundleKey = [renderBundleObject, renderPassFormat]; + let result = gpuRenderBundleMap.get(gpuRenderBundleKey); + if (result) return result.value; + + const renderBundleCommand = new RenderBundleCommand(); + + result = computed(() => + { + // 监听 + const r_renderBundleObject = reactive(renderBundleObject); + r_renderBundleObject.renderObjects; + r_renderBundleObject.descriptor?.depthReadOnly; + r_renderBundleObject.descriptor?.stencilReadOnly; + + // 执行 + const descriptor: GPURenderBundleEncoderDescriptor = { ...renderBundleObject.descriptor, ...renderPassFormat }; + + renderBundleCommand.descriptor = descriptor; + + const renderObjectCaches = this.runRenderObjects(renderPassFormat, renderBundleObject.renderObjects); + + // + const commands: CommandType[] = []; + const state = new RenderObjectCache(); + renderObjectCaches.forEach((renderObjectCache) => + { + renderObjectCache.run(undefined, commands, state); + }); + + renderBundleCommand.bundleCommands = commands.filter((command) => ( + command[0] !== "setViewport" + && command[0] !== "setScissorRect" + && command[0] !== "setBlendConstant" + && command[0] !== "setStencilReference" + )); + + return renderBundleCommand; + }); + gpuRenderBundleMap.set(gpuRenderBundleKey, result); + + return result.value; + } + + private runRenderObjects(renderPassFormat: RenderPassFormat, renderObjects: readonly RenderObject[]) + { + const renderObjectCachesKey: RenderObjectCachesKey = [renderObjects, renderPassFormat]; + let result = this._renderObjectCachesMap.get(renderObjectCachesKey); + if (result) return result.value; + + result = computed(() => + { + // + const renderObjectCaches = renderObjects.map((element) => + { + return this.runRenderObject(renderPassFormat, element as RenderObject); + }); + + return renderObjectCaches; + + }); + this._renderObjectCachesMap.set(renderObjectCachesKey, result); + + return result.value; + } + + /** + * 执行计算对象。 + * + * @param device GPU设备。 + * @param passEncoder 计算通道编码器。 + * @param computeObject 计算对象。 + */ + protected runComputeObject(computeObject: ComputeObject) + { + const device = this._device; + const { pipeline, bindingResources: bindingResources, workgroups } = computeObject; + + const computePipeline = getGPUComputePipeline(device, pipeline); + + const computeObjectCommand = new ComputeObjectCommand(); + computeObjectCommand.computePipeline = computePipeline; + + // 计算 bindGroups + computeObjectCommand.setBindGroup = []; + const layout = getGPUPipelineLayout(device, { compute: pipeline.compute.code }); + layout.bindGroupLayouts.forEach((bindGroupLayout, group) => + { + const gpuBindGroup: GPUBindGroup = getGPUBindGroup(device, bindGroupLayout, bindingResources); + computeObjectCommand.setBindGroup.push([group, gpuBindGroup]); + }); + + computeObjectCommand.dispatchWorkgroups = [workgroups.workgroupCountX, workgroups.workgroupCountY, workgroups.workgroupCountZ]; + + return computeObjectCommand; + } + + /** + * 执行渲染对象。 + * + * @param device GPU设备。 + * @param passEncoder 渲染通道编码器。 + * @param renderObject 渲染对象。 + * @param renderPass 渲染通道。 + */ + protected runRenderObject(renderPassFormat: RenderPassFormat, renderObject: RenderObject) + { + const device = this._device; + const renderObjectCacheKey: RenderObjectCacheKey = [device, renderObject, renderPassFormat]; + let result = renderObjectCacheMap.get(renderObjectCacheKey); + if (result) { return result.value; } + + const renderObjectCache = new RenderObjectCache(); + result = computed(() => + { + this.runviewport(renderObject, renderPassFormat, renderObjectCache); + this.runScissorRect(renderObject, renderPassFormat, renderObjectCache); + this.runRenderPipeline(renderPassFormat, renderObject, renderObjectCache); + this.runBindingResources(renderObject, renderObjectCache); + this.runVertexAttributes(renderObject, renderObjectCache); + this.runIndices(renderObject, renderObjectCache); + this.runDraw(renderObject, renderObjectCache); + + return renderObjectCache; + }); + renderObjectCacheMap.set(renderObjectCacheKey, result); + + return result.value; + } + + protected runviewport(renderObject: RenderObject, renderPassFormat: RenderPassFormat, renderObjectCache: RenderObjectCache) + { + const r_renderObject = reactive(renderObject); + const r_renderPassFormat = reactive(renderPassFormat); + computed(() => + { + const attachmentSize = r_renderPassFormat.attachmentSize; + const viewport = r_renderObject.viewport; + if (viewport) + { + const isYup = viewport.isYup ?? true; + const x = viewport.x ?? 0; + let y = viewport.y ?? 0; + const width = viewport.width; + const height = viewport.height; + const minDepth = viewport.minDepth ?? 0; + const maxDepth = viewport.maxDepth ?? 1; + + if (isYup) + { + y = attachmentSize.height - y - height; + } + // + renderObjectCache.push(["setViewport", x, y, width, height, minDepth, maxDepth]) + } + else + { + // + renderObjectCache.push(["setViewport", 0, 0, attachmentSize.width, attachmentSize.height, 0, 1]); + } + }).value; + } + + protected runScissorRect(renderObject: RenderObject, renderPassFormat: RenderPassFormat, renderObjectCache: RenderObjectCache) + { + const r_renderObject = reactive(renderObject); + const r_renderPassFormat = reactive(renderPassFormat); + computed(() => + { + const attachmentSize = r_renderPassFormat.attachmentSize; + const scissorRect = r_renderObject.scissorRect; + if (scissorRect) + { + const isYup = scissorRect.isYup ?? true; + const x = scissorRect.x ?? 0; + let y = scissorRect.y ?? 0; + const width = scissorRect.width; + const height = scissorRect.height; + + if (isYup) + { + y = attachmentSize.height - y - height; + } + + renderObjectCache.push(["setScissorRect", x, y, width, height]); + } + else + { + renderObjectCache.push(["setScissorRect", 0, 0, attachmentSize.width, attachmentSize.height]); + } + }).value; + } + + protected runRenderPipeline(renderPassFormat: RenderPassFormat, renderObject: RenderObject, renderObjectCache: RenderObjectCache) + { + const device = this._device; + const r_renderObject = reactive(renderObject); + computed(() => + { + // 监听 + r_renderObject.pipeline; + r_renderObject.vertices; + r_renderObject.indices; + + // + const { pipeline, vertices, indices } = renderObject; + // + const indexFormat: GPUIndexFormat = indices ? (indices.BYTES_PER_ELEMENT === 4 ? "uint32" : "uint16") : undefined; + const gpuRenderPipeline = getGPURenderPipeline(device, pipeline, renderPassFormat, vertices, indexFormat); + + // + renderObjectCache.push(["setPipeline", gpuRenderPipeline]); + + // + this.runStencilReference(pipeline, renderObjectCache); + this.runBlendConstant(pipeline, renderObjectCache); + }).value; + } + + protected runStencilReference(pipeline: RenderPipeline, renderObjectCache: RenderObjectCache) + { + const r_pipeline = reactive(pipeline); + computed(() => + { + const stencilReference = getStencilReference(r_pipeline.depthStencil); + if (stencilReference === undefined) + { + renderObjectCache.delete("setStencilReference"); + return; + } + + renderObjectCache.push(["setStencilReference", stencilReference]); + }).value; + } + + protected runBlendConstant(pipeline: RenderPipeline, renderObjectCache: RenderObjectCache) + { + const r_pipeline = reactive(pipeline); + computed(() => + { + // + const blendConstantColor = BlendState.getBlendConstantColor(r_pipeline.fragment?.targets?.[0]?.blend); + if (blendConstantColor === undefined) + { + renderObjectCache.delete("setBlendConstant"); + return; + } + + renderObjectCache.push(["setBlendConstant", blendConstantColor]); + }).value; + } + + protected runBindingResources(renderObject: RenderObject, renderObjectCache: RenderObjectCache) + { + const device = this._device; + const r_renderObject = reactive(renderObject); + computed(() => + { + // 监听 + r_renderObject.bindingResources; + + // 执行 + renderObjectCache.delete("setBindGroup"); + const { bindingResources } = renderObject; + const layout = getGPUPipelineLayout(device, { vertex: r_renderObject.pipeline.vertex.code, fragment: r_renderObject.pipeline.fragment?.code }); + layout.bindGroupLayouts.forEach((bindGroupLayout, group) => + { + const gpuBindGroup: GPUBindGroup = getGPUBindGroup(device, bindGroupLayout, bindingResources); + renderObjectCache.push(["setBindGroup", group, gpuBindGroup]); + }); + }).value; + } + + protected runVertexAttributes(renderObject: RenderObject, renderObjectCache: RenderObjectCache) + { + const device = this._device; + const r_renderObject = reactive(renderObject); + computed(() => + { + // 监听 + r_renderObject.vertices; + r_renderObject.pipeline.vertex; + + const { vertices, pipeline } = renderObject; + // + renderObjectCache.delete("setVertexBuffer"); + const vertexBuffers = getNVertexBuffers(pipeline.vertex, vertices) + vertexBuffers?.forEach((vertexBuffer, index) => + { + // 监听 + const r_vertexBuffer = reactive(vertexBuffer); + r_vertexBuffer.data; + r_vertexBuffer.offset; + r_vertexBuffer.size; + + // 执行 + const { data, offset, size } = vertexBuffer; + const buffer = getGBuffer(data); + (buffer as any).label = buffer.label || (`顶点属性 ${autoVertexIndex++}`); + + const gBuffer = getGPUBuffer(device, buffer); + + renderObjectCache.push(["setVertexBuffer", index, gBuffer, offset, size]); + }); + }).value; + } + + protected runIndices(renderObject: RenderObject, renderObjectCache: RenderObjectCache) + { + const r_renderObject = reactive(renderObject); + + computed(() => + { + // 监听 + r_renderObject.indices; + + const { indices } = renderObject; + if (!indices) + { + renderObjectCache.delete("setIndexBuffer"); + return; + } + + const device = this._device; + + const buffer = getGBuffer(indices); + (buffer as UnReadonly).label = buffer.label || (`顶点索引 ${autoIndex++}`); + + const gBuffer = getGPUBuffer(device, buffer); + + // + renderObjectCache.push(["setIndexBuffer", gBuffer, indices.BYTES_PER_ELEMENT === 4 ? "uint32" : "uint16", indices.byteOffset, indices.byteLength]); + + }).value; + } + + protected runDraw(renderObject: RenderObject, renderObjectCache: RenderObjectCache) + { + computed(() => + { + const { draw } = reactive(renderObject); + + renderObjectCache.delete("draw"); + renderObjectCache.delete("drawIndexed"); + if (draw.__type__ === 'DrawVertex') + { + renderObjectCache.push(["draw", draw.vertexCount, draw.instanceCount, draw.firstVertex, draw.firstInstance]); + } + else + { + renderObjectCache.push(["drawIndexed", draw.indexCount, draw.instanceCount, draw.firstIndex, draw.baseVertex, draw.firstInstance]); + } + }).value; + } + + private _renderPassCommandMap = new WeakMap(); + + private _renderObjectCachesMap = new ChainMap>(); + private _renderPassObjectCommandsMap = new ChainMap>(); +} + +let autoIndex = 0; +let autoVertexIndex = 0; + +/** + * 如果任意模板测试结果使用了 "replace" 运算,则需要再渲染前设置 `stencilReference` 值。 + * + * @param depthStencil + * @returns + */ +function getStencilReference(depthStencil?: DepthStencilState) +{ + if (!depthStencil) return undefined; + + const { stencilFront, stencilBack } = depthStencil; + + // 如果开启了模板测试,则需要设置模板索引值 + let stencilReference: number; + if (stencilFront) + { + const { failOp, depthFailOp, passOp } = stencilFront; + if (failOp === "replace" || depthFailOp === "replace" || passOp === "replace") + { + stencilReference = depthStencil?.stencilReference ?? 0; + } + } + if (stencilBack) + { + const { failOp, depthFailOp, passOp } = stencilBack; + if (failOp === "replace" || depthFailOp === "replace" || passOp === "replace") + { + stencilReference = depthStencil?.stencilReference ?? 0; + } + } + + return stencilReference; +} + +type GPURenderBundleKey = [renderBundle: RenderBundle, renderPassFormat: RenderPassFormat]; +const gpuRenderBundleMap = new ChainMap>(); + +type RenderObjectCacheKey = [device: GPUDevice, renderObject: RenderObject, renderPassFormat: RenderPassFormat]; +const renderObjectCacheMap = new ChainMap>(); + +type RenderObjectCachesKey = [renderObjects: readonly RenderObject[], renderPassFormat: RenderPassFormat]; +type RenderPassObjectCommandsKey = [renderPassObjects: readonly RenderPassObject[], renderPassFormat: RenderPassFormat]; \ No newline at end of file diff --git a/src/WebGPUStep.ts b/src/WebGPUStep.ts deleted file mode 100644 index e7735a0a13286feba6e7e3cd4c2637396d4a30e3..0000000000000000000000000000000000000000 --- a/src/WebGPUStep.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { ICopyBufferToBuffer, ICopyTextureToTexture, IRenderObject, IRenderPass, IRenderPassDescriptor, IRenderPassObject, ISubmit } from "@feng3d/render-api"; - -import { IGPUComputeObject } from "./data/IGPUComputeObject"; -import { IGPUComputePass } from "./data/IGPUComputePass"; -import { WebGPU } from "./WebGPU"; - -/** - * 按步骤来组织 IGPUSubmit 对象。 - * - * 不建议使用。 - */ -export class WebGPUStep -{ - private _currentSubmit: ISubmit; - private _currentRenderPassEncoder: IRenderPass; - private _currentComputePassEncoder: IGPUComputePass; - - readonly webGPU: WebGPU; - - constructor(webGPU: WebGPU) - { - this.webGPU = webGPU; - } - - renderPass(descriptor: IRenderPassDescriptor) - { - this._currentSubmit = this._currentSubmit || { commandEncoders: [{ passEncoders: [] }] }; - // - if (this._currentRenderPassEncoder?.descriptor === descriptor) return; - // - this._currentRenderPassEncoder = { descriptor, renderObjects: [] }; - this._currentComputePassEncoder = null; - this._currentSubmit.commandEncoders[0].passEncoders.push(this._currentRenderPassEncoder); - } - - renderObject(renderObject: IRenderObject) - { - (this._currentRenderPassEncoder.renderObjects as IRenderPassObject[]).push(renderObject); - } - - computePass() - { - this._currentSubmit = this._currentSubmit || { commandEncoders: [{ passEncoders: [] }] }; - // - this._currentRenderPassEncoder = null; - this._currentComputePassEncoder = { __type: "ComputePass", computeObjects: [] }; - this._currentSubmit.commandEncoders[0].passEncoders.push(this._currentComputePassEncoder); - } - - computeObject(computeObject: IGPUComputeObject) - { - this._currentComputePassEncoder.computeObjects.push(computeObject); - } - - copyTextureToTexture(copyTextureToTexture: ICopyTextureToTexture) - { - this._currentSubmit = this._currentSubmit || { commandEncoders: [{ passEncoders: [] }] }; - - this._currentRenderPassEncoder = null; - this._currentComputePassEncoder = null; - this._currentSubmit.commandEncoders[0].passEncoders.push(copyTextureToTexture); - } - - copyBufferToBuffer(copyBufferToBuffer: ICopyBufferToBuffer) - { - this._currentSubmit = this._currentSubmit || { commandEncoders: [{ passEncoders: [] }] }; - - this._currentRenderPassEncoder = null; - this._currentComputePassEncoder = null; - this._currentSubmit.commandEncoders[0].passEncoders.push(copyBufferToBuffer); - } - - /** - * 提交 GPU 。 - * - * @param submit 一次 GPU 提交内容。 - * - * @see GPUQueue.submit - */ - submit(submit?: ISubmit) - { - if (!submit) - { - if (!this._currentSubmit) return; - submit = this._currentSubmit; - this._currentSubmit = null; - this._currentRenderPassEncoder = null; - this._currentComputePassEncoder = null; - } - - this.webGPU.submit(submit); - } -} diff --git a/src/caches/getGPUBindGroup.ts b/src/caches/getGPUBindGroup.ts index 6d8a0d60c9492fdebd9410618bde96ed33759ac2..fb6235ae3b92f05a27de35c4c45b34232fc20f4a 100644 --- a/src/caches/getGPUBindGroup.ts +++ b/src/caches/getGPUBindGroup.ts @@ -1,126 +1,356 @@ -import { anyEmitter } from "@feng3d/event"; -import { IBufferBinding, ISampler, ITextureView } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { getRealGPUBindGroup } from "../const"; -import { IGPUCanvasTexture } from "../data/IGPUCanvasTexture"; -import { IGPUExternalTexture } from "../data/IGPUExternalTexture"; -import { GPUTextureView_destroy, IGPUSampler_changed } from "../eventnames"; -import { IGPUBindGroupDescriptor } from "../internal/IGPUBindGroupDescriptor"; -import { getGPUBindGroupLayout } from "./getGPUBindGroupLayout"; -import { getGPUBufferBinding } from "./getGPUBufferBinding"; +import { BindingResources, BufferBinding, BufferBindingInfo, ChainMap, computed, Computed, effect, reactive, Sampler, TextureView, UnReadonly } from "@feng3d/render-api"; +import { ArrayInfo, ResourceType, StructInfo, TemplateInfo, TypeInfo } from "wgsl_reflect"; +import { VideoTexture } from "../data/VideoTexture"; +import { webgpuEvents } from "../eventnames"; +import { ExternalSampledTextureType } from "../types/TextureType"; +import { getGPUBuffer } from "./getGPUBuffer"; import { getGPUSampler } from "./getGPUSampler"; import { getGPUTextureView } from "./getGPUTextureView"; +import { getGBuffer } from "./getIGPUBuffer"; -export function getGPUBindGroup(device: GPUDevice, bindGroup: IGPUBindGroupDescriptor) +export function getGPUBindGroup(device: GPUDevice, bindGroupLayout: GPUBindGroupLayout, bindingResources: BindingResources) { - const bindGroupMap: WeakMap = device["_bindGroupMap"] = device["_bindGroupMap"] || new WeakMap(); + const getGPUBindGroupKey: GetGPUBindGroupKey = [bindGroupLayout, bindingResources]; + let result = getGPUBindGroupMap.get(getGPUBindGroupKey); + if (result) return result.value; - let gBindGroup = bindGroupMap.get(bindGroup); - if (gBindGroup) return gBindGroup; + let gBindGroup: GPUBindGroup; + let numberBufferBinding: { [name: string]: number[] } = {}; + result = computed(() => + { + const entries = bindGroupLayout.entries.map((v) => + { + const { name, type, resourceType, binding } = v.variableInfo; + + // 监听 + const r_bindingResources = reactive(bindingResources); + r_bindingResources[name]; + + // 执行 + const entry: GPUBindGroupEntry = { binding, resource: null }; + + // + if (resourceType === ResourceType.Uniform || resourceType === ResourceType.Storage) + { + // 执行 + let resource = bindingResources[name]; + // 当值为number时,将其视为一个数组。 + if (typeof resource === "number") + { + numberBufferBinding[name] ??= []; + numberBufferBinding[name][0] = resource; + resource = numberBufferBinding[name]; + } + const bufferBinding = resource as BufferBinding; // 值为number且不断改变时将可能会产生无数细碎gpu缓冲区。 + entry.resource = getGPUBufferBinding(device, bufferBinding, type); + } + else if (ExternalSampledTextureType[type.name]) // 判断是否为外部纹理 + { + entry.resource = getGPUExternalTexture(device, bindingResources[name] as VideoTexture); + } + else if (resourceType === ResourceType.Texture || resourceType === ResourceType.StorageTexture) + { + entry.resource = getGPUTextureView(device, bindingResources[name] as TextureView); + } + else + { + entry.resource = getGPUSampler(device, bindingResources[name] as Sampler); + } + + return entry; + }); + + // + const resources = entries.map((v) => v.resource); + const gpuBindGroupKey: GPUBindGroupKey = [bindGroupLayout, ...resources]; + const cache = gpuBindGroupMap.get(gpuBindGroupKey); + if (cache) return cache; + + gBindGroup = device.createBindGroup({ layout: bindGroupLayout, entries }); + + gpuBindGroupMap.set(gpuBindGroupKey, gBindGroup); + + return gBindGroup; + }); + getGPUBindGroupMap.set(getGPUBindGroupKey, result); + + return result.value; +} - // 总是更新函数列表。 - const awaysUpdateFuncs: (() => void)[] = []; - // 执行一次函数列表 - const onceUpdateFuncs: (() => void)[] = []; +type GPUBindGroupKey = [bindGroupLayout: GPUBindGroupLayout, ...resources: GPUBindingResource[]]; +const gpuBindGroupMap = new ChainMap(); - const layout = getGPUBindGroupLayout(device, bindGroup.layout); +type GetGPUBindGroupKey = [bindGroupLayout: GPUBindGroupLayout, bindingResources: BindingResources]; +const getGPUBindGroupMap = new ChainMap>(); - const entries = bindGroup.entries.map((v) => +function getGPUBufferBinding(device: GPUDevice, bufferBinding: BufferBinding, type: TypeInfo) +{ + const getGPUBindingResourceKey: GetGPUBindingResourceKey = [device, bufferBinding, type]; + let result = getGPUBindingResourceMap.get(getGPUBindingResourceKey); + if (result) return result.value; + + result = computed(() => { - const entry: GPUBindGroupEntry = { binding: v.binding, resource: null }; + // 监听 + const r_bufferBinding = reactive(bufferBinding); + r_bufferBinding?.bufferView; - // 更新资源函数。 - let updateResource: () => void; + // 更新缓冲区绑定的数据。 + updateBufferBinding(bufferBinding, type); + const bufferView = bufferBinding.bufferView; + // + const gbuffer = getGBuffer(bufferView); + (gbuffer as any).label = gbuffer.label || (`BufferBinding ${type.name}`); + // + const buffer = getGPUBuffer(device, gbuffer); - // 资源变化后更新函数。 - const onResourceChanged = () => - { - onceUpdateFuncs.push(updateResource); + const offset = bufferView.byteOffset; + const size = bufferView.byteLength; - if (gBindGroup[getRealGPUBindGroup] !== getReal) - { - gBindGroup[getRealGPUBindGroup] = createBindGroup; - } + const gpuBufferBinding: GPUBufferBinding = { + buffer, + offset, + size, }; + const gpuBufferBindingKey: GPUBufferBindingKey = [buffer, offset, size]; + const cache = gpuBufferBindingMap.get(gpuBufferBindingKey); + if (cache) return cache; + gpuBufferBindingMap.set(gpuBufferBindingKey, gpuBufferBinding); - // - if ((v.resource as IBufferBinding).bufferView) - { - updateResource = () => - { - entry.resource = getGPUBufferBinding(device, v.resource as IBufferBinding); - }; - } - else if ((v.resource as ITextureView).texture) - { - updateResource = () => - { - entry.resource = getGPUTextureView(device, v.resource as ITextureView); + return gpuBufferBinding; + }); - anyEmitter.once(entry.resource, GPUTextureView_destroy, onResourceChanged); - }; + getGPUBindingResourceMap.set(getGPUBindingResourceKey, result); - if (((v.resource as ITextureView).texture as IGPUCanvasTexture).context) - { - awaysUpdateFuncs.push(updateResource); - } - } - else if ((v.resource as IGPUExternalTexture).source) - { - updateResource = () => - { - entry.resource = device.importExternalTexture(v.resource as IGPUExternalTexture); - }; + return result.value; +} +type GPUBufferBindingKey = [buffer: GPUBuffer, offset: number, size: number]; +const gpuBufferBindingMap = new ChainMap(); - awaysUpdateFuncs.push(updateResource); - } - else - { - updateResource = () => - { - entry.resource = getGPUSampler(device, v.resource as ISampler); - anyEmitter.once(v.resource as ISampler, IGPUSampler_changed, onResourceChanged); - }; - } +type GetGPUBindingResourceKey = [device: GPUDevice, bufferBinding: BufferBinding, type: TypeInfo]; +const getGPUBindingResourceMap = new ChainMap>(); + +function getGPUExternalTexture(device: GPUDevice, videoTexture: VideoTexture) +{ + const getGPUExternalTextureKey: GetGPUExternalTextureKey = [device, videoTexture]; + let result = getGPUExternalTextureMap.get(getGPUExternalTextureKey); + if (result) return result.value; - updateResource(); + result = computed(() => + { + // 在提交前确保收集到正确的外部纹理。 + reactive(webgpuEvents).preSubmit; - // 监听绑定资源发生改变 - watcher.watch(v, "resource", onResourceChanged); + // + const resource = device.importExternalTexture(videoTexture); - return entry; + return resource; }); + getGPUExternalTextureMap.set(getGPUExternalTextureKey, result); - const getReal = () => + return result.value; +} +type GetGPUExternalTextureKey = [device: GPUDevice, videoTexture: VideoTexture]; +const getGPUExternalTextureMap = new ChainMap>(); + +/** + * 初始化缓冲区绑定。 + * + * @param variableInfo + * @param uniformData + * @returns + */ +export function updateBufferBinding(uniformData: BufferBinding, type: TypeInfo) +{ + const bufferBindingInfo = getBufferBindingInfo(type); + + const size = bufferBindingInfo.size; + // 是否存在默认值。 + const hasDefautValue = !!uniformData.bufferView; + if (!hasDefautValue) { - awaysUpdateFuncs.forEach((v) => v()); - createBindGroup(); + (uniformData as UnReadonly).bufferView = new Uint8Array(size); + } - return gBindGroup; - }; + const buffer = getGBuffer(uniformData.bufferView); + const offset = uniformData.bufferView.byteOffset; - const createBindGroup = () => + for (let i = 0; i < bufferBindingInfo.items.length; i++) { - onceUpdateFuncs.forEach((v) => v()); + const { paths, offset: itemInfoOffset, size: itemInfoSize, Cls } = bufferBindingInfo.items[i]; + // 更新数据 + effect(() => + { + // 监听 + let value: any = reactive(uniformData); + for (let i = 0; i < paths.length; i++) + { + value = value[paths[i]]; + if (value === undefined) + { + if (!hasDefautValue) + { + console.warn(`没有找到 统一块变量属性 ${paths.join(".")} 的值!`); + } - gBindGroup = device.createBindGroup({ layout, entries }); + return; + } + } - bindGroupMap.set(bindGroup, gBindGroup); + // 更新数据 + let data: Float32Array | Int32Array | Uint32Array | Int16Array; + if (typeof value === "number") + { + data = new Cls([value]); + } + else if (value.constructor.name !== Cls.name) + { + data = new Cls(value as ArrayLike); + } + else + { + data = value as any; + } + + const writeBuffers = buffer.writeBuffers ?? []; + writeBuffers.push({ bufferOffset: offset + itemInfoOffset, data: data.buffer, dataOffset: data.byteOffset, size: Math.min(itemInfoSize, data.byteLength) }); + reactive(buffer).writeBuffers = writeBuffers; + }); + } +} - // 设置更新外部纹理/画布纹理视图 - if (awaysUpdateFuncs.length > 0) +/** + * 获取缓冲区绑定信息。 + * + * @param type 类型信息。 + * @returns + */ +function getBufferBindingInfo(type: TypeInfo) +{ + let result = bufferBindingInfoMap.get(type); + if (result) return result; + result = _getBufferBindingInfo(type); + + bufferBindingInfoMap.set(type, result); + return result; +} +const bufferBindingInfoMap = new Map(); + +/** + * 获取缓冲区绑定信息。 + * + * @param type 类型信息。 + * @param paths 当前路径。 + * @param offset 当前编译。 + * @param bufferBindingInfo 缓冲区绑定信息。 + * @returns + */ +function _getBufferBindingInfo(type: TypeInfo, paths: string[] = [], offset = 0, bufferBindingInfo: BufferBindingInfo = { size: type.size, items: [] }) +{ + if (type.isStruct) + { + const structInfo = type as StructInfo; + for (let i = 0; i < structInfo.members.length; i++) { - gBindGroup[getRealGPUBindGroup] = getReal; + const memberInfo = structInfo.members[i]; + _getBufferBindingInfo(memberInfo.type, paths.concat(memberInfo.name), offset + memberInfo.offset, bufferBindingInfo); } - else + } + else if (type.isArray) + { + const arrayInfo = type as ArrayInfo; + for (let i = 0; i < arrayInfo.count; i++) { - gBindGroup[getRealGPUBindGroup] = () => gBindGroup; + _getBufferBindingInfo(arrayInfo.format, paths.concat(`${i}`), offset + i * arrayInfo.format.size, bufferBindingInfo); } + } + else if (type.isTemplate) + { + const templateInfo = type as TemplateInfo; + const templateFormatName = templateInfo.format?.name; + bufferBindingInfo.items.push({ + paths: paths.concat(), + offset, + size: templateInfo.size, + Cls: getTemplateDataCls(templateFormatName as any), + }); + } + else + { + bufferBindingInfo.items.push({ + paths: paths.concat(), + offset, + size: type.size, + Cls: getBaseTypeDataCls(type.name), + }); + } - return gBindGroup; - }; + return bufferBindingInfo; +} - createBindGroup(); +function getBaseTypeDataCls(baseTypeName: string) +{ + const dataCls = baseTypeDataCls[baseTypeName]; + + console.assert(!!dataCls, `baseTypeName必须为以下值 ${Object.keys(baseTypeDataCls)}`); + + return dataCls; +} + +/** + * @see https://gpuweb.github.io/gpuweb/wgsl/#vec2i + */ +const baseTypeDataCls: { [key: string]: DataCls } = { + i32: Int32Array, + u32: Uint32Array, + f32: Float32Array, + f16: Int16Array, + vec2i: Int32Array, + vec3i: Int32Array, + vec4i: Int32Array, + vec2u: Uint32Array, + vec3u: Uint32Array, + vec4u: Uint32Array, + vec2f: Float32Array, + vec3f: Float32Array, + vec4f: Float32Array, + vec2h: Int16Array, + vec3h: Int16Array, + vec4h: Int16Array, + mat2x2f: Float32Array, + mat2x3f: Float32Array, + mat2x4f: Float32Array, + mat3x2f: Float32Array, + mat3x3f: Float32Array, + mat3x4f: Float32Array, + mat4x2f: Float32Array, + mat4x3f: Float32Array, + mat4x4f: Float32Array, + mat2x2h: Float32Array, + mat2x3h: Float32Array, + mat2x4h: Float32Array, + mat3x2h: Float32Array, + mat3x3h: Float32Array, + mat3x4h: Float32Array, + mat4x2h: Float32Array, + mat4x3h: Float32Array, + mat4x4h: Float32Array, +}; - return gBindGroup; +function getTemplateDataCls(templateFormatName: "i32" | "u32" | "f32" | "f16") +{ + const dataCls = templateFormatDataCls[templateFormatName]; + + console.assert(!!dataCls, `templateFormatName必须为以下值 ${Object.keys(templateFormatDataCls)}`); + + return dataCls; } +const templateFormatDataCls: { [key: string]: DataCls } = { + i32: Int32Array, + u32: Uint32Array, + f32: Float32Array, + f16: Int16Array, +}; + +type DataCls = Float32ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Int16ArrayConstructor; + diff --git a/src/caches/getGPUBindGroupLayout.ts b/src/caches/getGPUBindGroupLayout.ts deleted file mode 100644 index 783017ce9849b94deb07e3bc067b77a1bb53ed2d..0000000000000000000000000000000000000000 --- a/src/caches/getGPUBindGroupLayout.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function getGPUBindGroupLayout(device: GPUDevice, layout: GPUBindGroupLayoutDescriptor) -{ - let gpuBindGroupLayout = bindGroupLayoutMap.get(layout); - - if (gpuBindGroupLayout) return gpuBindGroupLayout; - - // - gpuBindGroupLayout = device.createBindGroupLayout(layout); - - bindGroupLayoutMap.set(layout, gpuBindGroupLayout); - - return gpuBindGroupLayout; -} - -const bindGroupLayoutMap = new WeakMap(); diff --git a/src/caches/getGPUBuffer.ts b/src/caches/getGPUBuffer.ts index 3b9904ffcefa0f9f799b204afc88de5f3a594d49..d8d1ab2271acd3fe165b06f25ba16f63e264c925 100644 --- a/src/caches/getGPUBuffer.ts +++ b/src/caches/getGPUBuffer.ts @@ -1,5 +1,4 @@ -import { IBuffer, UnReadonly } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; +import { ChainMap, computed, Computed, Buffer, reactive, effect } from "@feng3d/render-api"; /** * 除了GPU与CPU数据交换的`MAP_READ`与`MAP_WRITE`除外。 @@ -24,121 +23,135 @@ const defaultGPUBufferUsage = 0 * @param buffer * @returns */ -export function getGPUBuffer(device: GPUDevice, buffer: IBuffer) +export function getGPUBuffer(device: GPUDevice, buffer: Buffer) { - const gBufferMap: WeakMap = device["_gBufferMap"] = device["_gBufferMap"] || new WeakMap(); + const getGPUBufferKey: GetGPUBufferKey = [device, buffer]; + let result = getGPUBufferMap.get(getGPUBufferKey); + if (result) return result.value; - let gBuffer: GPUBuffer = gBufferMap.get(buffer); - if (gBuffer) return gBuffer; - - const size = buffer.size; - console.assert(size && (size % 4 === 0), `初始化缓冲区时必须设置缓冲区尺寸且必须为4的倍数!`); - - (buffer as UnReadonly).usage = buffer.usage ?? defaultGPUBufferUsage; + let gpuBuffer: GPUBuffer; + result = computed(() => + { + // 监听 + const r_buffer = reactive(buffer); + r_buffer.size; + r_buffer.usage; - const label = buffer.label; - const usage = buffer.usage; + // 执行 + const { label, size, usage } = buffer; + console.assert(size && (size % 4 === 0), `初始化缓冲区时必须设置缓冲区尺寸且必须为4的倍数!`); - // 初始化时存在数据,则使用map方式上传第一次数据。 - const mappedAtCreation = buffer.data !== undefined; + // 初始化时存在数据,则使用map方式上传第一次数据。 + const mappedAtCreation = buffer.data !== undefined; - gBuffer = device.createBuffer({ label, size, usage, mappedAtCreation }); + // 销毁旧的缓冲区 + if (gpuBuffer) gpuBuffer.destroy(); + gpuBuffer = device.createBuffer({ label, size, usage: usage ?? defaultGPUBufferUsage, mappedAtCreation }); - if (mappedAtCreation) - { - const bufferData = buffer.data; - if (ArrayBuffer.isView(bufferData)) + // 初始化时存在数据,则使用map方式上传第一次数据。 + if (mappedAtCreation) { - new Int8Array(gBuffer.getMappedRange()).set(new Int8Array(bufferData.buffer)); - } - else - { - new Int8Array(gBuffer.getMappedRange()).set(new Int8Array(bufferData)); + const bufferData = buffer.data; + if (ArrayBuffer.isView(bufferData)) + { + new Int8Array(gpuBuffer.getMappedRange()).set(new Int8Array(bufferData.buffer)); + } + else + { + new Int8Array(gpuBuffer.getMappedRange()).set(new Int8Array(bufferData)); + } + + gpuBuffer.unmap(); } - gBuffer.unmap(); - } + // 更新数据 + dataChange(buffer); - const writeBuffer = () => - { - // 处理数据写入GPU缓冲 - if (buffer.writeBuffers) - { - buffer.writeBuffers.forEach((v) => - { - const bufferData = v; - - let bufferOffset = 0; - let dataOffset = 0; - bufferOffset = bufferData.bufferOffset ?? bufferOffset; - const data = bufferData.data; - dataOffset = bufferData.dataOffset ?? dataOffset; - const size = bufferData.size; - - let arrayBuffer: ArrayBuffer; - let dataOffsetByte: number; - let sizeByte: number; - if (ArrayBuffer.isView(data)) - { - const bytesPerElement = (data as Uint8Array).BYTES_PER_ELEMENT; - - arrayBuffer = data.buffer; - dataOffsetByte = data.byteOffset + bytesPerElement * dataOffset; - sizeByte = size ? (bytesPerElement * size) : data.byteLength; - } - else - { - arrayBuffer = data; - dataOffsetByte = dataOffset ?? 0; - sizeByte = size ?? (data.byteLength - dataOffsetByte); - } - - // 防止给出数据不够的情况 - console.assert(sizeByte <= arrayBuffer.byteLength - dataOffsetByte, `上传的尺寸超出数据范围!`); - - console.assert(sizeByte % 4 === 0, `写入数据长度不是4的倍数!`); - - // - device.queue.writeBuffer( - gBuffer, - bufferOffset, - arrayBuffer, - dataOffsetByte, - sizeByte, - ); - }); - buffer.writeBuffers = null; - } - }; - writeBuffer(); + // 写入数据 + writeBuffer(device, buffer, gpuBuffer); - watcher.watch(buffer, "writeBuffers", writeBuffer); + return gpuBuffer; + }); + getGPUBufferMap.set(getGPUBufferKey, result); - const dataChange = () => + return result.value; +} +type GetGPUBufferKey = [device: GPUDevice, buffer: Buffer]; +const getGPUBufferMap = new ChainMap>; + +function dataChange(buffer: Buffer) +{ + let isInitData = true; + computed(() => { + // 监听数据变化 + const rb = reactive(buffer); + rb.data; + + // 第一次初始存在数据,则不再处理。 + if (isInitData) { isInitData = false; return } + + // 处理数据写入GPU缓冲 + const { data } = buffer; const writeBuffers = buffer.writeBuffers || []; - writeBuffers.push({ data: buffer.data }); - buffer.writeBuffers = writeBuffers; - }; + writeBuffers.push({ data }); - watcher.watch(buffer, "data", dataChange); + // 触发下次写入数据 + rb.writeBuffers = writeBuffers; + }).value; +}; - // - ((oldDestroy) => +function writeBuffer(device: GPUDevice, buffer: Buffer, gBuffer: GPUBuffer) +{ + return effect(() => { - gBuffer.destroy = () => + // 监听 + const rb = reactive(buffer); + rb.writeBuffers?.forEach(() => { }); + + // 处理数据写入GPU缓冲 + if (!buffer.writeBuffers) return; + buffer.writeBuffers.forEach((writeBuffer) => { - oldDestroy.apply(gBuffer); + const bufferOffset = writeBuffer.bufferOffset ?? 0; + const data = writeBuffer.data; + const dataOffset = writeBuffer.dataOffset ?? 0; + const size = writeBuffer.size; + + let arrayBuffer: ArrayBuffer; + let dataOffsetByte: number; + let sizeByte: number; + if (ArrayBuffer.isView(data)) + { + const bytesPerElement = (data as Uint8Array).BYTES_PER_ELEMENT; - gBufferMap.delete(buffer); + arrayBuffer = data.buffer; + dataOffsetByte = data.byteOffset + bytesPerElement * dataOffset; + sizeByte = size ? (bytesPerElement * size) : data.byteLength; + } + else + { + arrayBuffer = data; + dataOffsetByte = dataOffset ?? 0; + sizeByte = size ?? (data.byteLength - dataOffsetByte); + } - // - watcher.unwatch(buffer, "writeBuffers", writeBuffer); - watcher.unwatch(buffer, "data", dataChange); - }; - })(gBuffer.destroy); + // 防止给出数据不够的情况 + console.assert(sizeByte <= arrayBuffer.byteLength - dataOffsetByte, `上传的尺寸超出数据范围!`); - gBufferMap.set(buffer, gBuffer); + console.assert(sizeByte % 4 === 0, `写入数据长度不是4的倍数!`); - return gBuffer; -} + // + device.queue.writeBuffer( + gBuffer, + bufferOffset, + arrayBuffer, + dataOffsetByte, + sizeByte, + ); + }); + + // 清空写入数据 + rb.writeBuffers = null; + }); +}; \ No newline at end of file diff --git a/src/caches/getGPUBufferBinding.ts b/src/caches/getGPUBufferBinding.ts deleted file mode 100644 index 02e368d626dbeff2e2a292aee0dd08768d573174..0000000000000000000000000000000000000000 --- a/src/caches/getGPUBufferBinding.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IBufferBinding } from "@feng3d/render-api"; -import { getGPUBuffer } from "./getGPUBuffer"; -import { getIGPUBuffer } from "./getIGPUBuffer"; - -export function getGPUBufferBinding(device: GPUDevice, resource: IBufferBinding): GPUBufferBinding -{ - const b = getIGPUBuffer(resource.bufferView); - const buffer = getGPUBuffer(device, b); - - const offset = resource.bufferView.byteOffset; - const size = resource.bufferView.byteLength; - - return { - buffer, - offset, - size, - }; -} diff --git a/src/caches/getGPUCanvasContext.ts b/src/caches/getGPUCanvasContext.ts index 8c08e37ef2aa0b859a45d618a9380bd686392e1e..2c2a2d9bbdbd17957cb815630bf54fa725948c76 100644 --- a/src/caches/getGPUCanvasContext.ts +++ b/src/caches/getGPUCanvasContext.ts @@ -1,47 +1,62 @@ -import { watcher } from "@feng3d/watcher"; -import { IGPUCanvasContext } from "../data/IGPUCanvasContext"; +import { CanvasContext, ChainMap } from "@feng3d/render-api"; +import { computed, Computed, reactive } from "@feng3d/reactivity"; +import "../data/polyfills/CanvasContext"; -export function getGPUCanvasContext(device: GPUDevice, context: IGPUCanvasContext) +export function getGPUCanvasContext(device: GPUDevice, context: CanvasContext) { - let gpuCanvasContext = canvasContextMap[context.canvasId]; - if (gpuCanvasContext) return gpuCanvasContext; + const getGPUCanvasContextKey: GetGPUCanvasContextKey = [device, context]; + let result = getGPUCanvasContextMap.get(getGPUCanvasContextKey); + if (result) return result.value; - const canvas = document.getElementById(context.canvasId) as HTMLCanvasElement; - - gpuCanvasContext = canvas.getContext("webgpu"); - - canvasContextMap[context.canvasId] = gpuCanvasContext; - - const updateConfigure = () => + result = computed(() => { - // - context.configuration = context.configuration || {}; - const format = (context.configuration as any).format = context.configuration.format || navigator.gpu.getPreferredCanvasFormat(); + // 监听 + const ro = reactive(context); + ro.canvasId; - let usage = 0; + const canvas = typeof context.canvasId === "string" ? document.getElementById(context.canvasId) as HTMLCanvasElement : context.canvasId; - if (context.configuration.usage) + const gpuCanvasContext = canvas.getContext("webgpu") as GPUCanvasContext; + + // 监听 + const r_configuration = ro.configuration; + if (r_configuration) { - usage = context.configuration.usage; + r_configuration.format; + r_configuration.usage; + r_configuration.viewFormats?.forEach(() => { }); + r_configuration.colorSpace; + r_configuration.toneMapping?.mode; + r_configuration.alphaMode; } + // 执行 + const configuration = context.configuration || {}; + + const format = configuration.format || navigator.gpu.getPreferredCanvasFormat(); + // 附加上 GPUTextureUsage.RENDER_ATTACHMENT - usage = usage | GPUTextureUsage.RENDER_ATTACHMENT; + const usage = (configuration.usage ?? 0) + | GPUTextureUsage.COPY_SRC + | GPUTextureUsage.COPY_DST + | GPUTextureUsage.TEXTURE_BINDING + | GPUTextureUsage.STORAGE_BINDING + | GPUTextureUsage.RENDER_ATTACHMENT; // gpuCanvasContext.configure({ - ...context.configuration, + ...configuration, device, usage, format, }); - }; - updateConfigure(); + return gpuCanvasContext; + }); - watcher.watchobject(context, { configuration: { usage: undefined, format: undefined, colorSpace: undefined, toneMapping: { mode: undefined }, alphaMode: undefined } }, updateConfigure); + getGPUCanvasContextMap.set(getGPUCanvasContextKey, result); - return gpuCanvasContext; + return result.value; } - -const canvasContextMap: { [canvasId: string]: GPUCanvasContext } = {}; +type GetGPUCanvasContextKey = [device: GPUDevice, context: CanvasContext]; +const getGPUCanvasContextMap = new ChainMap>; \ No newline at end of file diff --git a/src/caches/getGPUComputePassDescriptor.ts b/src/caches/getGPUComputePassDescriptor.ts new file mode 100644 index 0000000000000000000000000000000000000000..73de52394edddf81deea269cf4a60745ebe35c8e --- /dev/null +++ b/src/caches/getGPUComputePassDescriptor.ts @@ -0,0 +1,14 @@ +import { ComputePass } from "../data/ComputePass"; +import { getGPUPassTimestampWrites } from "./getGPUPassTimestampWrites"; + +export function getGPUComputePassDescriptor(device: GPUDevice, computePass: ComputePass) +{ + const descriptor: GPUComputePassDescriptor = {}; + + if (computePass.descriptor?.timestampQuery) + { + descriptor.timestampWrites = getGPUPassTimestampWrites(device, computePass.descriptor.timestampQuery); + } + + return descriptor; +} diff --git a/src/caches/getGPUComputePipeline.ts b/src/caches/getGPUComputePipeline.ts index c7a300452be8f16a76a8743759331fb561ad3d1b..e7647196342adae8d2938b05938f8dc9915ded3f 100644 --- a/src/caches/getGPUComputePipeline.ts +++ b/src/caches/getGPUComputePipeline.ts @@ -1,29 +1,46 @@ -import { IGPUComputePipeline } from "../data/IGPUComputePipeline"; +import { ChainMap, UnReadonly } from "@feng3d/render-api"; +import { ComputePipeline } from "../data/ComputePipeline"; +import { ComputeStage } from "../data/ComputeStage"; import { getGPUPipelineLayout } from "./getGPUPipelineLayout"; import { getGPUShaderModule } from "./getGPUShaderModule"; -import { getIGPUPipelineLayout } from "./getIGPUPipelineLayout"; +import { getWGSLReflectInfo } from "./getWGSLReflectInfo"; -export function getGPUComputePipeline(device: GPUDevice, computePipeline: IGPUComputePipeline) +export function getGPUComputePipeline(device: GPUDevice, computePipeline: ComputePipeline) { - const computePipelineMap: WeakMap = device["_computePipelineMap"] = device["_computePipelineMap"] || new WeakMap(); - - let pipeline = computePipelineMap.get(computePipeline); + const getGPUComputePipelineKey: GetGPUComputePipeline = [device, computePipeline]; + let pipeline = _computePipelineMap.get(getGPUComputePipelineKey); if (pipeline) return pipeline; - // 从GPU管线中获取管线布局。 - const gpuPipelineLayout = getIGPUPipelineLayout({ compute: computePipeline.compute.code }); + const computeStage = computePipeline.compute; - const layout = getGPUPipelineLayout(device, gpuPipelineLayout); + const reflect = getWGSLReflectInfo(computeStage.code); + if (!computeStage.entryPoint) + { + const compute = reflect.entry.compute[0]; + console.assert(!!compute, `WGSL着色器 ${computeStage.code} 中不存在计算入口点。`); + (computeStage as UnReadonly).entryPoint = compute.name; + } + else + { + // 验证着色器中包含指定片段入口函数。 + const compute = reflect.entry.compute.filter((v) => v.name === computeStage.entryPoint)[0]; + console.assert(!!compute, `WGSL着色器 ${computeStage.code} 中不存在指定的计算入口点 ${computeStage.entryPoint}`); + } + + // 从GPU管线中获取管线布局。 + const layout = getGPUPipelineLayout(device, { compute: computePipeline.compute.code }); pipeline = device.createComputePipeline({ layout, compute: { - ...computePipeline.compute, - module: getGPUShaderModule(device, computePipeline.compute.code), + ...computeStage, + module: getGPUShaderModule(device, computeStage.code), }, }); - computePipelineMap.set(computePipeline, pipeline); + _computePipelineMap.set(getGPUComputePipelineKey, pipeline); return pipeline; } +type GetGPUComputePipeline = [device: GPUDevice, computePipeline: ComputePipeline]; +const _computePipelineMap = new ChainMap; \ No newline at end of file diff --git a/src/caches/getGPURenderTimestampQuery.ts b/src/caches/getGPUPassTimestampWrites.ts similarity index 60% rename from src/caches/getGPURenderTimestampQuery.ts rename to src/caches/getGPUPassTimestampWrites.ts index 04bed5a72f048344f616b5ac82c6dafb7c7b21ad..1cac7865b9083d25420c5d4876aa11d54f4e7155 100644 --- a/src/caches/getGPURenderTimestampQuery.ts +++ b/src/caches/getGPUPassTimestampWrites.ts @@ -1,25 +1,42 @@ import { anyEmitter } from "@feng3d/event"; -import { IRenderPass } from "@feng3d/render-api"; -import { IGPUComputePass } from "../data/IGPUComputePass"; -import { IGPUTimestampQuery } from "../data/IGPUTimestampQuery"; +import { ChainMap } from "@feng3d/render-api"; +import { TimestampQuery } from "../data/TimestampQuery"; import { GPUQueue_submit } from "../eventnames"; -export function getGPURenderTimestampQuery(device: GPUDevice, timestampQuery?: IGPUTimestampQuery): GPURenderTimestampQuery +declare global { - if (!timestampQuery) return defautGPURenderTimestampQuery; - let renderTimestampQuery: GPURenderTimestampQuery = timestampQuery["_GPURenderTimestampQuery"]; - if (renderTimestampQuery) return renderTimestampQuery; + interface GPURenderPassTimestampWrites + { + resolve?: (commandEncoder: GPUCommandEncoder) => void; + } + interface GPUComputePassTimestampWrites + { + resolve?: (commandEncoder: GPUCommandEncoder) => void; + } +} + +export function getGPUPassTimestampWrites(device: GPUDevice, timestampQuery?: TimestampQuery) +{ + if (!timestampQuery) return; + + const getGPUPassTimestampWritesKey: GetGPUPassTimestampWritesKey = [device, timestampQuery]; + let timestampWrites = getGPUPassTimestampWritesMap.get(getGPUPassTimestampWritesKey); + if (timestampWrites) return timestampWrites; // 判断是否支持 `timestamp-query` - timestampQuery.isSupports = device.features.has(`timestamp-query`); - if (!timestampQuery.isSupports) + if (timestampQuery["isSupports"] === undefined) + { + timestampQuery["isSupports"] = device.features.has(`timestamp-query`); + timestampQuery.onSupports?.(timestampQuery["isSupports"]); + } + if (!timestampQuery["isSupports"]) { console.warn(`WebGPU未开启或者不支持 timestamp-query 特性,请确认 WebGPU.init 初始化参数是否正确!`); - return defautGPURenderTimestampQuery; + return; } - let querySet: GPUQuerySet; + const querySet = device.createQuerySet({ type: "timestamp", count: 2 }); // Create a buffer where to store the result of GPU queries const timestampByteSize = 8; // timestamps are uint64 @@ -28,26 +45,13 @@ export function getGPURenderTimestampQuery(device: GPUDevice, timestampQuery?: I // Create a buffer to map the result back to the CPU let resultBuf: GPUBuffer; - /** - * 初始化。 - * - * 在渲染通道描述上设置 occlusionQuerySet 。 - * - * @param device - * @param passDescriptor - */ - const init = (device: GPUDevice, passDescriptor: GPURenderPassDescriptor | GPUComputePassDescriptor) => - { - querySet = querySet || device.createQuerySet({ type: "timestamp", count: 2 }); - - passDescriptor.timestampWrites = { - querySet, - beginningOfPassWriteIndex: 0, - endOfPassWriteIndex: 1, - }; + timestampWrites = { + querySet, + beginningOfPassWriteIndex: 0, + endOfPassWriteIndex: 1, }; - const resolve = (device: GPUDevice, commandEncoder: GPUCommandEncoder) => + timestampWrites.resolve = (commandEncoder: GPUCommandEncoder) => { resolveBuf = resolveBuf || device.createBuffer({ size: 2 * timestampByteSize, @@ -98,7 +102,7 @@ export function getGPURenderTimestampQuery(device: GPUDevice, timestampQuery?: I // (see spec https://gpuweb.github.io/gpuweb/#timestamp) if (elapsedNs >= 0) { - timestampQuery.elapsedNs = elapsedNs; + timestampQuery.onQuery(elapsedNs); } resultBuf.unmap(); @@ -113,15 +117,10 @@ export function getGPURenderTimestampQuery(device: GPUDevice, timestampQuery?: I anyEmitter.on(device.queue, GPUQueue_submit, getQueryResult); }; - timestampQuery["_GPURenderTimestampQuery"] = renderTimestampQuery = { init, resolve }; - - return renderTimestampQuery; -} + getGPUPassTimestampWritesMap.set(getGPUPassTimestampWritesKey, timestampWrites); -interface GPURenderTimestampQuery -{ - init: (device: GPUDevice, passDescriptor: GPURenderPassDescriptor | GPUComputePassDescriptor) => void - resolve: (device: GPUDevice, commandEncoder: GPUCommandEncoder, renderPass: IRenderPass | IGPUComputePass) => void + return timestampWrites; } -const defautGPURenderTimestampQuery = { init: () => { }, resolve: () => { } }; \ No newline at end of file +type GetGPUPassTimestampWritesKey = [GPUDevice, TimestampQuery]; +const getGPUPassTimestampWritesMap = new ChainMap(); \ No newline at end of file diff --git a/src/caches/getGPUPipelineLayout.ts b/src/caches/getGPUPipelineLayout.ts index 5157124be6adbb4eb4c82ab56d5a48c6999bb50a..ce86cc3e70d2b49df32570774f8900022463ed5d 100644 --- a/src/caches/getGPUPipelineLayout.ts +++ b/src/caches/getGPUPipelineLayout.ts @@ -1,17 +1,146 @@ -import { IGPUPipelineLayoutDescriptor } from "../internal/IGPUPipelineLayoutDescriptor"; -import { getGPUBindGroupLayout } from "./getGPUBindGroupLayout"; +import { ChainMap } from "@feng3d/render-api"; +import { getIGPUBindGroupLayoutEntryMap, GPUBindGroupLayoutEntryMap } from "./getWGSLReflectInfo"; -export function getGPUPipelineLayout(device: GPUDevice, layout: IGPUPipelineLayoutDescriptor) +declare global { - const bindGroupLayouts = layout.bindGroupLayouts.map((v) => + interface GPUPipelineLayout { - const gBindGroupLayout = getGPUBindGroupLayout(device, v); + /** + * 绑定组布局列表。 + * + * 注:wgsl着色器被反射过程中将会被引擎自动赋值。 + */ + bindGroupLayouts: GPUBindGroupLayout[]; + } - return gBindGroupLayout; - }); - const gPipelineLayout = device.createPipelineLayout({ - bindGroupLayouts, + interface GPUBindGroupLayout + { + /** + * 绑定组布局的入口列表。 + * + * 注:wgsl着色器被反射过程中将会被引擎自动赋值。 + */ + entries: GPUBindGroupLayoutEntry[]; + + /** + * 用于判断布局信息是否相同的标识。 + * + * 注:wgsl着色器被反射过程中将会被引擎自动赋值。 + */ + key: string, + } +} + +/** + * 从GPU管线中获取管线布局。 + * + * @param shader GPU管线。 + * @returns 管线布局。 + */ +export function getGPUPipelineLayout(device: GPUDevice, shader: { vertex: string, fragment: string } | { compute: string }) +{ + let shaderKey = ""; + if ("compute" in shader) + { + shaderKey += shader.compute; + } + else + { + shaderKey += shader.vertex; + if (shader.fragment) shaderKey += `\n// ------顶点与片段着色器分界--------\n` + shader.fragment; + } + + const getGPUPipelineLayoutKey: GetGPUPipelineLayoutKey = [device, shaderKey]; + let gpuPipelineLayout = getGPUPipelineLayoutMap.get(getGPUPipelineLayoutKey); + if (gpuPipelineLayout) return gpuPipelineLayout; + + let entryMap: GPUBindGroupLayoutEntryMap; + if ("compute" in shader) + { + entryMap = getIGPUBindGroupLayoutEntryMap(shader.compute); + } + else + { + entryMap = getIGPUBindGroupLayoutEntryMap(shader.vertex); + if ("fragment" in shader) + { + const fragmentEntryMap = getIGPUBindGroupLayoutEntryMap(shader.fragment); + for (const resourceName in fragmentEntryMap) + { + // 检测相同名称是否被定义在多个地方 + if (entryMap[resourceName]) + { + const preEntry = entryMap[resourceName].variableInfo; + const currentEntry = fragmentEntryMap[resourceName].variableInfo; + + if (preEntry.group !== currentEntry.group + || preEntry.binding !== currentEntry.binding + || preEntry.resourceType !== currentEntry.resourceType + ) + { + console.warn(`分别在 着色器 @group(${preEntry.group}) @binding(${preEntry.binding}) 与 @group(${currentEntry.group}) @binding(${currentEntry.binding}) 处存在相同名称的变量 ${currentEntry.name} 。`); + } + } + entryMap[resourceName] = fragmentEntryMap[resourceName]; + } + } + } + + // 绑定组布局描述列表。 + const bindGroupLayoutDescriptors: GPUBindGroupLayoutDescriptor[] = []; + for (const resourceName in entryMap) + { + const bindGroupLayoutEntry = entryMap[resourceName]; + const { group, binding } = bindGroupLayoutEntry.variableInfo; + // + const bindGroupLayoutDescriptor = bindGroupLayoutDescriptors[group] = bindGroupLayoutDescriptors[group] || { entries: [] }; + + // 检测相同位置是否存在多个定义 + if (bindGroupLayoutDescriptor.entries[binding]) + { + // 存在重复定义时,判断是否兼容 + const preEntry = bindGroupLayoutDescriptor.entries[binding]; + console.error(`在管线中 @group(${group}) @binding(${binding}) 处存在多个定义 ${preEntry.variableInfo.name} ${resourceName} !`); + } + + // + bindGroupLayoutDescriptor.entries[binding] = bindGroupLayoutEntry; + } + + // 绑定组布局列表。 + const bindGroupLayouts = bindGroupLayoutDescriptors.map((descriptor) => + { + // 排除 undefined 元素。 + const entries = (descriptor.entries as GPUBindGroupLayoutEntry[]).filter((v) => !!v); + const key = entries.map((v) => v.key).join(","); + // 相同的布局只保留一个。 + let bindGroupLayout = bindGroupLayoutMap[key]; + if (!bindGroupLayout) + { + bindGroupLayout = bindGroupLayoutMap[key] = device.createBindGroupLayout({ entries, label: key }); + bindGroupLayout.entries = entries; + bindGroupLayout.key = key; + } + return bindGroupLayout; }); - return gPipelineLayout; + // 管线布局描述标识符。 + const pipelineLayoutKey = bindGroupLayouts.map((v, i) => `[${i}: ${v.key}]`).join(","); + gpuPipelineLayout = pipelineLayoutDescriptorMap[pipelineLayoutKey]; + if (!gpuPipelineLayout) + { + gpuPipelineLayout = pipelineLayoutDescriptorMap[pipelineLayoutKey] = device.createPipelineLayout({ + bindGroupLayouts, + }); + gpuPipelineLayout.bindGroupLayouts = bindGroupLayouts; + } + + getGPUPipelineLayoutMap.set(getGPUPipelineLayoutKey, gpuPipelineLayout); + + return gpuPipelineLayout; } +type GetGPUPipelineLayoutKey = [device: GPUDevice, shaderKey: string]; +const getGPUPipelineLayoutMap = new ChainMap; + +const bindGroupLayoutMap: { [key: string]: GPUBindGroupLayout } = {}; +const pipelineLayoutDescriptorMap: { [key: string]: GPUPipelineLayout } = {}; diff --git a/src/caches/getGPURenderOcclusionQuery.ts b/src/caches/getGPURenderOcclusionQuery.ts deleted file mode 100644 index 523c9be19040b9e14d9f166a31db8103f59ee04a..0000000000000000000000000000000000000000 --- a/src/caches/getGPURenderOcclusionQuery.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { anyEmitter } from "@feng3d/event"; -import { IRenderPass, IRenderPassObject } from "@feng3d/render-api"; - -import { IGPUOcclusionQuery } from "../data/IGPUOcclusionQuery"; -import { GPUQueue_submit } from "../eventnames"; - -export function getGPURenderOcclusionQuery(renderObjects?: readonly IRenderPassObject[]): GPURenderOcclusionQuery -{ - if (!renderObjects) return defautRenderOcclusionQuery; - let renderOcclusionQuery: GPURenderOcclusionQuery = renderObjects["_GPURenderOcclusionQuery"]; - if (renderOcclusionQuery) return renderOcclusionQuery; - - const occlusionQueryObjects: IGPUOcclusionQuery[] = renderObjects.filter((cv) => cv.__type === "OcclusionQuery") as any; - if (occlusionQueryObjects.length === 0) - { - renderObjects["_GPURenderOcclusionQuery"] = defautRenderOcclusionQuery; - - return defautRenderOcclusionQuery; - } - - occlusionQueryObjects.forEach((v, i) => { v._queryIndex = i; }); - - let occlusionQuerySet: GPUQuerySet; - let resolveBuf: GPUBuffer; - let resultBuf: GPUBuffer; - - /** - * 初始化。 - * - * 在渲染通道描述上设置 occlusionQuerySet 。 - * - * @param device - * @param renderPassDescriptor - */ - const init = (device: GPUDevice, renderPassDescriptor: GPURenderPassDescriptor) => - { - occlusionQuerySet = renderPassDescriptor.occlusionQuerySet = device.createQuerySet({ type: "occlusion", count: occlusionQueryObjects.length }); - }; - - /** - * 查询结果。 - * - * @param device - * @param commandEncoder - * @param renderPass - */ - const resolve = (device: GPUDevice, commandEncoder: GPUCommandEncoder, renderPass: IRenderPass) => - { - resolveBuf = resolveBuf || device.createBuffer({ - label: "resolveBuffer", - // Query results are 64bit unsigned integers. - size: occlusionQueryObjects.length * BigUint64Array.BYTES_PER_ELEMENT, - usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, - }); - - commandEncoder.resolveQuerySet(occlusionQuerySet, 0, occlusionQueryObjects.length, resolveBuf, 0); - - resultBuf = resultBuf || device.createBuffer({ - label: "resultBuffer", - size: resolveBuf.size, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, - }); - - if (resultBuf.mapState === "unmapped") - { - commandEncoder.copyBufferToBuffer(resolveBuf, 0, resultBuf, 0, resultBuf.size); - } - - const getOcclusionQueryResult = () => - { - if (resultBuf.mapState === "unmapped") - { - resultBuf.mapAsync(GPUMapMode.READ).then(() => - { - const results = new BigUint64Array(resultBuf.getMappedRange()); - - occlusionQueryObjects.forEach((v, i) => - { - v.result = { result: Number(results[i]) }; - }); - - resultBuf.unmap(); - - renderPass.occlusionQueryResults = occlusionQueryObjects.concat(); - - // - anyEmitter.off(device.queue, GPUQueue_submit, getOcclusionQueryResult); - }); - } - }; - - // 监听提交WebGPU事件 - anyEmitter.on(device.queue, GPUQueue_submit, getOcclusionQueryResult); - }; - - renderObjects["_GPURenderOcclusionQuery"] = renderOcclusionQuery = { init, resolve }; - - return renderOcclusionQuery; -} - -interface GPURenderOcclusionQuery -{ - init: (device: GPUDevice, renderPassDescriptor: GPURenderPassDescriptor) => void - resolve: (device: GPUDevice, commandEncoder: GPUCommandEncoder, renderPass: IRenderPass) => void -} - -const defautRenderOcclusionQuery = { init: () => { }, resolve: () => { } }; \ No newline at end of file diff --git a/src/caches/getGPURenderPassDescriptor.ts b/src/caches/getGPURenderPassDescriptor.ts index f021fa8e6ee4ff279bfe2d4c49a072473deecb3e..fdfcb91df05ba779530ed837472f55040b8b8d0a 100644 --- a/src/caches/getGPURenderPassDescriptor.ts +++ b/src/caches/getGPURenderPassDescriptor.ts @@ -1,13 +1,19 @@ import { anyEmitter } from "@feng3d/event"; -import { IRenderPassColorAttachment, IRenderPassDepthStencilAttachment, IRenderPassDescriptor, ITextureLike, ITextureView } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { IGPUCanvasTexture } from "../data/IGPUCanvasTexture"; -import { IGPUTexture_resize } from "../eventnames"; -import { IGPUTextureMultisample } from "../internal/IGPUTextureMultisample"; -import { NGPURenderPassColorAttachment } from "../internal/internal"; +import { CanvasTexture, ChainMap, computed, Computed, effect, OcclusionQuery, reactive, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, Texture, TextureLike, TextureView } from "@feng3d/render-api"; +import { GPUQueue_submit } from "../eventnames"; +import { MultisampleTexture } from "../internal/MultisampleTexture"; +import { getGPUPassTimestampWrites } from "./getGPUPassTimestampWrites"; import { getGPUTextureFormat } from "./getGPUTextureFormat"; import { getGPUTextureView } from "./getGPUTextureView"; -import { getIGPUTextureLikeSize } from "./getIGPUTextureSize"; +import { getTextureSize } from "./getTextureSize"; + +declare global +{ + interface GPUQuerySet + { + resolve(commandEncoder: GPUCommandEncoder): void; + } +} /** * 获取GPU渲染通道描述。 @@ -16,217 +22,72 @@ import { getIGPUTextureLikeSize } from "./getIGPUTextureSize"; * @param descriptor 渲染通道描述。 * @returns GPU渲染通道描述。 */ -export function getGPURenderPassDescriptor(device: GPUDevice, descriptor: IRenderPassDescriptor): GPURenderPassDescriptor +export function getGPURenderPassDescriptor(device: GPUDevice, renderPass: RenderPass): GPURenderPassDescriptor { - const renderPassDescriptorMap: Map = device["_RenderPassDescriptorMap"] = device["_RenderPassDescriptorMap"] || new Map(); - let renderPassDescriptor = renderPassDescriptorMap.get(descriptor); - if (renderPassDescriptor) + const descriptor: RenderPassDescriptor = renderPass.descriptor; + // 缓存 + const getGPURenderPassDescriptorKey: GetGPURenderPassDescriptorKey = [device, descriptor]; + let renderPassDescriptor = getGPURenderPassDescriptorMap.get(getGPURenderPassDescriptorKey); + if (renderPassDescriptor) return renderPassDescriptor; + + // 避免重复创建,触发反应链。 + renderPassDescriptor = {} as any; + effect(() => { - // 执行更新函数。 - (renderPassDescriptor["_updates"] as Function[]).forEach((v) => v()); - - return renderPassDescriptor; - } - - renderPassDescriptor = { colorAttachments: [] }; - renderPassDescriptorMap.set(descriptor, renderPassDescriptor); - - const _updates: Function[] = renderPassDescriptor["_updates"] = []; - - // 更新渲染通道附件尺寸,使得附件上纹理尺寸一致。 - updateAttachmentSize(descriptor); - - // 获取颜色附件完整描述列表。 - const colorAttachments = getIGPURenderPassColorAttachments(descriptor.colorAttachments, descriptor.sampleCount); - - // 获取深度模板附件 - const depthStencilAttachment = getIGPURenderPassDepthStencilAttachment(descriptor.depthStencilAttachment, descriptor.attachmentSize, descriptor.sampleCount); - - // 附件尺寸变化时,渲染通道描述失效。 - const watchProperty = { attachmentSize: { width: 0, height: 0 } }; // 被监听的属性 - watcher.watchobject(descriptor, watchProperty, () => - { - // 由于深度纹理与多重采样纹理可能是引擎自动生成的,这部分纹理需要更新。 - setIGPURenderPassAttachmentSize(colorAttachments, depthStencilAttachment, descriptor.attachmentSize); + // 监听 + const r_descriptor = reactive(descriptor); + r_descriptor.label; + r_descriptor.maxDrawCount; + r_descriptor.colorAttachments; + r_descriptor.depthStencilAttachment; + + // 执行 + renderPassDescriptor.label = descriptor.label; + renderPassDescriptor.maxDrawCount = descriptor.maxDrawCount; + renderPassDescriptor.colorAttachments = getGPURenderPassColorAttachments(device, descriptor); + renderPassDescriptor.depthStencilAttachment = getGPURenderPassDepthStencilAttachment(device, descriptor); + + // 处理时间戳查询 + renderPassDescriptor.timestampWrites = getGPUPassTimestampWrites(device, descriptor.timestampQuery); + + setOcclusionQuerySet(device, renderPass, renderPassDescriptor); }); - colorAttachments?.forEach((v, i) => - { - if (!v) return; - - const { clearValue, loadOp, storeOp } = v; - - const attachment: GPURenderPassColorAttachment = { - ...v, - view: undefined, - resolveTarget: undefined, - clearValue, - loadOp, - storeOp, - }; - - const updateView = () => - { - attachment.view = getGPUTextureView(device, v.view); - }; - updateView(); - - // - if ((v.view.texture as IGPUCanvasTexture).context) - { - _updates.push(updateView); - } - anyEmitter.on(v.view.texture, IGPUTexture_resize, updateView); - - // - if (v.resolveTarget) - { - const updateResolveTarget = () => - { - attachment.resolveTarget = getGPUTextureView(device, v.resolveTarget); - }; - updateResolveTarget(); - // - if ((v.resolveTarget?.texture as IGPUCanvasTexture)?.context) - { - _updates.push(updateResolveTarget); - } - anyEmitter.on(v.resolveTarget.texture, IGPUTexture_resize, updateResolveTarget); - } - - // - renderPassDescriptor.colorAttachments[i] = attachment; - }); - - if (depthStencilAttachment) - { - const v = depthStencilAttachment; - - renderPassDescriptor.depthStencilAttachment = { - ...v, - view: undefined, - }; - - const updateView = () => - { - renderPassDescriptor.depthStencilAttachment.view = getGPUTextureView(device, v.view); - }; - updateView(); - - anyEmitter.on(v.view.texture, IGPUTexture_resize, updateView); - } + getGPURenderPassDescriptorMap.set(getGPURenderPassDescriptorKey, renderPassDescriptor); return renderPassDescriptor; } -/** - * 获取渲染通道附件上的纹理描述列表。 - * - * @param colorAttachments 颜色附件描述。 - * @param depthStencilAttachment 深度模板附件描述。 - * - * @returns 渲染通道附件上的纹理描述列表。 - */ -function getAttachmentTextures(colorAttachments: readonly IRenderPassColorAttachment[], depthStencilAttachment?: IRenderPassDepthStencilAttachment) -{ - const textures: ITextureLike[] = []; - - for (let i = 0; i < colorAttachments.length; i++) - { - const element = colorAttachments[i]; - if (!element) continue; - if (element.view) - { - textures.push(element.view.texture); - } - } - - if (depthStencilAttachment) - { - if (depthStencilAttachment.view) - { - textures.push(depthStencilAttachment.view.texture); - } - } - - return textures; -} - -/** - * 更新渲染通道附件尺寸,使得附件上纹理尺寸一致。 - * - * @param renderPass 渲染通道描述。 - * @param attachmentSize 附件尺寸。 - */ -function setIGPURenderPassAttachmentSize(colorAttachments: NGPURenderPassColorAttachment[], depthStencilAttachment: IRenderPassDepthStencilAttachment, attachmentSize: { width: number; height: number; }) -{ - const attachmentTextures = getIGPURenderPassAttachmentTextures(colorAttachments, depthStencilAttachment); - attachmentTextures.forEach((v) => setIGPUTextureSize(v, attachmentSize)); -} +type GetGPURenderPassDescriptorKey = [device: GPUDevice, descriptor: RenderPassDescriptor]; +const getGPURenderPassDescriptorMap = new ChainMap; /** - * 设置纹理与附件相同尺寸。 + * 设置纹理尺寸。 * * @param texture 纹理描述。 * @param attachmentSize 附件尺寸。 */ -function setIGPUTextureSize(texture: ITextureLike, attachmentSize: { width: number, height: number }) +function setTextureSize(texture: TextureLike, attachmentSize: { width: number, height: number }) { if ("context" in texture) { - texture = texture as IGPUCanvasTexture; - const element = document.getElementById(texture.context.canvasId) as HTMLCanvasElement; + texture = texture as CanvasTexture; + const element = typeof texture.context.canvasId === "string" ? document.getElementById(texture.context.canvasId) as HTMLCanvasElement : texture.context.canvasId; if (element.width !== attachmentSize.width) element.width = attachmentSize.width; if (element.height !== attachmentSize.height) element.height = attachmentSize.height; + reactive(texture)._canvasSizeVersion = ~~texture._canvasSizeVersion + 1; } else { + reactive(texture.size)[0] = attachmentSize.width; + reactive(texture.size)[1] = attachmentSize.height; if (texture.size?.[2]) { - texture.size = [attachmentSize.width, attachmentSize.height, texture.size[2]]; - } - else - { - texture.size = [attachmentSize.width, attachmentSize.height]; + reactive(texture.size)[2] = texture.size[2]; } } } -/** - * 获取渲染通道附件上的纹理描述列表。 - * - * @param colorAttachments 颜色附件列表。 - * @param depthStencilAttachment 深度模板附件。 - * @returns 渲染通道附件上的纹理描述列表。 - */ -function getIGPURenderPassAttachmentTextures(colorAttachments: NGPURenderPassColorAttachment[], depthStencilAttachment?: IRenderPassDepthStencilAttachment) -{ - const textures: ITextureLike[] = []; - - for (let i = 0; i < colorAttachments.length; i++) - { - const element = colorAttachments[i]; - if (element.view) - { - textures.push(element.view.texture); - } - if (element.resolveTarget) - { - textures.push(element.resolveTarget.texture); - } - } - - if (depthStencilAttachment) - { - if (depthStencilAttachment.view) - { - textures.push(depthStencilAttachment.view.texture); - } - } - - return textures; -} - /** * 获取用于解决多重采样的纹理视图。 * @@ -234,28 +95,28 @@ function getIGPURenderPassAttachmentTextures(colorAttachments: NGPURenderPassCol * @param sampleCount 多重采样数量。 * @returns 用于解决多重采样的纹理视图。 */ -function getMultisampleTextureView(texture: ITextureLike, sampleCount: 4) +function getMultisampleTextureView(texture: TextureLike, sampleCount: 4) { - let multisampleTextureView = multisampleTextureMap.get(texture); - if (!multisampleTextureView) + if (sampleCount !== 4) return undefined; + if (!texture) return undefined; + + let multisampleTextureView = getMultisampleTextureViewMap.get(texture); + if (multisampleTextureView) return multisampleTextureView; + + // 新增用于解决多重采样的纹理 + const multisampleTexture: MultisampleTexture = { label: "自动生成多重采样的纹理", sampleCount } as MultisampleTexture; + multisampleTextureView = { texture: multisampleTexture }; + effect(() => { - // 新增用于解决多重采样的纹理 - const size = getIGPUTextureLikeSize(texture); - const format = getGPUTextureFormat(texture); - const multisampleTexture: IGPUTextureMultisample = { - label: "自动生成多重采样的纹理", - size, - sampleCount, - format, - }; - multisampleTextureView = { texture: multisampleTexture }; - multisampleTextureMap.set(texture, multisampleTextureView); - } + // 新建的多重采样纹理尺寸与格式与原始纹理同步。 + reactive(multisampleTexture).size = getTextureSize(texture); + reactive(multisampleTexture).format = getGPUTextureFormat(texture); + }); + getMultisampleTextureViewMap.set(texture, multisampleTextureView); return multisampleTextureView; } - -const multisampleTextureMap = new WeakMap(); +const getMultisampleTextureViewMap = new WeakMap; /** * 获取深度模板附件完整描述。 @@ -265,37 +126,92 @@ const multisampleTextureMap = new WeakMap(); * @param multisample 多重采样次数。 * @returns 深度模板附件完整描述。 */ -function getIGPURenderPassDepthStencilAttachment(depthStencilAttachment: IRenderPassDepthStencilAttachment, attachmentSize: { width: number, height: number }, multisample: number) +function getGPURenderPassDepthStencilAttachment(device: GPUDevice, descriptor: RenderPassDescriptor) { + const depthStencilAttachment = descriptor.depthStencilAttachment; if (!depthStencilAttachment) return undefined; - let view = depthStencilAttachment.view; - if (!view) + // 初始化附件尺寸。 + if (!descriptor.attachmentSize) { - view = { - texture: { + const textureSize = getTextureSize(depthStencilAttachment.view.texture); + reactive(descriptor).attachmentSize = { width: textureSize[0], height: textureSize[1] }; + } + const attachmentSize = descriptor.attachmentSize; + + // 缓存 + const getGPURenderPassDepthStencilAttachmentKey: GetGPURenderPassDepthStencilAttachmentKey = [device, depthStencilAttachment]; + let result = getGPURenderPassDepthStencilAttachmentMap.get(getGPURenderPassDepthStencilAttachmentKey); + if (result) return result.value; + + // + let atuoCreateDepthTexture: Texture; + let atuoCreateDepthTextureView: TextureView; + + // 避免重复创建,触发反应链。 + const gpuDepthStencilAttachment: GPURenderPassDepthStencilAttachment = {} as any; + result = computed(() => + { + // 监听 + const r_depthStencilAttachment = reactive(depthStencilAttachment); + r_depthStencilAttachment.depthClearValue; + r_depthStencilAttachment.depthLoadOp; + r_depthStencilAttachment.depthStoreOp; + r_depthStencilAttachment.depthReadOnly; + r_depthStencilAttachment.stencilClearValue; + r_depthStencilAttachment.stencilLoadOp; + r_depthStencilAttachment.stencilStoreOp; + r_depthStencilAttachment.stencilReadOnly; + r_depthStencilAttachment.view; + + // 执行 + const { depthClearValue, depthLoadOp, depthStoreOp, depthReadOnly, stencilClearValue, stencilLoadOp, stencilStoreOp, stencilReadOnly } = depthStencilAttachment; + let view = depthStencilAttachment.view; + if (!view) + { + atuoCreateDepthTexture ??= { label: `自动生成的深度纹理`, size: [attachmentSize.width, attachmentSize.height], format: "depth24plus", - } - }; - } + }; + atuoCreateDepthTextureView ??= { texture: atuoCreateDepthTexture }; + // + view = atuoCreateDepthTextureView; + } - const depthClearValue = (depthStencilAttachment.depthClearValue !== undefined) ? depthStencilAttachment.depthClearValue : 1; - const depthLoadOp = depthStencilAttachment.depthLoadOp || "load"; - const depthStoreOp = depthStencilAttachment.depthStoreOp; + // 更新纹理尺寸 + computed(() => + { + // 监听 + const r_descriptor = reactive(descriptor); + r_descriptor.attachmentSize.width; + r_descriptor.attachmentSize.height; - // - const gpuDepthStencilAttachment: IRenderPassDepthStencilAttachment = { - ...depthStencilAttachment, - view, - depthClearValue, - depthLoadOp, - depthStoreOp, - }; + // 执行 + setTextureSize(view.texture, descriptor.attachmentSize); + + // 更改纹理尺寸将会销毁重新创建纹理,需要重新获取view。 + gpuDepthStencilAttachment.view = getGPUTextureView(device, view); + }).value; + + // + gpuDepthStencilAttachment.depthClearValue = depthClearValue ?? 1; + gpuDepthStencilAttachment.depthLoadOp = depthLoadOp; + gpuDepthStencilAttachment.depthStoreOp = depthStoreOp; + gpuDepthStencilAttachment.depthReadOnly = depthReadOnly; + gpuDepthStencilAttachment.stencilClearValue = stencilClearValue ?? 0; + gpuDepthStencilAttachment.stencilLoadOp = stencilLoadOp; + gpuDepthStencilAttachment.stencilStoreOp = stencilStoreOp; + gpuDepthStencilAttachment.stencilReadOnly = stencilReadOnly; + + return gpuDepthStencilAttachment; + }); + getGPURenderPassDepthStencilAttachmentMap.set(getGPURenderPassDepthStencilAttachmentKey, result); - return gpuDepthStencilAttachment; + return result.value; } +type GetGPURenderPassDepthStencilAttachmentKey = [device: GPUDevice, depthStencilAttachment: RenderPassDepthStencilAttachment]; +const getGPURenderPassDepthStencilAttachmentMap = new ChainMap>; /** * 获取颜色附件完整描述列表。 @@ -304,46 +220,170 @@ function getIGPURenderPassDepthStencilAttachment(depthStencilAttachment: IRender * @param sampleCount 多重采样次数。 * @returns 颜色附件完整描述列表。 */ -function getIGPURenderPassColorAttachments(colorAttachments: readonly IRenderPassColorAttachment[], sampleCount: 4) +function getGPURenderPassColorAttachments(device: GPUDevice, descriptor: RenderPassDescriptor) { - const gpuColorAttachments: NGPURenderPassColorAttachment[] = colorAttachments.map((v) => + const getGPURenderPassColorAttachmentsKey: GetGPURenderPassColorAttachmentsKey = [device, descriptor]; + let result = getIGPURenderPassColorAttachmentsMap.get(getGPURenderPassColorAttachmentsKey); + if (result) return result.value; + + const gpuColorAttachments: GPURenderPassColorAttachment[] = []; + result = computed(() => { - if (!v) return undefined; + // 监听 + const r_descriptor = reactive(descriptor); + r_descriptor.colorAttachments.forEach((v) => v); + + // 执行 + const { colorAttachments } = descriptor; + gpuColorAttachments.length = 0; + colorAttachments.forEach((v) => + { + if (!v) return; + + const attachment = getGPURenderPassColorAttachment(device, v, descriptor); - let view = v.view; - let resolveTarget: ITextureView; + gpuColorAttachments.push(attachment); + }); + + return gpuColorAttachments; + }); + getIGPURenderPassColorAttachmentsMap.set(getGPURenderPassColorAttachmentsKey, result); + return result.value; +} +type GetGPURenderPassColorAttachmentsKey = [device: GPUDevice, descriptor: RenderPassDescriptor]; +const getIGPURenderPassColorAttachmentsMap = new ChainMap>; + +/** + * 获取颜色附件完整描述。 + */ +function getGPURenderPassColorAttachment(device: GPUDevice, renderPassColorAttachment: RenderPassColorAttachment, descriptor: RenderPassDescriptor) +{ + const getGPURenderPassColorAttachmentKey: GetGPURenderPassColorAttachmentKey = [device, renderPassColorAttachment, descriptor]; + let attachment = getGPURenderPassColorAttachmentMap.get(getGPURenderPassColorAttachmentKey); + if (attachment) return attachment; + + // 初始化附件尺寸。 + if (!descriptor.attachmentSize) + { + const textureSize = getTextureSize(renderPassColorAttachment.view.texture); + reactive(descriptor).attachmentSize = { width: textureSize[0], height: textureSize[1] }; + } + + attachment = {} as any; + effect(() => + { + // 监听 + const r_renderPassColorAttachment = reactive(renderPassColorAttachment); + r_renderPassColorAttachment.view; + r_renderPassColorAttachment.depthSlice; + r_renderPassColorAttachment.clearValue; + r_renderPassColorAttachment.loadOp; + r_renderPassColorAttachment.storeOp; + const r_descriptor = reactive(descriptor); + r_descriptor.sampleCount; + + // + let view = renderPassColorAttachment.view; + const { depthSlice, clearValue, loadOp, storeOp } = renderPassColorAttachment; + + const { sampleCount } = descriptor; + let resolveTarget: TextureView; if (sampleCount) { resolveTarget = view; view = getMultisampleTextureView(view.texture, sampleCount); } - return { - view, - resolveTarget, - clearValue: v.clearValue, - loadOp: v.loadOp ?? "clear", - storeOp: v.storeOp ?? "store", - }; + // 更新纹理尺寸 + effect(() => + { + // 监听 + const r_descriptor = reactive(descriptor); + r_descriptor.attachmentSize.width; + r_descriptor.attachmentSize.height; + + // 执行 + setTextureSize(view.texture, descriptor.attachmentSize); + resolveTarget && setTextureSize(resolveTarget.texture, descriptor.attachmentSize); + + // 更改纹理尺寸将会销毁重新创建纹理,需要重新获取view。 + attachment.view = getGPUTextureView(device, view); + attachment.resolveTarget = getGPUTextureView(device, resolveTarget); + }); + + // + attachment.depthSlice = depthSlice; + attachment.clearValue = clearValue; + attachment.loadOp = loadOp ?? "clear"; + attachment.storeOp = storeOp ?? "store"; }); - return gpuColorAttachments; + getGPURenderPassColorAttachmentMap.set(getGPURenderPassColorAttachmentKey, attachment); + + return attachment; } +type GetGPURenderPassColorAttachmentKey = [device: GPUDevice, renderPassColorAttachment: RenderPassColorAttachment, descriptor: RenderPassDescriptor]; +const getGPURenderPassColorAttachmentMap = new ChainMap; -/** - * 更新渲染通道附件尺寸,使得附件上纹理尺寸一致。 - * - * @param renderPass 渲染通道描述。 - */ -function updateAttachmentSize(renderPass: IRenderPassDescriptor) +function setOcclusionQuerySet(device: GPUDevice, renderPass: RenderPass, renderPassDescriptor: GPURenderPassDescriptor) { - const attachmentTextures = getAttachmentTextures(renderPass.colorAttachments, renderPass.depthStencilAttachment); - if (!renderPass.attachmentSize) + const occlusionQuerys = renderPass.renderPassObjects?.filter((v) => v.__type__ === "OcclusionQuery") as OcclusionQuery[]; + if (!occlusionQuerys || occlusionQuerys.length === 0) return; + renderPassDescriptor.occlusionQuerySet = device.createQuerySet({ type: "occlusion", count: occlusionQuerys.length }); + const resolveBuf = device.createBuffer({ + label: "resolveBuffer", + // Query results are 64bit unsigned integers. + size: occlusionQuerys.length * BigUint64Array.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, + }); + const resultBuf = device.createBuffer({ + label: "resultBuffer", + size: resolveBuf.size, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + + // + renderPassDescriptor.occlusionQuerySet.resolve = (commandEncoder: GPUCommandEncoder) => { - const textureSize = getIGPUTextureLikeSize(attachmentTextures[0]); - renderPass.attachmentSize = { width: textureSize[0], height: textureSize[1] }; - } - attachmentTextures.forEach((v) => setIGPUTextureSize(v, renderPass.attachmentSize)); -} + if (occlusionQuerys.length === 0) return; + + commandEncoder.resolveQuerySet(renderPassDescriptor.occlusionQuerySet, 0, occlusionQuerys.length, resolveBuf, 0); + if (resultBuf.mapState === "unmapped") + { + commandEncoder.copyBufferToBuffer(resolveBuf, 0, resultBuf, 0, resultBuf.size); + } + + const getOcclusionQueryResult = () => + { + if (resultBuf.mapState === "unmapped") + { + resultBuf.mapAsync(GPUMapMode.READ).then(() => + { + const bigUint64Array = new BigUint64Array(resultBuf.getMappedRange()); + + const results = bigUint64Array.reduce((pv: number[], cv) => + { + pv.push(Number(cv)); + return pv; + }, []); + resultBuf.unmap(); + + occlusionQuerys.forEach((v, i) => + { + v.onQuery?.(results[i]); + }); + + renderPass.onOcclusionQuery?.(occlusionQuerys, results); + + // + anyEmitter.off(device.queue, GPUQueue_submit, getOcclusionQueryResult); + }); + } + }; + + // 监听提交WebGPU事件 + anyEmitter.on(device.queue, GPUQueue_submit, getOcclusionQueryResult); + }; +} diff --git a/src/caches/getGPURenderPassFormat.ts b/src/caches/getGPURenderPassFormat.ts index f54e74788a41ec27cd8f6480d7015515308eb0d0..6540a9a481458cc760d7efeb76e89ce28d91ec82 100644 --- a/src/caches/getGPURenderPassFormat.ts +++ b/src/caches/getGPURenderPassFormat.ts @@ -1,6 +1,6 @@ -import { IRenderPassDescriptor } from "@feng3d/render-api"; +import { computed, Computed, reactive, RenderPassDescriptor } from "@feng3d/render-api"; import { getGPUTextureFormat } from "../caches/getGPUTextureFormat"; -import { IGPURenderPassFormat } from "../internal/IGPURenderPassFormat"; +import { RenderPassFormat } from "../internal/RenderPassFormat"; /** * 获取渲染通道格式。 @@ -8,30 +8,51 @@ import { IGPURenderPassFormat } from "../internal/IGPURenderPassFormat"; * @param descriptor 渲染通道描述。 * @returns */ -export function getGPURenderPassFormat(descriptor: IRenderPassDescriptor): IGPURenderPassFormat +export function getGPURenderPassFormat(descriptor: RenderPassDescriptor): RenderPassFormat { - let gpuRenderPassFormat: IGPURenderPassFormat = descriptor[_RenderPassFormat]; - if (gpuRenderPassFormat) return gpuRenderPassFormat; + let result = getGPURenderPassFormatMap.get(descriptor); + if (result) return result.value; - const colorAttachmentTextureFormats = descriptor.colorAttachments.map((v) => getGPUTextureFormat(v.view.texture)); - - let depthStencilAttachmentTextureFormat: GPUTextureFormat; - if (descriptor.depthStencilAttachment) + result = computed(() => { - depthStencilAttachmentTextureFormat = getGPUTextureFormat(descriptor.depthStencilAttachment.view?.texture) || "depth24plus"; - } + // 监听 + const r_descriptor = reactive(descriptor); + r_descriptor.attachmentSize?.width; + r_descriptor.attachmentSize?.height; + r_descriptor.colorAttachments?.map((v) => v.view.texture); + r_descriptor.depthStencilAttachment?.view?.texture; + r_descriptor.sampleCount; - const _key = `${colorAttachmentTextureFormats.toString()}|${depthStencilAttachmentTextureFormat}|${descriptor.sampleCount}`; + // 计算 + const colorAttachmentTextureFormats = descriptor.colorAttachments.map((v) => getGPUTextureFormat(v.view.texture)); - descriptor[_RenderPassFormat] = gpuRenderPassFormat = { - attachmentSize: descriptor.attachmentSize, - colorFormats: colorAttachmentTextureFormats, - depthStencilFormat: depthStencilAttachmentTextureFormat, - sampleCount: descriptor.sampleCount, - _key, - }; + let depthStencilAttachmentTextureFormat: GPUTextureFormat; + if (descriptor.depthStencilAttachment) + { + depthStencilAttachmentTextureFormat = getGPUTextureFormat(descriptor.depthStencilAttachment.view?.texture) || "depth24plus"; + } - return gpuRenderPassFormat; -} + const renderPassFormat: RenderPassFormat = { + attachmentSize: descriptor.attachmentSize, + colorFormats: colorAttachmentTextureFormats, + depthStencilFormat: depthStencilAttachmentTextureFormat, + sampleCount: descriptor.sampleCount, + }; + + // 缓存 + const renderPassFormatKey = renderPassFormat.attachmentSize.width + "," + renderPassFormat.attachmentSize.height + + "|" + renderPassFormat.colorFormats.join('') + + "|" + renderPassFormat.depthStencilFormat + + "|" + renderPassFormat.sampleCount; + const cache = renderPassFormatMap[renderPassFormatKey]; + if (cache) return cache; + renderPassFormatMap[renderPassFormatKey] = renderPassFormat; -const _RenderPassFormat = "_RenderPassFormat"; \ No newline at end of file + return renderPassFormat; + }); + getGPURenderPassFormatMap.set(descriptor, result); + + return result.value; +} +const renderPassFormatMap: Record = {}; +const getGPURenderPassFormatMap = new WeakMap>(); diff --git a/src/caches/getGPURenderPipeline.ts b/src/caches/getGPURenderPipeline.ts index 2845320e9c3f98f7c9be1b75845dbed5047d7613..dfea2a8b4bf19c729c98e5f7792f1b0928ef1adf 100644 --- a/src/caches/getGPURenderPipeline.ts +++ b/src/caches/getGPURenderPipeline.ts @@ -1,41 +1,631 @@ -import { NGPURenderPipeline } from "../internal/NGPURenderPipeline"; +import { BlendComponent, BlendState, ChainMap, ColorTargetState, computed, Computed, DepthStencilState, FragmentState, PrimitiveState, reactive, RenderPipeline, StencilFaceState, VertexAttributes, VertexState, WGSLVertexType, WriteMask } from "@feng3d/render-api"; +import { TemplateInfo, TypeInfo } from "wgsl_reflect"; + +import { MultisampleState } from "../data/MultisampleState"; +import { RenderPassFormat } from "../internal/RenderPassFormat"; import { getGPUPipelineLayout } from "./getGPUPipelineLayout"; import { getGPUShaderModule } from "./getGPUShaderModule"; -import { getIGPUPipelineLayout } from "./getIGPUPipelineLayout"; - -export function getGPURenderPipeline(device: GPUDevice, renderPipeline: NGPURenderPipeline) -{ - let pipeline = pipelineMap.get(renderPipeline); - if (pipeline) return pipeline; - - // 从GPU管线中获取管线布局。 - const gpuPipelineLayout = getIGPUPipelineLayout({ vertex: renderPipeline.vertex.code, fragment: renderPipeline.fragment?.code }); - const layout = getGPUPipelineLayout(device, gpuPipelineLayout); - - const gpuRenderPipelineDescriptor: GPURenderPipelineDescriptor = { - layout, - vertex: { - ...renderPipeline.vertex, - module: getGPUShaderModule(device, renderPipeline.vertex.code), - }, - primitive: renderPipeline.primitive, - depthStencil: renderPipeline.depthStencil, - multisample: renderPipeline.multisample, +import { getGPUVertexBufferLayouts } from "./getNGPUVertexBuffers"; +import { getVertexEntryFunctionInfo } from "./getVertexEntryFunctionInfo"; +import { getWGSLReflectInfo } from "./getWGSLReflectInfo"; + +/** + * 从渲染管线描述、渲染通道描述以及完整的顶点属性数据映射获得完整的渲染管线描述以及顶点缓冲区数组。 + * + * @param renderPipeline 渲染管线描述。 + * @param renderPass 渲染通道描述。 + * @param vertices 顶点属性数据映射。 + * @returns 完整的渲染管线描述以及顶点缓冲区数组。 + */ +export function getGPURenderPipeline(device: GPUDevice, renderPipeline: RenderPipeline, renderPassFormat: RenderPassFormat, vertices: VertexAttributes, indexFormat: GPUIndexFormat) +{ + const getGPURenderPipelineKey: GetGPURenderPipelineKey = [device, renderPipeline, renderPassFormat, vertices, indexFormat]; + let result = getGPURenderPipelineMap.get(getGPURenderPipelineKey); + if (result) return result.value; + + result = computed(() => + { + // 监听 + const r_renderPipeline = reactive(renderPipeline); + r_renderPipeline.label; + r_renderPipeline.fragment; + r_renderPipeline.primitive; + r_renderPipeline.depthStencil; + // r_renderPipeline.vertex + r_renderPipeline.vertex.code; + // r_renderPipeline.fragment + r_renderPipeline.fragment?.code + r_renderPipeline.multisample + + // 计算 + const { label, vertex, fragment, primitive, depthStencil, multisample } = renderPipeline; + const shader = { vertex: vertex.code, fragment: fragment?.code }; + const { colorFormats, depthStencilFormat, sampleCount } = renderPassFormat; + const gpuVertexState = getGPUVertexState(device, vertex, vertices); + // + const gpuRenderPipelineDescriptor: GPURenderPipelineDescriptor = { + label: label, + layout: getGPUPipelineLayout(device, shader), + vertex: gpuVertexState, + fragment: getGPUFragmentState(device, fragment, colorFormats), + primitive: getGPUPrimitiveState(primitive, indexFormat), + depthStencil: getGPUDepthStencilState(depthStencil, depthStencilFormat), + multisample: getGPUMultisampleState(multisample, sampleCount), + }; + + const gpuRenderPipeline = device.createRenderPipeline(gpuRenderPipelineDescriptor); + + return gpuRenderPipeline; + }); + getGPURenderPipelineMap.set(getGPURenderPipelineKey, result); + + return result.value; +} +type GetGPURenderPipelineKey = [device: GPUDevice, renderPipeline: RenderPipeline, renderPassFormat: RenderPassFormat, vertices: VertexAttributes, indexFormat: GPUIndexFormat]; +const getGPURenderPipelineMap = new ChainMap>; + +/** + * 获取完整的顶点阶段描述与顶点缓冲区列表。 + * + * @param vertexState 顶点阶段信息。 + * @param vertices 顶点数据。 + * @returns 完整的顶点阶段描述与顶点缓冲区列表。 + */ +function getGPUVertexState(device: GPUDevice, vertexState: VertexState, vertices: VertexAttributes) +{ + const getGPUVertexStateKey: GetGPUVertexStateKey = [device, vertexState, vertices]; + const result = getGPUVertexStateMap.get(getGPUVertexStateKey); + if (result) return result.value; + + return getGPUVertexStateMap.set(getGPUVertexStateKey, computed(() => + { + // 监听 + const r_vertexState = reactive(vertexState); + r_vertexState.code; + r_vertexState.constants; + + // 计算 + const { code, constants } = vertexState; + + const vertexEntryFunctionInfo = getVertexEntryFunctionInfo(vertexState); + const vertexBufferLayouts = getGPUVertexBufferLayouts(vertexState, vertices); + + const gpuVertexState: GPUVertexState = { + module: getGPUShaderModule(device, code), + entryPoint: vertexEntryFunctionInfo.name, + buffers: vertexBufferLayouts, + constants: getConstants(constants), + }; + + // 缓存 + const gpuVertexStateKey: GPUVertexStateKey = [gpuVertexState.module, gpuVertexState.entryPoint, gpuVertexState.buffers, gpuVertexState.constants]; + const cache = gpuVertexStateMap.get(gpuVertexStateKey); + if (cache) return cache; + gpuVertexStateMap.set(gpuVertexStateKey, gpuVertexState); + + return gpuVertexState; + })).value; +} + +type GetGPUVertexStateKey = [device: GPUDevice, vertexState: VertexState, vertices: VertexAttributes]; +const getGPUVertexStateMap = new ChainMap>(); +type GPUVertexStateKey = [module: GPUShaderModule, entryPoint: string, buffers: Iterable, constants: Record]; +const gpuVertexStateMap = new ChainMap(); + +function getConstants(constants: Record) +{ + if (!constants) return undefined; + + let result: Computed> = getConstantsMap.get(constants); + if (result) return result.value; + + result = computed(() => + { + const r_constants = reactive(constants); + + let constantsKey = ""; + for (const key in r_constants) + { + constantsKey += `${key}:${r_constants[key]},`; + } + + if (constantsMap[constantsKey]) + { + return constantsMap[constantsKey]; + } + constantsMap[constantsKey] = constants; + + return constants; + }); + getConstantsMap.set(constants, result); + + return result.value; +} +const constantsMap: { [constantsKey: string]: Record } = {}; +const getConstantsMap = new WeakMap, Computed>>(); + +function getGPUPrimitiveState(primitive?: PrimitiveState, indexFormat?: GPUIndexFormat): GPUPrimitiveState +{ + if (!primitive) return defaultGPUPrimitiveState; + + const result: Computed = primitive["_cache_GPUPrimitiveState_" + indexFormat] ??= computed(() => + { + // 监听 + const r_primitive = reactive(primitive); + r_primitive.topology; + r_primitive.cullFace; + r_primitive.frontFace; + r_primitive.unclippedDepth; + + // 计算 + const { topology, cullFace, frontFace, unclippedDepth } = primitive; + const gpuPrimitive: GPUPrimitiveState = { + topology: topology ?? "triangle-list", + stripIndexFormat: (topology === "triangle-strip" || topology === "line-strip") ? indexFormat : undefined, + frontFace: frontFace ?? "ccw", + cullMode: cullFace ?? "none", + unclippedDepth: unclippedDepth ?? false, + }; + + return gpuPrimitive; + }); + + return result.value; +} +const defaultGPUPrimitiveState: GPUPrimitiveState = { topology: "triangle-list", cullMode: "none", frontFace: "ccw", } + +function getGPUMultisampleState(multisampleState?: MultisampleState, sampleCount?: 4) +{ + if (!sampleCount) return undefined; + if (!multisampleState) return defaultGPUMultisampleState; + + const result: Computed = multisampleState["_cache_GPUMultisampleState_" + sampleCount] ??= computed(() => + { + // 监听 + const r_multisampleState = reactive(multisampleState); + r_multisampleState.mask; + r_multisampleState.alphaToCoverageEnabled; + + // 计算 + const { mask, alphaToCoverageEnabled } = multisampleState; + const gpuMultisampleState: GPUMultisampleState = { + count: sampleCount, + mask: mask ?? 0xFFFFFFFF, + alphaToCoverageEnabled: alphaToCoverageEnabled ?? false, + }; + + return gpuMultisampleState; + }); + + return result.value; +} +const defaultGPUMultisampleState: GPUMultisampleState = { count: 4, mask: 0xFFFFFFFF, alphaToCoverageEnabled: false }; + +/** + * 获取深度模板阶段完整描述。 + * + * @param depthStencil 深度模板阶段描述。 + * @param depthStencilFormat 深度模板附件纹理格式。 + * @returns 深度模板阶段完整描述。 + */ +function getGPUDepthStencilState(depthStencil: DepthStencilState, depthStencilFormat?: GPUTextureFormat) +{ + if (!depthStencilFormat) return undefined; + + if (!depthStencil) return getDefaultGPUDepthStencilState(depthStencilFormat); + + const getGPUDepthStencilStateKey: GetGPUDepthStencilStateKey = [depthStencil, depthStencilFormat]; + let result = getGPUDepthStencilStateMap.get(getGPUDepthStencilStateKey); + if (result) return result.value; + + result = computed(() => + { + // 监听 + const r_depthStencil = reactive(depthStencil); + r_depthStencil.depthWriteEnabled; + r_depthStencil.depthCompare; + r_depthStencil.stencilFront; + r_depthStencil.stencilBack; + r_depthStencil.stencilReadMask; + r_depthStencil.stencilWriteMask; + r_depthStencil.depthBias; + r_depthStencil.depthBiasSlopeScale; + r_depthStencil.depthBiasClamp; + + // 计算 + const { depthWriteEnabled, + depthCompare, + stencilFront, + stencilBack, + stencilReadMask, + stencilWriteMask, + depthBias, + depthBiasSlopeScale, + depthBiasClamp, + } = depthStencil; + const gpuDepthStencilState: GPUDepthStencilState = { + format: depthStencilFormat, + depthWriteEnabled: depthWriteEnabled ?? true, + depthCompare: depthCompare ?? "less", + stencilFront: getGPUStencilFaceState(stencilFront), + stencilBack: getGPUStencilFaceState(stencilBack), + stencilReadMask: stencilReadMask ?? 0xFFFFFFFF, + stencilWriteMask: stencilWriteMask ?? 0xFFFFFFFF, + depthBias: depthBias ?? 0, + depthBiasSlopeScale: depthBiasSlopeScale ?? 0, + depthBiasClamp: depthBiasClamp ?? 0, + }; + + // 缓存 + const gpuDepthStencilStateKey: GPUDepthStencilStateKey = [ + gpuDepthStencilState.format, + gpuDepthStencilState.depthWriteEnabled, + gpuDepthStencilState.depthCompare, + gpuDepthStencilState.stencilFront, + gpuDepthStencilState.stencilBack, + gpuDepthStencilState.stencilReadMask, + gpuDepthStencilState.stencilWriteMask, + gpuDepthStencilState.depthBias, + gpuDepthStencilState.depthBiasSlopeScale, + gpuDepthStencilState.depthBiasClamp, + ]; + const cache = gpuDepthStencilStateMap.get(gpuDepthStencilStateKey); + if (cache) return cache; + gpuDepthStencilStateMap.set(gpuDepthStencilStateKey, gpuDepthStencilState); + + // + return gpuDepthStencilState; + }); + getGPUDepthStencilStateMap.set(getGPUDepthStencilStateKey, result); + + return result.value; +} +type GetGPUDepthStencilStateKey = [depthStencil: DepthStencilState, depthStencilFormat: GPUTextureFormat]; +const getGPUDepthStencilStateMap = new ChainMap>(); +type GPUDepthStencilStateKey = [format: GPUTextureFormat, depthWriteEnabled: boolean, depthCompare: GPUCompareFunction, stencilFront: GPUStencilFaceState, stencilBack: GPUStencilFaceState, stencilReadMask: number, stencilWriteMask: number, depthBias: number, depthBiasSlopeScale: number, depthBiasClamp: number] +const gpuDepthStencilStateMap = new ChainMap(); + +/** + * 获取片段阶段完整描述。 + * + * @param fragment 片段阶段描述。 + +/** + * 获取片段阶段完整描述。 + * + * @param fragment 片段阶段描述。 + */ +function getDefaultGPUDepthStencilState(depthStencilFormat: GPUTextureFormat) +{ + let result = defaultGPUDepthStencilStates[depthStencilFormat]; + if (result) return result; + + result = defaultGPUDepthStencilStates[depthStencilFormat] = { + format: depthStencilFormat, + depthWriteEnabled: true, + depthCompare: "less", + stencilFront: {}, + stencilBack: {}, + stencilReadMask: 0xFFFFFFFF, + stencilWriteMask: 0xFFFFFFFF, + depthBias: 0, + depthBiasSlopeScale: 0, + depthBiasClamp: 0, }; - if (renderPipeline.fragment) + return result; +} +const defaultGPUDepthStencilStates: Record = {} as any; + +function getGPUStencilFaceState(stencilFaceState?: StencilFaceState) +{ + if (!stencilFaceState) return defaultGPUStencilFaceState; + + let result = getGPUStencilFaceStateMap.get(stencilFaceState); + if (result) return result.value; + + result = computed(() => { - gpuRenderPipelineDescriptor.fragment = { - ...renderPipeline.fragment, - module: getGPUShaderModule(device, renderPipeline.fragment.code), + // 监听 + const r_stencilFaceState = reactive(stencilFaceState); + r_stencilFaceState.compare; + r_stencilFaceState.failOp; + r_stencilFaceState.depthFailOp; + r_stencilFaceState.passOp; + + // 计算 + const { compare, failOp, depthFailOp, passOp } = stencilFaceState; + const gpuStencilFaceState: GPUStencilFaceState = { + compare: compare ?? "always", + failOp: failOp ?? "keep", + depthFailOp: depthFailOp ?? "keep", + passOp: passOp ?? "keep", }; - } - pipeline = device.createRenderPipeline(gpuRenderPipelineDescriptor); + // 缓存 + const gpuStencilFaceStateKey: GPUStencilFaceStateKey = [gpuStencilFaceState.compare, gpuStencilFaceState.failOp, gpuStencilFaceState.depthFailOp, gpuStencilFaceState.passOp]; + const cache = GPUStencilFaceStateMap.get(gpuStencilFaceStateKey); + if (cache) return cache; + GPUStencilFaceStateMap.set(gpuStencilFaceStateKey, gpuStencilFaceState); + + // + return gpuStencilFaceState; + }); + getGPUStencilFaceStateMap.set(stencilFaceState, result); + + return result.value; +} +const defaultGPUStencilFaceState: GPUStencilFaceState = {}; +const getGPUStencilFaceStateMap = new WeakMap>(); +type GPUStencilFaceStateKey = [compare: GPUCompareFunction, failOp: GPUStencilOperation, depthFailOp: GPUStencilOperation, passOp: GPUStencilOperation]; +const GPUStencilFaceStateMap = new ChainMap(); + +function getGPUColorTargetState(colorTargetState: ColorTargetState, format: GPUTextureFormat) +{ + if (!colorTargetState) return getDefaultGPUColorTargetState(format); + + const result: Computed = colorTargetState["_GPUColorTargetState_" + format] ??= computed(() => + { + // 监听 + const r_colorTargetState = reactive(colorTargetState); + r_colorTargetState.writeMask; + r_colorTargetState.blend; + + // 计算 + const { writeMask, blend } = colorTargetState; + const gpuColorTargetState: GPUColorTargetState = { + format, + blend: getGPUBlendState(blend), + writeMask: getGPUColorWriteFlags(writeMask), + }; - pipelineMap.set(renderPipeline, pipeline); + return gpuColorTargetState; + }); - return pipeline; + return result.value; } -const pipelineMap = new Map(); +const getDefaultGPUColorTargetState = (format: GPUTextureFormat): GPUColorTargetState => +{ + return defaultGPUColorTargetState[format] ??= { format, blend: getGPUBlendState(undefined), writeMask: getGPUColorWriteFlags(undefined) } +}; +const defaultGPUColorTargetState: Record = {} as any; + +/** + * 获取片段阶段完整描述。 + * + * @param fragmentState 片段简单阶段。 + * @param colorAttachmentTextureFormats 颜色附件格式。 + * @returns 片段阶段完整描述。 + */ +function getGPUFragmentState(device: GPUDevice, fragmentState: FragmentState, colorAttachments: readonly GPUTextureFormat[]) +{ + if (!fragmentState) return undefined; + + const colorAttachmentsKey = colorAttachments.toLocaleString(); + + const getGPUFragmentStateKey: GetGPUFragmentStateKey = [device, fragmentState, colorAttachmentsKey]; + let gpuFragmentState = getGPUFragmentStateMap.get(getGPUFragmentStateKey); + if (gpuFragmentState) return gpuFragmentState.value; + + gpuFragmentState = computed(() => + { + // 监听 + const r_fragmentState = reactive(fragmentState); + r_fragmentState.code; + r_fragmentState.targets; + r_fragmentState.constants; + + // 计算 + const { code, targets, constants } = fragmentState; + const gpuFragmentState: GPUFragmentState = { + module: getGPUShaderModule(device, code), + entryPoint: getEntryPoint(fragmentState), + targets: getGPUColorTargetStates(targets, colorAttachments), + constants: getConstants(constants) + }; + + const gpuFragmentStateKey: GPUFragmentStateKey = [gpuFragmentState.module, gpuFragmentState.entryPoint, gpuFragmentState.targets, gpuFragmentState.constants]; + const cache = gpuFragmentStateMap.get(gpuFragmentStateKey); + if (cache) return cache; + + gpuFragmentStateMap.set(gpuFragmentStateKey, gpuFragmentState); + + return gpuFragmentState; + }); + + getGPUFragmentStateMap.set(getGPUFragmentStateKey, gpuFragmentState); + + return gpuFragmentState.value; +} +type GPUFragmentStateKey = [module: GPUShaderModule, entryPoint: string, targets: Iterable, constants: Record] +const gpuFragmentStateMap = new ChainMap(); +type GetGPUFragmentStateKey = [device: GPUDevice, fragmentState: FragmentState, colorAttachmentsKey: string]; +const getGPUFragmentStateMap = new ChainMap>; + +function getGPUColorTargetStates(targets: readonly ColorTargetState[], colorAttachments: readonly GPUTextureFormat[]): GPUColorTargetState[] +{ + if (!targets) return getDefaultGPUColorTargetStates(colorAttachments); + + const getGPUColorTargetStatesKey: GetGPUColorTargetStatesKey = [targets, colorAttachments]; + let result = getGPUColorTargetStatesMap.get(getGPUColorTargetStatesKey); + if (result) return result.value; + + result = computed(() => + { + return colorAttachments.map((format, i) => + { + if (!format) return undefined; + + // 监听 + reactive(targets)[i]; + + // 计算 + const gpuColorTargetState = getGPUColorTargetState(targets[i], format); + + return gpuColorTargetState; + }); + }); + getGPUColorTargetStatesMap.set(getGPUColorTargetStatesKey, result); + + return result.value; +} +type GetGPUColorTargetStatesKey = [targets: readonly ColorTargetState[], colorAttachments: readonly GPUTextureFormat[]]; +const getGPUColorTargetStatesMap = new ChainMap>(); + +const getDefaultGPUColorTargetStates = (colorAttachments: readonly GPUTextureFormat[]) => +{ + return defaultGPUColorTargetStates[colorAttachments.toString()] ??= colorAttachments.map((format) => + { + return getGPUColorTargetState(undefined, format); + }); +}; +const defaultGPUColorTargetStates: { [key: string]: GPUColorTargetState[] } = {}; + +function getEntryPoint(fragmentState: FragmentState) +{ + const result: Computed = fragmentState["_entryPoint"] ??= computed(() => + { + // 监听 + const r_fragmentState = reactive(fragmentState); + r_fragmentState.entryPoint; + r_fragmentState.code; + + // 计算 + const { entryPoint, code } = fragmentState; + // + if (entryPoint) return entryPoint; + const reflect = getWGSLReflectInfo(code); + const fragment = reflect.entry.fragment[0]; + console.assert(!!fragment, `WGSL着色器 ${code} 中不存在片元入口点。`); + + return fragment.name; + }); + + return result.value; +} + +function getGPUBlendState(blend?: BlendState): GPUBlendState +{ + if (!blend) return undefined; + + let result: Computed = blend["_GPUBlendState"]; + if (result) return result.value; + + result = blend["_GPUBlendState"] = computed(() => + { + // 监听 + const r_blend = reactive(blend); + r_blend.color; + r_blend.alpha; + // 计算 + const { color, alpha } = blend; + const gpuBlend: GPUBlendState = { + color: getGPUBlendComponent(color), + alpha: getGPUBlendComponent(alpha), + }; + return gpuBlend; + }) + + return result.value; +} + +function getGPUBlendComponent(blendComponent?: BlendComponent): GPUBlendComponent +{ + if (!blendComponent) return { operation: "add", srcFactor: "one", dstFactor: "zero" }; + + const result: Computed = blendComponent["_GPUBlendComponent"] ??= computed(() => + { + // 监听 + const r_blendComponent = reactive(blendComponent); + r_blendComponent.operation; + r_blendComponent.srcFactor; + r_blendComponent.dstFactor; + + // 计算 + const { operation, srcFactor, dstFactor } = blendComponent; + // 当 operation 为 max 或 min 时,srcFactor 和 dstFactor 必须为 one。 + const gpuBlendComponent: GPUBlendComponent = { + operation: operation ?? "add", + srcFactor: (operation === "max" || operation === "min") ? "one" : (srcFactor ?? "one"), + dstFactor: (operation === "max" || operation === "min") ? "one" : (dstFactor ?? "zero"), + }; + return gpuBlendComponent; + }); + + return result.value; +} + +function getGPUColorWriteFlags(writeMask?: WriteMask) +{ + if (!writeMask) return 15; + + const result: Computed = writeMask["_GPUColorWriteFlags"] ??= computed(() => + { + // 监听 + const r_writeMask = reactive(writeMask); + r_writeMask[0]; + r_writeMask[1]; + r_writeMask[2]; + r_writeMask[3]; + + // 计算 + const [red, green, blue, alpha] = writeMask; + let gpuWriteMask: GPUColorWriteFlags = 0; + if (red) + { + gpuWriteMask += 1; + } + if (green) + { + gpuWriteMask += 2; + } + if (blue) + { + gpuWriteMask += 4; + } + if (alpha) + { + gpuWriteMask += 8; + } + + return gpuWriteMask; + }); + + return result.value; +} + +function getWGSLType(type: TypeInfo) +{ + let wgslType = type.name; + if (isTemplateType(type)) + { + wgslType += `<${type.format.name}>`; + } + if (wgslTypeMap[wgslType]) + { + wgslType = wgslTypeMap[wgslType]; + } + + return wgslType as WGSLVertexType; +} + +/** + * 别名 + */ +const wgslTypeMap = { + vec2u: "vec2", + vec3u: "vec3", + vec4u: "vec4", + vec2i: "vec2", + vec3i: "vec3", + vec4i: "vec4", + vec2f: "vec2", + vec3f: "vec3", + vec4f: "vec4", +}; + +function isTemplateType(type: TypeInfo): type is TemplateInfo +{ + return !!(type as TemplateInfo).format; +} diff --git a/src/caches/getGPUSampler.ts b/src/caches/getGPUSampler.ts index b3539e16f6fa7d5f615b5bd732b7cae6c8cebe5d..1a3328117a38c5b24817cd7d94d5b18916494a94 100644 --- a/src/caches/getGPUSampler.ts +++ b/src/caches/getGPUSampler.ts @@ -1,43 +1,56 @@ -import { anyEmitter } from "@feng3d/event"; -import { ISampler } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { IGPUSampler_changed } from "../eventnames"; +import { ChainMap, computed, Computed, reactive, Sampler } from "@feng3d/render-api"; -export function getGPUSampler(device: GPUDevice, sampler: ISampler) +export function getGPUSampler(device: GPUDevice, sampler: Sampler) { - let gSampler = samplerMap.get(sampler); - if (gSampler) return gSampler; + const getGPUSamplerKey: GetGPUSamplerKey = [device, sampler]; + let result = getGPUSamplerMap.get(getGPUSamplerKey); + if (result) return result.value; - // 处理默认值 - sampler.addressModeU = sampler.addressModeU ?? "repeat"; - sampler.addressModeV = sampler.addressModeV ?? "repeat"; - sampler.addressModeW = sampler.addressModeW ?? "repeat"; - sampler.magFilter = sampler.magFilter ?? "nearest"; - sampler.minFilter = sampler.minFilter ?? "nearest"; - sampler.mipmapFilter = sampler.mipmapFilter ?? "nearest"; + result = computed(() => + { + // 监听 + const r_sampler = reactive(sampler); + const { + label, + addressModeU, + addressModeV, + addressModeW, + magFilter, + minFilter, + mipmapFilter, + lodMinClamp, + lodMaxClamp, + compare, + maxAnisotropy, + } = r_sampler; - // - gSampler = device.createSampler(sampler); - samplerMap.set(sampler, gSampler); + const gSampler = device.createSampler({ + label: label, + addressModeU: addressModeU ?? "repeat", + addressModeV: addressModeV ?? "repeat", + addressModeW: addressModeW ?? "repeat", + magFilter: magFilter ?? "nearest", + minFilter: minFilter ?? "nearest", + mipmapFilter: mipmapFilter ?? "nearest", + lodMinClamp: lodMinClamp ?? 0, + lodMaxClamp: lodMaxClamp, + compare: compare, + maxAnisotropy: (minFilter === "linear" && magFilter === "linear" && mipmapFilter === "linear") ? maxAnisotropy : 1, + }); - // - watcher.watchobject(sampler, defaultSampler, () => - { - // 移除监听,删除缓存 - watcher.unwatchobject(sampler, defaultSampler); - samplerMap.delete(sampler); - // - anyEmitter.emit(sampler, IGPUSampler_changed); + return gSampler; }); + getGPUSamplerMap.set(getGPUSamplerKey, result); - return gSampler; + return result.value; } -const samplerMap = new WeakMap(); +type GetGPUSamplerKey = [device: GPUDevice, sampler: Sampler]; +const getGPUSamplerMap = new ChainMap>; /** * GPU采样器默认值。 */ -const defaultSampler: ISampler = { +const defaultSampler: Sampler = { addressModeU: undefined, addressModeV: undefined, addressModeW: undefined, diff --git a/src/caches/getGPUShaderModule.ts b/src/caches/getGPUShaderModule.ts index 16a4efbcbc526e0511a2de1328089100b5c25c94..585554ebf0d9f8a49c7266a999003b39ae46e0da 100644 --- a/src/caches/getGPUShaderModule.ts +++ b/src/caches/getGPUShaderModule.ts @@ -1,14 +1,17 @@ +import { ChainMap } from "@feng3d/render-api"; + export function getGPUShaderModule(device: GPUDevice, code: string) { - let gShaderModule = shaderMap.get(code); + const getGPUShaderModuleKey: GetGPUShaderModuleKey = [device, code]; + let gShaderModule = getGPUShaderModuleMap.get(getGPUShaderModuleKey); if (gShaderModule) return gShaderModule; gShaderModule = device.createShaderModule({ code, }); - shaderMap.set(code, gShaderModule); + getGPUShaderModuleMap.set(getGPUShaderModuleKey, gShaderModule); return gShaderModule; } - -const shaderMap = new Map(); +type GetGPUShaderModuleKey = [device: GPUDevice, code: string]; +const getGPUShaderModuleMap = new ChainMap; \ No newline at end of file diff --git a/src/caches/getGPUTexture.ts b/src/caches/getGPUTexture.ts index 482d1c1a0b11b0c5d32a1ba1cf1ea050407a3ef5..1b931c6a53d31c858c9c485b7a1dfc782212f4f9 100644 --- a/src/caches/getGPUTexture.ts +++ b/src/caches/getGPUTexture.ts @@ -1,12 +1,8 @@ -import { anyEmitter } from "@feng3d/event"; -import { getTexImageSourceSize, getTextureBytesPerPixel, ITexture, ITextureDataSource, ITextureImageSource, ITextureLike, ITextureSize, ITextureSource } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { IGPUCanvasTexture } from "../data/IGPUCanvasTexture"; -import { GPUTexture_destroy, IGPUTexture_resize } from "../eventnames"; -import { IGPUTextureMultisample } from "../internal/IGPUTextureMultisample"; +import { ChainMap, computed, Computed, reactive, Texture, TextureDataSource, TextureDimension, TextureImageSource, TextureLike, TextureSource } from "@feng3d/render-api"; +import { webgpuEvents } from "../eventnames"; +import { MultisampleTexture } from "../internal/MultisampleTexture"; import { generateMipmap } from "../utils/generate-mipmap"; import { getGPUCanvasContext } from "./getGPUCanvasContext"; -import { getGPUTextureDimension } from "./getGPUTextureDimension"; import { getTextureUsageFromFormat } from "./getTextureUsageFromFormat"; /** @@ -16,31 +12,48 @@ import { getTextureUsageFromFormat } from "./getTextureUsageFromFormat"; * @param iGPUTextureBase 纹理描述。 * @returns GPU纹理。 */ -export function getGPUTexture(device: GPUDevice, textureLike: ITextureLike, autoCreate = true) +export function getGPUTexture(device: GPUDevice, textureLike: TextureLike, autoCreate = true) { - let gpuTexture: GPUTexture; - if ("context" in textureLike) - { - const canvasTexture = textureLike as IGPUCanvasTexture; - const context = getGPUCanvasContext(device, canvasTexture.context); + const getGPUTextureKey: GetGPUTextureMap = [device, textureLike]; + let result = getGPUTextureMap.get(getGPUTextureKey); + if (result) return result.value; - gpuTexture = context.getCurrentTexture(); + if (!autoCreate) return null; - return gpuTexture; - } + result = computed(() => + { + if ("context" in textureLike) + { + const canvasTexture = textureLike; - const texture = textureLike as ITexture; + // 确保在提交之前使用正确的画布纹理。 + reactive(webgpuEvents).preSubmit; + reactive(canvasTexture)._canvasSizeVersion; - const textureMap: Map = device[_GPUTextureMap] = device[_GPUTextureMap] || new Map(); - gpuTexture = textureMap.get(texture); - if (gpuTexture) return gpuTexture; + const context = getGPUCanvasContext(device, canvasTexture.context); - if (!autoCreate) return null; + const gpuTexture = context.getCurrentTexture(); + gpuTexture.label = "GPU画布纹理"; - // 创建纹理 - const createTexture = () => - { - const { format, sampleCount, dimension, viewFormats } = texture as IGPUTextureMultisample; + return gpuTexture; + } + + const texture = textureLike as MultisampleTexture; + + // 监听 + const r_texture = reactive(texture); + r_texture.format; + r_texture.sampleCount; + r_texture.dimension; + r_texture.viewFormats; + r_texture.generateMipmap; + r_texture.mipLevelCount; + r_texture.size[0]; + r_texture.size[1]; + r_texture.size[2]; + + // 执行 + const { format, sampleCount, dimension, viewFormats } = texture; let { label, mipLevelCount } = texture; const size = texture.size; @@ -55,16 +68,17 @@ export function getGPUTexture(device: GPUDevice, textureLike: ITextureLike, auto const maxSize = Math.max(size[0], size[1]); mipLevelCount = 1 + Math.log2(maxSize) | 0; } - mipLevelCount = (texture as any).mipLevelCount = mipLevelCount || 1; + mipLevelCount = mipLevelCount ?? 1; if (label === undefined) { label = `GPUTexture ${autoIndex++}`; } - const textureDimension = getGPUTextureDimension(dimension); + const textureDimension = dimensionMap[dimension]; - gpuTexture = device.createTexture({ + // 创建纹理 + const gpuTexture = device.createTexture({ label, size, mipLevelCount, @@ -74,179 +88,168 @@ export function getGPUTexture(device: GPUDevice, textureLike: ITextureLike, auto usage, viewFormats, }); + textureMap.get([device, textureLike])?.destroy(); // 销毁旧的纹理 + textureMap.set([device, textureLike], gpuTexture); - textureMap.set(texture, gpuTexture); - }; - createTexture(); + // 初始化纹理内容 + updateSources(texture); + updateWriteTextures(device, gpuTexture, texture); - // 更新纹理 - const updateSources = () => - { - if (texture.sources) + // 自动生成 mipmap。 + if (texture.generateMipmap) { - const writeTextures: ITextureSource[] = []; - texture.sources.forEach((v) => - { - writeTextures.push(v); - }); - texture.writeTextures = writeTextures.concat(texture.writeTextures || []); + generateMipmap(device, gpuTexture); } - }; - updateSources(); - watcher.watch(texture, "sources", updateSources); - const updateWriteTextures = () => + return gpuTexture; + }); + getGPUTextureMap.set(getGPUTextureKey, result); + + return result.value; +} +let autoIndex = 0; +type GetGPUTextureMap = [device: GPUDevice, texture: TextureLike]; +const getGPUTextureMap = new ChainMap>; + +const textureMap = new ChainMap<[device: GPUDevice, texture: Texture], GPUTexture>(); + +/** + * 更新纹理 + * @param texture + */ +function updateSources(texture: Texture) +{ + computed(() => + { + const r_texture = reactive(texture); + r_texture.sources; + + if (!texture.sources) return; + + const writeTextures: TextureSource[] = []; + texture.sources.forEach((v) => + { + writeTextures.push(v); + }); + reactive(texture).writeTextures = writeTextures.concat(texture.writeTextures || []); + }).value; +}; + +function updateWriteTextures(device: GPUDevice, gpuTexture: GPUTexture, texture: Texture) +{ + computed(() => { - if (texture.writeTextures) + // 监听 + const r_texture = reactive(texture) + r_texture.writeTextures + + // 执行 + if (!texture.writeTextures) return; + + const { writeTextures, format } = texture; + reactive(texture).writeTextures = null; + + writeTextures.forEach((v) => { - texture.writeTextures.forEach((v) => + // 处理图片纹理 + const imageSource = v as TextureImageSource; + if (imageSource.image) { - // 处理图片纹理 - const imageSource = v as ITextureImageSource; - if (imageSource.image) + const { image, flipY, colorSpace, premultipliedAlpha, mipLevel, textureOrigin, aspect } = imageSource; + + // + const imageSize = TextureImageSource.getTexImageSourceSize(imageSource.image); + const copySize = imageSource.size || imageSize; + + let imageOrigin = imageSource.imageOrigin; + + // 转换为WebGPU翻转模式 + if (flipY) { - const { image, flipY, colorSpace, premultipliedAlpha, mipLevel, textureOrigin, aspect } = imageSource; - - // - const copySize = imageSource.size || getTexImageSourceSize(imageSource.image); - - let imageOrigin = imageSource.imageOrigin; - - // 转换为WebGPU翻转模式 - if (flipY) - { - const x = imageOrigin?.[0]; - let y = imageOrigin?.[1]; - - const imageSize = getTexImageSourceSize(image); - y = imageSize[1] - y - copySize[1]; - - imageOrigin = [x, y]; - } - - // - const gpuSource: GPUImageCopyExternalImage = { - source: image, - origin: imageOrigin, - flipY, - }; - - // - const gpuDestination: GPUImageCopyTextureTagged = { - colorSpace, - premultipliedAlpha, - mipLevel, - origin: textureOrigin, - aspect, - texture: gpuTexture, - }; - - device.queue.copyExternalImageToTexture( - gpuSource, - gpuDestination, - copySize - ); - -return; + const x = imageOrigin?.[0] ?? 0; + let y = imageOrigin?.[1] ?? 0; + + y = imageSize[1] - y - copySize[1]; + + imageOrigin = [x, y]; } - // 处理数据纹理 - const bufferSource = v as ITextureDataSource; - const { data, dataLayout, dataImageOrigin, size, mipLevel, textureOrigin, aspect } = bufferSource; + // + const gpuSource: GPUImageCopyExternalImage = { + source: image, + origin: imageOrigin, + flipY, + }; - const gpuDestination: GPUImageCopyTexture = { + // + const gpuDestination: GPUImageCopyTextureTagged = { + colorSpace, + premultipliedAlpha, mipLevel, origin: textureOrigin, aspect, texture: gpuTexture, }; - // 计算 WebGPU 中支持的参数 - const offset = dataLayout?.offset || 0; - const width = dataLayout?.width || size[0]; - const height = dataLayout?.height || size[1]; - const x = dataImageOrigin?.[0] || 0; - const y = dataImageOrigin?.[1] || 0; - const depthOrArrayLayers = dataImageOrigin?.[2] || 0; - - // 获取纹理每个像素对应的字节数量。 - const bytesPerPixel = getTextureBytesPerPixel(texture.format); - - // 计算偏移 - const gpuOffset - = (offset || 0) // 头部 - + (depthOrArrayLayers || 0) * (width * height * bytesPerPixel) // 读取第几张图片 - + (x + (y * width)) * bytesPerPixel // 读取图片位置 - ; - - const gpuDataLayout: GPUImageDataLayout = { - offset: gpuOffset, - bytesPerRow: width * bytesPerPixel, - rowsPerImage: height, - }; - - device.queue.writeTexture( + device.queue.copyExternalImageToTexture( + gpuSource, gpuDestination, - data, - gpuDataLayout, - size, + copySize ); - }); - texture.writeTextures = null; - } - }; - updateWriteTextures(); - watcher.watch(texture, "writeTextures", updateWriteTextures); - - // 监听纹理尺寸发生变化 - const resize = (newValue: ITextureSize, oldValue: ITextureSize) => - { - if (!!newValue && !!oldValue) - { - if (newValue[0] === oldValue[0] - && newValue[1] === oldValue[1] - && (newValue[2] || 1) === (oldValue[2] || 1) - ) - { return; } - } - gpuTexture.destroy(); - // - anyEmitter.emit(texture, IGPUTexture_resize); - }; - watcher.watch(texture, "size", resize); - - // 自动生成 mipmap。 - if (texture.generateMipmap) - { - generateMipmap(device, gpuTexture); - } - - // - ((oldDestroy) => - { - gpuTexture.destroy = () => - { - oldDestroy.apply(gpuTexture); - // - textureMap.delete(texture); - // - watcher.unwatch(texture, "size", resize); - watcher.unwatch(texture, "sources", updateSources); - watcher.unwatch(texture, "writeTextures", updateWriteTextures); - - // 派发销毁事件 - anyEmitter.emit(gpuTexture, GPUTexture_destroy); - - return undefined; - }; - })(gpuTexture.destroy); - - return gpuTexture; -} -let autoIndex = 0; - -const _GPUTextureMap = "_GPUTextureMap"; \ No newline at end of file + // 处理数据纹理 + const bufferSource = v as TextureDataSource; + const { data, dataLayout, dataImageOrigin, size, mipLevel, textureOrigin, aspect } = bufferSource; + + const gpuDestination: GPUImageCopyTexture = { + mipLevel, + origin: textureOrigin, + aspect, + texture: gpuTexture, + }; + + // 计算 WebGPU 中支持的参数 + const offset = dataLayout?.offset || 0; + const width = dataLayout?.width || size[0]; + const height = dataLayout?.height || size[1]; + const x = dataImageOrigin?.[0] || 0; + const y = dataImageOrigin?.[1] || 0; + const depthOrArrayLayers = dataImageOrigin?.[2] || 0; + + // 获取纹理每个像素对应的字节数量。 + const bytesPerPixel = Texture.getTextureBytesPerPixel(format); + + // 计算偏移 + const gpuOffset + = (offset || 0) // 头部 + + (depthOrArrayLayers || 0) * (width * height * bytesPerPixel) // 读取第几张图片 + + (x + (y * width)) * bytesPerPixel // 读取图片位置 + ; + + const gpuDataLayout: GPUImageDataLayout = { + offset: gpuOffset, + bytesPerRow: width * bytesPerPixel, + rowsPerImage: height, + }; + + device.queue.writeTexture( + gpuDestination, + data, + gpuDataLayout, + size, + ); + }); + }).value; +}; + +const dimensionMap: Record = { + "1d": "1d", + "2d": "2d", + "2d-array": "2d", + cube: "2d", + "cube-array": "3d", + "3d": "3d", +}; diff --git a/src/caches/getGPUTextureDimension.ts b/src/caches/getGPUTextureDimension.ts deleted file mode 100644 index b1142e5c52220c248c8230e58f25848cb66e8f96..0000000000000000000000000000000000000000 --- a/src/caches/getGPUTextureDimension.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ITextureDimension } from "@feng3d/render-api"; - -export function getGPUTextureDimension(dimension: ITextureDimension) -{ - const textureDimension: GPUTextureDimension = dimensionMap[dimension]; - -return textureDimension; -} -const dimensionMap: { [key: string]: GPUTextureDimension } = { - "1d": "1d", - "2d": "2d", - "2d-array": "2d", - cube: "2d", - "cube-array": "3d", - "3d": "3d", -}; diff --git a/src/caches/getGPUTextureFormat.ts b/src/caches/getGPUTextureFormat.ts index fc6b04b0cc9ae36d239e7cccf86342096cc39650..3135a4b6d00a7a95ef3ec03fa3288844665874b9 100644 --- a/src/caches/getGPUTextureFormat.ts +++ b/src/caches/getGPUTextureFormat.ts @@ -1,4 +1,4 @@ -import { ITextureLike } from "@feng3d/render-api"; +import { computed, Computed, reactive, TextureLike } from "@feng3d/render-api"; /** * 获取纹理格式。 @@ -6,16 +6,31 @@ import { ITextureLike } from "@feng3d/render-api"; * @param texture 纹理。 * @returns 纹理格式。 */ -export function getGPUTextureFormat(texture: ITextureLike) +export function getGPUTextureFormat(texture: TextureLike): GPUTextureFormat { if (!texture) return undefined; - if ("context" in texture) + let result = getGPUTextureFormatMap.get(texture); + if (result) return result.value; + + result = computed(() => { - const format = texture.context?.configuration?.format || navigator.gpu.getPreferredCanvasFormat(); + // 监听 + const r_texture = reactive(texture); + + // 计算 + if ("context" in r_texture) + { + const format = r_texture.context?.configuration?.format || navigator.gpu.getPreferredCanvasFormat(); - return format; - } + return format; + } - return texture.format; + return r_texture.format; + }); + getGPUTextureFormatMap.set(texture, result); + + return result.value; } + +const getGPUTextureFormatMap = new WeakMap>(); \ No newline at end of file diff --git a/src/caches/getGPUTextureView.ts b/src/caches/getGPUTextureView.ts index 89560d5be7bd4233678594218b7d4897c4611815..b7f482f2f8c554164a45a8b84254d871fd0fcaa8 100644 --- a/src/caches/getGPUTextureView.ts +++ b/src/caches/getGPUTextureView.ts @@ -1,40 +1,51 @@ -import { anyEmitter } from "@feng3d/event"; -import { ITexture, ITextureView } from "@feng3d/render-api"; -import { IGPUCanvasTexture } from "../data/IGPUCanvasTexture"; -import { GPUTexture_destroy, GPUTextureView_destroy } from "../eventnames"; +import { ChainMap, computed, Computed, reactive, Texture, TextureView } from "@feng3d/render-api"; import { getGPUTexture } from "./getGPUTexture"; -export function getGPUTextureView(device: GPUDevice, view: ITextureView) +export function getGPUTextureView(device: GPUDevice, view: TextureView) { - const textureViewMap: WeakMap = device["_textureViewMap"] = device["_textureViewMap"] || new WeakMap(); + if (!view) return undefined; - if ((view.texture as IGPUCanvasTexture).context) - { - const texture = getGPUTexture(device, view.texture); + const getGPUTextureViewKey: GetGPUTextureViewKey = [device, view]; + let result = getGPUTextureViewMap.get(getGPUTextureViewKey); + if (result) return result.value; - const textureView = texture.createView(view); + result = computed(() => + { + // 监听 + const r_view = reactive(view); + r_view.texture; + r_view.label; + r_view.format; + r_view.dimension; + r_view.usage; + r_view.aspect; + r_view.baseMipLevel; + r_view.baseArrayLayer; + r_view.mipLevelCount; + r_view.arrayLayerCount; + + // 执行 + const { texture, label, format, dimension, usage, aspect, baseMipLevel, baseArrayLayer, mipLevelCount, arrayLayerCount } = view; + const gpuTexture = getGPUTexture(device, texture); + const textureView = gpuTexture.createView({ + label: label ?? `${gpuTexture.label}视图`, + format, + dimension: dimension ?? (texture as Texture).dimension, + usage, + aspect, + baseMipLevel, + mipLevelCount, + baseArrayLayer, + arrayLayerCount, + + }); return textureView; - } - - // - let textureView = textureViewMap.get(view); - if (textureView) return textureView; - - // - const texture = view.texture as ITexture; - const gpuTexture = getGPUTexture(device, texture); - const dimension = view.dimension ?? texture.dimension; - - textureView = gpuTexture.createView({ ...view, dimension }); - textureViewMap.set(view, textureView); - // 销毁纹理时清除对应的纹理视图。 - anyEmitter.once(gpuTexture, GPUTexture_destroy, () => - { - textureViewMap.delete(view); - anyEmitter.emit(textureView, GPUTextureView_destroy); }); + getGPUTextureViewMap.set(getGPUTextureViewKey, result); - return textureView; + return result.value; } +type GetGPUTextureViewKey = [device: GPUDevice, view: TextureView]; +const getGPUTextureViewMap = new ChainMap>; diff --git a/src/caches/getIGPUBuffer.ts b/src/caches/getIGPUBuffer.ts index 96bc2f3a456480a031e7cb55feabc2520dcb8881..f3f924e28d2988fbc06684464a21ece2513a3097 100644 --- a/src/caches/getIGPUBuffer.ts +++ b/src/caches/getIGPUBuffer.ts @@ -1,6 +1,6 @@ -import { IBuffer, IVertexDataTypes, TypedArray, UnReadonly } from "@feng3d/render-api"; +import { Buffer, TypedArray } from "@feng3d/render-api"; -export function getIGPUBuffer(bufferSource: TypedArray) +export function getGBuffer(bufferSource: TypedArray) { let arrayBuffer = bufferSource as ArrayBuffer; if ((bufferSource as ArrayBufferView).buffer) @@ -8,28 +8,16 @@ export function getIGPUBuffer(bufferSource: TypedArray) arrayBuffer = (bufferSource as ArrayBufferView).buffer; } - const gpuBuffer: IBuffer = arrayBuffer["_IGPUBuffer"] = arrayBuffer["_IGPUBuffer"] || { + let gpuBuffer = bufferMap.get(arrayBuffer); + if (gpuBuffer) return gpuBuffer; + + gpuBuffer = { size: Math.ceil(arrayBuffer.byteLength / 4) * 4, - data: arrayBuffer, + data: bufferSource, }; + bufferMap.set(arrayBuffer, gpuBuffer); return gpuBuffer; } -export function getIGPUVertexBuffer(data: IVertexDataTypes) -{ - const buffer = getIGPUBuffer(data); - (buffer as any).label = buffer.label || (`顶点属性 ${autoVertexIndex++}`); - - return buffer; -} -let autoVertexIndex = 0; - -export function getIGPUIndexBuffer(data: Uint16Array | Uint32Array) -{ - const buffer = getIGPUBuffer(data); - (buffer as UnReadonly).label = buffer.label || (`顶点索引 ${autoIndex++}`); - - return buffer; -} -let autoIndex = 0; \ No newline at end of file +const bufferMap = new WeakMap(); \ No newline at end of file diff --git a/src/caches/getIGPUComputePipeline.ts b/src/caches/getIGPUComputePipeline.ts deleted file mode 100644 index 82e8a2004d615a74fe7150db74349e69aacd9746..0000000000000000000000000000000000000000 --- a/src/caches/getIGPUComputePipeline.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { FunctionInfo } from "wgsl_reflect"; -import { getWGSLReflectInfo } from "./getWGSLReflectInfo"; -import { IGPUComputePipeline } from "../data/IGPUComputePipeline"; -import { IGPUComputeStage } from "../data/IGPUComputeStage"; - -/** - * 从渲染管线描述、渲染通道描述以及完整的顶点属性数据映射获得完整的渲染管线描述以及顶点缓冲区数组。 - *获取完整的计算管线描述。 - * - * @param computePipeline 计算管线描述。 - * @returns 完整的计算管线描述。 - */ -export function getIGPUComputePipeline(computePipeline: IGPUComputePipeline): IGPUComputePipeline -{ - let gpuComputePipeline = computePipelineMap.get(computePipeline); - if (gpuComputePipeline) return gpuComputePipeline; - - const gpuComputeStage = getIGPUComputeStage(computePipeline.compute); - - gpuComputePipeline = { - ...computePipeline, - compute: gpuComputeStage, - }; - - computePipelineMap.set(computePipeline, gpuComputePipeline); - - return gpuComputePipeline; -} - -const computePipelineMap = new Map(); - -/** -* 获取计算阶段完整描述。 -* -* @param computeStage 计算阶段描述。 -* @returns 计算阶段完整描述。 -*/ -function getIGPUComputeStage(computeStage: IGPUComputeStage) -{ - const reflect = getWGSLReflectInfo(computeStage.code); - let compute: FunctionInfo; - if (!computeStage.entryPoint) - { - compute = reflect.entry.compute[0]; - console.assert(!!compute, `WGSL着色器 ${computeStage.code} 中不存在计算入口点。`); - (computeStage as any).entryPoint = compute.name; - } - else - { - // 验证着色器中包含指定片段入口函数。 - compute = reflect.entry.compute.filter((v) => v.name === computeStage.entryPoint)[0]; - console.assert(!!compute, `WGSL着色器 ${computeStage.code} 中不存在指定的计算入口点 ${computeStage.entryPoint}`); - } - - return computeStage; -} diff --git a/src/caches/getIGPUPipelineLayout.ts b/src/caches/getIGPUPipelineLayout.ts deleted file mode 100644 index 2c97182fc8978d3986dc94048910422e7f1005b6..0000000000000000000000000000000000000000 --- a/src/caches/getIGPUPipelineLayout.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { IGPUBindGroupLayoutDescriptor, IGPUPipelineLayoutDescriptor } from "../internal/IGPUPipelineLayoutDescriptor"; -import { getIGPUBindGroupLayoutEntryMap, IGPUBindGroupLayoutEntryMap } from "./getWGSLReflectInfo"; - -export type IGPUShader = { readonly vertex?: string, readonly fragment?: string, readonly compute?: string }; - -export function getIGPUShaderKey(shader: IGPUShader) -{ - return shader.vertex + shader.fragment + shader.compute; -} - -/** - * 从GPU管线中获取管线布局。 - * - * @param shader GPU管线。 - * @returns 管线布局。 - */ -export function getIGPUPipelineLayout(shader: IGPUShader): IGPUPipelineLayoutDescriptor -{ - const shaderKey = getIGPUShaderKey(shader); - - // - let gpuPipelineLayout = gpuPipelineLayoutMap[shaderKey]; - if (gpuPipelineLayout) return gpuPipelineLayout; - - const vertexCode = shader.vertex; - const fragmentCode = shader.fragment; - const computeCode = shader.compute; - - let entryMap: IGPUBindGroupLayoutEntryMap = {}; - if (vertexCode) - { - const vertexEntryMap = getIGPUBindGroupLayoutEntryMap(vertexCode); - entryMap = mergeBindGroupLayouts(entryMap, vertexEntryMap); - } - if (fragmentCode && fragmentCode !== vertexCode) - { - const fragmentEntryMap = getIGPUBindGroupLayoutEntryMap(fragmentCode); - entryMap = mergeBindGroupLayouts(entryMap, fragmentEntryMap); - } - if (computeCode && computeCode !== vertexCode && computeCode !== fragmentCode) - { - const computeEntryMap = getIGPUBindGroupLayoutEntryMap(computeCode); - entryMap = mergeBindGroupLayouts(entryMap, computeEntryMap); - } - - // - const bindGroupLayouts: IGPUBindGroupLayoutDescriptor[] = []; - for (const resourceName in entryMap) - { - const bindGroupLayoutEntry = entryMap[resourceName]; - const { group, binding } = bindGroupLayoutEntry.variableInfo; - // - const bindGroupLayout = bindGroupLayouts[group] = bindGroupLayouts[group] || { entries: [], entryNames: [] }; - - // 检测相同位置是否存在多个定义 - if (bindGroupLayout.entries[binding]) - { - // 存在重复定义时,判断是否兼容 - const preEntry = bindGroupLayout.entries[binding]; - console.error(`在管线中 @group(${group}) @binding(${binding}) 处存在多个定义 ${preEntry.variableInfo.name} ${resourceName} 。`); - } - - // - bindGroupLayout.entries[binding] = bindGroupLayoutEntry; - bindGroupLayout.entryNames.push(resourceName); - } - - // 排除 undefined 元素。 - for (let i = 0; i < bindGroupLayouts.length; i++) - { - const entries = bindGroupLayouts[i].entries as GPUBindGroupLayoutEntry[]; - for (let i = entries.length - 1; i >= 0; i--) - { - if (!entries[i]) - { - entries.splice(i, 1); - } - } - } - - // - gpuPipelineLayout = gpuPipelineLayoutMap[shaderKey] = { bindGroupLayouts }; - - return gpuPipelineLayout; -} - -function mergeBindGroupLayouts(entryMap: IGPUBindGroupLayoutEntryMap, entryMap1: IGPUBindGroupLayoutEntryMap): IGPUBindGroupLayoutEntryMap -{ - for (const resourceName in entryMap1) - { - // 检测相同名称是否被定义在多个地方 - if (entryMap[resourceName]) - { - const preEntry = entryMap[resourceName].variableInfo; - const currentEntry = entryMap1[resourceName].variableInfo; - - if (preEntry.group !== currentEntry.group - || preEntry.binding !== currentEntry.binding - || preEntry.resourceType !== currentEntry.resourceType - ) - { - console.warn(`分别在 着色器 @group(${preEntry.group}) @binding(${preEntry.binding}) 与 @group(${currentEntry.group}) @binding(${currentEntry.binding}) 处存在相同名称的变量 ${currentEntry.name} 。`); - } - } - entryMap[resourceName] = entryMap1[resourceName]; - } - - return entryMap; -} - -const gpuPipelineLayoutMap: { [key: string]: IGPUPipelineLayoutDescriptor } = {}; diff --git a/src/caches/getIGPUSetBindGroups.ts b/src/caches/getIGPUSetBindGroups.ts deleted file mode 100644 index 3a15dc46341a0bde2339f8edb4c1a86cac41dbe8..0000000000000000000000000000000000000000 --- a/src/caches/getIGPUSetBindGroups.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { watcher } from "@feng3d/watcher"; - -import { IBufferBinding, IUniforms } from "@feng3d/render-api"; -import { getIGPUPipelineLayout, getIGPUShaderKey, IGPUShader } from "../caches/getIGPUPipelineLayout"; -import { IGPUBindGroupEntry } from "../internal/IGPUBindGroupDescriptor"; -import { IGPUBindGroupLayoutDescriptor } from "../internal/IGPUPipelineLayoutDescriptor"; -import { IGPUSetBindGroup } from "../internal/IGPUSetBindGroup"; -import { ChainMap } from "../utils/ChainMap"; -import { getBufferBindingInfo, IBufferBindingInfo } from "../utils/getBufferBindingInfo"; -import { updateBufferBinding } from "../utils/updateBufferBinding"; -import { getIGPUBuffer } from "./getIGPUBuffer"; - -export function getIGPUSetBindGroups(shader: IGPUShader, bindingResources: IUniforms) -{ - const shaderKey = getIGPUShaderKey(shader); - // - let gpuSetBindGroups = bindGroupsMap.get([shaderKey, bindingResources]); - if (gpuSetBindGroups) return gpuSetBindGroups; - - gpuSetBindGroups = []; - bindGroupsMap.set([shaderKey, bindingResources], gpuSetBindGroups); - - // - const layout = getIGPUPipelineLayout(shader); - layout.bindGroupLayouts.forEach((bindGroupLayout, group) => - { - gpuSetBindGroups[group] = getIGPUSetBindGroup(bindGroupLayout, bindingResources); - }); - - return gpuSetBindGroups; -} - -const bindGroupsMap = new ChainMap<[string, IUniforms], IGPUSetBindGroup[]>(); - -function getIGPUSetBindGroup(bindGroupLayout: IGPUBindGroupLayoutDescriptor, bindingResources: IUniforms): IGPUSetBindGroup -{ - const map: ChainMap, IGPUSetBindGroup> = bindGroupLayout["_bindingResources"] = bindGroupLayout["_bindingResources"] || new ChainMap(); - const subBindingResources = bindGroupLayout.entryNames.map((v) => bindingResources[v]); - let setBindGroup: IGPUSetBindGroup = map.get(subBindingResources); - if (setBindGroup) return setBindGroup; - - const entries: IGPUBindGroupEntry[] = []; - setBindGroup = { bindGroup: { layout: bindGroupLayout, entries } }; - map.set(subBindingResources, setBindGroup); - - // - bindGroupLayout.entries.forEach((entry1) => - { - const { variableInfo, binding } = entry1; - // - const entry: IGPUBindGroupEntry = { binding, resource: null }; - - entries.push(entry); - - const resourceName = variableInfo.name; - - const updateResource = () => - { - const bindingResource = bindingResources[resourceName]; - console.assert(!!bindingResource, `在绑定资源中没有找到 ${resourceName} 。`); - - // - if (entry1.buffer) - { - const bufferBinding = ((typeof bindingResource === "number") ? [bindingResource] : bindingResource) as IBufferBinding; // 值为number且不断改变时将可能会产生无数细碎gpu缓冲区。 - const bufferBindingInfo: IBufferBindingInfo = variableInfo["_bufferBindingInfo"] ||= getBufferBindingInfo(variableInfo.type); - // 更新缓冲区绑定的数据。 - updateBufferBinding(resourceName, bufferBindingInfo, bufferBinding); - // - const buffer = getIGPUBuffer(bufferBinding.bufferView); - (buffer as any).label = buffer.label || (`BufferBinding ${variableInfo.name}`); - // - entry.resource = bufferBinding; - } - else - { - entry.resource = bindingResource; - } - }; - - // - updateResource(); - watcher.watch(bindingResources, resourceName, updateResource); - }); - - return setBindGroup; -} diff --git a/src/caches/getIGPUTextureSize.ts b/src/caches/getIGPUTextureSize.ts deleted file mode 100644 index 0fca5e44d2613344c9730f0d4eab1ea029f619b4..0000000000000000000000000000000000000000 --- a/src/caches/getIGPUTextureSize.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { getTexImageSourceSize, ITextureImageSource, ITextureLike, ITextureSize } from "@feng3d/render-api"; -import { IGPUCanvasTexture } from "../data/IGPUCanvasTexture"; - -/** - * 获取纹理尺寸。 - * - * @param texture 纹理。 - * @returns 纹理尺寸。 - */ -export function getIGPUTextureLikeSize(texture: ITextureLike) -{ - if ("context" in texture) - { - const element = document.getElementById(texture.context.canvasId) as HTMLCanvasElement; - console.assert(!!element, `在 document 上没有找到 canvasId 为 ${(texture as IGPUCanvasTexture).context.canvasId} 的画布。`); - - return [element.width, element.height, 1] as ITextureSize; - } - - return texture.size; -} - -export function getIGPUTextureSourceSize(source?: ITextureImageSource[]): ITextureSize -{ - if (!source) return undefined; - - let width: number; - let height: number; - let maxDepthOrArrayLayers = 0; - - for (let i = 0; i < source.length; i++) - { - const element = source[i]; - // 获取mipLevel为0的资源尺寸。 - if (!element.mipLevel) - { - const copySize = element.size || getTexImageSourceSize(element.image); - if (width || height) - { - console.assert(width === copySize[0] && height === copySize[1], `纹理资源中提供的尺寸不正确!`); - } - else - { - width = copySize[0]; - height = copySize[1]; - } - - maxDepthOrArrayLayers = Math.max(maxDepthOrArrayLayers, element.textureOrigin?.[2] || 0); - } - } - - console.assert(width > 0 && height > 0, `没有从纹理资源中找到合适的尺寸!`); - - return [width, height, maxDepthOrArrayLayers + 1]; // 总深度比最大深度大1 -} \ No newline at end of file diff --git a/src/caches/getNGPURenderPipeline.ts b/src/caches/getNGPURenderPipeline.ts deleted file mode 100644 index 0690da6c810bbc12b970a2c83ab3ed67ed7fcfc6..0000000000000000000000000000000000000000 --- a/src/caches/getNGPURenderPipeline.ts +++ /dev/null @@ -1,532 +0,0 @@ -import { getBlendConstantColor, IBlendState, IDepthStencilState, IFragmentState, IIndicesDataTypes, IPrimitiveState, IRenderPipeline, IStencilFaceState, IVertexAttributes, IVertexState, IWriteMask } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { FunctionInfo, TemplateInfo, TypeInfo } from "wgsl_reflect"; - -import { gPartial } from "@feng3d/polyfill"; -import { IGPUMultisampleState } from "../data/IGPUMultisampleState"; -import { getIGPUSetIndexBuffer } from "../internal/getIGPUSetIndexBuffer"; -import { IGPURenderPassFormat } from "../internal/IGPURenderPassFormat"; -import { NGPUFragmentState } from "../internal/NGPUFragmentState"; -import { NGPURenderPipeline } from "../internal/NGPURenderPipeline"; -import { NGPUVertexBuffer } from "../internal/NGPUVertexBuffer"; -import { NGPUVertexState } from "../internal/NGPUVertexState"; -import { gpuVertexFormatMap, WGSLVertexType } from "../types/VertexFormat"; -import { ChainMap } from "../utils/ChainMap"; -import { getWGSLReflectInfo } from "./getWGSLReflectInfo"; - -/** - * 从渲染管线描述、渲染通道描述以及完整的顶点属性数据映射获得完整的渲染管线描述以及顶点缓冲区数组。 - * - * @param renderPipeline 渲染管线描述。 - * @param renderPass 渲染通道描述。 - * @param vertices 顶点属性数据映射。 - * @returns 完整的渲染管线描述以及顶点缓冲区数组。 - */ -export function getNGPURenderPipeline(renderPipeline: IRenderPipeline, renderPassFormat: IGPURenderPassFormat, vertices: IVertexAttributes, indices: IIndicesDataTypes) -{ - const indexFormat = indices ? getIGPUSetIndexBuffer(indices).indexFormat : undefined; - - let result = renderPipelineMap.get([renderPipeline, renderPassFormat._key, vertices, indexFormat]); - if (result) return result; - - const { label, primitive } = renderPipeline; - - const gpuPrimitive = getGPUPrimitiveState(primitive, indexFormat); - - // 获取完整的顶点阶段描述与顶点缓冲区列表。 - const vertexStateResult = getNGPUVertexState(renderPipeline.vertex, vertices); - - // 获取片段阶段完整描述。 - const gpuFragmentState = getNGPUFragmentState(renderPipeline.fragment, renderPassFormat.colorFormats); - - // 获取深度模板阶段完整描述。 - const gpuDepthStencilState = getGPUDepthStencilState(renderPipeline.depthStencil, renderPassFormat.depthStencilFormat); - - // 从渲染通道上获取多重采样数量 - const gpuMultisampleState = getGPUMultisampleState(renderPipeline.multisample, renderPassFormat.sampleCount); - - // - const stencilReference = getStencilReference(renderPipeline.depthStencil); - // - const blendConstantColor = getBlendConstantColor(renderPipeline.fragment?.targets?.[0]?.blend); - - // - const pipeline: NGPURenderPipeline = { - label, - primitive: gpuPrimitive, - vertex: vertexStateResult.gpuVertexState, - fragment: gpuFragmentState, - depthStencil: gpuDepthStencilState, - multisample: gpuMultisampleState, - stencilReference, - blendConstantColor, - }; - - result = { _version: 0, pipeline, vertexBuffers: vertexStateResult.vertexBuffers }; - renderPipelineMap.set([renderPipeline, renderPassFormat._key, vertices, indexFormat], result); - - // 监听管线变化 - const onchanged = () => - { - result._version++; - renderPipeline._version = ~~renderPipeline._version + 1; - renderPipelineMap.delete([renderPipeline, renderPassFormat._key, vertices, indexFormat]); - watcher.unwatch(vertexStateResult, "_version", onchanged); - watcher.unwatch(gpuFragmentState, "_version", onchanged); - } - watcher.watch(vertexStateResult, "_version", onchanged); - watcher.watch(gpuFragmentState, "_version", onchanged); - - return result; -} - -const renderPipelineMap = new ChainMap< - [IRenderPipeline, string, IVertexAttributes, GPUIndexFormat], - { - /** - * GPU渲染管线描述。 - */ - pipeline: NGPURenderPipeline; - /** - * GPU渲染时使用的顶点缓冲区列表。 - */ - vertexBuffers: NGPUVertexBuffer[]; - /** - * 版本号 - */ - _version: number; - } ->(); - -/** - * 如果任意模板测试结果使用了 "replace" 运算,则需要再渲染前设置 `stencilReference` 值。 - * - * @param depthStencil - * @returns - */ -function getStencilReference(depthStencil?: IDepthStencilState) -{ - if (!depthStencil) return undefined; - - const { stencilFront, stencilBack } = depthStencil; - - // 如果开启了模板测试,则需要设置模板索引值 - let stencilReference: number; - if (stencilFront) - { - const { failOp, depthFailOp, passOp } = stencilFront; - if (failOp === "replace" || depthFailOp === "replace" || passOp === "replace") - { - stencilReference = depthStencil?.stencilReference ?? 0; - } - } - if (stencilBack) - { - const { failOp, depthFailOp, passOp } = stencilBack; - if (failOp === "replace" || depthFailOp === "replace" || passOp === "replace") - { - stencilReference = depthStencil?.stencilReference ?? 0; - } - } - - return stencilReference; -} - -function getGPUPrimitiveState(primitive?: IPrimitiveState, indexFormat?: GPUIndexFormat) -{ - let stripIndexFormat: GPUIndexFormat; - if (primitive?.topology === "triangle-strip" || primitive?.topology === "line-strip") - { - stripIndexFormat = indexFormat; - } - - const topology: GPUPrimitiveTopology = primitive?.topology || "triangle-list"; - const cullMode: GPUCullMode = primitive?.cullFace || "none"; - const frontFace: GPUFrontFace = primitive?.frontFace || "ccw"; - const unclippedDepth: boolean = primitive?.unclippedDepth || false; - - // - const gpuPrimitive: GPUPrimitiveState = { - ...primitive, - topology, - stripIndexFormat, - frontFace, - cullMode, - unclippedDepth, - }; - - return gpuPrimitive; -} - -function getGPUMultisampleState(multisampleState?: IGPUMultisampleState, sampleCount?: 4) -{ - if (!sampleCount) return undefined; - - const gpuMultisampleState: GPUMultisampleState = { - ...multisampleState, - count: sampleCount, - }; - - return gpuMultisampleState; -} - -/** - * 获取深度模板阶段完整描述。 - * - * @param depthStencil 深度模板阶段描述。 - * @param depthStencilFormat 深度模板附件纹理格式。 - * @returns 深度模板阶段完整描述。 - */ -function getGPUDepthStencilState(depthStencil: IDepthStencilState, depthStencilFormat?: GPUTextureFormat) -{ - if (!depthStencilFormat) return undefined; - // - const gpuDepthStencilState: GPUDepthStencilState = { - format: depthStencilFormat, - depthWriteEnabled: depthStencil?.depthWriteEnabled ?? true, - depthCompare: depthStencil?.depthCompare ?? "less", - stencilFront: getGPUStencilFaceState(depthStencil?.stencilFront), - stencilBack: getGPUStencilFaceState(depthStencil?.stencilBack), - stencilReadMask: depthStencil?.stencilReadMask ?? 0xFFFFFFFF, - stencilWriteMask: depthStencil?.stencilWriteMask ?? 0xFFFFFFFF, - depthBias: depthStencil?.depthBias ?? 0, - depthBiasSlopeScale: depthStencil?.depthBiasSlopeScale ?? 0, - depthBiasClamp: depthStencil?.depthBiasClamp ?? 0, - }; - - return gpuDepthStencilState; -} - -function getGPUStencilFaceState(stencilFaceState?: IStencilFaceState) -{ - if (!stencilFaceState) return {}; - - const gpuStencilFaceState: GPUStencilFaceState = { - compare: stencilFaceState.compare ?? "always", - failOp: stencilFaceState.failOp ?? "keep", - depthFailOp: stencilFaceState.depthFailOp ?? "keep", - passOp: stencilFaceState.passOp ?? "keep", - }; - - return gpuStencilFaceState; -} - -/** - * 获取完整的顶点阶段描述与顶点缓冲区列表。 - * - * @param vertexState 顶点阶段信息。 - * @param vertices 顶点数据。 - * @returns 完整的顶点阶段描述与顶点缓冲区列表。 - */ -function getNGPUVertexState(vertexState: IVertexState, vertices: IVertexAttributes) -{ - let result = vertexStateMap.get([vertexState, vertices]); - if (result) return result; - - const code = vertexState.code; - - // 解析顶点着色器 - const reflect = getWGSLReflectInfo(code); - let vertex: FunctionInfo; - // - let entryPoint = vertexState.entryPoint; - if (!entryPoint) - { - console.assert(!!reflect.entry.vertex[0], `WGSL着色器 ${code} 中不存在顶点入口点。`); - entryPoint = reflect.entry.vertex[0].name; - } - - vertex = reflect.entry.vertex.filter((v) => v.name === entryPoint)[0]; - console.assert(!!vertex, `WGSL着色器 ${code} 中不存在顶点入口点 ${entryPoint} 。`); - - const { vertexBufferLayouts, vertexBuffers } = getNGPUVertexBuffers(vertex, vertices); - - const gpuVertexState: NGPUVertexState = { - code, - entryPoint, - buffers: vertexBufferLayouts, - constants: vertexState.constants, - }; - - // - result = { _version: 0, gpuVertexState, vertexBuffers }; - vertexStateMap.set([vertexState, vertices], result); - - // 监听变化 - const watchpropertys: gPartial = { code: "" }; - const onchanged = () => - { - vertexStateMap.delete([vertexState, vertices]); - watcher.unwatchobject(vertexState, watchpropertys, onchanged); - result._version++; - }; - watcher.watchobject(vertexState, watchpropertys, onchanged); - - return result; -} - -const vertexStateMap = new ChainMap<[IVertexState, IVertexAttributes], { - gpuVertexState: NGPUVertexState; - vertexBuffers: NGPUVertexBuffer[]; - /** - * 版本号,用于版本控制。 - */ - _version: number; -}>(); - -/** - * 从顶点属性信息与顶点数据中获取顶点缓冲区布局数组以及顶点缓冲区数组。 - * - * @param vertex 顶点着色器函数信息。 - * @param vertices 顶点数据。 - * @returns 顶点缓冲区布局数组以及顶点缓冲区数组。 - */ -function getNGPUVertexBuffers(vertex: FunctionInfo, vertices: IVertexAttributes) -{ - const vertexBufferLayouts: GPUVertexBufferLayout[] = []; - - const vertexBuffers: NGPUVertexBuffer[] = []; - - const map: WeakMap = new WeakMap(); - - vertex.inputs.forEach((v) => - { - // 跳过内置属性。 - if (v.locationType === "builtin") return; - - const shaderLocation = v.location as number; - const attributeName = v.name; - - const vertexAttribute = vertices[attributeName]; - console.assert(!!vertexAttribute, `在提供的顶点属性数据中未找到 ${attributeName} 。`); - // - const data = vertexAttribute.data; - const attributeOffset = vertexAttribute.offset || 0; - let arrayStride = vertexAttribute.arrayStride; - const stepMode = vertexAttribute.stepMode; - const format = vertexAttribute.format; - // 检查提供的顶点数据格式是否与着色器匹配 - // const wgslType = getWGSLType(v.type); - // let possibleFormats = wgslVertexTypeMap[wgslType].possibleFormats; - // console.assert(possibleFormats.indexOf(format) !== -1, `顶点${attributeName} 提供的数据格式 ${format} 与着色器中类型 ${wgslType} 不匹配!`); - console.assert(data.constructor.name === gpuVertexFormatMap[format].typedArrayConstructor.name, - `顶点${attributeName} 提供的数据类型 ${data.constructor.name} 与格式 ${format} 不匹配!请使用 ${data.constructor.name} 来组织数据或者更改数据格式。`); - - // 如果 偏移值大于 单个顶点尺寸,则该值被放入 IGPUVertexBuffer.offset。 - const vertexByteSize = gpuVertexFormatMap[format].byteSize; - // - if (!arrayStride) - { - arrayStride = vertexByteSize; - } - console.assert(attributeOffset + vertexByteSize <= arrayStride, `offset(${attributeOffset}) + vertexByteSize(${vertexByteSize}) 必须不超出 arrayStride(${arrayStride})。`); - - watcher.watch(vertexAttribute, "data", () => - { - const index = map.get(data); - const attributeData = vertexAttribute.data; - - vertexBuffers[index].data = attributeData; - vertexBuffers[index].offset = attributeData.byteOffset; - vertexBuffers[index].size = attributeData.byteLength; - }); - - let index = map.get(data); - if (index === undefined) - { - index = vertexBufferLayouts.length; - map.set(data, index); - - vertexBuffers[index] = { data, offset: data.byteOffset, size: data.byteLength }; - - // - vertexBufferLayouts[index] = { stepMode, arrayStride, attributes: [] }; - } - else - { - // 要求同一顶点缓冲区中 arrayStride 与 stepMode 必须相同。 - const gpuVertexBufferLayout = vertexBufferLayouts[index]; - console.assert(gpuVertexBufferLayout.arrayStride === arrayStride); - console.assert(gpuVertexBufferLayout.stepMode === stepMode); - } - - (vertexBufferLayouts[index].attributes as Array).push({ shaderLocation, offset: attributeOffset, format }); - }); - - return { vertexBufferLayouts, vertexBuffers }; -} - -/** - * 获取片段阶段完整描述。 - * - * @param fragmentState 片段简单阶段。 - * @param colorAttachmentTextureFormats 颜色附件格式。 - * @returns 片段阶段完整描述。 - */ -function getNGPUFragmentState(fragmentState: IFragmentState, colorAttachments: readonly GPUTextureFormat[]) -{ - if (!fragmentState) return undefined; - - const colorAttachmentsKey = colorAttachments.toString(); - - let gpuFragmentState: NGPUFragmentState = fragmentStateMap.get([fragmentState, colorAttachmentsKey]); - if (gpuFragmentState) return gpuFragmentState; - - const code = fragmentState.code; - let entryPoint = fragmentState.entryPoint; - let fragment: FunctionInfo; - const reflect = getWGSLReflectInfo(code); - if (!entryPoint) - { - fragment = reflect.entry.fragment[0]; - console.assert(!!fragment, `WGSL着色器 ${code} 中不存在片元入口点。`); - entryPoint = fragment.name; - } - else - { - // 验证着色器中包含指定片段入口函数。 - fragment = reflect.entry.fragment.filter((v) => v.name === entryPoint)[0]; - console.assert(!!fragment, `WGSL着色器 ${code} 中不存在指定的片元入口点 ${entryPoint} 。`); - } - - const targets = colorAttachments.map((format, i) => - { - if (!format) return undefined; - - const colorTargetState = fragmentState.targets?.[i]; - - // - const writeMask = getGPUColorWriteFlags(colorTargetState?.writeMask); - - const blend: GPUBlendState = getGPUBlendState(colorTargetState?.blend); - - // - const gpuColorTargetState: GPUColorTargetState = { - format, - blend, - writeMask, - }; - - return gpuColorTargetState; - }); - - gpuFragmentState = { - code, - entryPoint, - targets, - constants: fragmentState.constants - }; - - fragmentStateMap.set([fragmentState, colorAttachmentsKey], gpuFragmentState); - - // 监听变化 - const watchpropertys: gPartial = { code: "" }; - const onchanged = () => - { - fragmentStateMap.delete([fragmentState, colorAttachmentsKey]); - gpuFragmentState._version = ~~gpuFragmentState._version + 1; - watcher.unwatchobject(fragmentState, watchpropertys, onchanged); - }; - watcher.watchobject(fragmentState, watchpropertys, onchanged); - - return gpuFragmentState; -} - -const fragmentStateMap = new ChainMap<[IFragmentState, string], NGPUFragmentState>(); - -function getGPUBlendState(blend?: IBlendState): GPUBlendState -{ - if (!blend) undefined; - - // - const colorOperation: GPUBlendOperation = blend?.color?.operation || "add"; - let colorSrcFactor: GPUBlendFactor = blend?.color?.srcFactor || "one"; - let colorDstFactor: GPUBlendFactor = blend?.color?.dstFactor || "zero"; - if (colorOperation === "max" || colorOperation === "min") - { - colorSrcFactor = colorDstFactor = "one"; - } - // - const alphaOperation: GPUBlendOperation = blend?.alpha?.operation || colorOperation; - let alphaSrcFactor: GPUBlendFactor = blend?.alpha?.srcFactor || colorSrcFactor; - let alphaDstFactor: GPUBlendFactor = blend?.alpha?.dstFactor || colorDstFactor; - if (alphaOperation === "max" || alphaOperation === "min") - { - alphaSrcFactor = alphaDstFactor = "one"; - } - - const gpuBlend: GPUBlendState = { - color: { - operation: colorOperation, - srcFactor: colorSrcFactor, - dstFactor: colorDstFactor, - }, - alpha: { - operation: alphaOperation, - srcFactor: alphaSrcFactor, - dstFactor: alphaDstFactor, - }, - }; - - return gpuBlend; -} - -function getGPUColorWriteFlags(writeMask?: IWriteMask) -{ - if (!writeMask) return 15; - - let gpuWriteMask: GPUColorWriteFlags = 0; - if (writeMask[0]) - { - gpuWriteMask += 1; - } - if (writeMask[1]) - { - gpuWriteMask += 2; - } - if (writeMask[2]) - { - gpuWriteMask += 4; - } - if (writeMask[3]) - { - gpuWriteMask += 8; - } - - return gpuWriteMask; -} - -function getWGSLType(type: TypeInfo) -{ - let wgslType = type.name; - if (isTemplateType(type)) - { - wgslType += `<${type.format.name}>`; - } - if (wgslTypeMap[wgslType]) - { - wgslType = wgslTypeMap[wgslType]; - } - - return wgslType as WGSLVertexType; -} - -/** - * 别名 - */ -const wgslTypeMap = { - vec2u: "vec2", - vec3u: "vec3", - vec4u: "vec4", - vec2i: "vec2", - vec3i: "vec3", - vec4i: "vec4", - vec2f: "vec2", - vec3f: "vec3", - vec4f: "vec4", -}; - -function isTemplateType(type: TypeInfo): type is TemplateInfo -{ - return !!(type as TemplateInfo).format; -} diff --git a/src/caches/getNGPUVertexBuffers.ts b/src/caches/getNGPUVertexBuffers.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9ab4f9593491115a8f4f1bf824eab3650e8f9f1 --- /dev/null +++ b/src/caches/getNGPUVertexBuffers.ts @@ -0,0 +1,177 @@ +import { ChainMap, computed, Computed, reactive, VertexAttribute, VertexAttributes, VertexDataTypes, vertexFormatMap, VertexState } from "@feng3d/render-api"; +import { VertexBuffer } from "../internal/VertexBuffer"; +import { getVertexEntryFunctionInfo } from "./getVertexEntryFunctionInfo"; + +export function getGPUVertexBufferLayouts(vertexState: VertexState, vertices: VertexAttributes) +{ + const result = computed(() => + { + const { vertexBufferLayouts } = getVertexBuffersBuffers(vertexState, vertices); + return vertexBufferLayouts; + }); + return result.value; +} + +export function getNVertexBuffers(vertexState: VertexState, vertices: VertexAttributes) +{ + let _vertexBuffers: VertexBuffer[]; + const result = computed(() => + { + const { vertexBuffers } = getVertexBuffersBuffers(vertexState, vertices); + + if (_vertexBuffers && _vertexBuffers.length === vertexBuffers.length && _vertexBuffers.every((v, i) => v === vertexBuffers[i])) + { + return _vertexBuffers; + } + _vertexBuffers = vertexBuffers; + + return vertexBuffers; + }); + return result.value; +} + +declare global +{ + interface GPUVertexBufferLayout + { + /** + * 用于判断是否为相同的顶点缓冲区布局。 + */ + key: string; + } +} + +/** + * 从顶点属性信息与顶点数据中获取顶点缓冲区布局数组以及顶点缓冲区数组。 + * + * @param vertices 顶点数据。 + * @returns 顶点缓冲区布局数组以及顶点缓冲区数组。 + */ +function getVertexBuffersBuffers(vertexState: VertexState, vertices: VertexAttributes) +{ + const getVertexBuffersBuffersKey: GetVertexBuffersBuffersKey = [vertexState, vertices]; + let result = getVertexBuffersBuffersMap.get(getVertexBuffersBuffersKey); + if (result) return result.value; + + result = computed(() => + { + const vertexEntryFunctionInfo = getVertexEntryFunctionInfo(vertexState); + // 监听 + const r_vertices = vertices && reactive(vertices); + vertexEntryFunctionInfo.inputs.forEach((inputInfo) => + { + // 跳过内置属性。 + if (inputInfo.locationType === "builtin") return; + // 监听每个顶点属性数据。 + const vertexAttribute = r_vertices[inputInfo.name]; + if (vertexAttribute) + { + vertexAttribute.arrayStride; + vertexAttribute.format; + vertexAttribute.offset; + vertexAttribute.stepMode; + } + }); + + // 计算 + const vertexBufferLayouts: GPUVertexBufferLayout[] = []; + const vertexBuffers: VertexBuffer[] = []; + const bufferIndexMap = new Map(); + + vertexEntryFunctionInfo.inputs.forEach((inputInfo) => + { + // 跳过内置属性。 + if (inputInfo.locationType === "builtin") return; + + const shaderLocation = inputInfo.location as number; + const attributeName = inputInfo.name; + + const vertexAttribute = vertices[attributeName]; + console.assert(!!vertexAttribute, `在提供的顶点属性数据中未找到 ${attributeName} 。`); + // + const data = vertexAttribute.data; + const attributeOffset = vertexAttribute.offset || 0; + let arrayStride = vertexAttribute.arrayStride; + const stepMode = vertexAttribute.stepMode ?? "vertex"; + const format = vertexAttribute.format; + // 检查提供的顶点数据格式是否与着色器匹配 + // const wgslType = getWGSLType(v.type); + // let possibleFormats = wgslVertexTypeMap[wgslType].possibleFormats; + // console.assert(possibleFormats.indexOf(format) !== -1, `顶点${attributeName} 提供的数据格式 ${format} 与着色器中类型 ${wgslType} 不匹配!`); + console.assert(data.constructor.name === vertexFormatMap[format].typedArrayConstructor.name, + `顶点${attributeName} 提供的数据类型 ${data.constructor.name} 与格式 ${format} 不匹配!请使用 ${data.constructor.name} 来组织数据或者更改数据格式。`); + + // 如果 偏移值大于 单个顶点尺寸,则该值被放入 IGPUVertexBuffer.offset。 + const vertexByteSize = vertexFormatMap[format].byteSize; + // + if (!arrayStride) + { + arrayStride = vertexByteSize; + } + console.assert(attributeOffset + vertexByteSize <= arrayStride, `offset(${attributeOffset}) + vertexByteSize(${vertexByteSize}) 必须不超出 arrayStride(${arrayStride})。`); + + let index = bufferIndexMap.get(data); + let gpuVertexBufferLayout: GPUVertexBufferLayout; + if (index === undefined) + { + index = vertexBufferLayouts.length; + bufferIndexMap.set(data, index); + + vertexBuffers[index] = getVertexBuffers(vertexAttribute); + + // + gpuVertexBufferLayout = vertexBufferLayouts[index] = { stepMode, arrayStride, attributes: [], key: `${stepMode}-${arrayStride}` }; + } + else + { + gpuVertexBufferLayout = vertexBufferLayouts[index]; + if (__DEV__) + { + console.assert(vertexBufferLayouts[index].arrayStride === arrayStride && vertexBufferLayouts[index].stepMode === stepMode, "要求同一顶点缓冲区中 arrayStride 与 stepMode 必须相同。"); + } + } + + (gpuVertexBufferLayout.attributes as Array).push({ shaderLocation, offset: attributeOffset, format }); + gpuVertexBufferLayout.key += `-[${shaderLocation}, ${attributeOffset}, ${format}]`; + }); + + // 相同的顶点缓冲区布局合并为一个。 + const vertexBufferLayoutsKey = vertexBufferLayouts.reduce((prev, cur) => { return prev + cur.key }, ""); + vertexBufferLayoutsMap[vertexBufferLayoutsKey] ??= vertexBufferLayouts; + + return { vertexBufferLayouts: vertexBufferLayoutsMap[vertexBufferLayoutsKey], vertexBuffers }; + }); + + getVertexBuffersBuffersMap.set(getVertexBuffersBuffersKey, result); + + return result.value; +} + +const vertexBufferLayoutsMap: Record = {}; + +function getVertexBuffers(vertexAttribute: VertexAttribute) +{ + let result = getVertexBuffersMap.get(vertexAttribute); + if (result) return result.value; + const vertexBuffer: VertexBuffer = {} as any; + const r_vertexBuffer = reactive(vertexBuffer); + result = computed(() => + { + // 监听 + reactive(vertexAttribute).data; + + // + const data = vertexAttribute.data; + // 修改数据并通知更新 + r_vertexBuffer.data = data; + r_vertexBuffer.offset = data.byteOffset; + r_vertexBuffer.size = data.byteLength; + return vertexBuffer; + }); + getVertexBuffersMap.set(vertexAttribute, result); + return result.value; +} +const getVertexBuffersMap = new WeakMap>(); + +type GetVertexBuffersBuffersKey = [vertexState: VertexState, vertices: VertexAttributes]; +const getVertexBuffersBuffersMap = new ChainMap>(); \ No newline at end of file diff --git a/src/caches/getTextureSize.ts b/src/caches/getTextureSize.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0269cfec18ba31d5f6f4292b46a3d3fc54888ae --- /dev/null +++ b/src/caches/getTextureSize.ts @@ -0,0 +1,40 @@ +import { CanvasTexture, computed, Computed, reactive, TextureLike, TextureSize } from "@feng3d/render-api"; + +/** + * 获取纹理尺寸。 + * + * @param texture 纹理。 + * @returns 纹理尺寸。 + */ +export function getTextureSize(texture: TextureLike) +{ + let result = getTextureSizeMap.get(texture); + if (result) return result.value; + + result = computed(() => + { + if ("context" in texture) + { + // 监听 + const r_texture = reactive(texture); + r_texture.context.canvasId; + + // 计算 + const element = typeof texture.context.canvasId === "string" ? document.getElementById(texture.context.canvasId) as HTMLCanvasElement : texture.context.canvasId; + console.assert(!!element, `在 document 上没有找到 canvasId 为 ${(texture as CanvasTexture).context.canvasId} 的画布。`); + + return [element.width, element.height, 1] as TextureSize; + } + // 监听 + const r_texture = reactive(texture); + r_texture.size[0]; + r_texture.size[1]; + r_texture.size[2]; + + return texture.size; + }); + getTextureSizeMap.set(texture, result); + + return result.value; +} +const getTextureSizeMap = new WeakMap>(); \ No newline at end of file diff --git a/src/caches/getVertexEntryFunctionInfo.ts b/src/caches/getVertexEntryFunctionInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ca40eab259baa0d514b3a639e99c145817479bc --- /dev/null +++ b/src/caches/getVertexEntryFunctionInfo.ts @@ -0,0 +1,46 @@ +import { computed, reactive, VertexState, Computed } from "@feng3d/render-api"; +import { FunctionInfo } from "wgsl_reflect"; +import { getWGSLReflectInfo } from "./getWGSLReflectInfo"; + +/** + * 获取顶点入口函数信息。 + * + * @param vertexState 顶点阶段信息。 + * @returns + */ +export function getVertexEntryFunctionInfo(vertexState: VertexState) +{ + let result: Computed = _getVertexEntryFunctionInfoMap.get(vertexState); + if (result) return result.value; + + result = computed(() => + { + // 监听 + const r_vertexState = reactive(vertexState); + r_vertexState.code; + r_vertexState.entryPoint; + + // 计算 + const { code, entryPoint } = vertexState; + // 解析顶点着色器 + const reflect = getWGSLReflectInfo(code); + // + let vertexEntryFunctionInfo: FunctionInfo; + if (entryPoint) + { + vertexEntryFunctionInfo = reflect.entry.vertex.filter((v) => v.name === entryPoint)[0]; + console.assert(!!vertexEntryFunctionInfo, `WGSL着色器 ${code} 中不存在顶点入口点 ${entryPoint} 。`); + } + else + { + vertexEntryFunctionInfo = reflect.entry.vertex[0]; + console.assert(!!reflect.entry.vertex[0], `WGSL着色器 ${code} 中不存在顶点入口点。`); + } + + return vertexEntryFunctionInfo; + }); + _getVertexEntryFunctionInfoMap.set(vertexState, result); + + return result.value; +} +const _getVertexEntryFunctionInfoMap = new WeakMap>(); \ No newline at end of file diff --git a/src/caches/getWGSLReflectInfo.ts b/src/caches/getWGSLReflectInfo.ts index 55814ec56c68b4d3d419ac652f06231b965f32b7..403a30123f4e19cf54feecc4e43f73220a4c1a20 100644 --- a/src/caches/getWGSLReflectInfo.ts +++ b/src/caches/getWGSLReflectInfo.ts @@ -1,7 +1,26 @@ -import { ResourceType, TemplateInfo, WgslReflect } from "wgsl_reflect"; -import { IGPUBindGroupLayoutEntry } from "../internal/IGPUPipelineLayoutDescriptor"; +import { ResourceType, TemplateInfo, VariableInfo, WgslReflect } from "wgsl_reflect"; import { DepthTextureType, ExternalSampledTextureType, MultisampledTextureType, TextureType } from "../types/TextureType"; +declare global +{ + interface GPUBindGroupLayoutEntry + { + /** + * 绑定资源变量信息。 + * + * 注:wgsl着色器被反射过程中将会被引擎自动赋值。 + */ + variableInfo: VariableInfo; + + /** + * 用于判断布局信息是否相同的标识。 + * + * 注:wgsl着色器被反射过程中将会被引擎自动赋值。 + */ + key: string; + } +} + /** * 从WebGPU着色器代码中获取反射信息。 * @@ -19,16 +38,15 @@ export function getWGSLReflectInfo(code: string): WgslReflect } const reflectMap: { [code: string]: WgslReflect } = {}; -export type IGPUBindGroupLayoutEntryMap = { [name: string]: IGPUBindGroupLayoutEntry; }; +export type GPUBindGroupLayoutEntryMap = { [name: string]: GPUBindGroupLayoutEntry; }; -export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayoutEntryMap +export function getIGPUBindGroupLayoutEntryMap(code: string): GPUBindGroupLayoutEntryMap { if (shaderLayoutMap[code]) return shaderLayoutMap[code]; - const reflect = getWGSLReflectInfo(code); - - const entryMap: IGPUBindGroupLayoutEntryMap = shaderLayoutMap[code] = {}; + const entryMap: GPUBindGroupLayoutEntryMap = shaderLayoutMap[code] = {}; + const reflect = getWGSLReflectInfo(code); for (const uniform of reflect.uniforms) { const { binding, name } = uniform; @@ -41,6 +59,7 @@ export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayou entryMap[name] = { variableInfo: uniform, visibility: Visibility_ALL, binding, buffer: layout, + key: `[${binding}, ${name}, buffer, ${layout.type} , ${layout.minBindingSize}]`, }; } @@ -60,7 +79,8 @@ export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayou entryMap[name] = { variableInfo: storage, - visibility: type === "storage" ? Visibility_FRAGMENT_COMPUTE : Visibility_ALL, binding, buffer: layout + visibility: type === "storage" ? Visibility_FRAGMENT_COMPUTE : Visibility_ALL, binding, buffer: layout, + key: `[${binding}, ${name}, buffer, ${layout.type}]`, }; } else if (storage.resourceType === ResourceType.StorageTexture) @@ -82,7 +102,8 @@ export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayou entryMap[name] = { variableInfo: storage, - visibility: Visibility_FRAGMENT_COMPUTE, binding, storageTexture: layout + visibility: Visibility_FRAGMENT_COMPUTE, binding, storageTexture: layout, + key: `[${binding}, ${name}, storageTexture, ${layout.access}, ${layout.format}, ${layout.viewDimension}]`, }; } else @@ -103,7 +124,8 @@ export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayou { entryMap[name] = { variableInfo: texture, - visibility: Visibility_ALL, binding, externalTexture: {} + visibility: Visibility_ALL, binding, externalTexture: {}, + key: `[${binding}, ${name}, externalTexture]`, }; } else @@ -151,7 +173,8 @@ export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayou entryMap[name] = { variableInfo: texture, - visibility: Visibility_ALL, binding, texture: layout + visibility: Visibility_ALL, binding, texture: layout, + key: `[${binding}, ${name}, texture, ${layout.sampleType}, ${layout.viewDimension}, ${layout.multisampled}]`, }; } } @@ -169,14 +192,15 @@ export function getIGPUBindGroupLayoutEntryMap(code: string): IGPUBindGroupLayou entryMap[name] = { variableInfo: sampler, - visibility: Visibility_ALL, binding, sampler: layout + visibility: Visibility_ALL, binding, sampler: layout, + key: `[${binding}, ${name}, sampler, ${layout.type}]`, }; } return entryMap; } -const shaderLayoutMap: { [code: string]: IGPUBindGroupLayoutEntryMap } = {}; +const shaderLayoutMap: { [code: string]: GPUBindGroupLayoutEntryMap } = {}; /** * 片段与计算着色器可见。 diff --git a/src/const.ts b/src/const.ts deleted file mode 100644 index 38e90d35cf2e78014295518d37008ea8921f55f8..0000000000000000000000000000000000000000 --- a/src/const.ts +++ /dev/null @@ -1 +0,0 @@ -export const getRealGPUBindGroup = "getRealGPUBindGroup"; \ No newline at end of file diff --git a/src/data/IGPUCanvasConfiguration.ts b/src/data/CanvasConfiguration.ts similarity index 81% rename from src/data/IGPUCanvasConfiguration.ts rename to src/data/CanvasConfiguration.ts index 55b0627d053e13161e7baa2dc5567da483c188e0..fcad96c758938a7dc34332d2ca54e64d948cb43b 100644 --- a/src/data/IGPUCanvasConfiguration.ts +++ b/src/data/CanvasConfiguration.ts @@ -1,10 +1,10 @@ /** - * GPU画布配置。 + * WebGPU画布配置。 * * @see GPUCanvasConfiguration * @see GPUCanvasContext.configure */ -export interface IGPUCanvasConfiguration +export interface CanvasConfiguration { /** * The usage that textures returned by {@link GPUCanvasContext#getCurrentTexture} will have. @@ -13,7 +13,7 @@ export interface IGPUCanvasConfiguration * when setting a custom usage if you wish to use textures returned by * {@link GPUCanvasContext#getCurrentTexture} as color targets for a render pass. */ - usage?: GPUTextureUsageFlags; + readonly usage?: GPUTextureUsageFlags; /** * The format that textures returned by {@link GPUCanvasContext#getCurrentTexture} will have. @@ -21,7 +21,7 @@ export interface IGPUCanvasConfiguration * * 默认 `navigator.gpu.getPreferredCanvasFormat()` 。 */ - format?: GPUTextureFormat; + readonly format?: GPUTextureFormat; /** * Determines the effect that alpha values will have on the content of textures returned by @@ -29,21 +29,23 @@ export interface IGPUCanvasConfiguration * * 默认 'premultiplied' 。 */ - alphaMode?: GPUCanvasAlphaMode; + readonly alphaMode?: GPUCanvasAlphaMode; /** * The formats that views created from textures returned by * {@link GPUCanvasContext#getCurrentTexture} may use. */ - viewFormats?: Iterable; + readonly viewFormats?: readonly GPUTextureFormat[]; + /** * The color space that values written into textures returned by * {@link GPUCanvasContext#getCurrentTexture} should be displayed with. */ - colorSpace?: PredefinedColorSpace; + readonly colorSpace?: PredefinedColorSpace; + /** * The tone mapping determines how the content of textures returned by * {@link GPUCanvasContext#getCurrentTexture} are to be displayed. */ - toneMapping?: GPUCanvasToneMapping; -} + readonly toneMapping?: GPUCanvasToneMapping; +} \ No newline at end of file diff --git a/src/data/IGPUComputeObject.ts b/src/data/ComputeObject.ts similarity index 51% rename from src/data/IGPUComputeObject.ts rename to src/data/ComputeObject.ts index 3f5fe09a8e436e79000511f933ec08b788965baa..a7fcec547a18b04d4a79a10d81b0649c574ab00d 100644 --- a/src/data/IGPUComputeObject.ts +++ b/src/data/ComputeObject.ts @@ -1,9 +1,9 @@ -import { IUniforms } from "@feng3d/render-api"; -import { IGPUComputePipeline } from "./IGPUComputePipeline"; -import { IGPUWorkgroups } from "./IGPUWorkgroups"; +import { BindingResources } from "@feng3d/render-api"; +import { ComputePipeline } from "./ComputePipeline"; +import { Workgroups } from "./Workgroups"; /** - * GPU计算对象,包含GPU一次计算所有数据。 + * WebGPU计算对象,包含GPU一次计算所有数据。 * * {@link GPUComputePassEncoder.setPipeline} * @@ -11,22 +11,22 @@ import { IGPUWorkgroups } from "./IGPUWorkgroups"; * * {@link GPUComputePassEncoder.dispatchWorkgroups} */ -export interface IGPUComputeObject +export interface ComputeObject { /** * 计算管线。 */ - readonly pipeline: IGPUComputePipeline; + readonly pipeline: ComputePipeline; /** * 绑定资源。包含数值、纹理、采样、外部纹理。 */ - readonly uniforms?: IUniforms; + readonly bindingResources?: BindingResources; /** * {@link GPUComputePassEncoder.dispatchWorkgroups} * * 分配的工作组。 */ - readonly workgroups?: IGPUWorkgroups; + readonly workgroups?: Workgroups; } diff --git a/src/data/IGPUComputePass.ts b/src/data/ComputePass.ts similarity index 55% rename from src/data/IGPUComputePass.ts rename to src/data/ComputePass.ts index 241ef74726033c7fffaebfecd1537d32c27a84f7..a317891fd1edd3a889e3797987c683c7f19fd661 100644 --- a/src/data/IGPUComputePass.ts +++ b/src/data/ComputePass.ts @@ -1,28 +1,33 @@ -import { IGPUComputeObject } from "./IGPUComputeObject"; -import { IGPUTimestampQuery } from "./IGPUTimestampQuery"; +import { ComputeObject } from "./ComputeObject"; +import { TimestampQuery } from "./TimestampQuery"; /** - * GPU计算通道编码器。 + * WebGPU计算通道编码器。 * * @see GPUCommandEncoder.beginComputePass * @see GPUComputePassEncoder */ -export interface IGPUComputePass +export interface ComputePass { /** * 数据类型。 */ - readonly __type: "ComputePass"; + readonly __type__: "ComputePass"; + + descriptor?: ComputePassDescriptor; /** * 计算对象列表。 */ - computeObjects: IGPUComputeObject[]; + computeObjects: ComputeObject[]; +} +export interface ComputePassDescriptor +{ /** * 查询通道运行消耗时长(单位为纳秒)。 * * 如果需要查询通道运行消耗时长,需要为该属性赋值,如 `pass.timestampQuery = {};`。WebGPU渲染完成后引擎自动填充结果到属性`elapsedNs`。 */ - timestampQuery?: IGPUTimestampQuery; -} + timestampQuery?: TimestampQuery; +} \ No newline at end of file diff --git a/src/data/IGPUComputePipeline.ts b/src/data/ComputePipeline.ts similarity index 62% rename from src/data/IGPUComputePipeline.ts rename to src/data/ComputePipeline.ts index 4c2800187679c7a1f79d108e66905b3318c2998f..10c4703cddd4f94d9be9afe5ab1edadd796d29d2 100644 --- a/src/data/IGPUComputePipeline.ts +++ b/src/data/ComputePipeline.ts @@ -1,12 +1,12 @@ -import { IGPUComputeStage } from "./IGPUComputeStage"; +import { ComputeStage as ComputeStage } from "./ComputeStage"; /** - * GPU计算管线。 + * WebGPU计算管线。 * * {@link GPUDevice.createComputePipeline} * {@link GPUComputePipelineDescriptor} */ -export interface IGPUComputePipeline +export interface ComputePipeline { /** * The initial value of {@link GPUObjectBase#label|GPUObjectBase.label}. @@ -16,5 +16,5 @@ export interface IGPUComputePipeline /** * 计算程序。 */ - readonly compute: IGPUComputeStage; + readonly compute: ComputeStage; } \ No newline at end of file diff --git a/src/data/IGPUComputeStage.ts b/src/data/ComputeStage.ts similarity index 96% rename from src/data/IGPUComputeStage.ts rename to src/data/ComputeStage.ts index 9c5786d04ef41cb6143916f50b5486c7c04cb409..bcdd15455d67899b0beb8d6a86c16dfca79c885b 100644 --- a/src/data/IGPUComputeStage.ts +++ b/src/data/ComputeStage.ts @@ -1,7 +1,7 @@ /** - * GPU计算阶段。 + * WebGPU计算阶段。 */ -export interface IGPUComputeStage +export interface ComputeStage { /** * 着色器源码,将由 {@link GPUDevice.createShaderModule} 生成 {@link GPUShaderModule} 。 diff --git a/src/data/IGPUBindingResources.ts b/src/data/IGPUBindingResources.ts deleted file mode 100644 index b6505473423c52f36a21f0aad02c24661b627399..0000000000000000000000000000000000000000 --- a/src/data/IGPUBindingResources.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ISampler } from "@feng3d/render-api"; -import { IGPUExternalTexture } from "./IGPUExternalTexture"; - -declare module "@feng3d/render-api" -{ - export interface IUniformTypeMap - { - ISampler: ISampler; - ITextureView: ITextureView; - IGPUExternalTexture: IGPUExternalTexture; - } -} \ No newline at end of file diff --git a/src/data/IGPUCanvasContext.ts b/src/data/IGPUCanvasContext.ts deleted file mode 100644 index 5f62a2220823bf6fa9346f6317ba964681a59e3f..0000000000000000000000000000000000000000 --- a/src/data/IGPUCanvasContext.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ICanvasContext } from "@feng3d/render-api"; -import { IGPUCanvasConfiguration } from "./IGPUCanvasConfiguration"; - -/** - * @see GPUCanvasContext - * @see HTMLCanvasElement.getContext - * @see GPUCanvasContext.configure - */ -export interface IGPUCanvasContext extends ICanvasContext -{ - /** - * 画布配置。默认有引擎自动设置。 - */ - configuration?: IGPUCanvasConfiguration; -} diff --git a/src/data/IGPUCanvasTexture.ts b/src/data/IGPUCanvasTexture.ts deleted file mode 100644 index 7c9f77affd50debe2127a733bc902f4b4e29eb15..0000000000000000000000000000000000000000 --- a/src/data/IGPUCanvasTexture.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IGPUCanvasContext } from "./IGPUCanvasContext"; - -/** - * 画布纹理,从画布的WebGPU上下文获取纹理 - */ -export interface IGPUCanvasTexture -{ - context: IGPUCanvasContext; -} diff --git a/src/data/IGPUCommandEncoder.ts b/src/data/IGPUCommandEncoder.ts deleted file mode 100644 index 952489a99231ca7c7d97fbdca6747b748bf39f43..0000000000000000000000000000000000000000 --- a/src/data/IGPUCommandEncoder.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ICommandEncoder, IRenderPass } from "@feng3d/render-api"; -import { IGPUComputePass } from "./IGPUComputePass"; - -declare module "@feng3d/render-api" -{ - export interface IPassEncoderMap - { - IGPUComputePass: IGPUComputePass; - } -} diff --git a/src/data/IGPUOcclusionQuery.ts b/src/data/IGPUOcclusionQuery.ts deleted file mode 100644 index 7a2664303dd6cb9112421d4407e7ed7ad1553016..0000000000000000000000000000000000000000 --- a/src/data/IGPUOcclusionQuery.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { IRenderObject } from "@feng3d/render-api"; - -/** - * 被查询的渲染对象列表 - */ -export interface IGPUOcclusionQuery -{ - /** - * 数据类型。 - */ - readonly __type: "OcclusionQuery"; - - /** - * GPU渲染对象列表。 - */ - renderObjects: IRenderObject[]; - - /** - * 临时变量, 执行过程中由引擎自动填充 - * - * @internal - */ - _queryIndex?: GPUSize32; - - /** - * 渲染完成后由引擎自动填充。 - */ - result?: IGLQueryResult; - - _version?: number; -} - -export interface IGLQueryResult -{ - /** - * 查询结果。 - */ - result: number; -} \ No newline at end of file diff --git a/src/data/IGPUReadPixels.ts b/src/data/IGPUReadPixels.ts deleted file mode 100644 index c361a94384b1e9e9b86e6912114f8003992786ba..0000000000000000000000000000000000000000 --- a/src/data/IGPUReadPixels.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ITextureLike } from "@feng3d/render-api"; - -/** - * 读取GPU纹理像素 - */ -export interface IGPUReadPixels -{ - /** - * GPU纹理 - */ - texture: ITextureLike, - - /** - * 读取位置。 - */ - origin: GPUOrigin3D, - - /** - * 读取尺寸 - */ - copySize: { width: number, height: number } - - /** - * 用于保存最后结果。 - */ - result?: Uint8Array; -} \ No newline at end of file diff --git a/src/data/IGPURenderPass.ts b/src/data/IGPURenderPass.ts deleted file mode 100644 index 69960f5e780651650d4cad30c274f29a98cdbe53..0000000000000000000000000000000000000000 --- a/src/data/IGPURenderPass.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { IRenderPass, IRenderPassDescriptor } from "@feng3d/render-api"; -import { IGPUOcclusionQuery } from "./IGPUOcclusionQuery"; -import { IGPURenderBundle } from "./IGPURenderBundle"; -import { IGPUTimestampQuery } from "./IGPUTimestampQuery"; - -declare module "@feng3d/render-api" -{ - /** - * GPU渲染通道编码器。 - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder - * - * {@link GPURenderPassEncoder} - */ - export interface IRenderPass - { - /** - * 渲染不被遮挡查询结果。具体数据保存在各子项的"result"属性中。 - * - * 当提交WebGPU后自动获取结果后填充该属性。 - */ - occlusionQueryResults?: IGPUOcclusionQuery[]; - - /** - * 查询通道运行消耗时长(单位为纳秒)。 - * - * 如果需要查询通道运行消耗时长,需要为该属性赋值,如 `pass.timestampQuery = {};`。WebGPU渲染完成后引擎自动填充结果到属性`elapsedNs`。 - */ - timestampQuery?: IGPUTimestampQuery; - } - - export interface IRenderPassObjectMap - { - IGPURenderBundle: IGPURenderBundle; - IGPUOcclusionQuery: IGPUOcclusionQuery; - } -} diff --git a/src/data/IGPUStencilFaceState.ts b/src/data/IGPUStencilFaceState.ts deleted file mode 100644 index e84619fc5a405cd05e02bb0774e3da36af0a966b..0000000000000000000000000000000000000000 --- a/src/data/IGPUStencilFaceState.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * {@link GPUStencilFaceState} - * - * @see https://www.orillusion.com/zh/webgpu.html#dictdef-gpustencilfacestate - */ -export interface IGPUStencilFaceState -{ - /** - * 在测试片元与 depthStencilAttachment 模板值时使用的 GPUCompareFunction。 - * - * 默认为 "always"。 - */ - readonly compare?: GPUCompareFunction; - - /** - * 如果片元模板比较测试(由 compare 描述)失败,则执行的 GPUStencilOperation。 - * - * 默认为 "keep"。 - */ - readonly failOp?: GPUStencilOperation; - - /** - * 如果由 depthCompare 描述的片元深度比较失败,则执行的 GPUStencilOperation。 - * - * 默认为 "keep"。 - */ - readonly depthFailOp?: GPUStencilOperation; - - /** - * 如果片元模板比较测试通过,则执行由compare描述的GPUStencilOperation。 - * - * 默认为 "keep"。 - */ - readonly passOp?: GPUStencilOperation; -} \ No newline at end of file diff --git a/src/data/IGPUTimestampQuery.ts b/src/data/IGPUTimestampQuery.ts deleted file mode 100644 index e7e871835aa599c226c6a47ac4a22f9927682f17..0000000000000000000000000000000000000000 --- a/src/data/IGPUTimestampQuery.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * 查询通道运行消耗时长(单位为纳秒)。 - */ -export interface IGPUTimestampQuery -{ - /** - * (单位为纳秒) - */ - elapsedNs?: number; - - /** - * 当前WebGPU是否支持该特性。 - */ - isSupports?: boolean; -} \ No newline at end of file diff --git a/src/data/IGPUMultisampleState.ts b/src/data/MultisampleState.ts similarity index 57% rename from src/data/IGPUMultisampleState.ts rename to src/data/MultisampleState.ts index cfe185ef03132b838dd785f54de6d7760746c3df..c3a38cfdbb8369affc9d4152b669a1700e111445 100644 --- a/src/data/IGPUMultisampleState.ts +++ b/src/data/MultisampleState.ts @@ -1,20 +1,24 @@ -import { IRenderPassDescriptor } from "@feng3d/render-api"; +import { RenderPassDescriptor } from "@feng3d/render-api"; /** * 多重采样阶段描述。 * - * 多重采样次数将由 {@link IRenderPassDescriptor.sampleCount} 覆盖。 + * 多重采样次数将由 {@link RenderPassDescriptor.sampleCount} 覆盖。 */ -export interface IGPUMultisampleState +export interface MultisampleState { /** * Mask determining which samples are written to. + * + * 默认为 0xFFFFFFFF 。 */ readonly mask?: GPUSampleMask; /** * When `true` indicates that a fragment's alpha channel should be used to generate a sample * coverage mask. + * + * 默认为 `false` 。 */ readonly alphaToCoverageEnabled?: boolean; } \ No newline at end of file diff --git a/src/data/IGPURenderBundle.ts b/src/data/RenderBundle.ts similarity index 77% rename from src/data/IGPURenderBundle.ts rename to src/data/RenderBundle.ts index 55707c1fb92e14303be833748948e38e1bbcfe21..5432fb0ee93b49f609fbdd83008f498762e48367 100644 --- a/src/data/IGPURenderBundle.ts +++ b/src/data/RenderBundle.ts @@ -1,4 +1,4 @@ -import { IRenderObject } from "@feng3d/render-api"; +import { RenderObject } from "@feng3d/render-api"; /** * GPU渲染捆绑对象。 @@ -7,24 +7,22 @@ import { IRenderObject } from "@feng3d/render-api"; * * {@link GPUDevice.createRenderBundleEncoder} */ -export interface IGPURenderBundle +export interface RenderBundle { /** * 数据类型。 */ - readonly __type: "RenderBundle"; + readonly __type__: "RenderBundle"; /** * GPU渲染捆绑编码器描述。 */ - readonly descriptor?: IGPURenderBundleEncoderDescriptor + readonly descriptor?: RenderBundleDescriptor /** * GPU渲染对象列表。 */ - renderObjects: readonly IRenderObject[]; - - _version?: number; + readonly renderObjects: readonly RenderObject[]; } /** @@ -34,7 +32,7 @@ export interface IGPURenderBundle * * 'colorFormats' | 'depthStencilFormat' | 'sampleCount' 都将从GPU渲染通道中自动获取。 */ -export interface IGPURenderBundleEncoderDescriptor +export interface RenderBundleDescriptor { /** * If `true`, indicates that the render bundle does not modify the depth component of the diff --git a/src/data/TimestampQuery.ts b/src/data/TimestampQuery.ts new file mode 100644 index 0000000000000000000000000000000000000000..adfb6d9950b30d23e7eaa8481d99dfda91682609 --- /dev/null +++ b/src/data/TimestampQuery.ts @@ -0,0 +1,19 @@ +/** + * 查询通道运行消耗时长(单位为纳秒)。 + */ +export interface TimestampQuery +{ + /** + * 是否支持该特性时将调用此回调函数。 + * + * @param isSupports 当前WebGPU是否支持该特性。 + */ + onSupports?(isSupports: boolean): void; + + /** + * 获得结果时将调用此回调函数。 + * + * @param elapsedNs 通道运行消耗时长(单位为纳秒) + */ + onQuery?(elapsedNs: number): void; +} \ No newline at end of file diff --git a/src/data/IGPUExternalTexture.ts b/src/data/VideoTexture.ts similarity index 82% rename from src/data/IGPUExternalTexture.ts rename to src/data/VideoTexture.ts index 234bc6d82a6f5b9c2e093d3bada32e628ca17666..ec17a5385c90b6e31be239e4aad370f42cc640a0 100644 --- a/src/data/IGPUExternalTexture.ts +++ b/src/data/VideoTexture.ts @@ -1,11 +1,11 @@ /** - * 外部纹理。 - * + * 视频纹理,WebGPU外部纹理。 + * * @see GPUExternalTexture * @see GPUExternalTextureDescriptor * @see GPUDevice.importExternalTexture */ -export interface IGPUExternalTexture +export interface VideoTexture { /** * The initial value of {@link GPUObjectBase#label|GPUObjectBase.label}. @@ -16,9 +16,7 @@ export interface IGPUExternalTexture * The video source to import the external texture from. Source size is determined as described * by the external source dimensions table. */ - readonly source: - | HTMLVideoElement - | VideoFrame; + readonly source: HTMLVideoElement | VideoFrame; /** * The color space the image contents of {@link GPUExternalTextureDescriptor#source} will be diff --git a/src/data/IGPUWorkgroups.ts b/src/data/Workgroups.ts similarity index 85% rename from src/data/IGPUWorkgroups.ts rename to src/data/Workgroups.ts index 87f340aac7ce445f1ce04f56c6fac8264bc3ba7d..5120883fededbd3ca19ef96208066a65d4e0a997 100644 --- a/src/data/IGPUWorkgroups.ts +++ b/src/data/Workgroups.ts @@ -1,9 +1,9 @@ /** - * 分配的工作组。 + * WebGPU分配的工作组。 * * {@link GPUComputePassEncoder.dispatchWorkgroups} */ -export interface IGPUWorkgroups +export interface Workgroups { /** * X的维度工作组数量。 diff --git a/src/data/IGPUBuffer.ts b/src/data/polyfills/Buffer.ts similarity index 77% rename from src/data/IGPUBuffer.ts rename to src/data/polyfills/Buffer.ts index 9e1f65e4c07b56d018db561e14717e80f32950e5..90634850f40eee58e79d8b182f83f90086242292 100644 --- a/src/data/IGPUBuffer.ts +++ b/src/data/polyfills/Buffer.ts @@ -1,4 +1,4 @@ -import { IBuffer, IWriteBuffer } from "@feng3d/render-api"; +import { } from "@feng3d/render-api"; declare module "@feng3d/render-api" { @@ -8,7 +8,7 @@ declare module "@feng3d/render-api" * {@link GPUBufferDescriptor} * {@link GPUBuffer} */ - export interface IBuffer + export interface Buffer { /** * The allowed usages for the buffer. @@ -21,8 +21,12 @@ declare module "@feng3d/render-api" | GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.QUERY_RESOLVE 。 + + * 注:修改允许缓冲区使用的用途时,会重新创建缓冲区。 + * */ readonly usage?: GPUBufferUsageFlags; } } +GPUBufferUsage \ No newline at end of file diff --git a/src/data/polyfills/CanvasContext.ts b/src/data/polyfills/CanvasContext.ts new file mode 100644 index 0000000000000000000000000000000000000000..108eb9708accab10eb833b9277aab1ff67ad7449 --- /dev/null +++ b/src/data/polyfills/CanvasContext.ts @@ -0,0 +1,17 @@ +import { CanvasContext } from "@feng3d/render-api"; +import { CanvasConfiguration } from "../CanvasConfiguration"; + +declare module "@feng3d/render-api" +{ + /** + * @see GPUCanvasContext + * @see GPUCanvasContext.configure + */ + export interface CanvasContext + { + /** + * WebGPU画布配置。默认有引擎自动设置。 + */ + readonly configuration?: CanvasConfiguration; + } +} diff --git a/src/data/polyfills/CommandEncoder.ts b/src/data/polyfills/CommandEncoder.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a57f46fcbd549f8566a7245a7ddce5589fc28c1 --- /dev/null +++ b/src/data/polyfills/CommandEncoder.ts @@ -0,0 +1,10 @@ +import { } from "@feng3d/render-api"; +import { ComputePass } from "../ComputePass"; + +declare module "@feng3d/render-api" +{ + export interface PassEncoderMap + { + GPUComputePass: ComputePass; + } +} diff --git a/src/data/IGPUPrimitiveState.ts b/src/data/polyfills/PrimitiveState.ts similarity index 80% rename from src/data/IGPUPrimitiveState.ts rename to src/data/polyfills/PrimitiveState.ts index 7be3993743886e52999670d1dded984bab9dc17c..f9ff0bc46f13a0d3bab30f9f744934ed3bbc98d3 100644 --- a/src/data/IGPUPrimitiveState.ts +++ b/src/data/polyfills/PrimitiveState.ts @@ -1,4 +1,4 @@ -import { IPrimitiveState } from "@feng3d/render-api"; +import { PrimitiveState } from "@feng3d/render-api"; declare module "@feng3d/render-api" { @@ -7,7 +7,7 @@ declare module "@feng3d/render-api" * * `stripIndexFormat` 将由引擎自动设置。 */ - export interface IPrimitiveState + export interface PrimitiveState { /** * If true, indicates that depth clipping is disabled. @@ -15,4 +15,5 @@ declare module "@feng3d/render-api" */ readonly unclippedDepth?: boolean; } + } diff --git a/src/data/polyfills/ReadPixels.ts b/src/data/polyfills/ReadPixels.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d3a606d920c2715d8d94d36d983367909119b8c --- /dev/null +++ b/src/data/polyfills/ReadPixels.ts @@ -0,0 +1,12 @@ +import { TextureLike } from "@feng3d/render-api"; + +declare module "@feng3d/render-api" +{ + export interface ReadPixels + { + /** + * GPU纹理 + */ + texture: TextureLike, + } +} diff --git a/src/data/IGPURenderObject.ts b/src/data/polyfills/RenderObject.ts similarity index 76% rename from src/data/IGPURenderObject.ts rename to src/data/polyfills/RenderObject.ts index 60541fc80664cf7f0dd1e39f824561533bf23d6f..cc62ea091b69c878afd64f378632e2cea0bf5024 100644 --- a/src/data/IGPURenderObject.ts +++ b/src/data/polyfills/RenderObject.ts @@ -1,5 +1,5 @@ -import { IUniforms } from "@feng3d/render-api"; -import { } from "./IGPUBindingResources"; +import { DrawIndexed, DrawVertex, Viewport } from "@feng3d/render-api"; +import { } from "./Uniforms"; declare module "@feng3d/render-api" { @@ -10,7 +10,7 @@ declare module "@feng3d/render-api" * * {@link GPURenderPassEncoder.setViewport} */ - export interface IViewport + export interface Viewport { /** * Minimum depth value of the viewport. @@ -19,7 +19,7 @@ declare module "@feng3d/render-api" * * 默认为 0 。 */ - readonly minDepth: number, + minDepth?: number, /** * Maximum depth value of the viewport. @@ -28,7 +28,7 @@ declare module "@feng3d/render-api" * * 默认为 1 。 */ - readonly maxDepth: number + maxDepth?: number } /** @@ -38,12 +38,14 @@ declare module "@feng3d/render-api" * * @see GPURenderCommandsMixin.draw */ - export interface IDrawVertex + export interface DrawVertex { /** * First instance to draw. + * + * 默认为 0 。 */ - readonly firstInstance?: number; + firstInstance?: number; } /** @@ -51,20 +53,20 @@ declare module "@feng3d/render-api" * * {@link GPURenderCommandsMixin.drawIndexed} */ - export interface IDrawIndexed + export interface DrawIndexed { /** * Added to each index value before indexing into the vertex buffers. * * 默认为 0 。 */ - readonly baseVertex?: number; + baseVertex?: number; /** * First instance to draw. * * 默认为 0 。 */ - readonly firstInstance?: number; + firstInstance?: number; } } diff --git a/src/data/polyfills/RenderPass.ts b/src/data/polyfills/RenderPass.ts new file mode 100644 index 0000000000000000000000000000000000000000..736d9e7d371a1ff0fb31ca77fba656364e9e4845 --- /dev/null +++ b/src/data/polyfills/RenderPass.ts @@ -0,0 +1,21 @@ +import { OcclusionQuery } from "@feng3d/render-api"; +import { RenderBundle } from "../RenderBundle"; +import { TimestampQuery } from "../TimestampQuery"; + +declare module "@feng3d/render-api" +{ + export interface RenderPassDescriptor + { + /** + * 查询通道运行消耗时长(单位为纳秒)。 + * + * 如果需要查询通道运行消耗时长,需要为该属性赋值,如 `pass.timestampQuery = {};`。WebGPU渲染完成后引擎自动填充结果到属性`elapsedNs`。 + */ + timestampQuery?: TimestampQuery; + } + + export interface RenderPassObjectMap + { + RenderBundle: RenderBundle; + } +} diff --git a/src/data/IGPURenderPassColorAttachment.ts b/src/data/polyfills/RenderPassColorAttachment.ts similarity index 85% rename from src/data/IGPURenderPassColorAttachment.ts rename to src/data/polyfills/RenderPassColorAttachment.ts index 4a20b09c9605c811cd9705c71729a973ad2876f2..8ce007959ec7bba058c301acff917d91af78eb1c 100644 --- a/src/data/IGPURenderPassColorAttachment.ts +++ b/src/data/polyfills/RenderPassColorAttachment.ts @@ -1,4 +1,4 @@ -import { ITextureView } from "@feng3d/render-api"; +import { TextureView } from "@feng3d/render-api"; declare module "@feng3d/render-api" { @@ -8,13 +8,13 @@ declare module "@feng3d/render-api" * * {@link GPURenderPassColorAttachment} */ - export interface IRenderPassColorAttachment + export interface RenderPassColorAttachment { /** * A {@link GPUTextureView} describing the texture subresource that will be output to for this * color attachment. */ - readonly view?: ITextureView; + readonly view?: TextureView; /** * The store operation to perform on {@link GPURenderPassColorAttachment#view} diff --git a/src/data/IGPURenderPassDepthStencilAttachment.ts b/src/data/polyfills/RenderPassDepthStencilAttachment.ts similarity index 87% rename from src/data/IGPURenderPassDepthStencilAttachment.ts rename to src/data/polyfills/RenderPassDepthStencilAttachment.ts index badab2a8a20fe942e633bd50b8645fc61fd6e57e..98c21a6e84d660effd699504132591d85714e3dd 100644 --- a/src/data/IGPURenderPassDepthStencilAttachment.ts +++ b/src/data/polyfills/RenderPassDepthStencilAttachment.ts @@ -1,4 +1,4 @@ -import { IRenderPassDepthStencilAttachment, ITextureView } from "@feng3d/render-api"; +import { RenderPassDepthStencilAttachment, TextureView } from "@feng3d/render-api"; declare module "@feng3d/render-api" { @@ -7,7 +7,7 @@ declare module "@feng3d/render-api" * * @see GPURenderPassDepthStencilAttachment */ - export interface IRenderPassDepthStencilAttachment + export interface RenderPassDepthStencilAttachment { /** * A {@link GPUTextureView} describing the texture subresource that will be output to @@ -15,7 +15,7 @@ declare module "@feng3d/render-api" * * 当值为空时,将自动从颜色附件中获取尺寸来创建深度纹理。 */ - readonly view?: ITextureView; + readonly view?: TextureView; /** * The store operation to perform on {@link GPURenderPassDepthStencilAttachment#view}'s diff --git a/src/data/IGPURenderPassDescriptor.ts b/src/data/polyfills/RenderPassDescriptor.ts similarity index 73% rename from src/data/IGPURenderPassDescriptor.ts rename to src/data/polyfills/RenderPassDescriptor.ts index 3e68b71a64d5083956df4b30e3594402e7cf12e4..3170db69315293648e897146e141f1f4c2c972f9 100644 --- a/src/data/IGPURenderPassDescriptor.ts +++ b/src/data/polyfills/RenderPassDescriptor.ts @@ -1,4 +1,4 @@ -import { IRenderPassColorAttachment, IRenderPassDescriptor } from "@feng3d/render-api"; +import { RenderPassColorAttachment, RenderPassDescriptor } from "@feng3d/render-api"; declare module "@feng3d/render-api" { @@ -7,7 +7,7 @@ declare module "@feng3d/render-api" * * {@link GPURenderPassDescriptor} */ - export interface IRenderPassDescriptor + export interface RenderPassDescriptor { /** * 附件尺寸。 @@ -16,14 +16,14 @@ declare module "@feng3d/render-api" * * 该值被修改后将会改变所有附件的尺寸,并释放附件上过时的GPU纹理资源。 */ - attachmentSize?: { width: number, height: number }; + readonly attachmentSize?: { readonly width: number, readonly height: number }; /** * The maximum number of draw calls that will be done in the render pass. Used by some * implementations to size work injected before the render pass. Keeping the default value * is a good default, unless it is known that more draw calls will be done. */ - maxDrawCount?: GPUSize64; + readonly maxDrawCount?: GPUSize64; } } diff --git a/src/data/IGPURenderPipeline.ts b/src/data/polyfills/RenderPipeline.ts similarity index 93% rename from src/data/IGPURenderPipeline.ts rename to src/data/polyfills/RenderPipeline.ts index a7a5da5d2fd0655a46e29bb1e4c88d5b72cedb9f..fbace273a223ebf2beb3b4fec83867cb1dbbcece 100644 --- a/src/data/IGPURenderPipeline.ts +++ b/src/data/polyfills/RenderPipeline.ts @@ -1,6 +1,6 @@ -import { IBlendState } from "@feng3d/render-api"; +import { BlendState,DepthStencilState } from "@feng3d/render-api"; -import { IGPUMultisampleState } from "./IGPUMultisampleState"; +import { MultisampleState } from "../MultisampleState"; declare module "@feng3d/render-api" { @@ -9,12 +9,12 @@ declare module "@feng3d/render-api" * * @see https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createRenderPipeline */ - export interface IRenderPipeline + export interface RenderPipeline { /** * 多重采样阶段描述。 */ - readonly multisample?: IGPUMultisampleState; + readonly multisample?: MultisampleState; } /** @@ -26,7 +26,7 @@ declare module "@feng3d/render-api" * * @see GPUVertexState */ - export interface IVertexState + export interface VertexState { /** * The name of the function in {@link GPUProgrammableStage#module} that this stage will use to @@ -63,7 +63,7 @@ declare module "@feng3d/render-api" * * {@link GPUFragmentState} */ - export interface IFragmentState + export interface FragmentState { /** * The name of the function in {@link GPUProgrammableStage#module} that this stage will use to @@ -115,7 +115,7 @@ declare module "@feng3d/render-api" * * @see https://www.orillusion.com/zh/webgpu.html#depth-stencil-state */ - export interface IDepthStencilState + export interface DepthStencilState { /** * 片元的最大深度偏差。 diff --git a/src/data/IGPUTexture.ts b/src/data/polyfills/Texture.ts similarity index 94% rename from src/data/IGPUTexture.ts rename to src/data/polyfills/Texture.ts index 0245156be17f8d0ffeb5f30d1cc550b366551624..e39661e5b45b1b9856a1b5e9170a17680e75aaf1 100644 --- a/src/data/IGPUTexture.ts +++ b/src/data/polyfills/Texture.ts @@ -1,4 +1,4 @@ -import { ITexture } from "@feng3d/render-api"; +import { Texture } from "@feng3d/render-api"; declare module "@feng3d/render-api" { @@ -7,7 +7,7 @@ declare module "@feng3d/render-api" * * @see GPUQueue.copyExternalImageToTexture */ - export interface ITextureImageSource + export interface TextureImageSource { /** * Defines which aspects of the {@link GPUImageCopyTexture#texture} to copy to/from. @@ -33,7 +33,7 @@ declare module "@feng3d/render-api" * * @see GPUQueue.writeTexture */ - export interface ITextureDataSource + export interface TextureDataSource { /** * Defines which aspects of the {@link GPUImageCopyTexture#texture} to copy to/from. @@ -48,7 +48,7 @@ declare module "@feng3d/render-api" * @see GPUDevice.createTexture * @see GPUTextureDescriptor */ - export interface ITexture + export interface Texture { /** * Specifies what view {@link GPUTextureViewDescriptor#format} values will be allowed when calling diff --git a/src/data/IGPUTextureView.ts b/src/data/polyfills/TextureView.ts similarity index 89% rename from src/data/IGPUTextureView.ts rename to src/data/polyfills/TextureView.ts index 61b2aaddaecc47e06614e1723ce7b3fd28344689..603bebcdc36a28111756cf7e38ac604d390f1b3c 100644 --- a/src/data/IGPUTextureView.ts +++ b/src/data/polyfills/TextureView.ts @@ -1,14 +1,13 @@ -import { ITextureLike } from "@feng3d/render-api"; -import { IGPUCanvasTexture } from "./IGPUCanvasTexture"; +import { CanvasTexture, TextureLike } from "@feng3d/render-api"; declare module "@feng3d/render-api" { - export interface ITextureLikeMap + export interface TextureLikeMap { /** * 画布纹理。 */ - IGPUCanvasTexture: IGPUCanvasTexture; + CanvasTexture: CanvasTexture; } /** @@ -18,7 +17,7 @@ declare module "@feng3d/render-api" * @see GPUTexture.createView * @see GPUTextureViewDescriptor */ - export interface ITextureView + export interface TextureView { /** * The format of the texture view. Must be either the {@link GPUTextureDescriptor#format} of the diff --git a/src/data/polyfills/Uniforms.ts b/src/data/polyfills/Uniforms.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c0286816810e7c8be8fb3ad9697629f2919b234 --- /dev/null +++ b/src/data/polyfills/Uniforms.ts @@ -0,0 +1,12 @@ +import { Sampler } from "@feng3d/render-api"; +import { VideoTexture } from "../VideoTexture"; + +declare module "@feng3d/render-api" +{ + export interface BindingResourceTypeMap + { + Sampler: Sampler; + TextureView: TextureView; + VideoTexture: VideoTexture; + } +} \ No newline at end of file diff --git a/src/eventnames.ts b/src/eventnames.ts index 036ab32eb6cf52164f512d62a7a6d08d7e511a71..c1bd5f3eee0baa797b1fd2318ddefd668249816b 100644 --- a/src/eventnames.ts +++ b/src/eventnames.ts @@ -1,14 +1,23 @@ -/** - * WebGPU纹理销毁事件 - */ -export const GPUTexture_destroy = "GPUTexture_destroy"; -/** - * WebGPU纹理视图销毁事件 - */ -export const GPUTextureView_destroy = "GPUTextureView_destroy"; - export const GPUQueue_submit = "GPUQueue_submit"; -export const IGPUTexture_resize = "IGPUTexture_resize"; +export const IGPUSampler_changed = "IGPUSampler_changed"; -export const IGPUSampler_changed = "IGPUSampler_changed"; \ No newline at end of file +/** + * 通过反应式机制更改数值来触发事件。 + */ +export const webgpuEvents: { + /** + * 提交WebGPU前数值加一。 + * + * 用于处理提交前需要执行的操作。 + * + * 例如 {@link GPUCanvasContext.getCurrentTexture} 与 {@linkGPUDevice.importExternalTexture } 需要在提交前执行,检查结果是否变化。 + * + * 注:引擎内部处理,外部无需关心。 + * + * @private + */ + readonly preSubmit: number; +} = { + preSubmit: 0, +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 620c38f6ec47ca80b9680ffbe5f356f00ccf54e1..62598cac84cd7890b0861e20fa5afa65aac7dcdc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,34 +1,25 @@ export * from "./WebGPU"; -export * from "./data/IGPUBindingResources"; -export * from "./data/IGPUBuffer"; -export * from "./data/IGPUCanvasContext"; -export * from "./data/IGPUCanvasTexture"; -export * from "./data/IGPUCommandEncoder"; -export * from "./data/IGPUComputeObject"; -export * from "./data/IGPUComputePass"; -export * from "./data/IGPUComputePipeline"; -export * from "./data/IGPUOcclusionQuery"; -export * from "./data/IGPUPrimitiveState"; -export * from "./data/IGPUReadPixels"; -export * from "./data/IGPURenderBundle"; -export * from "./data/IGPURenderObject"; -export * from "./data/IGPURenderPass"; -export * from "./data/IGPURenderPassColorAttachment"; -export * from "./data/IGPURenderPassDepthStencilAttachment"; -export * from "./data/IGPURenderPassDescriptor"; -export * from "./data/IGPURenderPipeline"; -export * from "./data/IGPUTexture"; -export * from "./data/IGPUTextureView"; -export * from "./data/IGPUTimestampQuery"; +export * from "./data/ComputeObject"; +export * from "./data/ComputePass"; +export * from "./data/ComputePipeline"; +export * from "./data/polyfills/Texture"; +export * from "./data/polyfills/TextureView"; +export * from "./data/RenderBundle"; +export * from "./data/TimestampQuery"; + +export * from "./data/polyfills/Buffer"; +export * from "./data/polyfills/CanvasContext"; +export * from "./data/polyfills/CommandEncoder"; +export * from "./data/polyfills/PrimitiveState"; +export * from "./data/polyfills/ReadPixels"; +export * from "./data/polyfills/RenderObject"; +export * from "./data/polyfills/RenderPass"; +export * from "./data/polyfills/RenderPassColorAttachment"; +export * from "./data/polyfills/RenderPassDepthStencilAttachment"; +export * from "./data/polyfills/RenderPassDescriptor"; +export * from "./data/polyfills/RenderPipeline"; +export * from "./data/polyfills/Uniforms"; export * from "./caches/getIGPUBuffer"; export * from "./types/VertexFormat"; - -/** - * 内部 - */ -export * as internal from "./internal"; -export * from "./utils/ChainMap"; -export * from "./utils/getOffscreenCanvasId"; - diff --git a/src/internal.ts b/src/internal.ts deleted file mode 100644 index fdc85840dedb8fa58376a030f939d990a8c7eceb..0000000000000000000000000000000000000000 --- a/src/internal.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { getIGPUTextureLikeSize as getGPUTextureSize } from "./caches/getIGPUTextureSize"; - -export * from "./caches/getWGSLReflectInfo"; - diff --git a/src/internal/IGPUBindGroupDescriptor.ts b/src/internal/IGPUBindGroupDescriptor.ts deleted file mode 100644 index f9c9a8205d2ad6d947b6e9e8f2d630d31c0f3d61..0000000000000000000000000000000000000000 --- a/src/internal/IGPUBindGroupDescriptor.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { IBufferBinding, ISampler, ITextureView } from "@feng3d/render-api"; -import { IGPUExternalTexture } from "../data/IGPUExternalTexture"; - -/** - * GPU 绑定组。 - * - * @see GPUBindGroupDescriptor - * @see GPUDevice.createBindGroup - */ -export interface IGPUBindGroupDescriptor -{ - /** - * The initial value of {@link GPUObjectBase#label|GPUObjectBase.label}. - */ - label?: string; - - /** - * The {@link IGPUBindGroupLayoutDescriptor} the entries of this bind group will conform to. - */ - layout: GPUBindGroupLayoutDescriptor; - - /** - * A list of entries describing the resources to expose to the shader for each binding - * described by the {@link GPUBindGroupDescriptor#layout}. - * - * {@link GPUBindGroupEntry} - */ - entries: IGPUBindGroupEntry[]; -} - -/** - * 绑定资源入口,指定资源绑定的位置。 - * - * @see GPUBindGroupEntry - */ -export interface IGPUBindGroupEntry -{ - binding: GPUIndex32; - - /** - * The resource to bind, which may be a {@link GPUSampler}, {@link GPUTextureView}, - * {@link GPUExternalTexture}, or {@link GPUBufferBinding}. - */ - resource: IGPUBindingResource; -} - -export type IGPUBindingResource = ISampler | ITextureView | IBufferBinding | IGPUExternalTexture; diff --git a/src/internal/IGPUIndexBuffer.ts b/src/internal/IGPUIndexBuffer.ts deleted file mode 100644 index 87838c8618248d90dd8156824c9ac42dcf487d4a..0000000000000000000000000000000000000000 --- a/src/internal/IGPUIndexBuffer.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { IBuffer } from "@feng3d/render-api"; - -/** - * GPU渲染时使用的索引缓冲区。 - * - * {@link GPURenderCommandsMixin.setIndexBuffer} - */ -export interface IGPUSetIndexBuffer -{ - /** - * Buffer containing index data to use for subsequent drawing commands. - * - * 顶点索引缓冲区,包含提供给后续绘制命令使用的顶点索引数据。 - */ - buffer: IBuffer; - - /** - * Format of the index data contained in `buffer`. - * - * 缓冲区中提供的顶点索引数据格式。 - */ - indexFormat: GPUIndexFormat; - - /** - * Offset in bytes into `buffer` where the index data begins. Defaults to `0`. - * - * 索引数据在缓冲区中的起始偏移值。默认为 `0` 。 - */ - offset?: number; - - /** - * Size in bytes of the index data in `buffer`. Defaults to the size of the buffer minus the offset. - * - * 索引数据在缓冲区中所占字节尺寸。默认为缓冲区尺寸减去起始偏移值。 - */ - size?: number; -} diff --git a/src/internal/IGPUPipelineLayoutDescriptor.ts b/src/internal/IGPUPipelineLayoutDescriptor.ts deleted file mode 100644 index e6d4427ea563134b6f2cd750b98aad5b49e80990..0000000000000000000000000000000000000000 --- a/src/internal/IGPUPipelineLayoutDescriptor.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { VariableInfo } from "wgsl_reflect"; - -/** - * GPU管线布局描述。 - * - * {@link GPUPipelineLayoutDescriptor} - * - * {@link GPUDevice.createPipelineLayout} - * - * {@link GPUPipelineLayout} - */ -export interface IGPUPipelineLayoutDescriptor extends Omit -{ - /** - * A list of {@link GPUBindGroupLayout}s the pipeline will use. Each element corresponds to a - * @group attribute in the {@link GPUShaderModule}, with the `N`th element corresponding with - * `@group(N)`. - */ - bindGroupLayouts: IGPUBindGroupLayoutDescriptor[]; -} - -export interface IGPUBindGroupLayoutDescriptor extends GPUBindGroupLayoutDescriptor -{ - entries: IGPUBindGroupLayoutEntry[]; - entryNames: string[], -} - -export interface IGPUBindGroupLayoutEntry extends GPUBindGroupLayoutEntry -{ - variableInfo: VariableInfo; -} \ No newline at end of file diff --git a/src/internal/IGPUSetBindGroup.ts b/src/internal/IGPUSetBindGroup.ts deleted file mode 100644 index 17f6bb87c1cf6c2e0bec9ef8c3fdaa8bdda35d46..0000000000000000000000000000000000000000 --- a/src/internal/IGPUSetBindGroup.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IGPUBindGroupDescriptor } from "./IGPUBindGroupDescriptor"; - -/** - * GPU渲染时使用的绑定组。 - * - * {@link GPUBindingCommandsMixin.setBindGroup} - */ -export interface IGPUSetBindGroup -{ - /** - * GPU绑定组。 - * - * Bind group to use for subsequent render or compute commands. - */ - bindGroup: IGPUBindGroupDescriptor; - - /** - * Array containing buffer offsets in bytes for each entry in `bindGroup` marked as {@link GPUBindGroupLayoutEntry#buffer}.{@link GPUBufferBindingLayout#hasDynamicOffset}.--> - */ - dynamicOffsets?: number[]; -} diff --git a/src/internal/IGPUTextureMultisample.ts b/src/internal/MultisampleTexture.ts similarity index 75% rename from src/internal/IGPUTextureMultisample.ts rename to src/internal/MultisampleTexture.ts index 895f7719d12e11f1aeac9dcd3f6f9893b87ed24c..ea10b0028db8ce2dca2314b2b9a2656f18b1f8a2 100644 --- a/src/internal/IGPUTextureMultisample.ts +++ b/src/internal/MultisampleTexture.ts @@ -1,9 +1,9 @@ -import { ITexture } from "@feng3d/render-api"; +import { Texture } from "@feng3d/render-api"; /** * 多重采样纹理,一般只在渲染通道中需要解决多重采样时使用。 */ -export interface IGPUTextureMultisample extends ITexture +export interface MultisampleTexture extends Texture { /** * The sample count of the texture. A {@link GPUTextureDescriptor#sampleCount} > `1` indicates diff --git a/src/internal/NGPUFragmentState.ts b/src/internal/NGPUFragmentState.ts deleted file mode 100644 index 1ee87274623612b55d1775ab99f1e2fed9000c8d..0000000000000000000000000000000000000000 --- a/src/internal/NGPUFragmentState.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * 内部对象。 - */ -export interface NGPUFragmentState -{ - readonly code: string; - readonly entryPoint: string; - readonly targets: readonly GPUColorTargetState[]; - readonly constants: Readonly>; - - _version?: number; -} \ No newline at end of file diff --git a/src/internal/NGPURenderPipeline.ts b/src/internal/NGPURenderPipeline.ts deleted file mode 100644 index f10bafb675ccf8b723d45c42a46b0fc38585e6d4..0000000000000000000000000000000000000000 --- a/src/internal/NGPURenderPipeline.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { IColor } from "@feng3d/render-api"; -import { NGPUFragmentState } from "./NGPUFragmentState"; -import { NGPUVertexState } from "./NGPUVertexState"; - -/** - * 内部对象。 - */ -export interface NGPURenderPipeline -{ - readonly label: string; - readonly primitive: GPUPrimitiveState; - readonly vertex: NGPUVertexState - readonly fragment: NGPUFragmentState, - readonly depthStencil: GPUDepthStencilState, - readonly multisample: GPUMultisampleState, - - /** - * 如果任意模板测试结果使用了 "replace" 运算,则需要再渲染前设置 `stencilReference` 值。 - */ - readonly stencilReference: number; - - /** - * 当混合系数用到了混合常量值时设置混合常量值。 - */ - readonly blendConstantColor: IColor; -} \ No newline at end of file diff --git a/src/internal/NGPUVertexState.ts b/src/internal/NGPUVertexState.ts deleted file mode 100644 index 41728b65881f5b4e4262ad68b19278f20df09b63..0000000000000000000000000000000000000000 --- a/src/internal/NGPUVertexState.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IVertexState } from "@feng3d/render-api"; - -/** - * 内部对象。 - */ -export interface NGPUVertexState extends IVertexState -{ - readonly entryPoint: string; - readonly constants: Readonly>; - - /** - * A list of {@link GPUVertexBufferLayout}s defining the layout of the vertex attribute data in the - * vertex buffers used by this pipeline. - * - * 自动根据反射信息生成,不用设置。 - */ - readonly buffers: GPUVertexBufferLayout[]; -} diff --git a/src/internal/RenderObjectCache.ts b/src/internal/RenderObjectCache.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f9e7462d5218979cce141b6fb4412c4707655b6 --- /dev/null +++ b/src/internal/RenderObjectCache.ts @@ -0,0 +1,320 @@ +import { anyEmitter } from "@feng3d/event"; +import { ChainMap, reactive } from "@feng3d/render-api"; +import { GPUQueue_submit, webgpuEvents } from "../eventnames"; + +const cache = new ChainMap(); + +function setVaule>(cache: ChainMap, keys: T): T +{ + const v = cache.get(keys); + if (v) return v; + cache.set(keys, keys); + return keys; +} + +export type CommandType = + | [func: "setViewport", x: number, y: number, width: number, height: number, minDepth: number, maxDepth: number] + | [func: "setScissorRect", x: GPUIntegerCoordinate, y: GPUIntegerCoordinate, width: GPUIntegerCoordinate, height: GPUIntegerCoordinate] + | [func: "setPipeline", pipeline: GPURenderPipeline] + | [func: "setBindGroup", index: number, bindGroup: GPUBindGroup] + | [func: "setVertexBuffer", slot: GPUIndex32, buffer: GPUBuffer, offset?: GPUSize64, size?: GPUSize64] + | [func: "setIndexBuffer", buffer: GPUBuffer, indexFormat: GPUIndexFormat, offset?: GPUSize64, size?: GPUSize64] + | [func: "draw", vertexCount: GPUSize32, instanceCount?: GPUSize32, firstVertex?: GPUSize32, firstInstance?: GPUSize32] + | [func: "drawIndexed", indexCount: GPUSize32, instanceCount?: GPUSize32, firstIndex?: GPUSize32, baseVertex?: GPUSignedOffset32, firstInstance?: GPUSize32] + | [func: "setBlendConstant", color: GPUColor] + | [func: "setStencilReference", reference: GPUStencilValue] + | [func: "executeBundles", bundles: GPURenderBundle[]] + | [func: "beginOcclusionQuery", queryIndex: GPUSize32] + | [func: "endOcclusionQuery"] + ; + +export class RenderObjectCache implements RenderPassObjectCommand +{ + protected setViewport?: [func: "setViewport", x: number, y: number, width: number, height: number, minDepth: number, maxDepth: number]; + protected setScissorRect?: [func: "setScissorRect", x: GPUIntegerCoordinate, y: GPUIntegerCoordinate, width: GPUIntegerCoordinate, height: GPUIntegerCoordinate]; + protected setPipeline: [func: "setPipeline", pipeline: GPURenderPipeline]; + protected setBlendConstant?: [func: "setBlendConstant", color: GPUColor]; + protected setStencilReference?: [func: "setStencilReference", reference: GPUStencilValue]; + protected setBindGroup?: [func: "setBindGroup", index: number, bindGroup: GPUBindGroup][] = []; + protected setVertexBuffer?: [func: "setVertexBuffer", slot: GPUIndex32, buffer: GPUBuffer, offset?: GPUSize64, size?: GPUSize64][] = []; + protected setIndexBuffer?: [func: "setIndexBuffer", buffer: GPUBuffer, indexFormat: GPUIndexFormat, offset?: GPUSize64, size?: GPUSize64]; + protected draw?: [func: "draw", vertexCount: GPUSize32, instanceCount?: GPUSize32, firstVertex?: GPUSize32, firstInstance?: GPUSize32]; + protected drawIndexed?: [func: "drawIndexed", indexCount: GPUSize32, instanceCount?: GPUSize32, firstIndex?: GPUSize32, baseVertex?: GPUSignedOffset32, firstInstance?: GPUSize32]; + + push(command: CommandType) + { + let command1 = commandMap.get(command); + if (!command1) + { + command1 = command; + commandMap.set(command, command1); + } + else + { + command = command1; + } + + const func = command[0]; + if (func === "setBindGroup") + { + this.setBindGroup[command[1]] = command; + return; + } + else if (func === "setVertexBuffer") + { + this.setVertexBuffer[command[1]] = command; + return; + } + command = setVaule(cache, command); + this[command[0]] = command as any; + } + + delete(func: CommandType[0]) + { + if (func === "setBindGroup") + { + this.setBindGroup = []; + return; + } + else if (func === "setVertexBuffer") + { + this.setVertexBuffer = []; + return; + } + this[func as any] = undefined; + } + + run(device: GPUDevice, commands: CommandType[], state: RenderObjectCache) + { + const { setViewport, setScissorRect, setPipeline, setBlendConstant, setStencilReference, setBindGroup, setVertexBuffer, setIndexBuffer, draw, drawIndexed } = this; + + if (state.setViewport !== setViewport && setViewport) + { + commands.push(setViewport); + state.setViewport = setViewport; + } + if (state.setScissorRect !== setScissorRect && setScissorRect) + { + commands.push(setScissorRect); + state.setScissorRect = setScissorRect; + } + if (state.setBlendConstant !== setBlendConstant && setBlendConstant) + { + commands.push(setBlendConstant); + state.setBlendConstant = setBlendConstant; + } + if (state.setStencilReference !== setStencilReference && setStencilReference) + { + commands.push(setStencilReference); + } + if (state.setPipeline !== setPipeline) + { + commands.push(setPipeline); + state.setPipeline = setPipeline; + } + for (let i = 0, len = setBindGroup.length; i < len; i++) + { + if (state.setBindGroup[i] !== setBindGroup[i] && setBindGroup[i]) + { + commands.push(setBindGroup[i]); + state.setBindGroup[i] = setBindGroup[i]; + } + } + for (let i = 0, len = setVertexBuffer.length; i < len; i++) + { + if (state.setVertexBuffer[i] !== setVertexBuffer[i]) + { + commands.push(setVertexBuffer[i]); + state.setVertexBuffer[i] = setVertexBuffer[i]; + } + } + if (state.setIndexBuffer !== setIndexBuffer && setIndexBuffer) + { + commands.push(setIndexBuffer); + state.setIndexBuffer = setIndexBuffer; + } + draw && commands.push(draw); + drawIndexed && commands.push(drawIndexed); + } +} + +export class OcclusionQueryCache implements RenderPassObjectCommand +{ + queryIndex: number; + renderObjectCaches: RenderObjectCache[]; + + run(device: GPUDevice, commands: CommandType[], state: RenderObjectCache) + { + commands.push(["beginOcclusionQuery", this.queryIndex]); + for (let i = 0, len = this.renderObjectCaches.length; i < len; i++) + { + this.renderObjectCaches[i].run(undefined, commands, state); + } + commands.push(["endOcclusionQuery"]); + } +} + +export interface RenderPassObjectCommand +{ + run(device: GPUDevice, commands: CommandType[], state: RenderObjectCache): void; +} + +export class RenderBundleCommand implements RenderPassObjectCommand +{ + gpuRenderBundle: GPURenderBundle; + descriptor: GPURenderBundleEncoderDescriptor; + bundleCommands: CommandType[]; + run(device: GPUDevice, commands: CommandType[], state: RenderObjectCache): void + { + if (!this.gpuRenderBundle) + { + // + const renderBundleEncoder = device.createRenderBundleEncoder(this.descriptor); + + runCommands(renderBundleEncoder, this.bundleCommands); + + this.gpuRenderBundle = renderBundleEncoder.finish(); + } + + commands.push(["executeBundles", [this.gpuRenderBundle]]); + } +} + +export interface PassEncoderCommand +{ + run(commandEncoder: GPUCommandEncoder): void; +} + +export class RenderPassCommand +{ + run(commandEncoder: GPUCommandEncoder) + { + const { renderPassDescriptor, commands } = this; + const { device } = commandEncoder; + + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.device = device; + + runCommands(passEncoder, commands); + passEncoder.end(); + + renderPassDescriptor.timestampWrites?.resolve(commandEncoder); + renderPassDescriptor.occlusionQuerySet?.resolve(commandEncoder); + } + renderPassDescriptor: GPURenderPassDescriptor; + commands: CommandType[] +} + +export class ComputeObjectCommand +{ + run(passEncoder: GPUComputePassEncoder) + { + passEncoder.setPipeline(this.computePipeline); + this.setBindGroup.forEach(([index, bindGroup]) => + { + passEncoder.setBindGroup(index, bindGroup); + }); + const [workgroupCountX, workgroupCountY, workgroupCountZ] = this.dispatchWorkgroups; + passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ); + } + computePipeline: GPUComputePipeline; + setBindGroup: [index: GPUIndex32, bindGroup: GPUBindGroup][]; + dispatchWorkgroups: [workgroupCountX: GPUSize32, workgroupCountY?: GPUSize32, workgroupCountZ?: GPUSize32]; +} + +export class ComputePassCommand +{ + run(commandEncoder: GPUCommandEncoder) + { + const { descriptor, computeObjectCommands } = this; + // + const passEncoder = commandEncoder.beginComputePass(descriptor); + computeObjectCommands.forEach((command) => command.run(passEncoder)); + passEncoder.end(); + // 处理时间戳查询 + descriptor.timestampWrites?.resolve(commandEncoder); + } + descriptor: GPUComputePassDescriptor; + computeObjectCommands: ComputeObjectCommand[]; +} + +export class CopyTextureToTextureCommand +{ + run(commandEncoder: GPUCommandEncoder) + { + const { source, destination, copySize } = this; + + commandEncoder.copyTextureToTexture( + source, + destination, + copySize, + ); + } + source: GPUImageCopyTexture; + destination: GPUImageCopyTexture; + copySize: GPUExtent3DStrict; +} + +export class CopyBufferToBufferCommand +{ + run(commandEncoder: GPUCommandEncoder) + { + const { source, sourceOffset, destination, destinationOffset, size } = this; + + commandEncoder.copyBufferToBuffer( + source, sourceOffset, destination, destinationOffset, size + ); + } + source: GPUBuffer; + sourceOffset: number; + destination: GPUBuffer; + destinationOffset: number; + size: number; +} + +export class CommandEncoderCommand +{ + run(device: GPUDevice) + { + const gpuCommandEncoder = device.createCommandEncoder(); + gpuCommandEncoder.device = device; + this.passEncoders.forEach((passEncoder) => passEncoder.run(gpuCommandEncoder)); + return gpuCommandEncoder.finish(); + } + passEncoders: (RenderPassCommand | ComputePassCommand | CopyTextureToTextureCommand | CopyBufferToBufferCommand)[]; +} + +export class SubmitCommand +{ + run(device: GPUDevice) + { + const { commandBuffers } = this; + + // 提交前数值加一,用于处理提交前需要执行的操作。 + reactive(webgpuEvents).preSubmit = ~~reactive(webgpuEvents).preSubmit + 1; + + device.queue.submit(commandBuffers.map((v) => v.run(device))); + + // 派发提交WebGPU事件 + anyEmitter.emit(device.queue, GPUQueue_submit); + } + commandBuffers: CommandEncoderCommand[]; +} + +function runCommands(renderBundleEncoder: GPURenderBundleEncoder | GPURenderPassEncoder, commands: CommandType[]) +{ + for (let i = 0, n = commands.length; i < n; i++) + { + const command = commands[i]; + if (command[0] === "setBindGroup") + { + renderBundleEncoder.setBindGroup(command[1], command[2]); + } + else + { + renderBundleEncoder[command[0]](command[1], command[2], command[3], command[4], command[5], command[6]); + } + } +} + +const commandMap = new ChainMap(); \ No newline at end of file diff --git a/src/internal/IGPURenderPassFormat.ts b/src/internal/RenderPassFormat.ts similarity index 57% rename from src/internal/IGPURenderPassFormat.ts rename to src/internal/RenderPassFormat.ts index 65a5fa2a9d8e4c61e05f385a92bd4f76f65a0f0e..00beb78e42aea48132e7ce6df27af2f2d25c9312 100644 --- a/src/internal/IGPURenderPassFormat.ts +++ b/src/internal/RenderPassFormat.ts @@ -1,11 +1,12 @@ -export interface IGPURenderPassFormat +/** + * 渲染通道格式。 + * + * @private + */ +export interface RenderPassFormat extends GPURenderPassLayout { readonly attachmentSize: { readonly width: number, readonly height: number } readonly colorFormats: readonly GPUTextureFormat[], readonly depthStencilFormat: GPUTextureFormat, readonly sampleCount?: 4 - /** - * 初始化后被自动赋值,用于识别通道格式是否相同。 - */ - readonly _key?: string; -} \ No newline at end of file +} diff --git a/src/internal/NGPUVertexBuffer.ts b/src/internal/VertexBuffer.ts similarity index 87% rename from src/internal/NGPUVertexBuffer.ts rename to src/internal/VertexBuffer.ts index 86338a4fe1a40e13ffbb0ed6d0500df6986a3cc0..bd3debab0394c7e2e329b0e460e1a52624b068cc 100644 --- a/src/internal/NGPUVertexBuffer.ts +++ b/src/internal/VertexBuffer.ts @@ -1,4 +1,4 @@ -import { IVertexDataTypes } from "@feng3d/render-api"; +import { VertexDataTypes } from "@feng3d/render-api"; /** * Sets the current vertex buffer for the given slot. @@ -7,14 +7,14 @@ import { IVertexDataTypes } from "@feng3d/render-api"; * * {@link GPURenderCommandsMixin.setVertexBuffer} */ -export interface NGPUVertexBuffer +export interface VertexBuffer { /** * Buffer containing vertex data to use for subsequent drawing commands. * * GPU缓冲区,包含后续绘制命令所包含的顶点数据的 */ - data: IVertexDataTypes; + data: VertexDataTypes; /** * Offset in bytes into `buffer` where the vertex data begins. Defaults to `0`. diff --git a/src/internal/getIGPUSetIndexBuffer.ts b/src/internal/getIGPUSetIndexBuffer.ts deleted file mode 100644 index 5160a8bc9fa040e861370c6c19f3bbc6fa996dd3..0000000000000000000000000000000000000000 --- a/src/internal/getIGPUSetIndexBuffer.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getIGPUIndexBuffer } from "../caches/getIGPUBuffer"; -import { IGPUSetIndexBuffer } from "./IGPUIndexBuffer"; - -export function getIGPUSetIndexBuffer(index: Uint16Array | Uint32Array) -{ - const indexBuffer: IGPUSetIndexBuffer = index["_IGPUIndexBuffer"] = index["_IGPUIndexBuffer"] || { - buffer: getIGPUIndexBuffer(index), - indexFormat: index.BYTES_PER_ELEMENT === 4 ? "uint32" : "uint16", - offset: index.byteOffset, - size: index.byteLength, - }; - - return indexBuffer; -} diff --git a/src/internal/internal.ts b/src/internal/internal.ts deleted file mode 100644 index 401a79e376c4030b156428aaaab5d253c10d4529..0000000000000000000000000000000000000000 --- a/src/internal/internal.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IRenderPassColorAttachment, ITextureView } from "@feng3d/render-api"; - -/** - * 内部使用 - */ -export interface NGPURenderPassColorAttachment extends IRenderPassColorAttachment -{ - /** - * A {@link GPUTextureView} describing the texture subresource that will receive the resolved - * output for this color attachment if {@link GPURenderPassColorAttachment#view} is - * multisampled. - */ - resolveTarget?: ITextureView; -} diff --git a/src/runs/RunWebGPU.ts b/src/runs/RunWebGPU.ts deleted file mode 100644 index 9134abeeede496f68e01460f134e0e553605004f..0000000000000000000000000000000000000000 --- a/src/runs/RunWebGPU.ts +++ /dev/null @@ -1,467 +0,0 @@ -import { anyEmitter } from "@feng3d/event"; -import { ICommandEncoder, ICopyBufferToBuffer, ICopyTextureToTexture, IDrawIndexed, IDrawVertex, IIndicesDataTypes, IRenderObject, IRenderPass, IRenderPassObject, IRenderPipeline, IScissorRect, ISubmit, IUniforms, IVertexAttributes, IViewport } from "@feng3d/render-api"; - -import { getGPUBindGroup } from "../caches/getGPUBindGroup"; -import { getGPUBuffer } from "../caches/getGPUBuffer"; -import { getGPUComputePipeline } from "../caches/getGPUComputePipeline"; -import { getGPURenderOcclusionQuery } from "../caches/getGPURenderOcclusionQuery"; -import { getGPURenderPassDescriptor } from "../caches/getGPURenderPassDescriptor"; -import { getGPURenderPassFormat } from "../caches/getGPURenderPassFormat"; -import { getGPURenderPipeline } from "../caches/getGPURenderPipeline"; -import { getGPURenderTimestampQuery } from "../caches/getGPURenderTimestampQuery"; -import { getGPUTexture } from "../caches/getGPUTexture"; -import { getIGPUVertexBuffer } from "../caches/getIGPUBuffer"; -import { getIGPUComputePipeline } from "../caches/getIGPUComputePipeline"; -import { IGPUShader } from "../caches/getIGPUPipelineLayout"; -import { getIGPUSetBindGroups } from "../caches/getIGPUSetBindGroups"; -import { getNGPURenderPipeline } from "../caches/getNGPURenderPipeline"; -import { getRealGPUBindGroup } from "../const"; -import { IGPUComputeObject } from "../data/IGPUComputeObject"; -import { IGPUComputePass } from "../data/IGPUComputePass"; -import { IGPUComputePipeline } from "../data/IGPUComputePipeline"; -import { IGPUOcclusionQuery } from "../data/IGPUOcclusionQuery"; -import { IGPURenderBundle } from "../data/IGPURenderBundle"; -import { IGPUWorkgroups } from "../data/IGPUWorkgroups"; -import { GPUQueue_submit } from "../eventnames"; -import { IGPURenderPassFormat } from "../internal/IGPURenderPassFormat"; -import { getIGPUSetIndexBuffer } from "../internal/getIGPUSetIndexBuffer"; -import { ChainMap } from "../utils/ChainMap"; -import { watcher } from "@feng3d/watcher"; - -export class RunWebGPU -{ - runSubmit(device: GPUDevice, submit: ISubmit) - { - const commandBuffers = submit.commandEncoders.map((v) => - { - const commandBuffer = this.runCommandEncoder(device, v); - - return commandBuffer; - }); - - device.queue.submit(commandBuffers); - - // 派发提交WebGPU事件 - anyEmitter.emit(device.queue, GPUQueue_submit); - } - - protected runCommandEncoder(device: GPUDevice, commandEncoder: ICommandEncoder) - { - const gpuCommandEncoder = device.createCommandEncoder(); - - commandEncoder.passEncoders.forEach((passEncoder) => - { - if (!passEncoder.__type) - { - this.runRenderPass(device, gpuCommandEncoder, passEncoder as IRenderPass); - } - else if (passEncoder.__type === "RenderPass") - { - this.runRenderPass(device, gpuCommandEncoder, passEncoder); - } - else if (passEncoder.__type === "ComputePass") - { - this.runComputePass(device, gpuCommandEncoder, passEncoder); - } - else if (passEncoder.__type === "CopyTextureToTexture") - { - this.runCopyTextureToTexture(device, gpuCommandEncoder, passEncoder); - } - else if (passEncoder.__type === "CopyBufferToBuffer") - { - this.runCopyBufferToBuffer(device, gpuCommandEncoder, passEncoder); - } - else - { - console.error(`未处理 passEncoder ${passEncoder}`); - } - }); - - return gpuCommandEncoder.finish(); - } - - protected runRenderPass(device: GPUDevice, commandEncoder: GPUCommandEncoder, renderPass: IRenderPass) - { - const { descriptor, renderObjects } = renderPass; - - const renderPassDescriptor = getGPURenderPassDescriptor(device, descriptor); - const renderPassFormat = getGPURenderPassFormat(descriptor); - - // 处理时间戳查询 - const timestampQuery = getGPURenderTimestampQuery(device, renderPass.timestampQuery); - timestampQuery.init(device, renderPassDescriptor); - - // 处理不被遮挡查询。 - const occlusionQuery = getGPURenderOcclusionQuery(renderObjects); - occlusionQuery.init(device, renderPassDescriptor); - - const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); - - this.runRenderPassObjects(device, passEncoder, renderPassFormat, renderObjects); - - passEncoder.end(); - - // 处理不被遮挡查询。 - occlusionQuery.resolve(device, commandEncoder, renderPass); - - // 处理时间戳查询 - timestampQuery.resolve(device, commandEncoder, renderPass); - } - - protected runRenderPassObjects(device: GPUDevice, passEncoder: GPURenderPassEncoder, renderPassFormat: IGPURenderPassFormat, renderObjects?: readonly IRenderPassObject[]) - { - if (!renderObjects) return; - // - renderObjects.forEach((element) => - { - if (!element.__type) - { - this.runRenderObject(device, passEncoder, renderPassFormat, element as IRenderObject); - } - else if (element.__type === "RenderObject") - { - this.runRenderObject(device, passEncoder, renderPassFormat, element); - } - else if (element.__type === "RenderBundle") - { - this.runRenderBundle(device, passEncoder, renderPassFormat, element); - } - else if (element.__type === "OcclusionQuery") - { - this.runRenderOcclusionQueryObject(device, passEncoder, renderPassFormat, element); - } - else - { - throw `未处理 ${(element as IRenderPassObject).__type} 类型的渲染通道对象!`; - } - }); - } - - /** - * 执行计算通道。 - * - * @param device GPU设备。 - * @param commandEncoder 命令编码器。 - * @param computePass 计算通道。 - */ - protected runComputePass(device: GPUDevice, commandEncoder: GPUCommandEncoder, computePass: IGPUComputePass) - { - const descriptor: GPUComputePassDescriptor = {}; - // 处理时间戳查询 - const timestampQuery = getGPURenderTimestampQuery(device, computePass?.timestampQuery); - timestampQuery.init(device, descriptor); - - const passEncoder = commandEncoder.beginComputePass(descriptor); - - this.runComputeObjects(device, passEncoder, computePass.computeObjects); - - passEncoder.end(); - - // 处理时间戳查询 - timestampQuery.resolve(device, commandEncoder, computePass); - } - - protected runComputeObjects(device: GPUDevice, passEncoder: GPUComputePassEncoder, computeObjects: IGPUComputeObject[]) - { - computeObjects.forEach((computeObject) => - { - this.runComputeObject(device, passEncoder, computeObject); - }); - } - - protected runCopyTextureToTexture(device: GPUDevice, commandEncoder: GPUCommandEncoder, copyTextureToTexture: ICopyTextureToTexture) - { - const sourceTexture = getGPUTexture(device, copyTextureToTexture.source.texture); - const destinationTexture = getGPUTexture(device, copyTextureToTexture.destination.texture); - - const source: GPUImageCopyTexture = { - ...copyTextureToTexture.source, - texture: sourceTexture, - }; - - const destination: GPUImageCopyTexture = { - ...copyTextureToTexture.destination, - texture: destinationTexture, - }; - - commandEncoder.copyTextureToTexture( - source, - destination, - copyTextureToTexture.copySize, - ); - } - - protected runCopyBufferToBuffer(device: GPUDevice, commandEncoder: GPUCommandEncoder, v: ICopyBufferToBuffer) - { - v.sourceOffset ||= 0; - v.destinationOffset ||= 0; - v.size ||= v.source.size; - - // - const sourceBuffer = getGPUBuffer(device, v.source); - const destinationBuffer = getGPUBuffer(device, v.destination); - - commandEncoder.copyBufferToBuffer( - sourceBuffer, - v.sourceOffset, - destinationBuffer, - v.destinationOffset, - v.size, - ); - } - - protected runRenderOcclusionQueryObject(device: GPUDevice, passEncoder: GPURenderPassEncoder, renderPassFormat: IGPURenderPassFormat, renderOcclusionQueryObject: IGPUOcclusionQuery) - { - passEncoder.beginOcclusionQuery(renderOcclusionQueryObject._queryIndex); - renderOcclusionQueryObject.renderObjects.forEach((renderObject) => - { - this.runRenderObject(device, passEncoder, renderPassFormat, renderObject); - }); - passEncoder.endOcclusionQuery(); - } - - protected runRenderBundle(device: GPUDevice, passEncoder: GPURenderPassEncoder, renderPassFormat: IGPURenderPassFormat, renderBundleObject: IGPURenderBundle) - { - const renderBundleMap: ChainMap<[IGPURenderBundle, string], GPURenderBundle> = device["_renderBundleMap"] = device["_renderBundleMap"] || new ChainMap(); - // - let gpuRenderBundle: GPURenderBundle = renderBundleMap.get([renderBundleObject, renderPassFormat._key]); - if (!gpuRenderBundle) - { - const descriptor: GPURenderBundleEncoderDescriptor = { ...renderBundleObject.descriptor, ...renderPassFormat }; - - // - const renderBundleEncoder = device.createRenderBundleEncoder(descriptor); - - this.runRenderBundleObjects(device, renderBundleEncoder, renderPassFormat, renderBundleObject.renderObjects); - - gpuRenderBundle = renderBundleEncoder.finish(); - renderBundleMap.set([renderBundleObject, renderPassFormat._key], gpuRenderBundle); - } - - passEncoder.executeBundles([gpuRenderBundle]); - } - - protected runRenderBundleObjects(device: GPUDevice, passEncoder: GPURenderBundleEncoder, renderPassFormat: IGPURenderPassFormat, renderObjects?: readonly IRenderObject[]) - { - // - renderObjects.forEach((element) => - { - this.runRenderObject(device, passEncoder, renderPassFormat, element as IRenderObject); - }); - } - - /** - * 执行计算对象。 - * - * @param device GPU设备。 - * @param passEncoder 计算通道编码器。 - * @param computeObject 计算对象。 - */ - protected runComputeObject(device: GPUDevice, passEncoder: GPUComputePassEncoder, computeObject: IGPUComputeObject) - { - const { pipeline, uniforms: bindingResources, workgroups } = computeObject; - - const shader: IGPUShader = { compute: pipeline.compute.code }; - - this.runComputePipeline(device, passEncoder, pipeline); - - this.runBindingResources(device, passEncoder, shader, bindingResources); - - this.runWorkgroups(passEncoder, workgroups); - } - - protected runComputePipeline(device: GPUDevice, passEncoder: GPUComputePassEncoder, pipeline: IGPUComputePipeline) - { - const gpuComputePipeline = getIGPUComputePipeline(pipeline); - - const computePipeline = getGPUComputePipeline(device, gpuComputePipeline); - passEncoder.setPipeline(computePipeline); - } - - /** - * 执行计算工作组。 - * - * @param passEncoder 计算通道编码器。 - * @param workgroups 计算工作组。 - */ - protected runWorkgroups(passEncoder: GPUComputePassEncoder, workgroups?: IGPUWorkgroups) - { - const { workgroupCountX, workgroupCountY, workgroupCountZ } = workgroups; - passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ); - } - - /** - * 执行渲染对象。 - * - * @param device GPU设备。 - * @param passEncoder 渲染通道编码器。 - * @param renderObject 渲染对象。 - * @param renderPass 渲染通道。 - */ - protected runRenderObject(device: GPUDevice, passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, renderPassFormat: IGPURenderPassFormat, renderObject: IRenderObject) - { - const { viewport, scissorRect, pipeline, vertices, indices, uniforms: bindingResources, drawVertex, drawIndexed } = renderObject; - - const shader: IGPUShader = { vertex: pipeline.vertex.code, fragment: pipeline.fragment?.code }; - - if ("setViewport" in passEncoder) - { - this.runViewport(passEncoder, renderPassFormat.attachmentSize, viewport); - } - if ("setScissorRect" in passEncoder) - { - this.runScissorRect(passEncoder as GPURenderPassEncoder, renderPassFormat.attachmentSize, scissorRect); - } - - this.runRenderPipeline(device, passEncoder, renderPassFormat, pipeline, vertices, indices); - - this.runBindingResources(device, passEncoder, shader, bindingResources); - - this.runVertices(device, passEncoder, renderPassFormat, pipeline, vertices, indices); - - this.runIndices(device, passEncoder, indices); - - this.runDrawVertex(passEncoder, drawVertex); - - this.runDrawIndexed(passEncoder, drawIndexed); - } - - protected runRenderPipeline(device: GPUDevice, passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, renderPassFormat: IGPURenderPassFormat, pipeline: IRenderPipeline, vertices: IVertexAttributes, indices: IIndicesDataTypes) - { - // - const renderPipelineResult = getNGPURenderPipeline(pipeline, renderPassFormat, vertices, indices); - - const nPipeline = renderPipelineResult.pipeline; - - const gpuRenderPipeline = getGPURenderPipeline(device, nPipeline); - - // - passEncoder.setPipeline(gpuRenderPipeline); - - // 设置模板测试替换值 - if (nPipeline.stencilReference !== undefined) - { - if ("setStencilReference" in passEncoder) - { - passEncoder.setStencilReference(nPipeline.stencilReference); - } - else - { - console.warn(`不支持在 ${passEncoder.constructor.name} 中设置 stencilReference 值!`); - } - } - - if (nPipeline.blendConstantColor !== undefined) - { - if ("setBlendConstant" in passEncoder) - { - passEncoder.setBlendConstant(nPipeline.blendConstantColor); - } - else - { - console.warn(`不支持在 ${passEncoder.constructor.name} 中设置 setBlendConstant 值!`); - } - } - } - - protected runViewport(passEncoder: GPURenderPassEncoder, attachmentSize: { width: number, height: number }, viewport: IViewport) - { - if (viewport) - { - const isYup = viewport.isYup ?? true; - const x = viewport.x ?? 0; - let y = viewport.y ?? 0; - const width = viewport.width ?? attachmentSize.width; - const height = viewport.height ?? attachmentSize.height; - const minDepth = viewport.minDepth ?? 0; - const maxDepth = viewport.maxDepth ?? 0; - - if (isYup) - { - y = attachmentSize.height - y - height; - } - passEncoder.setViewport(x, y, width, height, minDepth, maxDepth); - } - else - { - passEncoder.setViewport(0, 0, attachmentSize.width, attachmentSize.height, 0, 1); - } - } - - protected runScissorRect(passEncoder: GPURenderPassEncoder, attachmentSize: { width: number, height: number }, scissorRect: IScissorRect) - { - if (scissorRect) - { - const isYup = scissorRect.isYup ?? true; - const x = scissorRect.x ?? 0; - let y = scissorRect.y ?? 0; - const width = scissorRect.width ?? attachmentSize.width; - const height = scissorRect.height ?? attachmentSize.height; - - if (isYup) - { - y = attachmentSize.height - y - height; - } - - passEncoder.setScissorRect(x, y, width, height); - } - else - { - passEncoder.setScissorRect(0, 0, attachmentSize.width, attachmentSize.height); - } - } - - protected runBindingResources(device: GPUDevice, passEncoder: GPUBindingCommandsMixin, shader: IGPUShader, bindingResources: IUniforms) - { - // 计算 bindGroups - const setBindGroups = getIGPUSetBindGroups(shader, bindingResources); - - setBindGroups?.forEach((setBindGroup, index) => - { - const gpuBindGroup = getGPUBindGroup(device, setBindGroup.bindGroup)[getRealGPUBindGroup](); - passEncoder.setBindGroup(index, gpuBindGroup, setBindGroup.dynamicOffsets); - }); - } - - protected runVertices(device: GPUDevice, passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, renderPassFormat: IGPURenderPassFormat, pipeline: IRenderPipeline, vertices: IVertexAttributes, indices: IIndicesDataTypes) - { - const renderPipeline = getNGPURenderPipeline(pipeline, renderPassFormat, vertices, indices); - - // - renderPipeline.vertexBuffers?.forEach((vertexBuffer, index) => - { - const buffer = getIGPUVertexBuffer(vertexBuffer.data); - const gBuffer = getGPUBuffer(device, buffer); - - passEncoder.setVertexBuffer(index, gBuffer, vertexBuffer.offset, vertexBuffer.size); - }); - } - - protected runIndices(device: GPUDevice, passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, indices: IIndicesDataTypes) - { - if (!indices) return; - - const indexBuffer = getIGPUSetIndexBuffer(indices); - - const { buffer, indexFormat, offset, size } = indexBuffer; - const gBuffer = getGPUBuffer(device, buffer); - - // - passEncoder.setIndexBuffer(gBuffer, indexFormat, offset, size); - } - - protected runDrawVertex(passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, drawVertex: IDrawVertex) - { - if (!drawVertex) return; - // - passEncoder.draw(drawVertex.vertexCount, drawVertex.instanceCount, drawVertex.firstVertex, drawVertex.firstInstance); - } - - protected runDrawIndexed(passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, drawIndexed: IDrawIndexed) - { - if (!drawIndexed) return; - // - passEncoder.drawIndexed(drawIndexed.indexCount, drawIndexed.instanceCount, drawIndexed.firstIndex, drawIndexed.baseVertex, drawIndexed.firstInstance); - } -} - diff --git a/src/runs/RunWebGPUCommandCache.ts b/src/runs/RunWebGPUCommandCache.ts deleted file mode 100644 index 778d1ebef48719cea6ffaa0bd5fd5aa401cabd98..0000000000000000000000000000000000000000 --- a/src/runs/RunWebGPUCommandCache.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { IRenderObject, IRenderPassObject } from "@feng3d/render-api"; - -import { watcher } from "@feng3d/watcher"; -import { getRealGPUBindGroup } from "../const"; -import { IGPURenderPassFormat } from "../internal/IGPURenderPassFormat"; -import { ChainMap } from "../utils/ChainMap"; -import { RunWebGPU } from "./RunWebGPU"; - -/** - * 套壳模式(RunWebGPUCommandCache)优于覆盖函数(RunWebGPUCommandCache1)的形式。 - */ -export class RunWebGPUCommandCache extends RunWebGPU -{ - protected runRenderPassObjects(device: GPUDevice, passEncoder: GPURenderPassEncoder, renderPassFormat: IGPURenderPassFormat, renderObjects?: IRenderPassObject[]) - { - const map: ChainMap<[string, IRenderPassObject[]], { commands: Array, setBindGroupCommands: Array }> = device["_IGPURenderPassObjectsCommandMap"] = device["_IGPURenderPassObjectsCommandMap"] || new ChainMap(); - let caches = map.get([renderPassFormat._key, renderObjects]); - if (!caches) - { - // 收集命令 - const renderPassRecord = new GPURenderPassRecord(); - const commands = renderPassRecord["_commands"] = []; - - super.runRenderPassObjects(device, renderPassRecord, renderPassFormat, renderObjects); - - // 排除无效命令 - paichuWuxiaoCommands(renderPassFormat.attachmentSize, commands); - - // - const setBindGroupCommands = commands.filter((v) => v[0] === "setBindGroup"); - - caches = { commands, setBindGroupCommands }; - - map.set([renderPassFormat._key, renderObjects], caches); - - // 监听变化 - const onchanged = () => - { - map.delete([renderPassFormat._key, renderObjects]); - // - renderObjects.forEach((v) => { watcher.unwatch(v, "_version", onchanged); }); - }; - renderObjects.forEach((v) => { watcher.watch(v, "_version", onchanged); }); - } - - // 执行命令 - runCommands(passEncoder, caches); - } - - protected runRenderBundleObjects(device: GPUDevice, bundleEncoder: GPURenderBundleEncoder, renderPassFormat: IGPURenderPassFormat, renderObjects?: IRenderObject[]) - { - const map: ChainMap<[string, IRenderObject[]], { commands: Array, setBindGroupCommands: Array }> = device["_IGPURenderPassObjectsCommandMap"] = device["_IGPURenderPassObjectsCommandMap"] || new ChainMap(); - let caches = map.get([renderPassFormat._key, renderObjects]); - if (!caches) - { - // 收集命令 - // const renderBundleRecord = new GPURenderBundleRecord(); - const renderBundleRecord = new GPURenderPassRecord(); - const commands = renderBundleRecord["_commands"] = []; - - super.runRenderBundleObjects(device, renderBundleRecord as any, renderPassFormat, renderObjects); - - // 排除无效命令 - paichuWuxiaoCommands(renderPassFormat.attachmentSize, commands); - // - const setBindGroupCommands = commands.filter((v) => v[0] === "setBindGroup"); - - caches = { commands, setBindGroupCommands }; - - map.set([renderPassFormat._key, renderObjects], caches); - - // 监听变化 - const onchanged = () => - { - map.delete([renderPassFormat._key, renderObjects]); - // - renderObjects.forEach((v) => { watcher.unwatch(v, "_version", onchanged); }); - }; - renderObjects.forEach((v) => { watcher.watch(v, "_version", onchanged); }); - } - - // 排除在 GPURenderBundleEncoder 中不支持的命令 - const commands = caches.commands.filter((v) => (v[0] in bundleEncoder)); - - // 执行命令 - runCommands(bundleEncoder, { ...caches, commands }); - } - - protected runRenderObject(device: GPUDevice, passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder, renderPassFormat: IGPURenderPassFormat, renderObject: IRenderObject) - { - const map: ChainMap<[string, IRenderObject], Array> = device["_IGPURenderObjectCommandMap"] = device["_IGPURenderObjectCommandMap"] || new ChainMap(); - const _commands = passEncoder["_commands"] as any[]; - - const commands = map.get([renderPassFormat._key, renderObject]); - if (commands) - { - commands.forEach((v) => _commands.push(v)); - - return; - } - - const start = _commands.length; - - super.runRenderObject(device, passEncoder, renderPassFormat, renderObject); - - map.set([renderPassFormat._key, renderObject], _commands.slice(start)); - - // - const onchanged = () => - { - map.delete([renderPassFormat._key, renderObject]); - // - renderObject._version = ~~renderObject._version + 1; - watcher.unwatch(renderObject.pipeline, '_version', onchanged); - } - watcher.watch(renderObject.pipeline, '_version', onchanged); - } -} - -class GPURenderBundleRecord implements GPURenderBundleEncoder -{ - __brand: "GPURenderBundleEncoder"; - label: string; - // - setPipeline(...args: any): undefined { this["_commands"].push(["setPipeline", args]); } - setVertexBuffer(...args: any): undefined { this["_commands"].push(["setVertexBuffer", args]); } - setIndexBuffer(...args: any): undefined { this["_commands"].push(["setIndexBuffer", args]); } - setBindGroup(...args: any): undefined { this["_commands"].push(["setBindGroup", args]); } - draw(...args: any): undefined { this["_commands"].push(["draw", args]); } - drawIndexed(...args: any): undefined { this["_commands"].push(["drawIndexed", args]); } - drawIndirect(...args: any): undefined { this["_commands"].push(["drawIndirect", args]); } - drawIndexedIndirect(...args: any): undefined { this["_commands"].push(["drawIndexedIndirect", args]); } - // - finish(...args: any): undefined { this["_commands"].push(["finish", args]); } - // - pushDebugGroup(...args: any): undefined { this["_commands"].push(["pushDebugGroup", args]); } - popDebugGroup(...args: any): undefined { this["_commands"].push(["popDebugGroup", args]); } - insertDebugMarker(...args: any): undefined { this["_commands"].push(["insertDebugMarker", args]); } -} - -class GPURenderPassRecord implements GPURenderPassEncoder -{ - __brand: "GPURenderPassEncoder" = "GPURenderPassEncoder"; - label: string; - // - setViewport(...args: any): undefined { this["_commands"].push(["setViewport", args]); } - setScissorRect(...args: any): undefined { this["_commands"].push(["setScissorRect", args]); } - setBlendConstant(...args: any): undefined { this["_commands"].push(["setBlendConstant", args]); } - setStencilReference(...args: any): undefined { this["_commands"].push(["setStencilReference", args]); } - // - setPipeline(...args: any): undefined { this["_commands"].push(["setPipeline", args]); } - setVertexBuffer(...args: any): undefined { this["_commands"].push(["setVertexBuffer", args]); } - setIndexBuffer(...args: any): undefined { this["_commands"].push(["setIndexBuffer", args]); } - setBindGroup(...args: any): undefined { this["_commands"].push(["setBindGroup", args]); } - draw(...args: any): undefined { this["_commands"].push(["draw", args]); } - drawIndexed(...args: any): undefined { this["_commands"].push(["drawIndexed", args]); } - drawIndirect(...args: any): undefined { this["_commands"].push(["drawIndirect", args]); } - drawIndexedIndirect(...args: any): undefined { this["_commands"].push(["drawIndexedIndirect", args]); } - // - beginOcclusionQuery(...args: any): undefined { this["_commands"].push(["beginOcclusionQuery", args]); } - endOcclusionQuery(...args: any): undefined { this["_commands"].push(["endOcclusionQuery", args]); } - // - executeBundles(...args: any): undefined { this["_commands"].push(["executeBundles", args]); } - // - end(...args: any): undefined { this["_commands"].push(["end", args]); } - // - pushDebugGroup(...args: any): undefined { this["_commands"].push(["pushDebugGroup", args]); } - popDebugGroup(...args: any): undefined { this["_commands"].push(["popDebugGroup", args]); } - insertDebugMarker(...args: any): undefined { this["_commands"].push(["insertDebugMarker", args]); } -} - -function runCommands(_passEncoder: GPURenderPassEncoder | GPUComputePassEncoder | GPURenderBundleEncoder, caches: { - commands: Array; - setBindGroupCommands: Array; -}) -{ - const { commands, setBindGroupCommands } = caches; - - setBindGroupCommands.forEach((v) => - { - v[1][1] = v[1][1][getRealGPUBindGroup](); - }); - - commands.forEach((v) => - { - _passEncoder[v[0]].apply(_passEncoder, v[1]); - }); -} - -function paichuWuxiaoCommands(attachmentSize: { readonly width: number; readonly height: number; }, commands: any[]) -{ - const _obj = { - setBindGroup: [], setVertexBuffer: [], - setViewport: [0, 0, attachmentSize.width, attachmentSize.height, 0, 1], - setScissorRect: [0, 0, attachmentSize.width, attachmentSize.height], - }; - // - let length = 0; - commands.concat().forEach((v) => - { - // 排除重复的无效命令 - if (v[0] === "setBindGroup" || v[0] === "setVertexBuffer") - { - if (!arrayEq1(_obj, v[0], v[1][0], v[1])) - { - commands[length++] = v; - } - } - else if (0 - || v[0] === "setPipeline" - || v[0] === "setIndexBuffer" - || v[0] === "setViewport" - || v[0] === "setScissorRect" - || v[0] === "setBlendConstant" - || v[0] === "setStencilReference" - ) - { - if (!arrayEq0(_obj, v[0], v[1])) - { - commands[length++] = v; - } - } - else - { - commands[length++] = v; - } - }); - commands.length = length; -} - -function arrayEq0(_obj: any, name: string, args: any[]) -{ - const obj = _obj; - const oldArgs: any[] = obj[name]; - if (!oldArgs) - { - obj[name] = args; - - return false; - } - - for (let i = 0, n = oldArgs.length; i < n; i++) - { - if (oldArgs[i] !== args[i]) - { - obj[name] = args; - - return false; - } - } - - return true; -} - -function arrayEq1(_obj: any, name: string, index: number, args: any[]) -{ - const obj = _obj[name]; - const oldArgs: any[] = obj[index]; - if (!oldArgs) - { - obj[index] = args; - - return false; - } - - for (let i = 1, n = oldArgs.length; i < n; i++) - { - if (oldArgs[i] !== args[i]) - { - obj[index] = args; - - return false; - } - } - - return true; -} \ No newline at end of file diff --git a/src/types/TextureType.ts b/src/types/TextureType.ts index 62b2a03fe0f1bce979912dbd1bbabd1d6a62d6b2..f3d26cf4f21cf080acd88f60511a4525298134b2 100644 --- a/src/types/TextureType.ts +++ b/src/types/TextureType.ts @@ -1,27 +1,27 @@ /** * 纹理维度。 */ -export const TextureDimensionality = { +const TextureDimensionality = { "1D": "1D", "2D": "2D", "3D": "3D", Cube: "Cube", }; -export type TextureDimensionality = keyof typeof TextureDimensionality; +type TextureDimensionality = keyof typeof TextureDimensionality; /** * 纹理为是否数组。 */ -export const TextureArrayed = { +const TextureArrayed = { No: false, Yes: true, }; -export type TextureArrayed = keyof typeof TextureArrayed; +type TextureArrayed = keyof typeof TextureArrayed; /** * 纹理第二类型,描述纹理维度以及是否为数组。 */ -export type TextureSecondType = [TextureDimensionality, TextureArrayed, GPUTextureViewDimension]; +type TextureSecondType = [TextureDimensionality, TextureArrayed, GPUTextureViewDimension]; /** * 采样纹理类型。 diff --git a/src/types/VertexFormat.ts b/src/types/VertexFormat.ts index ff021eb0b8c3aab33c0749452350721f731f81fc..4faa9f623d71de713b619ddd0c47a0b7bf80093e 100644 --- a/src/types/VertexFormat.ts +++ b/src/types/VertexFormat.ts @@ -1,109 +1,5 @@ -/** - * 有类型数组构造器。 - */ -export type TypedArrayConstructor = Int8ArrayConstructor | Uint8ArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor; - -/** - * GPU顶点数据类型 - */ -export type GPUVertexDataType = - | "unsigned int" - | "signed int" - | "unsigned normalized" - | "signed normalized" - | "float" - ; - -/** - * 顶点数据在WGSL中的类型。 - */ -export type WGSLVertexType = - | "vec2" - | "vec4" - | "vec2" - | "vec4" - | "vec2" - | "vec4" - | "vec2" - | "vec4" - | "f32" - | "vec3" - | "u32" - | "vec3" - | "i32" - | "vec3" - ; +import { WGSLVertexType } from "@feng3d/render-api"; -/** - * GPU顶点格式对应的信息 - */ -export type GPUVertexFormatValue = { - /** - * 数据类型。 - */ - dataType: GPUVertexDataType, - - /** - * 部件数量。 - */ - components: 1 | 2 | 3 | 4, - - /** - * 所占字节尺寸。 - */ - byteSize: 2 | 4 | 8 | 12 | 16, - - /** - * 在着色器中对应类型。 - */ - wgslType: WGSLVertexType, - - /** - * 对应类型数组构造器。 - */ - typedArrayConstructor: TypedArrayConstructor, -}; - -/** - * 顶点格式对应信息映射 - * - * 以 {@link GPUVertexFormat} 为键值。 - * - * @see https://www.orillusion.com/zh/webgpu.html#vertex-formats - */ -export const gpuVertexFormatMap: Record = { - uint8x2: { dataType: "unsigned int", components: 2, byteSize: 2, wgslType: "vec2", typedArrayConstructor: Uint8Array }, - uint8x4: { dataType: "unsigned int", components: 4, byteSize: 4, wgslType: "vec4", typedArrayConstructor: Uint8Array }, - sint8x2: { dataType: "signed int", components: 2, byteSize: 2, wgslType: "vec2", typedArrayConstructor: Int8Array }, - sint8x4: { dataType: "signed int", components: 4, byteSize: 4, wgslType: "vec4", typedArrayConstructor: Int8Array }, - unorm8x2: { dataType: "unsigned normalized", components: 2, byteSize: 2, wgslType: "vec2", typedArrayConstructor: Uint8Array }, - unorm8x4: { dataType: "unsigned normalized", components: 4, byteSize: 4, wgslType: "vec4", typedArrayConstructor: Uint8Array }, - snorm8x2: { dataType: "signed normalized", components: 2, byteSize: 2, wgslType: "vec2", typedArrayConstructor: Int8Array }, - snorm8x4: { dataType: "signed normalized", components: 4, byteSize: 4, wgslType: "vec4", typedArrayConstructor: Int8Array }, - uint16x2: { dataType: "unsigned int", components: 2, byteSize: 4, wgslType: "vec2", typedArrayConstructor: Uint16Array }, - uint16x4: { dataType: "unsigned int", components: 4, byteSize: 8, wgslType: "vec4", typedArrayConstructor: Uint16Array }, - sint16x2: { dataType: "signed int", components: 2, byteSize: 4, wgslType: "vec2", typedArrayConstructor: Int16Array }, - sint16x4: { dataType: "signed int", components: 4, byteSize: 8, wgslType: "vec4", typedArrayConstructor: Int16Array }, - unorm16x2: { dataType: "unsigned normalized", components: 2, byteSize: 4, wgslType: "vec2", typedArrayConstructor: Uint16Array }, - unorm16x4: { dataType: "unsigned normalized", components: 4, byteSize: 8, wgslType: "vec4", typedArrayConstructor: Uint16Array }, - snorm16x2: { dataType: "signed normalized", components: 2, byteSize: 4, wgslType: "vec2", typedArrayConstructor: Int16Array }, - snorm16x4: { dataType: "signed normalized", components: 4, byteSize: 8, wgslType: "vec4", typedArrayConstructor: Int16Array }, - float16x2: { dataType: "float", components: 2, byteSize: 4, wgslType: "vec2", typedArrayConstructor: undefined }, // 没有找到与之对应的 typedArrayConstructor - float16x4: { dataType: "float", components: 4, byteSize: 8, wgslType: "vec4", typedArrayConstructor: undefined }, // 没有找到与之对应的 typedArrayConstructor - float32: { dataType: "float", components: 1, byteSize: 4, wgslType: "f32", typedArrayConstructor: Float32Array }, - float32x2: { dataType: "float", components: 2, byteSize: 8, wgslType: "vec2", typedArrayConstructor: Float32Array }, - float32x3: { dataType: "float", components: 3, byteSize: 12, wgslType: "vec3", typedArrayConstructor: Float32Array }, - float32x4: { dataType: "float", components: 4, byteSize: 16, wgslType: "vec4", typedArrayConstructor: Float32Array }, - uint32: { dataType: "unsigned int", components: 1, byteSize: 4, wgslType: "u32", typedArrayConstructor: Uint32Array }, - uint32x2: { dataType: "unsigned int", components: 2, byteSize: 8, wgslType: "vec2", typedArrayConstructor: Uint32Array }, - uint32x3: { dataType: "unsigned int", components: 3, byteSize: 12, wgslType: "vec3", typedArrayConstructor: Uint32Array }, - uint32x4: { dataType: "unsigned int", components: 4, byteSize: 16, wgslType: "vec4", typedArrayConstructor: Uint32Array }, - sint32: { dataType: "signed int", components: 1, byteSize: 4, wgslType: "i32", typedArrayConstructor: Int32Array }, - sint32x2: { dataType: "signed int", components: 2, byteSize: 8, wgslType: "vec2", typedArrayConstructor: Int32Array }, - sint32x3: { dataType: "signed int", components: 3, byteSize: 12, wgslType: "vec3", typedArrayConstructor: Int32Array }, - sint32x4: { dataType: "signed int", components: 4, byteSize: 16, wgslType: "vec4", typedArrayConstructor: Int32Array }, - "unorm10-10-10-2": { dataType: "unsigned normalized", components: 4, byteSize: 4, wgslType: "vec4", typedArrayConstructor: Int32Array }, -}; /** * WGSL着色器中顶点类型对应的GPU顶点数据格式。 diff --git a/src/utils/ChainMap.ts b/src/utils/ChainMap.ts deleted file mode 100644 index 14e069d664f642d2d84c2c604fbdb892f2947c59..0000000000000000000000000000000000000000 --- a/src/utils/ChainMap.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * 链式Map。 - * - * 多个key数组对应一个值。 - * - * 由于键值可能是字面值也可能是对象,因此无法使用 {@link WeakMap} 来构建{@link ChainMap},只能使用 {@link Map}。 - */ -export class ChainMap, V> -{ - private _map = new Map(); - - /** - * 获取键对应的值。 - * - * @param keys 键。 - * @returns 值。 - */ - get(keys: K): V - { - let map = this._map; - - for (let i = 0, n = keys.length - 1; i < n; i++) - { - map = map.get(keys[i]); - - if (map === undefined) return undefined; - } - - return map.get(keys[keys.length - 1]); - } - - /** - * 设置映射。 - * - * @param keys 键。 - * @param value 值。 - */ - set(keys: K, value: V) - { - let map = this._map; - - for (let i = 0; i < keys.length - 1; i++) - { - const key = keys[i]; - - if (map.has(key) === false) map.set(key, new Map()); - - map = map.get(key); - } - - map.set(keys[keys.length - 1], value); - } - - /** - * 删除映射。 - * - * @param keys 键。 - * @returns 如果找到目标值且被删除返回 `true` ,否则返回 `false` 。 - */ - delete(keys: K): boolean - { - let map = this._map; - - for (let i = 0; i < keys.length - 1; i++) - { - map = map.get(keys[i]); - - if (map === undefined) return false; - } - - return map.delete(keys[keys.length - 1]); - } -} - diff --git a/src/utils/getBufferBindingInfo.ts b/src/utils/getBufferBindingInfo.ts deleted file mode 100644 index 90ed7b37585895bbdcf537a60719e3ae903220c4..0000000000000000000000000000000000000000 --- a/src/utils/getBufferBindingInfo.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { ArrayInfo, StructInfo, TemplateInfo, TypeInfo } from "wgsl_reflect"; - -/** - * 获取缓冲区绑定信息。 - * - * @param type 类型信息。 - * @param paths 当前路径。 - * @param offset 当前编译。 - * @param bufferBindingInfo 缓冲区绑定信息。 - * @returns - */ -export function getBufferBindingInfo(type: TypeInfo, paths: string[] = [], offset = 0, bufferBindingInfo: IBufferBindingInfo = { size: type.size, items: [] }) -{ - if (type.isStruct) - { - const structInfo = type as StructInfo; - for (let i = 0; i < structInfo.members.length; i++) - { - const memberInfo = structInfo.members[i]; - getBufferBindingInfo(memberInfo.type, paths.concat(memberInfo.name), offset + memberInfo.offset, bufferBindingInfo); - } - } - else if (type.isArray) - { - const arrayInfo = type as ArrayInfo; - for (let i = 0; i < arrayInfo.count; i++) - { - getBufferBindingInfo(arrayInfo.format, paths.concat(`${i}`), offset + i * arrayInfo.format.size, bufferBindingInfo); - } - } - else if (type.isTemplate) - { - const templateInfo = type as TemplateInfo; - const templateFormatName = templateInfo.format?.name; - bufferBindingInfo.items.push({ - paths: paths.concat(), - offset, - size: templateInfo.size, - Cls: getTemplateDataCls(templateFormatName as any), - }); - } - else - { - bufferBindingInfo.items.push({ - paths: paths.concat(), - offset, - size: type.size, - Cls: getBaseTypeDataCls(type.name), - }); - } - - return bufferBindingInfo; -} - -function getBaseTypeDataCls(baseTypeName: string) -{ - const dataCls = baseTypeDataCls[baseTypeName]; - - console.assert(!!dataCls, `baseTypeName必须为以下值 ${Object.keys(baseTypeDataCls)}`); - - return dataCls; -} - -/** - * @see https://gpuweb.github.io/gpuweb/wgsl/#vec2i - */ -const baseTypeDataCls: { [key: string]: DataCls } = { - i32: Int32Array, - u32: Uint32Array, - f32: Float32Array, - f16: Int16Array, - vec2i: Int32Array, - vec3i: Int32Array, - vec4i: Int32Array, - vec2u: Uint32Array, - vec3u: Uint32Array, - vec4u: Uint32Array, - vec2f: Float32Array, - vec3f: Float32Array, - vec4f: Float32Array, - vec2h: Int16Array, - vec3h: Int16Array, - vec4h: Int16Array, - mat2x2f: Float32Array, - mat2x3f: Float32Array, - mat2x4f: Float32Array, - mat3x2f: Float32Array, - mat3x3f: Float32Array, - mat3x4f: Float32Array, - mat4x2f: Float32Array, - mat4x3f: Float32Array, - mat4x4f: Float32Array, - mat2x2h: Float32Array, - mat2x3h: Float32Array, - mat2x4h: Float32Array, - mat3x2h: Float32Array, - mat3x3h: Float32Array, - mat3x4h: Float32Array, - mat4x2h: Float32Array, - mat4x3h: Float32Array, - mat4x4h: Float32Array, -}; - -function getTemplateDataCls(templateFormatName: "i32" | "u32" | "f32" | "f16") -{ - const dataCls = templateFormatDataCls[templateFormatName]; - - console.assert(!!dataCls, `templateFormatName必须为以下值 ${Object.keys(templateFormatDataCls)}`); - - return dataCls; -} -const templateFormatDataCls: { [key: string]: DataCls } = { - i32: Int32Array, - u32: Uint32Array, - f32: Float32Array, - f16: Int16Array, -}; - -type DataCls = Float32ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Int16ArrayConstructor; - -/** - * 缓冲区绑定信息。 - */ -export interface IBufferBindingInfo -{ - size: number; - items: { - paths: string[]; - offset: number; - size: number; - Cls: DataCls; - }[] -} diff --git a/src/utils/getGPUDevice.ts b/src/utils/getGPUDevice.ts new file mode 100644 index 0000000000000000000000000000000000000000..9be65a2502fc5719877ec100da25637d8b89db2e --- /dev/null +++ b/src/utils/getGPUDevice.ts @@ -0,0 +1,31 @@ +import { quitIfWebGPUNotAvailable } from "./quitIfWebGPUNotAvailable"; + +export async function getGPUDevice(options?: GPURequestAdapterOptions, descriptor?: GPUDeviceDescriptor) +{ + const adapter = await navigator.gpu?.requestAdapter(options); + // 获取支持的特性 + const features: GPUFeatureName[] = []; + adapter?.features.forEach((v) => { features.push(v as any); }); + // 判断请求的特性是否被支持 + const requiredFeatures = Array.from(descriptor?.requiredFeatures || []); + if (requiredFeatures.length > 0) + { + for (let i = requiredFeatures.length - 1; i >= 0; i--) + { + if (features.indexOf(requiredFeatures[i]) === -1) + { + console.error(`当前 GPUAdapter 不支持特性 ${requiredFeatures[i]}!`); + requiredFeatures.splice(i, 1); + } + } + descriptor.requiredFeatures = requiredFeatures; + } + // 默认开启当前本机支持的所有WebGPU特性。 + descriptor = descriptor || {}; + descriptor.requiredFeatures = (descriptor.requiredFeatures || features) as any; + // + const device = await adapter?.requestDevice(descriptor); + quitIfWebGPUNotAvailable(adapter, device); + + return device; +} \ No newline at end of file diff --git a/src/utils/getOffscreenCanvasId.ts b/src/utils/getOffscreenCanvasId.ts deleted file mode 100644 index 186a2887dda0e922cc1e88bef43afb8e0cfa56e3..0000000000000000000000000000000000000000 --- a/src/utils/getOffscreenCanvasId.ts +++ /dev/null @@ -1,19 +0,0 @@ -export function getOffscreenCanvasId(canvas: OffscreenCanvas) -{ - const id = canvas["id"] = canvas["id"] || (`OffscreenCanvas_${OffscreenCanvasAutoId++}`); - OffscreenCanvasMap[id] = canvas; - - return id; -} -let OffscreenCanvasAutoId = 0; - -const OffscreenCanvasMap = {}; - -if (!globalThis.document) -{ - globalThis.document = {} as any; -} -if (!globalThis.document.getElementById) -{ - globalThis.document.getElementById = (elementId: string) => OffscreenCanvasMap[elementId]; -} \ No newline at end of file diff --git a/src/utils/quitIfWebGPUNotAvailable.ts b/src/utils/quitIfWebGPUNotAvailable.ts index 30b2f9b9323df665a8d05438d28c3bdbbd5d8705..e84f63d2cf1f172fb4e226cd793b4911567a9ec8 100644 --- a/src/utils/quitIfWebGPUNotAvailable.ts +++ b/src/utils/quitIfWebGPUNotAvailable.ts @@ -79,6 +79,11 @@ const fail = (() => // (show the first error, not the most recent error). if (!dialogBox.open) { + if (msg.indexOf("allow_unsafe_apis") > -1) + { + msg += "\n\n使用到了您的浏览器不支持的特性,请更换您的浏览器试试。"; + } + dialogText.textContent = msg; dialogBox.showModal(); } diff --git a/src/utils/readPixels.ts b/src/utils/readPixels.ts index 700519e79f923695e8374e3792fddc2389245fd5..4b84feca804f72997c476dca170d0e665a64806a 100644 --- a/src/utils/readPixels.ts +++ b/src/utils/readPixels.ts @@ -1,3 +1,5 @@ +import { Texture } from "@feng3d/render-api"; + /** * 从 GPU纹理 上读取数据。 * @@ -10,16 +12,21 @@ * * @returns 读取到的数据。 */ -export async function readPixels(device: GPUDevice, params: { texture: GPUTexture, origin: GPUOrigin3D, copySize: { width: number, height: number } }) +export async function readPixels(device: GPUDevice, params: { texture: GPUTexture, origin: [x: number, y: number], copySize: [width: number, height: number] }) { const commandEncoder = device.createCommandEncoder(); const { texture, origin, copySize } = params; - const { width, height } = copySize; + const [width, height] = copySize; + + const bytesPerPixel = Texture.getTextureBytesPerPixel(texture.format); + const dataConstructor = Texture.getTextureDataConstructor(texture.format); - const bytesPerRow = Math.ceil((width * 4) / 256) * 256; + const bytesPerRow = width * bytesPerPixel; const bufferSize = bytesPerRow * height; + const bufferData = new dataConstructor(bufferSize / dataConstructor.BYTES_PER_ELEMENT); + // const buffer = device.createBuffer({ size: bufferSize, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); commandEncoder.copyTextureToBuffer( @@ -38,40 +45,12 @@ export async function readPixels(device: GPUDevice, params: { texture: GPUTextur device.queue.submit([commandEncoder.finish()]); await buffer.mapAsync(GPUMapMode.READ); - const bufferData = new Uint8Array(buffer.size); const source = new Uint8Array(buffer.getMappedRange()); bufferData.set(source); - buffer.unmap(); buffer.destroy(); - const result = new Uint8Array(width * height * 4); - - for (let i = 0; i < width; i++) - { - for (let j = 0; j < height; j++) - { - // rgba8unorm - let rgba = [ - bufferData[j * bytesPerRow + i * 4], - bufferData[j * bytesPerRow + i * 4 + 1], - bufferData[j * bytesPerRow + i * 4 + 2], - bufferData[j * bytesPerRow + i * 4 + 3], - ]; - - if (texture.format === "bgra8unorm") - { - rgba = [rgba[2], rgba[1], rgba[0], rgba[3]]; - } - - result[j * width * 4 + i * 4] = rgba[0]; - result[j * width * 4 + i * 4 + 1] = rgba[1]; - result[j * width * 4 + i * 4 + 2] = rgba[2]; - result[j * width * 4 + i * 4 + 3] = rgba[3]; - } - } - - return result; + return bufferData; } diff --git a/src/utils/updateBufferBinding.ts b/src/utils/updateBufferBinding.ts deleted file mode 100644 index 254b2ab2fdd63fa39b69a7532b56c78f91d6a724..0000000000000000000000000000000000000000 --- a/src/utils/updateBufferBinding.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { IBufferBinding, UnReadonly } from "@feng3d/render-api"; -import { watcher } from "@feng3d/watcher"; -import { VariableInfo } from "wgsl_reflect"; - -import { getIGPUBuffer } from "../caches/getIGPUBuffer"; -import { IBufferBindingInfo } from "./getBufferBindingInfo"; - -/** - * 初始化缓冲区绑定。 - * - * @param variableInfo - * @param uniformData - * @returns - */ -export function updateBufferBinding(resourceName: string, bufferBindingInfo: IBufferBindingInfo, uniformData: IBufferBinding) -{ - if (uniformData["_variableInfo"] !== undefined) - { - const preVariableInfo = uniformData["_variableInfo"] as any as VariableInfo; - if (preVariableInfo.size !== bufferBindingInfo.size) - { - console.warn(`updateBufferBinding ${resourceName} 出现一份数据对应多个 variableInfo`, { uniformData, bufferBindingInfo, preVariableInfo }); - } - - return; - } - uniformData["_variableInfo"] = bufferBindingInfo as any; - - const size = bufferBindingInfo.size; - // 是否存在默认值。 - const hasDefautValue = !!uniformData.bufferView; - if (!hasDefautValue) - { - (uniformData as UnReadonly).bufferView = new Uint8Array(size); - } - - const buffer = getIGPUBuffer(uniformData.bufferView); - const offset = uniformData.bufferView.byteOffset; - - for (let i = 0; i < bufferBindingInfo.items.length; i++) - { - const { paths, offset: itemInfoOffset, size: itemInfoSize, Cls } = bufferBindingInfo.items[i]; - const update = () => - { - let value: any = uniformData; - for (let i = 0; i < paths.length; i++) - { - value = value[paths[i]]; - if (value === undefined) - { - if (!hasDefautValue) - { - console.warn(`没有找到 统一块变量属性 ${paths.join(".")} 的值!`); - } - -return; - } - } - - let data: Float32Array | Int32Array | Uint32Array | Int16Array; - if (typeof value === "number") - { - data = new Cls([value]); - } - else if (value.constructor.name !== Cls.name) - { - data = new Cls(value as ArrayLike); - } - else - { - data = value as any; - } - - const writeBuffers = buffer.writeBuffers ?? []; - writeBuffers.push({ bufferOffset: offset + itemInfoOffset, data: data.buffer, dataOffset: data.byteOffset, size: Math.min(itemInfoSize, data.byteLength) }); - buffer.writeBuffers = writeBuffers; - }; - - update(); - watcher.watchchain(uniformData, paths.join("."), update, undefined, false); - } -} diff --git a/test/types/VertexFormat.spec.ts b/test/types/VertexFormat.spec.ts deleted file mode 100644 index 23604f313da258bde4618a179702408fda255e90..0000000000000000000000000000000000000000 --- a/test/types/VertexFormat.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { assert, describe, it } from "vitest"; -const { ok, equal, deepEqual, strictEqual } = assert; - -import { GPUVertexFormatValue, WGSLVertexType, gpuVertexFormatMap, wgslVertexTypeMap } from "../../src/types/VertexFormat"; - -describe("VertexFormat", () => -{ - const WGSLVertexTypes: WGSLVertexType[] = [ - "vec2", - "vec4", - "vec2", - "vec4", - "vec2", - "vec4", - "vec2", - "vec4", - "f32", - "vec3", - "u32", - "vec3", - "i32", - "vec3", - ]; - - const GPUVertexFormats: GPUVertexFormat[] = [ - "uint8x2", - "uint8x4", - "sint8x2", - "sint8x4", - "unorm8x2", - "unorm8x4", - "snorm8x2", - "snorm8x4", - "uint16x2", - "uint16x4", - "sint16x2", - "sint16x4", - "unorm16x2", - "unorm16x4", - "snorm16x2", - "snorm16x4", - "float16x2", - "float16x4", - "float32", - "float32x2", - "float32x3", - "float32x4", - "uint32", - "uint32x2", - "uint32x3", - "uint32x4", - "sint32", - "sint32x2", - "sint32x3", - "sint32x4", - "unorm10-10-10-2", - ]; - - it("constructor", () => - { - const wgslVertexTypes = Object.keys(wgslVertexTypeMap) as WGSLVertexType[]; - equal(wgslVertexTypes.length, WGSLVertexTypes.length); - - const gpuVertexFormats = Object.keys(gpuVertexFormatMap) as GPUVertexFormat[]; - equal(gpuVertexFormats.length, GPUVertexFormats.length); - - // - wgslVertexTypes.forEach((wgslVertexType) => - { - const wgslVertexTypeValue = wgslVertexTypeMap[wgslVertexType]; - equal(wgslVertexTypeValue.possibleFormats.includes(wgslVertexTypeValue.format), true); - - wgslVertexTypeValue.possibleFormats.forEach((gpuVertexFormat) => - { - const gpuVertexFormatValue = gpuVertexFormatMap[gpuVertexFormat] as GPUVertexFormatValue; - - equal(gpuVertexFormatValue.wgslType, wgslVertexType); - }); - }); - }); -}); diff --git a/vite.config.js b/vite.config.js index e51dbba71e3ff4fb5886f37fd7af1ca1e02c8d48..711ba2a7a5ae82533461b1ee2c3feab21c35c203 100644 --- a/vite.config.js +++ b/vite.config.js @@ -9,6 +9,9 @@ const external = pkg.standalone ? [] : Object.keys(pkg.dependencies || []); const globals = () => namespace; export default defineConfig({ + define: { + __DEV__: process.env.NODE_ENV === 'development' ? true : false + }, publicDir: false, build: { lib: {