| /* |
| * Copyright (C) 2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| const whlslShaders = ` |
| struct VertexOutput { |
| float4 position : SV_Position; |
| float4 color : attribute(1); |
| } |
| |
| vertex VertexOutput vertexMain(float4 position : attribute(0), |
| float4 color : attribute(1), |
| constant float[] timeUniform : register(b0, space0), |
| constant float[] uniforms : register(b0, space1)) { |
| float scale = uniforms[0]; |
| float offsetX = uniforms[1]; |
| float offsetY = uniforms[2]; |
| float scalar = uniforms[3]; |
| float scalarOffset = uniforms[4]; |
| float time = timeUniform[0]; |
| |
| float fade = fmod(scalarOffset + time * scalar / 10.0, 1.0); |
| if (fade < 0.5) { |
| fade = fade * 2.0; |
| } else { |
| fade = (1.0 - fade) * 2.0; |
| } |
| float xpos = position.x * scale; |
| float ypos = position.y * scale; |
| float angle = 3.14159 * 2.0 * fade; |
| float xrot = xpos * cos(angle) - ypos * sin(angle); |
| float yrot = xpos * sin(angle) + ypos * cos(angle); |
| xpos = xrot + offsetX; |
| ypos = yrot + offsetY; |
| |
| VertexOutput out; |
| out.position = float4(xpos, ypos, 0.0, 1.0); |
| out.color = float4(fade, 1.0 - fade, 0.0, 1.0) + color; |
| return out; |
| } |
| |
| fragment float4 fragmentMain(float4 inColor : attribute(1)) : SV_Target 0 { |
| return inColor; |
| } |
| `; |
| |
| (function() { |
| |
| WebGLStage = Utilities.createSubclass(Stage, |
| function(element, options) |
| { |
| Stage.call(this); |
| }, |
| { |
| initialize: function(benchmark, options) |
| { |
| Stage.prototype.initialize.call(this, benchmark, options); |
| |
| this._numTriangles = 0; |
| |
| const gpuContext = this.element.getContext('gpu'); |
| |
| navigator.gpu.requestAdapter({ powerPreference: "low-power" }).then(adapter => { |
| return adapter.requestDevice().then(device => { |
| this._device = device; |
| |
| const swapChainFormat = "bgra8unorm"; |
| this._swapChain = gpuContext.configureSwapChain({ |
| device: device, |
| format: swapChainFormat, |
| usage: GPUTextureUsage.OUTPUT_ATTACHMENT |
| }); |
| |
| this._timeBindGroupLayout = device.createBindGroupLayout({ |
| bindings: [ |
| { binding: 0, visibility: GPUShaderStageBit.VERTEX, type: "uniform-buffer" }, |
| ], |
| }); |
| |
| this._bindGroupLayout = device.createBindGroupLayout({ |
| bindings: [ |
| { binding: 0, visibility: GPUShaderStageBit.VERTEX, type: "uniform-buffer" }, |
| ], |
| }); |
| |
| const vec4Size = 4 * Float32Array.BYTES_PER_ELEMENT; |
| |
| const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [this._timeBindGroupLayout, this._bindGroupLayout] }); |
| const shaderModule = device.createShaderModule({ code: whlslShaders, isWHLSL: true }); |
| |
| const pipelineDesc = { |
| layout: pipelineLayout, |
| vertexStage: { |
| module: shaderModule, |
| entryPoint: "vertexMain", |
| }, |
| fragmentStage: { |
| module: shaderModule, |
| entryPoint: "fragmentMain" |
| }, |
| |
| primitiveTopology: "triangle-list", |
| |
| vertexInput: { |
| indexFormat: "uint32", |
| vertexBuffers: [{ |
| // vertex buffer |
| stride: 2 * vec4Size, |
| stepMode: "vertex", |
| attributeSet: [{ |
| // vertex positions |
| shaderLocation: 0, |
| offset: 0, |
| format: "float4" |
| }, { |
| // vertex colors |
| shaderLocation: 1, |
| offset: vec4Size, |
| format: "float4" |
| }], |
| }], |
| }, |
| |
| rasterizationState: { |
| frontFace: 'ccw', |
| cullMode: 'none', |
| }, |
| |
| colorStates: [{ |
| format: swapChainFormat, |
| alphaBlend: {}, |
| colorBlend: {}, |
| }], |
| }; |
| |
| this._pipeline = device.createRenderPipeline(pipelineDesc); |
| |
| const [vertexBuffer, vertexArrayBuffer] = device.createBufferMapped({ |
| size: 2 * 3 * vec4Size, |
| usage: GPUBufferUsage.VERTEX |
| }); |
| const vertexWriteBuffer = new Float32Array(vertexArrayBuffer); |
| vertexWriteBuffer.set([ |
| // position data /**/ color data |
| 0, 0.1, 0, 1, /**/ 1, 0, 0, 1, |
| -0.1, -0.1, 0, 1, /**/ 0, 1, 0, 1, |
| 0.1, -0.1, 0, 1, /**/ 0, 0, 1, 1, |
| ]); |
| vertexBuffer.unmap(); |
| |
| this._vertexBuffer = vertexBuffer; |
| this._timeMappedBuffers = []; |
| |
| this._resetIfNecessary(); |
| |
| benchmark._initPromise.resolve(); |
| }); |
| }); |
| }, |
| |
| _getFunctionSource: function(id) |
| { |
| return document.getElementById(id).text; |
| }, |
| |
| _resetIfNecessary: function() |
| { |
| if (this._bindGroups != undefined && this._numTriangles <= this._bindGroups.length) |
| return; |
| |
| const numTriangles = this._numTriangles; |
| |
| const device = this._device; |
| |
| // Minimum buffer offset alignment is 256 bytes. |
| const uniformBytes = 5 * Float32Array.BYTES_PER_ELEMENT; |
| const alignedUniformBytes = Math.ceil(uniformBytes / 256) * 256; |
| const alignedUniformFloats = alignedUniformBytes / Float32Array.BYTES_PER_ELEMENT; |
| |
| const [uniformBuffer, uniformArrayBuffer] = device.createBufferMapped({ |
| size: numTriangles * alignedUniformBytes + Float32Array.BYTES_PER_ELEMENT, |
| usage: GPUBufferUsage.TRANSFER_DST | GPUBufferUsage.UNIFORM |
| }); |
| const uniformWriteArray = new Float32Array(uniformArrayBuffer); |
| |
| this._bindGroups = new Array(numTriangles); |
| for (let i = 0; i < numTriangles; ++i) { |
| uniformWriteArray[alignedUniformFloats * i + 0] = Stage.random(0.2, 0.4); // scale |
| uniformWriteArray[alignedUniformFloats * i + 1] = Stage.random(-0.9, 0.9); // offsetX |
| uniformWriteArray[alignedUniformFloats * i + 2] = Stage.random(-0.9, 0.9); // offsetY |
| uniformWriteArray[alignedUniformFloats * i + 3] = Stage.random(0.5, 2); // scalar |
| uniformWriteArray[alignedUniformFloats * i + 4] = Stage.random(0, 10); // scalarOffset |
| |
| this._bindGroups[i] = device.createBindGroup({ |
| layout: this._bindGroupLayout, |
| bindings: [{ |
| binding: 0, |
| resource: { |
| buffer: uniformBuffer, |
| offset: i * alignedUniformBytes, |
| size: 6 * Float32Array.BYTES_PER_ELEMENT, |
| } |
| }] |
| }); |
| } |
| |
| uniformBuffer.unmap(); |
| |
| this._timeOffset = numTriangles * alignedUniformBytes; |
| this._timeBindGroup = device.createBindGroup({ |
| layout: this._timeBindGroupLayout, |
| bindings: [{ |
| binding: 0, |
| resource: { |
| buffer: uniformBuffer, |
| offset: this._timeOffset, |
| size: Float32Array.BYTES_PER_ELEMENT, |
| } |
| }] |
| }); |
| |
| this._uniformBuffer = uniformBuffer; |
| }, |
| |
| tune: function(count) |
| { |
| if (!count) |
| return; |
| |
| this._numTriangles += count; |
| this._numTriangles = Math.max(this._numTriangles, 0); |
| |
| this._resetIfNecessary(); |
| }, |
| |
| animate: function(timeDelta) |
| { |
| const device = this._device; |
| |
| if (!this._startTime) |
| this._startTime = Stage.dateCounterValue(1000); |
| |
| const elapsedTimeData = new Float32Array([Stage.dateCounterValue(1000) - this._startTime]); |
| |
| // Update time uniform |
| let mappedBuffer; |
| |
| if (this._timeMappedBuffers.length === 0) { |
| mappedBuffer = device.createBufferMapped({ |
| size: Float32Array.BYTES_PER_ELEMENT, |
| usage: GPUBufferUsage.TRANSFER_SRC | GPUBufferUsage.MAP_WRITE |
| }); |
| } else |
| mappedBuffer = this._timeMappedBuffers.shift(); |
| |
| const [timeStagingBuffer, timeStagingArrayBuffer] = mappedBuffer; |
| |
| const writeArray = new Float32Array(timeStagingArrayBuffer); |
| writeArray.set(elapsedTimeData); |
| timeStagingBuffer.unmap(); |
| |
| const commandEncoder = device.createCommandEncoder({}); |
| commandEncoder.copyBufferToBuffer(timeStagingBuffer, 0, this._uniformBuffer, this._timeOffset, elapsedTimeData.byteLength); |
| |
| const renderPassDescriptor = { |
| colorAttachments: [{ |
| loadOp: "clear", |
| storeOp: "store", |
| clearColor: { r: 1, g: 1, b: 1, a: 1.0 }, |
| attachment: this._swapChain.getCurrentTexture().createDefaultView(), |
| }], |
| }; |
| |
| const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); |
| passEncoder.setPipeline(this._pipeline); |
| passEncoder.setVertexBuffers(0, [this._vertexBuffer], [0]); |
| passEncoder.setBindGroup(0, this._timeBindGroup); |
| for (let i = 0; i < this._numTriangles; ++i) { |
| passEncoder.setBindGroup(1, this._bindGroups[i]); |
| passEncoder.draw(3, 1, 0, 0); |
| } |
| passEncoder.endPass(); |
| |
| device.getQueue().submit([commandEncoder.finish()]); |
| |
| timeStagingBuffer.mapWriteAsync().then(arrayBuffer => { |
| mappedBuffer[1] = arrayBuffer; |
| this._timeMappedBuffers.push(mappedBuffer); |
| }); |
| }, |
| |
| complexity: function() |
| { |
| return this._numTriangles; |
| } |
| } |
| ); |
| |
| WebGLBenchmark = Utilities.createSubclass(Benchmark, |
| function(options) |
| { |
| Benchmark.call(this, new WebGLStage(), options); |
| }, { |
| |
| waitUntilReady: function() { |
| this._initPromise = new SimplePromise; |
| return this._initPromise; |
| }, |
| }); |
| |
| window.benchmarkClass = WebGLBenchmark; |
| |
| })(); |