Title: [246628] trunk
Revision
246628
Author
justin_...@apple.com
Date
2019-06-19 20:18:37 -0700 (Wed, 19 Jun 2019)

Log Message

[WHLSL] Create a shading language test harness
https://bugs.webkit.org/show_bug.cgi?id=198978

Reviewed by Myles C. Maxfield.

Source/WebCore:

When creating MTLArgumentEncoders for argument buffers, the user's arguments
must match the order that they are declared in the shader. Move back-end information
such as buffer lengths to the end of the argument arrays.

Test: webgpu/whlsl-harness-test.html

* Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.cpp:
(WebCore::WHLSL::Metal::EntryPointScaffolding::resourceHelperTypes):
* platform/graphics/gpu/cocoa/GPUBindGroupLayoutMetal.mm:
(WebCore::GPUBindGroupLayout::tryCreate):

LayoutTests:

Introduce a test harness that can be used to test WebGPU shader compilation and functionality.
Currently using MSL.
Will be replaced with WHLSL as it gains the minimum features needed to support.

* webgpu/js/whlsl-test-harness.js: Added.
(isVectorType):
(convertTypeToArrayType):
(convertTypeToWHLSLType):
(Data):
(Data.prototype.async.getArrayBuffer):
(Data.prototype.get type):
(Data.prototype.get isPointer):
(Data.prototype.get buffer):
(Data.prototype.get byteLength):
(Harness.prototype._initialize):
(Harness.prototype.async.callTypedFunction):
(Harness.prototype.async.callVoidFunction):
(Harness.prototype._setUpArguments):
(Harness.prototype._callFunction):
(Harness):
(harness._initialize.async):
(makeBool):
(makeInt):
(makeUchar):
(makeUint):
(makeFloat):
(makeFloat4):
(async.callBoolFunction):
(async.callIntFunction):
(async.callUcharFunction):
(async.callUintFunction):
(async.callFloatFunction):
(async.callFloat4Function):
(callVoidFunction):
* webgpu/whlsl-harness-test-expected.txt: Added.
* webgpu/whlsl-harness-test.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (246627 => 246628)


--- trunk/LayoutTests/ChangeLog	2019-06-20 02:18:21 UTC (rev 246627)
+++ trunk/LayoutTests/ChangeLog	2019-06-20 03:18:37 UTC (rev 246628)
@@ -1,3 +1,47 @@
+2019-06-19  Justin Fan  <justin_...@apple.com>
+
+        [WHLSL] Create a shading language test harness
+        https://bugs.webkit.org/show_bug.cgi?id=198978
+
+        Reviewed by Myles C. Maxfield.
+
+        Introduce a test harness that can be used to test WebGPU shader compilation and functionality.
+        Currently using MSL. 
+        Will be replaced with WHLSL as it gains the minimum features needed to support.
+
+        * webgpu/js/whlsl-test-harness.js: Added.
+        (isVectorType):
+        (convertTypeToArrayType):
+        (convertTypeToWHLSLType):
+        (Data):
+        (Data.prototype.async.getArrayBuffer):
+        (Data.prototype.get type):
+        (Data.prototype.get isPointer):
+        (Data.prototype.get buffer):
+        (Data.prototype.get byteLength):
+        (Harness.prototype._initialize):
+        (Harness.prototype.async.callTypedFunction):
+        (Harness.prototype.async.callVoidFunction):
+        (Harness.prototype._setUpArguments):
+        (Harness.prototype._callFunction):
+        (Harness):
+        (harness._initialize.async):
+        (makeBool):
+        (makeInt):
+        (makeUchar):
+        (makeUint):
+        (makeFloat):
+        (makeFloat4):
+        (async.callBoolFunction):
+        (async.callIntFunction):
+        (async.callUcharFunction):
+        (async.callUintFunction):
+        (async.callFloatFunction):
+        (async.callFloat4Function):
+        (callVoidFunction):
+        * webgpu/whlsl-harness-test-expected.txt: Added.
+        * webgpu/whlsl-harness-test.html: Added.
+
 2019-06-19  Saam Barati  <sbar...@apple.com>
 
         [WHLSL] The checker needs to resolve types for the anonymous variables in ReadModifyWrite expressions

Added: trunk/LayoutTests/webgpu/js/whlsl-test-harness.js (0 => 246628)


--- trunk/LayoutTests/webgpu/js/whlsl-test-harness.js	                        (rev 0)
+++ trunk/LayoutTests/webgpu/js/whlsl-test-harness.js	2019-06-20 03:18:37 UTC (rev 246628)
@@ -0,0 +1,386 @@
+/* Type Utilties */
+
+// FIXME: Support all WHLSL scalar and vector types.
+// FIXME: Support textures and samplers.
+const Types = Object.freeze({
+    BOOL: Symbol("bool"),
+    INT: Symbol("int"),
+    UCHAR: Symbol("uchar"),
+    UINT: Symbol("uint"),
+    FLOAT: Symbol("float"),
+    FLOAT4: Symbol("float4"),
+    MAX_SIZE: 16 // This needs to be big enough to hold any singular WHLSL type.
+});
+
+function isVectorType(type)
+{
+    switch(type) {
+        case Types.FLOAT4:
+            return true;
+        default: 
+            return false;
+    }
+}
+
+function convertTypeToArrayType(type)
+{
+    switch(type) {
+        case Types.BOOL:
+            return Uint8Array;
+        case Types.INT:
+            return Int32Array;
+        case Types.UCHAR:
+            return Uint8Array;
+        case Types.UINT:
+            return Uint32Array;
+        case Types.FLOAT:
+        case Types.FLOAT4:
+            return Float32Array;
+        default:
+            throw new Error("Invalid TYPE provided!");
+    }
+}
+
+function convertTypeToWHLSLType(type)
+{
+    switch(type) {
+        case Types.BOOL:
+            return "bool";
+        case Types.INT:
+            return "int";
+        case Types.UCHAR:
+            return "uchar";
+        case Types.UINT:
+            return "uint";
+        case Types.FLOAT:
+            return "float";
+        case Types.FLOAT4:
+            return "float4";
+        default:
+            throw new Error("Invalid TYPE provided!");
+    }
+}
+
+/* Harness Classes */
+
+class Data {
+    /**
+     * Upload typed data to and return a wrapper of a GPUBuffer.
+     * @param {Types} type - The WHLSL type to be stored in this Data.
+     * @param {Number or Array[Number]} values - The raw data to be uploaded.
+     */
+    constructor(harness, type, values, isPointer = false)
+    {
+        // One or more scalars in an array can be accessed through a pointer to buffer.
+        // However, vector types are also created via an array of scalars.
+        // This ensures that buffers of just one vector are usable in a test function.
+        if (Array.isArray(values))
+            this._isPointer = isVectorType(type) ? isPointer : true;
+        else {
+            this._isPointer = false;
+            values = [values];
+        }
+
+        this._type = type;
+        this._byteLength = (convertTypeToArrayType(type)).BYTES_PER_ELEMENT * values.length;
+
+        const [buffer, arrayBuffer] = harness._device.createBufferMapped({
+            size: this._byteLength,
+            usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ
+        });
+
+        const typedArray = new (convertTypeToArrayType(type))(arrayBuffer);
+        typedArray.set(values);
+        buffer.unmap();
+
+        this._buffer = buffer;
+    }
+
+    /**
+     * @returns An ArrayBuffer containing the contents of this Data.
+     */
+    async getArrayBuffer()
+    {
+        let result;
+        try {
+            result = await this._buffer.mapReadAsync();
+            this._buffer.unmap();
+        } catch {
+            throw new Error("Data error: Unable to get ArrayBuffer!");
+        }
+        return result;
+    }
+
+    get type() { return this._type; }
+    get isPointer() { return this._isPointer; }
+    get buffer() { return this._buffer; }
+    get byteLength() { return this._byteLength; }
+}
+
+class Harness {
+    constructor()
+    {
+        this._shaderHeader = `#include <metal_stdlib>
+        using namespace metal;
+        `;
+    }
+
+    _initialize(callback)
+    {
+        callback.bind(this)();
+    }
+
+    /**
+     * Return the return value of a WHLSL function.
+     * @param {Types} type - The return type of the WHLSL function.
+     * @param {String} functions - Custom WHLSL code to be tested.
+     * @param {String} name - The name of the WHLSL function which must be present in 'functions'.
+     * @param {Data or Array[Data]} args - Data arguments to be passed to the call of 'name'.
+     * @returns {TypedArray} - A typed array containing the return value of the function call.
+     */
+    async callTypedFunction(type, functions, name, args)
+    {   
+        const [argsLayouts, argsResourceBindings, argsStructCode, functionCallArgs] = this._setUpArguments(args);
+
+        if (!this._resultBuffer) {
+            this._resultBuffer = this._device.createBuffer({ 
+                size: Types.MAX_SIZE, 
+                usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ 
+            });
+        }
+
+        argsLayouts.unshift({
+            binding: 0,
+            visibility: GPUShaderStageBit.COMPUTE,
+            type: "storage-buffer"
+        });
+        argsResourceBindings.unshift({
+            binding: 0,
+            resource: {
+                buffer: this._resultBuffer,
+                size: Types.MAX_SIZE
+            }
+        });
+
+        const code = this._shaderHeader + functions + `
+        struct _compute_args {
+            device ${convertTypeToWHLSLType(type)}* result [[id(0)]];
+            ${argsStructCode}};
+    
+        kernel void _compute_main(device _compute_args& args [[buffer(0)]]) 
+        {
+            *args.result = ${name}(${functionCallArgs.join(", ")});
+        }
+        `;
+    
+        this._callFunction(code, argsLayouts, argsResourceBindings);
+    
+        try {
+            var result = await this._resultBuffer.mapReadAsync();
+        } catch {
+            throw new Error("Harness error: Unable to read results!");
+        }
+        const array = new (convertTypeToArrayType(type))(result);
+        this._resultBuffer.unmap();
+
+        return array;
+    }
+
+    /**
+     * Call a WHLSL function to modify the value of argument(buffer)s.
+     * @param {String} functions - Custom WHLSL code to be tested.
+     * @param {String} name - The name of the WHLSL function which must be present in 'functions'.
+     * @param {Data or Array[Data]} args - Data arguments to be passed to the call of 'name'.
+     */
+    async callVoidFunction(functions, name, args)
+    {
+        const [argsLayouts, argsResourceBindings, argsStructCode, functionCallArgs] = this._setUpArguments(args);
+
+        const code = this._shaderHeader + functions + `
+        struct _compute_args {
+            ${argsStructCode}};
+
+        kernel void _compute_main(device _compute_args& args [[buffer(0)]])
+        {
+            ${name}(${functionCallArgs.join(", ")});
+        }
+        `;
+
+        this._callFunction(code, argsLayouts, argsResourceBindings);
+    }
+
+    _setUpArguments(args)
+    {
+        if (!Array.isArray(args)) {
+            if (args instanceof Data)
+                args = [args];
+            else if (!args)
+                args = [];
+        }
+
+        // FIXME: Replace with WHLSL.
+        // Expand bind group structure to represent any arguments.
+        let argsStructCode = "";
+        let functionCallArgs = [];
+        let argsLayouts = [];
+        let argsResourceBindings = [];
+
+        for (let i = 1; i <= args.length; ++i) {
+            const arg = args[i - 1];
+            argsStructCode += `device ${convertTypeToWHLSLType(arg.type)}* arg${i} [[id(${i})]];
+            `;
+            const optionalDeref = (!arg.isPointer) ? "*" : "";
+            functionCallArgs.push(optionalDeref + `args.arg${i}`);
+            argsLayouts.push({
+                binding: i,
+                visibility: GPUShaderStageBit.COMPUTE,
+                type: "storage-buffer"
+            });
+            argsResourceBindings.push({
+                binding: i,
+                resource: {
+                    buffer: arg.buffer,
+                    size: arg.byteLength
+                }
+            });
+        }
+
+        return [argsLayouts, argsResourceBindings, argsStructCode, functionCallArgs];
+    }
+
+    _callFunction(code, argsLayouts, argsResourceBindings)
+    {
+        const shaders = this._device.createShaderModule({ code: code });
+        // FIXME: Compile errors should be caught and reported here.
+        const pipeline = this._device.createComputePipeline({
+            computeStage: {
+                module: shaders,
+                entryPoint: "_compute_main"
+            }
+        });
+
+        const layout = this._device.createBindGroupLayout({
+            bindings: argsLayouts
+        });
+    
+        const bindGroup = this._device.createBindGroup({
+            layout: layout,
+            bindings: argsResourceBindings
+        });
+        
+        const commandEncoder = this._device.createCommandEncoder();
+        const passEncoder = commandEncoder.beginComputePass();
+        passEncoder.setBindGroup(0, bindGroup);
+        passEncoder.setPipeline(pipeline);
+        passEncoder.dispatch(1, 1, 1);
+        passEncoder.endPass();
+        
+        this._device.getQueue().submit([commandEncoder.finish()]);
+    }
+}
+
+/* Harness Setup */
+
+const harness = new Harness();
+harness._initialize(async () => {
+    try {
+        const adapter = await navigator.gpu.requestAdapter();
+        harness._device = await adapter.requestDevice();
+    } catch (e) {
+        throw new Error("Harness error: Unable to acquire GPUDevice!");
+    }
+});
+
+/* Global Helper Functions */
+
+/**
+ * The make___ functions are wrappers around the Data constructor.
+ * Values passed in as an array will be passed in via a device-addressed pointer type in the shader.
+ * @param {Boolean, Number, or Array} values - The data to be stored on the GPU.
+ * @returns A new Data object with storage allocated to store values.
+ */
+function makeBool(values)
+{
+    return new Data(harness, Types.BOOL, values);
+}
+
+function makeInt(values)
+{
+    return new Data(harness, Types.INT, values);
+}
+
+function makeUchar(values)
+{
+    return new Data(harness, Types.UCHAR, values);
+}
+
+function makeUint(values)
+{
+    return new Data(harness, Types.UINT, values);
+}
+
+function makeFloat(values)
+{
+    return new Data(harness, Types.FLOAT, values);
+}
+
+/**
+ * @param {Array or Array[Array]} values - 1D or 2D array of float values.
+ * The total number of float values must be divisible by 4.
+ * A single 4-element array of floats will be treated as a single float4 argument in the shader.
+ */
+function makeFloat4(values)
+{
+    const originalLength = values.length;
+    // This works because float4 is tightly packed.
+    // When implementing other vector types, add padding if needed.
+    values = values.flat();
+    if (values.length % 4 != 0)
+        throw new Error("makeFloat4: Invalid number of elements!");
+    return new Data(harness, Types.FLOAT4, values, originalLength === 1 || values.length > 4);
+}
+
+/**
+ * @param {String} functions - Shader source code that must contain a definition for 'name'.
+ * @param {String} name - The function to be called from 'functions'.
+ * @param {Data or Array[Data]} args - The arguments to be passed to the call of 'name'.
+ * @returns A Promise that resolves to the return value of a call to 'name' with 'args'.
+ */
+async function callBoolFunction(functions, name, args)
+{
+    return !!(await harness.callTypedFunction(Types.BOOL, functions, name, args))[0];
+}
+
+async function callIntFunction(functions, name, args)
+{
+    return (await harness.callTypedFunction(Types.INT, functions, name, args))[0];
+}
+
+async function callUcharFunction(functions, name, args)
+{
+    return (await harness.callTypedFunction(Types.UCHAR, functions, name, args))[0];
+}
+
+async function callUintFunction(functions, name, args)
+{
+    return (await harness.callTypedFunction(Types.UINT, functions, name, args))[0];
+}
+
+async function callFloatFunction(functions, name, args)
+{
+    return (await harness.callTypedFunction(Types.FLOAT, functions, name, args))[0];
+}
+
+async function callFloat4Function(functions, name, args)
+{
+    return (await harness.callTypedFunction(Types.FLOAT4, functions, name, args)).subarray(0, 4);
+}
+
+/**
+ * Does not return a Promise. To observe the results of a call, 
+ * call 'getArrayBuffer' on the Data object retaining your output buffer.
+ */
+function callVoidFunction(functions, name, args)
+{
+    harness.callVoidFunction(functions, name, args);
+}
\ No newline at end of file

Added: trunk/LayoutTests/webgpu/whlsl-harness-test-expected.txt (0 => 246628)


--- trunk/LayoutTests/webgpu/whlsl-harness-test-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/webgpu/whlsl-harness-test-expected.txt	2019-06-20 03:18:37 UTC (rev 246628)
@@ -0,0 +1,35 @@
+
+PASS Return a literal of type bool. 
+PASS Return an expected float4 value. 
+PASS Return an expected int value. 
+PASS Return an expected uchar value. 
+PASS Return an expected uint value. 
+PASS Return an expected float value. 
+PASS Upload and return a bool value. 
+PASS Return an expected float4 value. 
+PASS Return an expected int value. 
+PASS Return an expected uchar value. 
+PASS Return an expected uint value. 
+PASS Return an expected float value. 
+PASS Upload many bool values and return a calculated result. 
+PASS Return an expected float4 value. 
+PASS Return an expected int value. 
+PASS Return an expected uchar value. 
+PASS Return an expected uint value. 
+PASS Return an expected float value. 
+PASS Access and return a single bool through a bool*. 
+PASS Return an expected float4 value. 
+PASS Return an expected int value. 
+PASS Return an expected uchar value. 
+PASS Return an expected uint value. 
+PASS Return an expected float value. 
+PASS Access multiple bools through various buffers and return a bool. 
+PASS Return an expected float4 value. 
+PASS Return an expected int value. 
+PASS Return an expected uchar value. 
+PASS Return an expected uint value. 
+PASS Return an expected float value. 
+PASS Upload and calculate a result from varied argument types. 
+PASS Store into a float4*. 
+PASS Upload a uchar* and store into a uchar*. 
+

Added: trunk/LayoutTests/webgpu/whlsl-harness-test.html (0 => 246628)


--- trunk/LayoutTests/webgpu/whlsl-harness-test.html	                        (rev 0)
+++ trunk/LayoutTests/webgpu/whlsl-harness-test.html	2019-06-20 03:18:37 UTC (rev 246628)
@@ -0,0 +1,208 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:WebGPUEnabled=true ] -->
+<html>
+<meta charset=utf-8>
+<title>Test the WHLSL test harness.</title>
+<script src=""
+<script src=""
+<script src=""
+<script>
+const epsilon = 0.0001;
+
+const numericScalarTypes = ["int", "uchar", "uint", "float"];
+
+const numericScalarFuncs = {
+    "int": callIntFunction,
+    "uchar": callUcharFunction,
+    "uint": callUintFunction,
+    "float": callFloatFunction
+};
+
+const scalarArgMakers = {
+    "int": makeInt,
+    "uchar": makeUchar,
+    "uint": makeUint,
+    "float": makeFloat
+};
+
+let tests = {};
+
+tests.literals = () => {
+    checkBools("Return a literal of type bool.", "return true;");
+    checkFloat4s("return float4(0, 1, 2, 3);");
+    checkNumericScalars("return 42;", [], 42);
+};
+
+tests.singleArgument = () => {
+    checkBools("Upload and return a bool value.", "return in0;", [true]);
+    checkFloat4s("return in0.wzyx;", [[3, 2, 1, 0]]);
+    checkNumericScalars("return in0;", [42], 42);
+};
+
+tests.manyArguments = () => {
+    checkBools("Upload many bool values and return a calculated result.",
+        "return in0 & in1 & in2 & in3 & in4 & in5 & in6 & in7;",  
+        [true, true, true, true, true, true, true, true]);
+
+    const body = `return in0 + in1 + in2 + in3 + in4 + in5 + in6 + in7;`;
+    let args = [];
+    for (let i = 0; i < 8; ++i)
+        args.push([0, 1, 2, 3]);
+    checkFloat4s(body, args, [0, 8, 16, 24]);
+    checkNumericScalars(body, [0, 1, 2, 3, 4, 5, 6, 7], 28);
+};
+
+tests.buffersWithOneValue = () => {
+    const body = `return in0[0];`
+    checkBools("Access and return a single bool through a bool*.", body, [[true]]);
+    checkFloat4s(body, [[[0, 1, 2, 3]]]);
+    checkNumericScalars(body, [[42]], 42);
+};
+
+tests.multipleBufferArguments = () => {
+    checkBools("Access multiple bools through various buffers and return a bool.", 
+        "return in0[0] & in0[1] & in0[2] & in1 & in2[0];", 
+        [[true, true, true], true, [true]]);
+    
+    const body = `return in0[0] + in0[1] + in0[2] + in1 + in2[0];`;
+    const vector = [0, 1, 2, 3];
+    checkFloat4s(body, [[vector, vector, vector], vector, [vector]], [0, 5, 10, 15]);
+    checkNumericScalars(body, [[0, 1, 2], 3, [4]], 10);
+};
+
+tests.multipleArgumentTypes = () => {
+    const src = "" test(int i, uchar c, device uint* u, bool b, device bool* bs, float4 f4, device float* fs)
+    {
+        if (b && bs[0] && bs[1])
+            return i + c + u[0] + f4.x + f4.y + f4.z + f4.w + fs[0] + fs[1];
+        
+        return 0;
+    }`;
+    const i = makeInt(1);
+    const c = makeUchar(2);
+    const u = makeUint([3]);
+    const b = makeBool(true);
+    const bs = makeBool([true, true]);
+    const f4 = makeFloat4([4, 5, 6, 7]);
+    const fs = makeFloat([8, 9]);
+    promise_test(() => {
+        return callFloatFunction(src, "test", [i, c, u, b, bs, f4, fs]).then(result => {
+            assert_approx_equals(result, 45, epsilon, "Test returned expected value.");
+        });
+    }, "Upload and calculate a result from varied argument types.");
+};
+
+tests.bufferStores = () => {
+    let src = "" test(device float4* out) {
+        *out = float4(0, 1, 2, 3);
+    }`;
+    const float4Out = makeFloat4([[0, 0, 0, 0]]);
+    callVoidFunction(src, "test", float4Out);
+
+    promise_test(() => {
+        return float4Out.getArrayBuffer().then(arrayBuffer => {
+            const result = new Float32Array(arrayBuffer);
+            for (let i; i < 4; ++i) {
+                assert_approx_equals(result[i], i, "Test stored expected values.");
+            }
+        });
+    }, "Store into a float4*.");
+
+    src = "" test(device uchar* in, device uchar* out) {
+        for (uint i = 0; i < 5; ++i)
+            out[i] = in[i];
+    }`;
+    const array = [0, 1, 2, 3, 4];
+    const input = makeUchar(array);
+    const output = makeUchar([0, 0, 0, 0, 0]);
+    callVoidFunction(src, "test", [input, output]);
+
+    promise_test(() => {
+        return output.getArrayBuffer().then(arrayBuffer => {
+            const result = new Uint8Array(arrayBuffer);
+            assert_array_equals(array, result, "Test stored expected values.");
+        });
+    }, "Upload a uchar* and store into a uchar*.");
+};
+
+window.addEventListener("load", () => {
+    for (const name in tests) {
+        tests[name]();
+    }
+});
+
+/* Helper functions */
+
+const checkNumericScalars = (body, argValues, expected) => {
+    let functions = [];
+    let src = ""
+    for (let type of numericScalarTypes) {
+        const name = `${type}Test`;
+
+        let inputArgs = [];
+        let values = [];
+        for (let i = 0; i < argValues.length; ++i) {
+            const isPointer = Array.isArray(argValues[i]);
+            inputArgs.push(`${isPointer ? "device " : ""}${type}${isPointer ? "*" : ""} in${i}`);
+            values.push(scalarArgMakers[type](argValues[i]));
+        }
+        
+        src += `${type} ${name}(${inputArgs.join(", ")}) { ${body} }
+        `;
+        functions.push({ type: type, name: name, args: values, expected: expected });
+    }
+
+    for (const f of functions) {
+        const callFunc = numericScalarFuncs[f.type];
+        promise_test(async () => {
+            return callFunc(src, f.name, f.args).then(result => {
+                assert_approx_equals(result, f.expected, epsilon, "Test returned expected value.");
+            });
+        }, `Return an expected ${f.type} value.`);
+    }
+};
+
+const checkBools = (msg = "Return an expected bool value.", body, argValues = [], expected = true) => {
+    let src = ""
+    let inputArgs = [];
+    let values = [];
+    for (let i = 0; i < argValues.length; ++i) {
+        const isPointer = Array.isArray(argValues[i]);
+        inputArgs.push(`${isPointer ? "device " : ""}bool${isPointer ? "*" : ""} in${i}`);
+        values.push(makeBool(argValues[i]));
+    }
+
+    src += `bool boolTest(${inputArgs.join(", ")}) { ${body} }
+    `;
+
+    promise_test(async () => {
+        return callBoolFunction(src, "boolTest", values).then(result => {
+            assert_equals(result, expected, "Test returned expected value.");
+        });
+    }, msg);
+};
+
+const checkFloat4s = (body, argValues = [], expected = [0, 1, 2, 3]) => {
+    let src = ""
+    let inputArgs = [];
+    let values = [];
+
+    for (let i = 0; i < argValues.length; ++i) {
+        // Support arrays of float4, including one with a single float4.
+        const totalLength = argValues[i].flat().length;
+        const isPointer = argValues[i].length === 1 || totalLength > 4;
+        inputArgs.push(`${isPointer ? "device " : ""}float4${isPointer ? "*" : ""} in${i}`);
+        values.push(makeFloat4(argValues[i]));
+    }
+
+    src += `float4 float4Test(${inputArgs.join(", ")}) { ${body} }
+    `;
+
+    promise_test(async () => {
+        return callFloat4Function(src, "float4Test", values).then(result => {
+            for (let i = 0; i < 4; ++i)
+                assert_approx_equals(result[i], expected[i], epsilon, "Test returned expected value.");
+        });
+    }, "Return an expected float4 value.");
+}
+</script>
+</html>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (246627 => 246628)


--- trunk/Source/WebCore/ChangeLog	2019-06-20 02:18:21 UTC (rev 246627)
+++ trunk/Source/WebCore/ChangeLog	2019-06-20 03:18:37 UTC (rev 246628)
@@ -1,3 +1,21 @@
+2019-06-19  Justin Fan  <justin_...@apple.com>
+
+        [WHLSL] Create a shading language test harness
+        https://bugs.webkit.org/show_bug.cgi?id=198978
+
+        Reviewed by Myles C. Maxfield.
+
+        When creating MTLArgumentEncoders for argument buffers, the user's arguments
+        must match the order that they are declared in the shader. Move back-end information
+        such as buffer lengths to the end of the argument arrays.
+
+        Test: webgpu/whlsl-harness-test.html
+
+        * Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.cpp:
+        (WebCore::WHLSL::Metal::EntryPointScaffolding::resourceHelperTypes):
+        * platform/graphics/gpu/cocoa/GPUBindGroupLayoutMetal.mm:
+        (WebCore::GPUBindGroupLayout::tryCreate):
+
 2019-06-19  Saam Barati  <sbar...@apple.com>
 
         [WHLSL] The checker needs to resolve types for the anonymous variables in ReadModifyWrite expressions

Modified: trunk/Source/WebCore/Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.cpp (246627 => 246628)


--- trunk/Source/WebCore/Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.cpp	2019-06-20 02:18:21 UTC (rev 246627)
+++ trunk/Source/WebCore/Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.cpp	2019-06-20 03:18:37 UTC (rev 246628)
@@ -37,6 +37,7 @@
 #include "WHLSLStageInOutSemantic.h"
 #include "WHLSLStructureDefinition.h"
 #include "WHLSLTypeNamer.h"
+#include <algorithm>
 #include <wtf/Optional.h>
 #include <wtf/text/StringBuilder.h>
 #include <wtf/text/StringConcatenateNumbers.h>
@@ -143,6 +144,7 @@
     StringBuilder stringBuilder;
     for (size_t i = 0; i < m_layout.size(); ++i) {
         stringBuilder.append(makeString("struct ", m_namedBindGroups[i].structName, " {\n"));
+        Vector<std::pair<unsigned, String>> structItems;
         for (size_t j = 0; j < m_layout[i].bindings.size(); ++j) {
             auto iterator = m_resourceMap.find(&m_layout[i].bindings[j]);
             if (iterator == m_resourceMap.end())
@@ -153,10 +155,15 @@
             auto addressSpace = toString(referenceType.addressSpace());
             auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
             auto index = m_namedBindGroups[i].namedBindings[j].index;
-            stringBuilder.append(makeString("    ", addressSpace, " ", mangledTypeName, "* ", elementName, " [[id(", index, ")]];\n"));
+            structItems.append(std::make_pair(index, makeString("    ", addressSpace, " ", mangledTypeName, "* ", elementName, " [[id(", index, ")]];\n")));
             if (auto lengthInformation = m_namedBindGroups[i].namedBindings[j].lengthInformation)
-                stringBuilder.append(makeString("    uint2 ", lengthInformation->elementName, " [[id(", lengthInformation->index, ")]];\n"));
+                structItems.append(std::make_pair(lengthInformation->index, makeString("uint2 ", lengthInformation->elementName, " [[id(", lengthInformation->index, ")]];")));
         }
+        std::sort(structItems.begin(), structItems.end(), [](const std::pair<unsigned, String>& left, const std::pair<unsigned, String>& right) {
+            return left.first < right.first;
+        });
+        for (const auto& structItem : structItems)
+            stringBuilder.append(makeString("    ", structItem.second, '\n'));
         stringBuilder.append("};\n\n");
     }
     return stringBuilder.toString();

Modified: trunk/Source/WebCore/platform/graphics/gpu/cocoa/GPUBindGroupLayoutMetal.mm (246627 => 246628)


--- trunk/Source/WebCore/platform/graphics/gpu/cocoa/GPUBindGroupLayoutMetal.mm	2019-06-20 02:18:21 UTC (rev 246627)
+++ trunk/Source/WebCore/platform/graphics/gpu/cocoa/GPUBindGroupLayoutMetal.mm	2019-06-20 03:18:37 UTC (rev 246628)
@@ -96,7 +96,7 @@
         return nullptr;
     }
 
-    ArgumentArray vertexArgsArray, fragmentArgsArray, computeArgsArray;
+    ArgumentArray vertexArgs, fragmentArgs, computeArgs, vertexLengths, fragmentLengths, computeLengths;
     BindingsMapType bindingsMap;
 
     unsigned internalName = 0;
@@ -137,8 +137,8 @@
             return nullptr;
         }
 
-        auto addIndices = [&](ArgumentArray& array) -> bool {
-            appendArgumentToArray(array, mtlArgument);
+        auto addIndices = [&](ArgumentArray& args, ArgumentArray& lengths) -> bool {
+            appendArgumentToArray(args, mtlArgument);
             if (extraIndex) {
                 RetainPtr<MTLArgumentDescriptor> mtlArgument = argumentDescriptor(MTLDataTypeUInt2, *extraIndex);
                 if (!mtlArgument) {
@@ -145,25 +145,31 @@
                     LOG(WebGPU, "GPUBindGroupLayout::tryCreate(): Unable to create MTLArgumentDescriptor for binding %u!", binding.binding);
                     return false;
                 }
-                appendArgumentToArray(array, mtlArgument);
+                appendArgumentToArray(lengths, mtlArgument);
             }
             return true;
         };
-        if ((binding.visibility & GPUShaderStageBit::Flags::Vertex) && !addIndices(vertexArgsArray))
+        if ((binding.visibility & GPUShaderStageBit::Flags::Vertex) && !addIndices(vertexArgs, vertexLengths))
             return nullptr;
-        if ((binding.visibility & GPUShaderStageBit::Flags::Fragment) && !addIndices(fragmentArgsArray))
+        if ((binding.visibility & GPUShaderStageBit::Flags::Fragment) && !addIndices(fragmentArgs, fragmentLengths))
             return nullptr;
-        if ((binding.visibility & GPUShaderStageBit::Flags::Compute) && !addIndices(computeArgsArray))
+        if ((binding.visibility & GPUShaderStageBit::Flags::Compute) && !addIndices(computeArgs, computeLengths))
             return nullptr;
     }
 
+    BEGIN_BLOCK_OBJC_EXCEPTIONS;
+    [vertexArgs addObjectsFromArray:vertexLengths.get()];
+    [fragmentArgs addObjectsFromArray:fragmentLengths.get()];
+    [computeArgs addObjectsFromArray:computeLengths.get()];
+    END_BLOCK_OBJC_EXCEPTIONS;
+
     RetainPtr<MTLArgumentEncoder> vertex, fragment, compute;
 
-    if (vertexArgsArray && !(vertex = tryCreateMtlArgumentEncoder(device, vertexArgsArray)))
+    if (vertexArgs && !(vertex = tryCreateMtlArgumentEncoder(device, vertexArgs)))
         return nullptr;
-    if (fragmentArgsArray && !(fragment = tryCreateMtlArgumentEncoder(device, fragmentArgsArray)))
+    if (fragmentArgs && !(fragment = tryCreateMtlArgumentEncoder(device, fragmentArgs)))
         return nullptr;
-    if (computeArgsArray && !(compute = tryCreateMtlArgumentEncoder(device, computeArgsArray)))
+    if (computeArgs && !(compute = tryCreateMtlArgumentEncoder(device, computeArgs)))
         return nullptr;
 
     return adoptRef(new GPUBindGroupLayout(WTFMove(bindingsMap), WTFMove(vertex), WTFMove(fragment), WTFMove(compute)));
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to