chromium/third_party/blink/perf_tests/MotionMark/tests/3d/resources/webgpu.js

/*
 * 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;

})();