From 83992131da17da3a00cf4465a7764e3c9709cf7a Mon Sep 17 00:00:00 2001 From: xiaoiver Date: Thu, 28 Sep 2023 17:16:46 +0800 Subject: [PATCH] feat: support multiple render target (#43) (#44) * feat: support multiple render target (#43) * feat: support multiple render targets #42 * chore: split utils * chore: commit changeset * chore(release): bump version (#45) Co-authored-by: github-actions[bot] --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] --- CHANGELOG.md | 6 + examples/demos/add-2-vectors.ts | 21 +-- examples/demos/ar-three.ts | 13 +- examples/demos/ar.ts | 13 +- examples/demos/blit.ts | 11 +- examples/demos/compute-boids.ts | 11 +- examples/demos/cubemap.ts | 12 +- examples/demos/draw-index.ts | 11 +- examples/demos/index.ts | 35 ++-- examples/demos/instanced-cubes.ts | 11 +- examples/demos/msaa.ts | 11 +- examples/demos/multiple-render-targets.ts | 173 ++++++++++++++++++ examples/demos/primitive-topology-points.ts | 11 +- .../demos/primitive-topology-triangles.ts | 11 +- examples/demos/resize-canvas.ts | 11 +- examples/demos/rotating-cube.ts | 11 +- examples/demos/sampler.ts | 11 +- examples/demos/scissor.ts | 11 +- examples/demos/stencil.ts | 11 +- examples/demos/textured-cube.ts | 12 +- examples/demos/two-cubes.ts | 11 +- examples/main.ts | 3 +- examples/{demos => }/utils.ts | 33 +--- examples/utils/image.ts | 16 ++ package.json | 2 +- src/shader/compiler.ts | 85 +++++---- src/webgl/Device.ts | 136 +++++++------- src/webgl/Texture.ts | 2 + src/webgpu/Device.ts | 10 + 29 files changed, 417 insertions(+), 298 deletions(-) create mode 100644 examples/demos/multiple-render-targets.ts rename examples/{demos => }/utils.ts (79%) create mode 100644 examples/utils/image.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2811d81..9db674a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @antv/g-device-api +## 1.3.0 + +### Minor Changes + +- 5d930c0: Support multiple render targets. + ## 1.2.3 ### Patch Changes diff --git a/examples/demos/add-2-vectors.ts b/examples/demos/add-2-vectors.ts index af26d76..1fa93f6 100644 --- a/examples/demos/add-2-vectors.ts +++ b/examples/demos/add-2-vectors.ts @@ -1,13 +1,4 @@ -import { - DeviceContribution, - VertexStepMode, - Format, - TransparentWhite, - Buffer, - Bindings, - BufferUsage, -} from '../../src'; -import { initExample } from './utils'; +import { DeviceContribution, BufferUsage } from '../../src'; /** * Use Compute Shader with WebGPU @@ -94,9 +85,7 @@ export async function render( }; } -export async function Add2Vectors($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgpu'], - default: 'webgpu', - }); -} +render.params = { + targets: ['webgpu'], + default: 'webgpu', +}; diff --git a/examples/demos/ar-three.ts b/examples/demos/ar-three.ts index 5d72f33..c684129 100644 --- a/examples/demos/ar-three.ts +++ b/examples/demos/ar-three.ts @@ -1,5 +1,4 @@ import { ARButton } from 'three/examples/jsm/webxr/ARButton'; -import { initExample } from './utils'; import { DeviceContribution } from '../../src'; import { AmbientLight, @@ -65,10 +64,8 @@ export async function render( return () => {}; } -export async function ARThree($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2'], - xrCompatible: true, - default: 'webgl1', - }); -} +render.params = { + targets: ['webgl1', 'webgl2'], + xrCompatible: true, + default: 'webgl1', +}; diff --git a/examples/demos/ar.ts b/examples/demos/ar.ts index 7a0f699..43e990b 100644 --- a/examples/demos/ar.ts +++ b/examples/demos/ar.ts @@ -13,7 +13,6 @@ import { CompareFunction, TransparentWhite, } from '../../src'; -import { initExample } from './utils'; import { vec3, mat4, quat } from 'gl-matrix'; import { cubeVertexArray, @@ -279,10 +278,8 @@ export async function render( }; } -export async function AR($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2'], - xrCompatible: true, - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2'], + xrCompatible: true, + default: 'webgl2', +}; diff --git a/examples/demos/blit.ts b/examples/demos/blit.ts index f7ba9d3..4420cb3 100644 --- a/examples/demos/blit.ts +++ b/examples/demos/blit.ts @@ -13,7 +13,6 @@ import { TransparentBlack, CompareFunction, } from '../../src'; -import { initExample } from './utils'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -234,9 +233,7 @@ void main() { }; } -export async function Blit($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/compute-boids.ts b/examples/demos/compute-boids.ts index 8418797..262d26c 100644 --- a/examples/demos/compute-boids.ts +++ b/examples/demos/compute-boids.ts @@ -7,7 +7,6 @@ import { Bindings, BufferUsage, } from '../../src'; -import { initExample } from './utils'; /** * Use Compute Shader with WebGPU @@ -350,9 +349,7 @@ fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3) { }; } -export async function ComputeBoids($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgpu'], - default: 'webgpu', - }); -} +render.params = { + targets: ['webgpu'], + default: 'webgpu', +}; diff --git a/examples/demos/cubemap.ts b/examples/demos/cubemap.ts index dd52a8c..e002930 100644 --- a/examples/demos/cubemap.ts +++ b/examples/demos/cubemap.ts @@ -17,7 +17,7 @@ import { MipmapFilterMode, TextureDimension, } from '../../src'; -import { initExample, loadImage } from './utils'; +import { loadImage } from '../utils/image'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -314,9 +314,7 @@ void main() { }; } -export async function Cubemap($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/draw-index.ts b/examples/demos/draw-index.ts index 48c7215..0638a8c 100644 --- a/examples/demos/draw-index.ts +++ b/examples/demos/draw-index.ts @@ -7,7 +7,6 @@ import { BufferFrequencyHint, TextureUsage, } from '../../src'; -import { initExample } from './utils'; export async function render( deviceContribution: DeviceContribution, @@ -142,9 +141,7 @@ void main() { }; } -export async function DrawIndex($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/index.ts b/examples/demos/index.ts index abfc514..9682c30 100644 --- a/examples/demos/index.ts +++ b/examples/demos/index.ts @@ -1,17 +1,18 @@ -export { PrimitiveTopologyPoints } from './primitive-topology-points'; -export { PrimitiveTopologyTriangles } from './primitive-topology-triangles'; -export { DrawIndex } from './draw-index'; -export { MSAA } from './msaa'; -export { Blit } from './blit'; -export { RotatingCube } from './rotating-cube'; -export { Stencil } from './stencil'; -export { Scissor } from './scissor'; -export { TwoCubes } from './two-cubes'; -export { TexturedCube } from './textured-cube'; -export { Sampler } from './sampler'; -export { InstancedCubes } from './instanced-cubes'; -export { Cubemap } from './cubemap'; -export { Add2Vectors } from './add-2-vectors'; -export { ComputeBoids } from './compute-boids'; -export { AR } from './ar'; -export { ARThree } from './ar-three'; +export { render as PrimitiveTopologyPoints } from './primitive-topology-points'; +export { render as PrimitiveTopologyTriangles } from './primitive-topology-triangles'; +export { render as DrawIndex } from './draw-index'; +export { render as MSAA } from './msaa'; +export { render as MultipleRenderTargets } from './multiple-render-targets'; +export { render as Blit } from './blit'; +export { render as RotatingCube } from './rotating-cube'; +export { render as Stencil } from './stencil'; +export { render as Scissor } from './scissor'; +export { render as TwoCubes } from './two-cubes'; +export { render as TexturedCube } from './textured-cube'; +export { render as Sampler } from './sampler'; +export { render as InstancedCubes } from './instanced-cubes'; +export { render as Cubemap } from './cubemap'; +export { render as Add2Vectors } from './add-2-vectors'; +export { render as ComputeBoids } from './compute-boids'; +export { render as AR } from './ar'; +export { render as ARThree } from './ar-three'; diff --git a/examples/demos/instanced-cubes.ts b/examples/demos/instanced-cubes.ts index 128c8b1..265e38c 100644 --- a/examples/demos/instanced-cubes.ts +++ b/examples/demos/instanced-cubes.ts @@ -13,7 +13,6 @@ import { CompareFunction, CullMode, } from '../../src'; -import { initExample } from './utils'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -277,9 +276,7 @@ void main() { }; } -export async function InstancedCubes($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/msaa.ts b/examples/demos/msaa.ts index 7ef4420..47b1c6d 100644 --- a/examples/demos/msaa.ts +++ b/examples/demos/msaa.ts @@ -6,7 +6,6 @@ import { BufferUsage, BufferFrequencyHint, } from '../../src'; -import { initExample } from './utils'; export async function render( deviceContribution: DeviceContribution, @@ -131,9 +130,7 @@ void main() { }; } -export async function MSAA($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/multiple-render-targets.ts b/examples/demos/multiple-render-targets.ts new file mode 100644 index 0000000..69127a6 --- /dev/null +++ b/examples/demos/multiple-render-targets.ts @@ -0,0 +1,173 @@ +import { + DeviceContribution, + VertexStepMode, + Format, + TransparentWhite, + BufferUsage, + BufferFrequencyHint, + TextureUsage, +} from '../../src'; + +/** + * @see https://github.com/shrekshao/MoveWebGL1EngineToWebGL2/blob/master/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-1.md#multiple-render-targets + */ + +export async function render( + deviceContribution: DeviceContribution, + $canvas: HTMLCanvasElement, + useRAF = true, +) { + // create swap chain and get device + const swapChain = (await deviceContribution.createSwapChain($canvas))!; + // TODO: resize + swapChain.configureSwapChain($canvas.width, $canvas.height); + const device = swapChain.getDevice(); + + const program = device.createProgram({ + vertex: { + glsl: ` +layout(location = 0) in vec2 a_Position; + +void main() { + gl_Position = vec4(a_Position, 0.0, 1.0); +} +`, + }, + fragment: { + glsl: ` +layout(location = 0) out vec4 outputColor1; +layout(location = 1) out vec4 outputColor2; +layout(location = 2) out vec4 outputColor3; + +void main() { + outputColor1 = vec4(1.0, 0.0, 0.0, 1.0); + outputColor2 = vec4(0.0, 1.0, 0.0, 1.0); + outputColor3 = vec4(0.0, 0.0, 1.0, 1.0); +} +`, + }, + }); + + const vertexBuffer = device.createBuffer({ + viewOrSize: new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]), + usage: BufferUsage.VERTEX, + hint: BufferFrequencyHint.DYNAMIC, + }); + device.setResourceName(vertexBuffer, 'a_Position'); + + const inputLayout = device.createInputLayout({ + vertexBufferDescriptors: [ + { + arrayStride: 4 * 2, + stepMode: VertexStepMode.VERTEX, + attributes: [ + { + shaderLocation: 0, + offset: 0, + format: Format.F32_RG, + }, + ], + }, + ], + indexBufferFormat: null, + program, + }); + + const pipeline = device.createRenderPipeline({ + inputLayout, + program, + colorAttachmentFormats: [ + Format.U8_RGBA_RT, + Format.U8_RGBA_RT, + Format.U8_RGBA_RT, + ], + }); + + const renderTarget1 = device.createRenderTargetFromTexture( + device.createTexture({ + format: Format.U8_RGBA_RT, + width: $canvas.width, + height: $canvas.height, + usage: TextureUsage.RENDER_TARGET, + }), + ); + device.setResourceName(renderTarget1, 'Main Render Target1'); + + const renderTarget2 = device.createRenderTargetFromTexture( + device.createTexture({ + format: Format.U8_RGBA_RT, + width: $canvas.width, + height: $canvas.height, + usage: TextureUsage.RENDER_TARGET, + }), + ); + device.setResourceName(renderTarget2, 'Main Render Target2'); + + const renderTarget3 = device.createRenderTargetFromTexture( + device.createTexture({ + format: Format.U8_RGBA_RT, + width: $canvas.width, + height: $canvas.height, + usage: TextureUsage.RENDER_TARGET, + }), + ); + device.setResourceName(renderTarget3, 'Main Render Target3'); + + let id: number; + const frame = () => { + /** + * An application should call getCurrentTexture() in the same task that renders to the canvas texture. + * Otherwise, the texture could get destroyed by these steps before the application is finished rendering to it. + */ + const onscreenTexture = swapChain.getOnscreenTexture(); + + const renderPass = device.createRenderPass({ + colorAttachment: [renderTarget1, renderTarget2, renderTarget3], + colorResolveTo: [null, onscreenTexture, null], + colorClearColor: [TransparentWhite, TransparentWhite, TransparentWhite], + colorStore: [true, true, true], + }); + + renderPass.setPipeline(pipeline); + renderPass.setVertexInput( + inputLayout, + [ + { + buffer: vertexBuffer, + }, + ], + null, + ); + renderPass.setViewport(0, 0, $canvas.width, $canvas.height); + renderPass.draw(3); + + device.submitPass(renderPass); + if (useRAF) { + id = requestAnimationFrame(frame); + } + }; + + frame(); + + return () => { + if (useRAF && id) { + cancelAnimationFrame(id); + } + program.destroy(); + vertexBuffer.destroy(); + inputLayout.destroy(); + pipeline.destroy(); + renderTarget1.destroy(); + renderTarget2.destroy(); + renderTarget3.destroy(); + device.destroy(); + + // For debug. + device.checkForLeaks(); + }; +} + +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/primitive-topology-points.ts b/examples/demos/primitive-topology-points.ts index de17cff..3202ebf 100644 --- a/examples/demos/primitive-topology-points.ts +++ b/examples/demos/primitive-topology-points.ts @@ -8,7 +8,6 @@ import { BufferFrequencyHint, TextureUsage, } from '../../src'; -import { initExample } from './utils'; /** * WebGPU doesn't support gl_PointSize @@ -136,9 +135,7 @@ void main() { }; } -export async function PrimitiveTopologyPoints($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2'], + default: 'webgl2', +}; diff --git a/examples/demos/primitive-topology-triangles.ts b/examples/demos/primitive-topology-triangles.ts index 1ed625e..64612e6 100644 --- a/examples/demos/primitive-topology-triangles.ts +++ b/examples/demos/primitive-topology-triangles.ts @@ -7,7 +7,6 @@ import { BufferFrequencyHint, TextureUsage, } from '../../src'; -import { initExample } from './utils'; export async function render( deviceContribution: DeviceContribution, @@ -133,9 +132,7 @@ void main() { }; } -export async function PrimitiveTopologyTriangles($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/resize-canvas.ts b/examples/demos/resize-canvas.ts index 7ef4420..47b1c6d 100644 --- a/examples/demos/resize-canvas.ts +++ b/examples/demos/resize-canvas.ts @@ -6,7 +6,6 @@ import { BufferUsage, BufferFrequencyHint, } from '../../src'; -import { initExample } from './utils'; export async function render( deviceContribution: DeviceContribution, @@ -131,9 +130,7 @@ void main() { }; } -export async function MSAA($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/rotating-cube.ts b/examples/demos/rotating-cube.ts index 6b95766..5ec074a 100644 --- a/examples/demos/rotating-cube.ts +++ b/examples/demos/rotating-cube.ts @@ -13,7 +13,6 @@ import { TransparentBlack, CompareFunction, } from '../../src'; -import { initExample } from './utils'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -233,9 +232,7 @@ void main() { }; } -export async function RotatingCube($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/sampler.ts b/examples/demos/sampler.ts index 74845e6..ba0ec74 100644 --- a/examples/demos/sampler.ts +++ b/examples/demos/sampler.ts @@ -7,7 +7,6 @@ import { BufferFrequencyHint, TextureUsage, } from '../../src'; -import { initExample } from './utils'; // Low-res, pixelated render target so it's easier to see fine details. const kCanvasSize = 200; @@ -230,9 +229,7 @@ void main() { }; } -export async function Sampler($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgpu', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgpu', +}; diff --git a/examples/demos/scissor.ts b/examples/demos/scissor.ts index 7849e00..9e19689 100644 --- a/examples/demos/scissor.ts +++ b/examples/demos/scissor.ts @@ -13,7 +13,6 @@ import { TransparentBlack, CompareFunction, } from '../../src'; -import { initExample } from './utils'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -234,9 +233,7 @@ void main() { }; } -export async function Scissor($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/stencil.ts b/examples/demos/stencil.ts index cae3be3..1bb5486 100644 --- a/examples/demos/stencil.ts +++ b/examples/demos/stencil.ts @@ -14,7 +14,6 @@ import { CompareFunction, StencilOp, } from '../../src'; -import { initExample } from './utils'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -359,9 +358,7 @@ void main() { }; } -export async function Stencil($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/textured-cube.ts b/examples/demos/textured-cube.ts index 54e257a..798fb54 100644 --- a/examples/demos/textured-cube.ts +++ b/examples/demos/textured-cube.ts @@ -16,7 +16,7 @@ import { FilterMode, MipmapFilterMode, } from '../../src'; -import { initExample, loadImage } from './utils'; +import { loadImage } from '../utils/image'; import { vec3, mat4 } from 'gl-matrix'; import { cubeVertexArray, @@ -278,9 +278,7 @@ void main() { }; } -export async function TexturedCube($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl1', 'webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl1', 'webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/demos/two-cubes.ts b/examples/demos/two-cubes.ts index e99fe04..7d1e3ac 100644 --- a/examples/demos/two-cubes.ts +++ b/examples/demos/two-cubes.ts @@ -13,7 +13,6 @@ import { TransparentBlack, CompareFunction, } from '../../src'; -import { initExample } from './utils'; import { cubeVertexArray, cubeVertexSize, @@ -291,9 +290,7 @@ void main() { }; } -export async function TwoCubes($container: HTMLDivElement) { - return initExample($container, render, { - targets: ['webgl2', 'webgpu'], - default: 'webgl2', - }); -} +render.params = { + targets: ['webgl2', 'webgpu'], + default: 'webgl2', +}; diff --git a/examples/main.ts b/examples/main.ts index bf4ab56..1851c1f 100644 --- a/examples/main.ts +++ b/examples/main.ts @@ -1,4 +1,5 @@ import * as demos from './demos'; +import { initExample } from './utils'; const select = document.createElement('select'); select.id = 'example-select'; @@ -32,7 +33,7 @@ async function render() { $container.innerHTML = ''; const demo = demos[select.value]; - callback = await demo($container); + callback = await initExample($container, demo); } function onChange() { diff --git a/examples/demos/utils.ts b/examples/utils.ts similarity index 79% rename from examples/demos/utils.ts rename to examples/utils.ts index b4536d2..1cb81d5 100644 --- a/examples/demos/utils.ts +++ b/examples/utils.ts @@ -3,21 +3,19 @@ import { WebGLDeviceContribution, WebGPUDeviceContribution, DeviceContribution, -} from '../../src'; +} from '../src'; export async function initExample( - $container: HTMLDivElement, + $container: HTMLElement, render: ( deviceContribution: DeviceContribution, $canvas: HTMLCanvasElement, useRAF?: boolean, ) => Promise<() => void>, - params: { - targets: ('webgl1' | 'webgl2' | 'webgpu')[]; - xrCompatible?: boolean; - default: 'webgl1' | 'webgl2' | 'webgpu'; - }, ) { + // @ts-ignore + const { params } = render; + const deviceContributionWebGL1 = new WebGLDeviceContribution({ targets: ['webgl1'], xrCompatible: params.xrCompatible, @@ -33,7 +31,7 @@ export async function initExample( onContextRestored(e) {}, }); const shaderCompilerPath = new URL( - '../public/glsl_wgsl_compiler_bg.wasm', + './public/glsl_wgsl_compiler_bg.wasm', import.meta.url, ).href; const deviceContributionWebGPU = new WebGPUDeviceContribution({ @@ -44,7 +42,7 @@ export async function initExample( const rerender = async ( deviceContribution: DeviceContribution, - $container: HTMLDivElement, + $container: HTMLElement, ) => { let $canvasContainer = document.getElementById('canvas')!; if ($canvasContainer) { @@ -103,20 +101,3 @@ export async function initExample( return disposeCallback; } - -export async function loadImage( - url: string, -): Promise { - if (!!window.createImageBitmap) { - const response = await fetch(url); - const imageBitmap = await createImageBitmap(await response.blob()); - return imageBitmap; - } else { - const image = new window.Image(); - return new Promise((res) => { - image.onload = () => res(image); - image.src = url; - image.crossOrigin = 'Anonymous'; - }); - } -} diff --git a/examples/utils/image.ts b/examples/utils/image.ts new file mode 100644 index 0000000..a32978f --- /dev/null +++ b/examples/utils/image.ts @@ -0,0 +1,16 @@ +export async function loadImage( + url: string, +): Promise { + if (!!window.createImageBitmap) { + const response = await fetch(url); + const imageBitmap = await createImageBitmap(await response.blob()); + return imageBitmap; + } else { + const image = new window.Image(); + return new Promise((res) => { + image.onload = () => res(image); + image.src = url; + image.crossOrigin = 'Anonymous'; + }); + } +} diff --git a/package.json b/package.json index 46b3648..f5dba95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g-device-api", - "version": "1.2.3", + "version": "1.3.0", "description": "A Device API references WebGPU implementations", "keywords": [ "antv", diff --git a/src/shader/compiler.ts b/src/shader/compiler.ts index bcafc19..be08436 100644 --- a/src/shader/compiler.ts +++ b/src/shader/compiler.ts @@ -156,8 +156,10 @@ export function preprocessShader_GLSL( defines: Record | null = null, ): string { const isGLSL100 = vendorInfo.glslVersion === '#version 100'; - // const supportMRT = vendorInfo.supportMRT && !!features.MRT; - const supportMRT = false; + const useMRT = + type === 'frag' && + source.match(/^\s*layout\(location\s*=\s*\d*\)\s*out\s+vec4\s*(.*);$/gm) + ?.length > 1; const lines = source .split('\n') @@ -299,7 +301,7 @@ layout(set = ${set}, binding = ${ // headless-gl will throw the following error if we prepend `#version 100`: // #version directive must occur before anything else, except for comments and white space let concat = `${isGLSL100 ? '' : vendorInfo.glslVersion} -${isGLSL100 && supportMRT ? '#extension GL_EXT_draw_buffers : require\n' : ''} +${isGLSL100 && useMRT ? '#extension GL_EXT_draw_buffers : require\n' : ''} ${ isGLSL100 && type === 'frag' ? '#extension GL_OES_standard_derivatives : enable\n' @@ -362,50 +364,47 @@ ${rest} ); if (type === 'frag') { - let glFragColor: string; - concat = concat.replace( - /^\s*out\s+(\S+)\s*(.*);$/gm, - (_, dataType, name) => { - glFragColor = name; - return `${dataType} ${name};\n`; - }, - ); - - const lastIndexOfMain = concat.lastIndexOf('}'); - concat = - concat.substring(0, lastIndexOfMain) + - ` + if (useMRT) { + const gBuffers = []; + concat = concat.replace( + /^\s*layout\(location\s*=\s*\d*\)\s*out\s+vec4\s*(.*);$/gm, + (_, buffer) => { + gBuffers.push(buffer); + return `vec4 ${buffer};\n`; + }, + ); + + const lastIndexOfMain = concat.lastIndexOf('}'); + concat = + concat.substring(0, lastIndexOfMain) + + ` + ${gBuffers + .map( + (gBuffer, i) => `gl_FragData[${i}] = ${gBuffer}; + `, + ) + .join('\n')}` + + concat.substring(lastIndexOfMain); + } else { + let glFragColor: string; + concat = concat.replace( + /^\s*out\s+(\S+)\s*(.*);$/gm, + (_, dataType, name) => { + glFragColor = name; + return `${dataType} ${name};\n`; + }, + ); + + const lastIndexOfMain = concat.lastIndexOf('}'); + concat = + concat.substring(0, lastIndexOfMain) + + ` gl_FragColor = vec4(${glFragColor}); ` + - concat.substring(lastIndexOfMain); + concat.substring(lastIndexOfMain); + } } - // MRT - // if (supportMRT) { - // if (type === 'frag') { - // const gBuffers = []; - // concat = concat.replace( - // /^\s*layout\(location\s*=\s*\d*\)\s*out\s+vec4\s*(.*);$/gm, - // (_, buffer) => { - // gBuffers.push(buffer); - // return `vec4 ${buffer};\n`; - // }, - // ); - - // const lastIndexOfMain = concat.lastIndexOf('}'); - // concat = - // concat.substring(0, lastIndexOfMain) + - // ` - // ${gBuffers - // .map( - // (gBuffer, i) => `gl_FragData[${i}] = ${gBuffer}; - // `, - // ) - // .join('\n')}` + - // concat.substring(lastIndexOfMain); - // } - // } - // remove layout(location = 0) concat = concat.replace(/^\s*layout\((.*)\)/gm, ''); diff --git a/src/webgl/Device.ts b/src/webgl/Device.ts index b3c0d84..f810003 100644 --- a/src/webgl/Device.ts +++ b/src/webgl/Device.ts @@ -125,7 +125,7 @@ export class Device_GL implements SwapChain, Device { // @see https://www.khronos.org/registry/webgl/extensions/OES_draw_buffers_indexed/ OES_draw_buffers_indexed: OES_draw_buffers_indexed | null = null; // @see https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_draw_buffers - // WEBGL_draw_buffers: WEBGL_draw_buffers | null = null; + WEBGL_draw_buffers: WEBGL_draw_buffers | null = null; // @see https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_depth_texture WEBGL_depth_texture: WEBGL_depth_texture | null = null; WEBGL_compressed_texture_s3tc: WEBGL_compressed_texture_s3tc | null = null; @@ -237,7 +237,7 @@ export class Device_GL implements SwapChain, Device { // TODO: when ANGLE_instanced_arrays unavailable... this.ANGLE_instanced_arrays = gl.getExtension('ANGLE_instanced_arrays'); this.OES_texture_float = gl.getExtension('OES_texture_float'); - // this.WEBGL_draw_buffers = gl.getExtension('WEBGL_draw_buffers'); + this.WEBGL_draw_buffers = gl.getExtension('WEBGL_draw_buffers'); // @see https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_depth_texture this.WEBGL_depth_texture = gl.getExtension('WEBGL_depth_texture'); // @see https://developer.mozilla.org/en-US/docs/Web/API/EXT_frag_depth @@ -246,14 +246,8 @@ export class Device_GL implements SwapChain, Device { gl.getExtension('OES_element_index_uint'); // @see https://developer.mozilla.org/en-US/docs/Web/API/OES_standard_derivatives gl.getExtension('OES_standard_derivatives'); - - // won't use MRT anymore... - // if (this.WEBGL_draw_buffers) { - // this.supportMRT = true; - // } } else { this.EXT_texture_norm16 = gl.getExtension('EXT_texture_norm16'); - // this.supportMRT = true; } this.WEBGL_compressed_texture_s3tc = gl.getExtension( @@ -1293,14 +1287,22 @@ export class Device_GL implements SwapChain, Device { ); } } else if (attachment.type === ResourceType.Texture) { - // TODO: use Tex2D array with gl.framebufferTextureLayer - gl.framebufferTexture2D( - framebuffer, - binding, - GL.TEXTURE_2D, - getPlatformTexture(attachment as Texture_GL), - level, - ); + const texture = getPlatformTexture(attachment as Texture_GL); + if (attachment.dimension === TextureDimension.TEXTURE_2D) { + gl.framebufferTexture2D( + framebuffer, + binding, + GL.TEXTURE_2D, + texture, + level, + ); + } else if ( + isWebGL2(gl) && + attachment.dimension === TextureDimension.TEXTURE_2D_ARRAY + ) { + // TODO: use Tex2D array with gl.framebufferTextureLayer + // gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, binding, texture, 0, 0); + } } } @@ -1403,6 +1405,25 @@ export class Device_GL implements SwapChain, Device { } } + if (isWebGL2(gl)) { + gl.drawBuffers([ + GL.COLOR_ATTACHMENT0, + GL.COLOR_ATTACHMENT1, + GL.COLOR_ATTACHMENT2, + GL.COLOR_ATTACHMENT3, + ]); + } else { + if (!this.inBlitRenderPass && this.WEBGL_draw_buffers) { + // MRT @see https://github.com/shrekshao/MoveWebGL1EngineToWebGL2/blob/master/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-1.md#multiple-render-targets + this.WEBGL_draw_buffers.drawBuffersWEBGL([ + GL.COLOR_ATTACHMENT0_WEBGL, // gl_FragData[0] + GL.COLOR_ATTACHMENT1_WEBGL, // gl_FragData[1] + GL.COLOR_ATTACHMENT2_WEBGL, // gl_FragData[2] + GL.COLOR_ATTACHMENT3_WEBGL, // gl_FragData[3] + ]); + } + } + if (!this.inBlitRenderPass) { for ( let i = numColorAttachments; @@ -1424,25 +1445,6 @@ export class Device_GL implements SwapChain, Device { } } this.currentColorAttachments.length = numColorAttachments; - - // if (isWebGL2(gl)) { - // gl.drawBuffers([ - // GL.COLOR_ATTACHMENT0, - // GL.COLOR_ATTACHMENT1, - // GL.COLOR_ATTACHMENT2, - // GL.COLOR_ATTACHMENT3, - // ]); - // } else { - // if (!this.inBlitRenderPass) { - // // MRT @see https://github.com/shrekshao/MoveWebGL1EngineToWebGL2/blob/master/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-1.md#multiple-render-targets - // this.WEBGL_draw_buffers.drawBuffersWEBGL([ - // GL.COLOR_ATTACHMENT0_WEBGL, // gl_FragData[0] - // GL.COLOR_ATTACHMENT1_WEBGL, // gl_FragData[1] - // GL.COLOR_ATTACHMENT2_WEBGL, // gl_FragData[2] - // GL.COLOR_ATTACHMENT3_WEBGL, // gl_FragData[3] - // ]); - // } - // } } private setRenderPassParametersColor( @@ -1453,6 +1455,7 @@ export class Device_GL implements SwapChain, Device { resolveToLevel: number, ): void { const gl = this.gl; + const gl2 = isWebGL2(gl); if ( this.currentColorAttachments[i] !== colorAttachment || @@ -1461,12 +1464,10 @@ export class Device_GL implements SwapChain, Device { this.currentColorAttachments[i] = colorAttachment; this.currentColorAttachmentLevels[i] = attachmentLevel; - // disable MRT in WebGL1 - if (isWebGL2(gl) || i === 0) { + if (gl2 || (!gl2 && this.WEBGL_draw_buffers)) { this.bindFramebufferAttachment( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, - (isWebGL2(gl) ? GL.COLOR_ATTACHMENT0 : GL.COLOR_ATTACHMENT0_WEBGL) + - i, + gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, + (gl2 ? GL.COLOR_ATTACHMENT0 : GL.COLOR_ATTACHMENT0_WEBGL) + i, colorAttachment, attachmentLevel, ); @@ -2271,6 +2272,7 @@ export class Device_GL implements SwapChain, Device { private endPass(): void { const gl = this.gl; + const gl2 = isWebGL2(gl); let didUnbindDraw = false; @@ -2289,14 +2291,14 @@ export class Device_GL implements SwapChain, Device { assert(colorResolveFrom.format === colorResolveTo.format); this.setScissorRectEnabled(false); - if (isWebGL2(gl)) { + if (gl2) { gl.bindFramebuffer( gl.READ_FRAMEBUFFER, this.resolveColorReadFramebuffer, ); } if (this.resolveColorAttachmentsChanged) { - if (isWebGL2(gl)) { + if (gl2) { this.bindFramebufferAttachment( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, @@ -2310,17 +2312,17 @@ export class Device_GL implements SwapChain, Device { // Special case: Blitting to the on-screen. if (colorResolveTo === this.scTexture) { gl.bindFramebuffer( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, this.scPlatformFramebuffer, ); } else { gl.bindFramebuffer( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, this.resolveColorDrawFramebuffer, ); if (this.resolveColorAttachmentsChanged) gl.framebufferTexture2D( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorResolveTo.gl_texture, @@ -2328,7 +2330,7 @@ export class Device_GL implements SwapChain, Device { ); } - if (isWebGL2(gl)) { + if (gl2) { gl.blitFramebuffer( 0, 0, @@ -2352,29 +2354,26 @@ export class Device_GL implements SwapChain, Device { if (!this.currentRenderPassDescriptor.colorStore[i]) { if (!didBindRead) { gl.bindFramebuffer( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, this.resolveColorReadFramebuffer, ); if (this.resolveColorAttachmentsChanged) this.bindFramebufferAttachment( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorResolveFrom, this.currentColorAttachmentLevels[i], ); } - if (isWebGL2(gl)) { + if (gl2) { gl.invalidateFramebuffer(gl.READ_FRAMEBUFFER, [ gl.COLOR_ATTACHMENT0, ]); } } - gl.bindFramebuffer( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, - null, - ); + gl.bindFramebuffer(gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, null); } } @@ -2394,26 +2393,26 @@ export class Device_GL implements SwapChain, Device { this.setScissorRectEnabled(false); gl.bindFramebuffer( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, this.resolveDepthStencilReadFramebuffer, ); gl.bindFramebuffer( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, this.resolveDepthStencilDrawFramebuffer, ); if (this.resolveDepthStencilAttachmentsChanged) { this.bindFramebufferDepthStencilAttachment( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, depthStencilResolveFrom, ); this.bindFramebufferDepthStencilAttachment( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, depthStencilResolveTo, ); } didBindRead = true; - if (isWebGL2(gl)) { + if (gl2) { gl.blitFramebuffer( 0, 0, @@ -2428,28 +2427,25 @@ export class Device_GL implements SwapChain, Device { ); } else { } - gl.bindFramebuffer( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, - null, - ); + gl.bindFramebuffer(gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, null); didUnbindDraw = true; } if (!this.currentRenderPassDescriptor!.depthStencilStore) { if (!didBindRead) { gl.bindFramebuffer( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, this.resolveDepthStencilReadFramebuffer, ); if (this.resolveDepthStencilAttachmentsChanged) this.bindFramebufferDepthStencilAttachment( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, + gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, depthStencilResolveFrom, ); didBindRead = true; } - if (isWebGL2(gl)) { + if (gl2) { gl.invalidateFramebuffer(gl.READ_FRAMEBUFFER, [ gl.DEPTH_STENCIL_ATTACHMENT, ]); @@ -2457,20 +2453,14 @@ export class Device_GL implements SwapChain, Device { } if (didBindRead) - gl.bindFramebuffer( - isWebGL2(gl) ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, - null, - ); + gl.bindFramebuffer(gl2 ? GL.READ_FRAMEBUFFER : GL.FRAMEBUFFER, null); this.resolveDepthStencilAttachmentsChanged = false; } if (!didUnbindDraw) { // If we did not unbind from a resolve, then we need to unbind our render pass draw FBO here. - gl.bindFramebuffer( - isWebGL2(gl) ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, - null, - ); + gl.bindFramebuffer(gl2 ? GL.DRAW_FRAMEBUFFER : GL.FRAMEBUFFER, null); } } diff --git a/src/webgl/Texture.ts b/src/webgl/Texture.ts index 0bc3a1c..5ab0d9a 100644 --- a/src/webgl/Texture.ts +++ b/src/webgl/Texture.ts @@ -28,6 +28,7 @@ export class Texture_GL extends ResourceBase_GL implements Texture { gl_texture: WebGLTexture; gl_target: GLenum; format: Format; + dimension: TextureDimension; width: number; height: number; depthOrArrayLayers: number; @@ -71,6 +72,7 @@ export class Texture_GL extends ResourceBase_GL implements Texture { this.immutable = descriptor.usage === TextureUsage.RENDER_TARGET; this.pixelStore = descriptor.pixelStore; this.format = descriptor.format; + this.dimension = descriptor.dimension; this.formatKind = getFormatSamplerKind(descriptor.format); this.width = descriptor.width; this.height = descriptor.height; diff --git a/src/webgpu/Device.ts b/src/webgpu/Device.ts index 4f8a62d..1b9af18 100644 --- a/src/webgpu/Device.ts +++ b/src/webgpu/Device.ts @@ -46,6 +46,7 @@ import { defaultMegaState, PrimitiveTopology, copyMegaState, + copyAttachmentState, } from '../api'; import type { glsl_compile as glsl_compile_ } from '../../rust/pkg/glsl_wgsl_compiler'; import { Bindings_WebGPU } from './Bindings'; @@ -566,6 +567,15 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU { ...rest, }; + const defaultAttachmentState = + descriptor.megaStateDescriptor.attachmentsState[0]; + descriptor.colorAttachmentFormats.forEach((format, i) => { + if (!descriptor.megaStateDescriptor.attachmentsState[i]) { + descriptor.megaStateDescriptor.attachmentsState[i] = + copyAttachmentState(undefined, defaultAttachmentState); + } + }); + const primitive = translatePrimitiveState( descriptor.topology ?? PrimitiveTopology.TRIANGLES, descriptor.megaStateDescriptor,