UNPKG

@antv/g-webgpu-raytracer

Version:

A simple ray tracer implemented with WebGPU

675 lines (624 loc) 33.3 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _regeneratorRuntime from "@babel/runtime/regenerator"; import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import { isSafari } from '@antv/g-webgpu-core'; import { Camera } from '@antv/g-webgpu'; import { WebGPUEngine } from '@antv/g-webgpu-engine'; import * as WebGPUConstants from '@webgpu/types/dist/constants'; import { mat4, vec3 } from 'gl-matrix'; import { InteractionSystem } from './interaction'; import { Mesh } from './Mesh'; import { cornellBoxScene } from './scene/cornell'; /* babel-plugin-inline-import './shaders/blit.frag.glsl' */ var blitFragCode = "layout(origin_upper_left) in vec4 gl_FragCoord;\n\nlayout(set = 0, binding = 0, rgba32f) readonly uniform highp image2D output_image;\n\nlayout(location = 0) out vec4 output_color;\n\nvoid main() {\n output_color = vec4(imageLoad(output_image, ivec2(gl_FragCoord.x, gl_FragCoord.y)).xyz, 1.0);\n}"; /* babel-plugin-inline-import './shaders/blit.vert.glsl' */ var blitVertCode = "// https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/\n\nout gl_PerVertex {\n vec4 gl_Position;\n};\n\nvoid main() {\n vec2 outUV = vec2(((gl_VertexIndex) << 1) & 2, (gl_VertexIndex) & 2);\n gl_Position = vec4(outUV * 2.0 - 1.0, 0.0, 1.0);\n}\n"; // import rayComputeCode from './shaders/raycasting.comp.glsl'; /* babel-plugin-inline-import './shaders/whitted.comp.glsl' */ var rayComputeCode = "layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;\n\nlayout(std140, set = 0, binding = 0) uniform Globals {\n mat4 uCameraToWorld;\n mat4 uCameraInverseProjection;\n float uInitialSeed;\n float uSamples;\n} globals;\n\nlayout(set = 0, binding = 1, rgba32f) writeonly uniform highp image2D outputTex;\nlayout(set = 0, binding = 2, rgba32f) readonly uniform highp image2D accumulatedTex;\n\nstruct Mesh\n{\n int offset;\n int triangle_count;\n vec3 diffuse;\n vec3 emission;\n};\n\nlayout(std430, set = 0, binding = 3) readonly buffer Vertices {\n vec3 vertices[];\n};\n\nlayout(std430, set = 0, binding = 4) readonly buffer Triangles {\n int triangles[];\n};\n\nlayout(std430, set = 0, binding = 5) readonly buffer Meshes {\n Mesh meshes[];\n};\n\n#define M_PI 3.14159265358979323846\n#define M_TWO_PI 6.28318530718\n#define EPSILON 1e-3\n#define zero3 vec3(0.0)\n#define MAX_FLOAT 3.402823466e+30F\n\n//\n// Pseudo random numbers generator.\n//\n// References:\n// - http://blog.three-eyed-games.com/2018/05/12/gpu-path-tracing-in-unity-part-2/\n//\nfloat rand(inout float seed, vec2 pixel) {\n float result = fract(sin(seed / 100.0f * dot(pixel, vec2(12.9898f, 78.233f))) * 43758.5453f);\n seed += 1.0f;\n return result;\n}\n\nvec2 rand2(inout float seed, vec2 pixel) {\n return vec2(rand(seed, pixel), rand(seed, pixel));\n}\n\nmat3 get_tangent_space(vec3 normal) {\n // Choose a helper vector for the cross product\n vec3 helper = vec3(1.0, 0.0, 0.0);\n if (abs(normal.x) > 0.99) {\n helper = vec3(0.0, 0.0, 1.0);\n }\n\n // Generate vectors\n vec3 tangent = normalize(cross(normal, helper));\n vec3 binormal = normalize(cross(normal, tangent));\n return mat3(tangent, binormal, normal);\n}\n\nvec3 sample_sphere_uniform(vec3 normal, vec2 s, float alpha) {\n // Sample the hemisphere, where alpha determines the kind of the sampling\n float cos_theta = pow(s.x, 1.0 / (alpha + 1.0));\n float sin_theta = sqrt(1.0 - cos_theta * cos_theta);\n float phi = M_TWO_PI * s.y;\n vec3 tangentSpaceDir = vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);\n\n // Transform direction to world space\n return get_tangent_space(normal) * tangentSpaceDir;\n}\n\nstruct Ray {\n vec3 origin;\n vec3 direction;\n float t_max;\n float t_min;\n vec3 energy;\n};\n\nstruct RayHit {\n float distance;\n vec3 normal;\n int mesh_indice;\n};\n\nvec3 ray_at(Ray r, float t) {\n return r.origin + r.direction * t;\n}\n\nfloat energy(vec3 color) {\n return dot(color, vec3(1.0 / 3.0));\n}\n\n// @see https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection\nbool hit_triangle_mt(Ray r, vec3 v0, vec3 v1, vec3 v2, out float t) {\n vec3 e1 = v1 - v0;\n vec3 e2 = v2 - v0;\n vec3 h = cross(r.direction, e2);\n float a = dot(e1, h);\n\n if (a < EPSILON && a > EPSILON)\n return false;\n\n float f = 1.0 / a;\n vec3 s = r.origin - v0;\n float u = f * dot(s, h);\n\n if (u < 0.0 || u > 1.0)\n return false;\n\n vec3 q = cross(s, e1);\n float v = f * dot(r.direction, q);\n if (v < 0.0 || u + v > 1.0)\n return false;\n \n t = f * dot(e2, q);\n if (t > EPSILON)\n {\n return true;\n }\n\n return false;\n}\n\nbool hit_world(Ray r, inout RayHit hit) {\n bool does_hit = false;\n float t = 0.0;\n hit.distance = 0.0;\n hit.mesh_indice = -1;\n float best_min_t = r.t_max;\n\n for (int i = 0; i < meshes.length(); ++i) {\n Mesh mesh = meshes[i];\n\n for (int j = 0; j < mesh.triangle_count * 3; j += 3) {\n vec3 v0 = vertices[triangles[mesh.offset + j]];\n vec3 v1 = vertices[triangles[mesh.offset + j + 1]];\n vec3 v2 = vertices[triangles[mesh.offset + j + 2]];\n\n if (hit_triangle_mt(r, v0, v1, v2, t) && t >= r.t_min && t < r.t_max && t < best_min_t) {\n best_min_t = t;\n does_hit = true;\n hit.mesh_indice = i;\n hit.normal = normalize(cross(v1 - v0, v2 - v0));\n }\n\n }\n }\n\n if (does_hit) {\n hit.distance = best_min_t;\n }\n\n return does_hit;\n}\n\nvec3 random_point_on_mesh(Mesh m, inout float seed, vec2 pixel, out float p) {\n // Pick a random triangle.\n int triangle = min(int(rand(seed, pixel) * float(m.triangle_count)), m.triangle_count - 1);\n\n // Pick vertices.\n vec3 v0 = vertices[triangles[m.offset + triangle]];\n vec3 v1 = vertices[triangles[m.offset + triangle + 1]];\n vec3 v2 = vertices[triangles[m.offset + triangle + 2]];\n\n float r = rand(seed, pixel);\n float s = rand(seed, pixel);\n\n if (r + s > 1.0)\n {\n r = 1.0 - r;\n s = 1.0 - s;\n }\n\n float t = 1.0 - r - s;\n \n float triangle_area = length(cross(v1 - v0, v2 - v0)) * 0.5;\n\n p = (1.0 / float(m.triangle_count)) / triangle_area;\n\n return v0 * r + v1 * s + v2 * t;\n}\n\nvec3 trace(Ray r, inout float seed, vec2 pixel) {\n int max_depth = 5;\n int depth = 0;\n RayHit hit;\n\n vec3 res = vec3(0.0);\n\n int light_mesh_indice = 0;\n Mesh light = meshes[light_mesh_indice];\n int light_count = light.triangle_count;\n\n while (depth < max_depth\n && hit_world(r, hit))\n {\n Mesh mesh = meshes[hit.mesh_indice];\n vec3 surface_normal = hit.normal;\n\n // \u51FB\u4E2D\u4E86\u5149\u6E90\uFF0C\u6E32\u67D3\u5149\u6E90\u989C\u8272\uFF0C\u7EC8\u6B62\u5FAA\u73AF\n if (mesh.emission != vec3(0.0) && depth == 0) {\n return mesh.emission;\n }\n\n vec3 hit_point = ray_at(r, hit.distance);\n\n if (mesh.emission == vec3(0.0)) {\n float light_pdf = 0.0;\n\n // Generate a point on the light.\n vec3 light_point = random_point_on_mesh(light, seed, pixel, light_pdf);\n\n vec3 lh = light_point - hit_point;\n float dist = length(lh);\n\n // \u7EE7\u7EED\u8FFD\u8E2A shadow ray\n Ray shadow_ray;\n shadow_ray.origin = hit_point;\n shadow_ray.direction = normalize(lh);\n shadow_ray.t_min = EPSILON;\n shadow_ray.t_max = dist;\n\n if (!hit_world(shadow_ray, hit) \n || hit.mesh_indice == light_mesh_indice) {\n // Diffuse\n res += 2.0 * light_pdf * mesh.diffuse * light.emission * abs(dot(surface_normal, shadow_ray.direction));\n }\n }\n\n vec2 s = rand2(seed, pixel);\n r.origin = hit_point + hit.normal * EPSILON;\n r.direction = normalize(sample_sphere_uniform(hit.normal, s, 1.0));\n depth++;\n }\n\n return res / float(depth);\n}\n\nRay create_camera_ray(vec2 uv) {\n vec3 rayOriginWorld = (globals.uCameraToWorld * vec4(0.0, 0.0, 0.0, 1.0)).xyz;\n\n vec3 direction = (globals.uCameraInverseProjection * vec4(uv, 0.0, 1.0)).xyz;\n direction = (globals.uCameraToWorld * vec4(direction, 0.0)).xyz;\n direction = normalize(direction);\n\n Ray cameraRay;\n cameraRay.origin = rayOriginWorld;\n cameraRay.direction = direction;\n cameraRay.t_min = EPSILON;\n cameraRay.t_max = MAX_FLOAT;\n cameraRay.energy = vec3(1.0);\n return cameraRay;\n}\n\nvoid main() {\n ivec2 imageSize = ivec2(gl_NumWorkGroups.xy * gl_WorkGroupSize.xy);\n ivec2 storePos = ivec2(gl_GlobalInvocationID.xy);\n vec2 uv = vec2(storePos) / vec2(imageSize);\n float seed = float(globals.uInitialSeed);\n\n vec2 sample_pos = (vec2(storePos) + rand2(seed, uv)) / vec2(imageSize);\n\n Ray r = create_camera_ray(sample_pos);\n\n vec3 finalColor = trace(r, seed, uv);\n\n vec4 color;\n // flipY (0, 0) is top-left\n // @see https://gpuweb.github.io/gpuweb/#coordinate-systems\n storePos.y = imageSize.y - storePos.y;\n if (globals.uSamples == 0.0) {\n color = vec4(finalColor, 1.0);\n } else {\n vec3 initial = imageLoad(accumulatedTex, storePos).rgb;\n color = vec4(initial + (finalColor - initial) / float(globals.uSamples), 1.0);\n }\n\n imageStore(outputTex, storePos, color);\n}"; export var RayTracer = /*#__PURE__*/function () { function RayTracer(options) { var _this = this; _classCallCheck(this, RayTracer); this.options = options; this.engine = void 0; this.inited = false; this.rafHandle = void 0; this.computeBindGroupLayout = void 0; this.computePipeline = void 0; this.renderBindGroupLayout = void 0; this.renderPipeline = void 0; this.outputTexture = void 0; this.accumulatedTexture = void 0; this.verticesBuffer = void 0; this.meshesBuffer = void 0; this.trianglesBuffer = void 0; this.camera = void 0; this.sampleCount = 0; this.randomSeed = 0; this.interactionSystem = void 0; this.update = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() { return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return _this.render(); case 2: // 考虑运行在 Worker 中,不能使用 window.requestAnimationFrame _this.rafHandle = requestAnimationFrame(_this.update); case 3: case "end": return _context.stop(); } } }, _callee); })); } _createClass(RayTracer, [{ key: "init", value: function () { var _init = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: this.engine = new WebGPUEngine(); _context2.next = 3; return this.engine.init({ canvas: this.options.canvas, swapChainFormat: WebGPUConstants.TextureFormat.BGRA8Unorm, antialiasing: false, supportCompute: true }); case 3: _context2.next = 5; return this.update(); case 5: case "end": return _context2.stop(); } } }, _callee2, this); })); function init() { return _init.apply(this, arguments); } return init; }() }, { key: "setSize", value: function setSize(width, height) { var canvas = this.engine.getCanvas(); var pixelRatio = window.devicePixelRatio; canvas.width = Math.floor(width * pixelRatio); canvas.height = Math.floor(height * pixelRatio); canvas.style.width = "".concat(width, "px"); canvas.style.height = "".concat(height, "px"); this.engine.viewport({ x: 0, y: 0, width: canvas.width, height: canvas.height }); } }, { key: "destroy", value: function destroy() { this.engine.destroy(); this.interactionSystem.tearDown(); cancelAnimationFrame(this.rafHandle); } }, { key: "render", value: function () { var _render = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() { return _regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: this.engine.beginFrame(); this.engine.clear({ color: [1, 1, 1, 1], depth: 1 }); if (this.inited) { _context3.next = 13; break; } if (this.options.onInit) { this.options.onInit(); } this.createCamera(); this.createInteraction(); this.createMeshes(); this.createOutputTexture(); _context3.next = 10; return this.createComputePipeline(); case 10: _context3.next = 12; return this.createRenderPipeline(); case 12: this.inited = true; case 13: if (this.options.onUpdate) { this.options.onUpdate(); } this.runComputePipeline(); this.runRenderPipeline(); this.engine.endFrame(); case 17: case "end": return _context3.stop(); } } }, _callee3, this); })); function render() { return _render.apply(this, arguments); } return render; }() }, { key: "createInteraction", value: function createInteraction() { var _this2 = this; this.interactionSystem = new InteractionSystem(this.options.canvas, this.camera, function () { _this2.sampleCount = 0; _this2.randomSeed = 0; }); this.interactionSystem.initialize(); } }, { key: "createCamera", value: function createCamera() { this.camera = new Camera(); this.camera.position = vec3.fromValues(-2.75, 2.75, 8.35); this.camera.setFocalPoint(vec3.fromValues(-2.75, 2.75, 0)); this.camera.setPerspective(0.1, 100, 40, 1); } }, { key: "createMeshes", value: function createMeshes() { var totalVerticeCount = 0; var totalTriangleCount = 0; cornellBoxScene.forEach(function (mesh) { totalVerticeCount += mesh.vertices.length; totalTriangleCount += mesh.indices.length; }); var verticesBuffer = new Float32Array(totalVerticeCount / 3 * 4); var index = 0; var trianglesArray = new Array(); var indicesOffset = 0; var accumulatingTriangleCount = 0; cornellBoxScene.forEach(function (mesh) { var vertices = mesh.vertices; for (var i = 0; i < vertices.length; i += 3) { verticesBuffer[index++] = vertices[i]; verticesBuffer[index++] = vertices[i + 1]; verticesBuffer[index++] = vertices[i + 2]; verticesBuffer[index++] = 0.0; } trianglesArray = trianglesArray.concat(mesh.indices.map(function (i) { return i + indicesOffset; })); mesh.offset = accumulatingTriangleCount; accumulatingTriangleCount += mesh.triangleCount * 3; indicesOffset += mesh.verticeCount; }); var trianglesBuffer = new Int32Array(trianglesArray); var meshesBuffer = new Float32Array(Mesh.createMeshesBuffer(cornellBoxScene)); this.verticesBuffer = verticesBuffer; this.trianglesBuffer = trianglesBuffer; this.meshesBuffer = meshesBuffer; } }, { key: "createOutputTexture", value: function createOutputTexture() { this.outputTexture = this.engine.device.createTexture({ label: 'Output Texture', size: { width: this.options.canvas.width, height: this.options.canvas.height, depth: 1 }, // TODO: arrayLayerCount is deprecated: use size.depth // arrayLayerCount: 1, mipLevelCount: 1, sampleCount: 1, dimension: WebGPUConstants.TextureDimension.E2d, format: WebGPUConstants.TextureFormat.RGBA32Float, usage: WebGPUConstants.TextureUsage.Storage }); this.accumulatedTexture = this.engine.device.createTexture({ label: 'Accumulated Texture', size: { width: this.options.canvas.width, height: this.options.canvas.height, depth: 1 }, // TODO: arrayLayerCount is deprecated: use size.depth // arrayLayerCount: 1, mipLevelCount: 1, sampleCount: 1, dimension: WebGPUConstants.TextureDimension.E2d, format: WebGPUConstants.TextureFormat.RGBA32Float, usage: WebGPUConstants.TextureUsage.Storage }); } }, { key: "createRenderPipeline", value: function () { var _createRenderPipeline = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() { var _yield$this$compilePi, vertexStage, fragmentStage, bindGroupLayoutEntries, pipelineLayout; return _regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: _context4.next = 2; return this.compilePipelineStageDescriptor(blitVertCode, blitFragCode); case 2: _yield$this$compilePi = _context4.sent; vertexStage = _yield$this$compilePi.vertexStage; fragmentStage = _yield$this$compilePi.fragmentStage; bindGroupLayoutEntries = [{ binding: 0, visibility: WebGPUConstants.ShaderStage.Fragment, type: 'readonly-storage-texture', viewDimension: WebGPUConstants.TextureViewDimension.E2d, storageTextureFormat: WebGPUConstants.TextureFormat.RGBA32Float }]; this.renderBindGroupLayout = this.engine.device.createBindGroupLayout(isSafari ? // @ts-ignore { bindings: bindGroupLayoutEntries } : { entries: bindGroupLayoutEntries }); pipelineLayout = this.engine.device.createPipelineLayout({ bindGroupLayouts: [this.renderBindGroupLayout] }); this.renderPipeline = this.engine.device.createRenderPipeline({ sampleCount: this.engine.mainPassSampleCount, primitiveTopology: WebGPUConstants.PrimitiveTopology.TriangleList, rasterizationState: { frontFace: WebGPUConstants.FrontFace.CCW, cullMode: WebGPUConstants.CullMode.Back, depthBias: 0, depthBiasClamp: 0, depthBiasSlopeScale: 0 }, depthStencilState: { depthWriteEnabled: false, // depthCompare: depthFuncMap[depth?.func || gl.ALWAYS], format: WebGPUConstants.TextureFormat.Depth24PlusStencil8, // stencilFront: stencilFrontBack, // stencilBack: stencilFrontBack, stencilReadMask: 0xffffffff, stencilWriteMask: 0xffffffff }, colorStates: [{ format: this.engine.options.swapChainFormat, alphaBlend: { srcFactor: WebGPUConstants.BlendFactor.One, dstFactor: WebGPUConstants.BlendFactor.One, operation: WebGPUConstants.BlendOperation.Add }, colorBlend: { srcFactor: WebGPUConstants.BlendFactor.SrcAlpha, dstFactor: WebGPUConstants.BlendFactor.OneMinusDstAlpha, operation: WebGPUConstants.BlendOperation.Add }, writeMask: WebGPUConstants.ColorWrite.All }], alphaToCoverageEnabled: false, layout: pipelineLayout, vertexStage: vertexStage, fragmentStage: fragmentStage, vertexState: { indexFormat: WebGPUConstants.IndexFormat.Uint32, vertexBuffers: [] } }); case 9: case "end": return _context4.stop(); } } }, _callee4, this); })); function createRenderPipeline() { return _createRenderPipeline.apply(this, arguments); } return createRenderPipeline; }() }, { key: "createComputePipeline", value: function () { var _createComputePipeline = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() { var _yield$this$compileCo, computeStage, bindGroupLayoutEntries, pipelineLayout; return _regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; return this.compileComputePipelineStageDescriptor(rayComputeCode); case 2: _yield$this$compileCo = _context5.sent; computeStage = _yield$this$compileCo.computeStage; bindGroupLayoutEntries = [{ binding: 0, visibility: WebGPUConstants.ShaderStage.Compute, type: 'uniform-buffer' }, { binding: 1, visibility: WebGPUConstants.ShaderStage.Compute, type: 'writeonly-storage-texture', viewDimension: WebGPUConstants.TextureViewDimension.E2d, storageTextureFormat: WebGPUConstants.TextureFormat.RGBA32Float }, { binding: 2, visibility: WebGPUConstants.ShaderStage.Compute, type: 'readonly-storage-texture', viewDimension: WebGPUConstants.TextureViewDimension.E2d, storageTextureFormat: WebGPUConstants.TextureFormat.RGBA32Float }, { binding: 3, visibility: WebGPUConstants.ShaderStage.Compute, type: 'readonly-storage-buffer' }, { binding: 4, visibility: WebGPUConstants.ShaderStage.Compute, type: 'readonly-storage-buffer' }, { binding: 5, visibility: WebGPUConstants.ShaderStage.Compute, type: 'readonly-storage-buffer' }]; this.computeBindGroupLayout = this.engine.device.createBindGroupLayout(isSafari ? // @ts-ignore { bindings: bindGroupLayoutEntries } : { entries: bindGroupLayoutEntries }); pipelineLayout = this.engine.device.createPipelineLayout({ bindGroupLayouts: [this.computeBindGroupLayout] }); this.computePipeline = this.engine.device.createComputePipeline({ layout: pipelineLayout, computeStage: computeStage }); case 8: case "end": return _context5.stop(); } } }, _callee5, this); })); function createComputePipeline() { return _createComputePipeline.apply(this, arguments); } return createComputePipeline; }() }, { key: "runComputePipeline", value: function runComputePipeline() { if (this.engine.currentComputePass) { var cameraWorldMatrix = this.camera.matrix; var projectionInverseMatrix = mat4.invert(mat4.create(), this.camera.getPerspective()); var NDCToScreenMatrix = mat4.create(); mat4.identity(NDCToScreenMatrix); mat4.translate(NDCToScreenMatrix, NDCToScreenMatrix, [-1, -1, 0]); mat4.scale(NDCToScreenMatrix, NDCToScreenMatrix, [2, 2, 1]); mat4.multiply(projectionInverseMatrix, projectionInverseMatrix, NDCToScreenMatrix); var mergedUniformData = [].concat(_toConsumableArray(cameraWorldMatrix), _toConsumableArray(projectionInverseMatrix), [this.randomSeed, this.sampleCount, 0, 0]); var uniformBuffer = this.engine.createBuffer({ data: new Float32Array(mergedUniformData), usage: WebGPUConstants.BufferUsage.Uniform | WebGPUConstants.BufferUsage.CopyDst }); var verticeBuffer = this.engine.createBuffer({ data: this.verticesBuffer, // @ts-ignore usage: WebGPUConstants.BufferUsage.Storage | WebGPUConstants.BufferUsage.CopyDst }); var trianglesBuffer = this.engine.createBuffer({ data: this.trianglesBuffer, // @ts-ignore usage: WebGPUConstants.BufferUsage.Storage | WebGPUConstants.BufferUsage.CopyDst }); var meshesBuffer = this.engine.createBuffer({ data: this.meshesBuffer, // @ts-ignore usage: WebGPUConstants.BufferUsage.Storage | WebGPUConstants.BufferUsage.CopyDst }); var bindGroupEntries = [{ binding: 0, // https://gpuweb.github.io/gpuweb/#typedefdef-gpubindingresource resource: { // @ts-ignore buffer: uniformBuffer.get() } }, { binding: 1, resource: isSafari ? // @ts-ignore this.outputTexture.createDefaultView() : this.outputTexture.createView() }, { binding: 2, resource: isSafari ? // @ts-ignore this.accumulatedTexture.createDefaultView() : this.accumulatedTexture.createView() }, { binding: 3, resource: { // @ts-ignore buffer: verticeBuffer.get() } }, { binding: 4, resource: { // @ts-ignore buffer: trianglesBuffer.get() } }, { binding: 5, resource: { // @ts-ignore buffer: meshesBuffer.get() } }]; var computeBindGroup = this.engine.device.createBindGroup(isSafari ? { label: 'Compute Bind Group', layout: this.computeBindGroupLayout, // @ts-ignore bindings: bindGroupEntries } : { label: 'Compute Bind Group', layout: this.computeBindGroupLayout, entries: bindGroupEntries }); this.engine.currentComputePass.setPipeline(this.computePipeline); this.engine.currentComputePass.setBindGroup(0, computeBindGroup); this.engine.currentComputePass.dispatch(this.options.canvas.width / 16, this.options.canvas.height / 16, 1); this.sampleCount++; this.randomSeed++; } } }, { key: "runRenderPipeline", value: function runRenderPipeline() { var renderPass = this.engine.bundleEncoder || this.engine.currentRenderPass; if (this.renderPipeline) { renderPass.setPipeline(this.renderPipeline); } var bindGroupEntries = [{ binding: 0, resource: isSafari ? // @ts-ignore this.outputTexture.createDefaultView() : this.outputTexture.createView() }]; var renderBindGroup = this.engine.device.createBindGroup(isSafari ? { label: 'Render Bind Group', layout: this.renderBindGroupLayout, // @ts-ignore bindings: bindGroupEntries } : { label: 'Render Bind Group', layout: this.renderBindGroupLayout, entries: bindGroupEntries }); renderPass.setBindGroup(0, renderBindGroup); renderPass.draw(3); // swap var tmp = this.accumulatedTexture; this.accumulatedTexture = this.outputTexture; this.outputTexture = tmp; } }, { key: "compileShaderToSpirV", value: function compileShaderToSpirV(source, type, shaderVersion) { return this.compileRawShaderToSpirV(shaderVersion + source, type); } }, { key: "compileRawShaderToSpirV", value: function compileRawShaderToSpirV(source, type) { return this.engine.glslang.compileGLSL(source, type); } }, { key: "compilePipelineStageDescriptor", value: function () { var _compilePipelineStageDescriptor = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6(vertexCode, fragmentCode) { var shaderVersion, vertexShader, fragmentShader; return _regeneratorRuntime.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: shaderVersion = '#version 450\n'; _context6.next = 3; return this.compileShaderToSpirV(vertexCode, 'vertex', shaderVersion); case 3: vertexShader = _context6.sent; _context6.next = 6; return this.compileShaderToSpirV(fragmentCode, 'fragment', shaderVersion); case 6: fragmentShader = _context6.sent; return _context6.abrupt("return", this.createPipelineStageDescriptor(vertexShader, fragmentShader)); case 8: case "end": return _context6.stop(); } } }, _callee6, this); })); function compilePipelineStageDescriptor(_x, _x2) { return _compilePipelineStageDescriptor.apply(this, arguments); } return compilePipelineStageDescriptor; }() }, { key: "createPipelineStageDescriptor", value: function createPipelineStageDescriptor(vertexShader, fragmentShader) { return { vertexStage: { module: this.engine.device.createShaderModule({ code: vertexShader, // @ts-ignore isWHLSL: isSafari }), entryPoint: 'main' }, fragmentStage: { module: this.engine.device.createShaderModule({ code: fragmentShader, // @ts-ignore isWHLSL: isSafari }), entryPoint: 'main' } }; } }, { key: "compileComputePipelineStageDescriptor", value: function () { var _compileComputePipelineStageDescriptor = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7(computeCode) { var computeShader, shaderVersion; return _regeneratorRuntime.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: if (!isSafari) { _context7.next = 4; break; } computeShader = computeCode; _context7.next = 8; break; case 4: shaderVersion = '#version 450\n'; _context7.next = 7; return this.compileShaderToSpirV(computeCode, 'compute', shaderVersion); case 7: computeShader = _context7.sent; case 8: return _context7.abrupt("return", { computeStage: { module: this.engine.device.createShaderModule({ code: computeShader, // @ts-ignore isWHLSL: isSafari }), entryPoint: 'main' } }); case 9: case "end": return _context7.stop(); } } }, _callee7, this); })); function compileComputePipelineStageDescriptor(_x3) { return _compileComputePipelineStageDescriptor.apply(this, arguments); } return compileComputePipelineStageDescriptor; }() }]); return RayTracer; }(); //# sourceMappingURL=index.js.map