diff --git a/BUILD.gn b/BUILD.gn index 490cc949c03fc..470c8c2d7a126 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -104,6 +104,7 @@ group("flutter") { public_deps += [ "//flutter/flow:flow_unittests", "//flutter/fml:fml_unittests", + "//flutter/lib/spirv/test/exception_shaders:spirv_compile_exception_shaders", "//flutter/lib/ui:ui_unittests", "//flutter/runtime:no_dart_plugin_registrant_unittests", "//flutter/runtime:runtime_unittests", diff --git a/ci/analyze.sh b/ci/analyze.sh index 83cbaf4cc9e49..f04610f07ff44 100755 --- a/ci/analyze.sh +++ b/ci/analyze.sh @@ -64,6 +64,12 @@ analyze \ --options "$FLUTTER_DIR/analysis_options.yaml" \ "$SRC_DIR/out/host_debug_unopt/gen/sky/bindings/dart_ui/ui.dart" +echo "Analyzing spirv library..." +analyze \ + --packages="$FLUTTER_DIR/lib/spirv/.dart_tool/package_config.json" \ + --options "$FLUTTER_DIR/analysis_options.yaml" \ + "$FLUTTER_DIR/lib/spirv" + echo "Analyzing ci/" analyze \ --packages="$FLUTTER_DIR/ci/.dart_tool/package_config.json" \ diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 6f6add5324db0..ce45fcf252124 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -302,6 +302,10 @@ FILE: ../../../flutter/lib/io/dart_io.cc FILE: ../../../flutter/lib/io/dart_io.h FILE: ../../../flutter/lib/snapshot/libraries.json FILE: ../../../flutter/lib/snapshot/snapshot.h +FILE: ../../../flutter/lib/spirv/lib/spirv.dart +FILE: ../../../flutter/lib/spirv/lib/src/constants.dart +FILE: ../../../flutter/lib/spirv/lib/src/transpiler.dart +FILE: ../../../flutter/lib/spirv/lib/src/types.dart FILE: ../../../flutter/lib/ui/annotations.dart FILE: ../../../flutter/lib/ui/channel_buffers.dart FILE: ../../../flutter/lib/ui/compositing.dart diff --git a/lib/spirv/README.md b/lib/spirv/README.md new file mode 100644 index 0000000000000..5f7cf05f2d557 --- /dev/null +++ b/lib/spirv/README.md @@ -0,0 +1,45 @@ +# SPIR-V Transpiler + +Note: This library is currently considered experimental until shader compilation is verified by engine unit tests, see the Testing section below for more details. + +A dart library for transpiling a subset of SPIR-V to the shader languages used by Flutter internally. + +- [SkSL](https://skia.org/docs/user/sksl/) +- [GLSL ES 100](https://www.khronos.org/files/opengles_shading_language.pdf) +- [GLSL ES 300](https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf) + +All exported symbols are documented in `lib/spirv.dart`. + +The supported subset of SPIR-V is specified in `lib/src/constants.dart`. + +If you're using GLSL to generate SPIR-V with `glslangValidator` or `shaderc`, +the code will need to adhere to the following rules. + +- There must be a single vec4 output at location 0. +- The output can only be written to from the main function. +- `gl_FragCoord` can only be read from the main function, and its z and w components + have no meaning. +- Control flow is prohibited aside from function calls and `return`. + `if`, `while`, `for`, `switch`, etc. +- No inputs from other shader stages. +- Only float, float-vector types, and square float-matrix types. +- Only square matrices are supported. +- Only built-in functions present in GLSL ES 100 are used. +- Debug symbols must be stripped, you can use the `spirv-opt` `--strip-debug` flag. + +These rules may become less strict in future versions. Confirmant SPIR-V should succesfully transpile from the current version onwards. In other words, a spir-v shader you use now that meets these rules should keep working, but the output of the transpiler may change for that shader. + +Support for textures, control flow, and structured types is planned, but not currently included. + +## Testing + +## Exception Tests + +These tests rely on the `.spvasm` (SPIR-V Assembly) and `.glsl` files contained under `test/exception_shaders` in this directory. They are compiled to binary SPIR-V using `spirv-asm`, from the SwiftShader dependency. They are tested by testing/dart/spirv_exception_test.dart as part of the normal suite of dart tests. The purpose of these tests is to exercise every explicit failure path for shader transpilation. Each `glsl` or `spvasm` file should include a comment describing the failure that it is testing. The given files should be valid apart from the single failure case they are testing. + +## Pixel Tests + +Pixel test are not yet checked in, and should run as part of unit-testing for each implementation of `dart:ui`. These tests aim to validate the correctness of transpilation to each target language. Each shader should render the color green #00FF00 for a correct transpilation, and any other color for failure. They will be a combination of `.spvasm` files and more-readable GLSL files that are compiled to SPIR-V via `glslang`, provided by the SwiftShader dependency. Information for pixel tests will be expanded in a follow-up PR. + +These tests will be able to be run alone by executing `./ui_unittests` in the build-output directory. + diff --git a/lib/spirv/lib/spirv.dart b/lib/spirv/lib/spirv.dart new file mode 100644 index 0000000000000..898e2fb81a1ad --- /dev/null +++ b/lib/spirv/lib/spirv.dart @@ -0,0 +1,70 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// This library defines a transpiler for converting SPIR-V into SkSL or GLSL. +// @dart = 2.12 +library spirv; + +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +// These parts only contain private members, all public +// members are in this file (spirv.dart) +part 'src/constants.dart'; +part 'src/transpiler.dart'; +part 'src/types.dart'; + +/// The language to transpile to. +enum TargetLanguage { + /// SkSL, for Skia. + sksl, + + /// GLSL ES 1.00, for WebGL 1. + glslES, + + /// GLSL ES 3.00, for WebGL 2. + glslES300, +} + +/// The result of a transpilation. +class TranspileResult { + /// Source code string in [language]. + final String src; + + /// The shader language in [src]. + final TargetLanguage language; + + /// The number of float uniforms used in this shader. + final int uniformFloatCount; + + TranspileResult._(this.src, this.uniformFloatCount, this.language); +} + +/// Thrown during transpilation due to malformed or unsupported SPIR-V. +class TranspileException implements Exception { + /// The SPIR-V operator last read, or zero if there was none. + final int op; + + /// Human readable message explaining the exception. + final String message; + + @override + String toString() => '$op: $message'; + + TranspileException._(this.op, this.message); +} + +/// Transpile the provided SPIR-V buffer into a string of the [target] lang. +/// Throws an instance of [TranspileException] for malformed or unsupported +/// SPIR-V. +TranspileResult transpile(ByteBuffer spirv, TargetLanguage target) { + final _Transpiler t = _Transpiler(spirv.asUint32List(), target); + t.transpile(); + return TranspileResult._( + t.src.toString(), + t.uniformFloatCount, + target, + ); +} diff --git a/lib/spirv/lib/src/constants.dart b/lib/spirv/lib/src/constants.dart new file mode 100644 index 0000000000000..0b3ec2d801c9d --- /dev/null +++ b/lib/spirv/lib/src/constants.dart @@ -0,0 +1,196 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// @dart = 2.12 + +part of spirv; + +// This file contains a subset of SPIR-V constants defined at +// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html + +// Header constants +const int _magicNumber = 0x07230203; + +// Supported ExecutionModes +const int _originLowerLeft = 8; + +// Supported memory models +const int _addressingModelLogical = 0; +const int _memoryModelGLSL450 = 1; + +// Supported capabilities +const int _capabilityMatrix = 0; +const int _capabilityShader = 1; + +// Supported storage classes +const int _storageClassUniformConstant = 0; +const int _storageClassInput = 1; +const int _storageClassOutput = 3; +const int _storageClassFunction = 7; + +// Explicity supported decorations, others are ignored +const int _decorationBuiltIn = 11; +const int _decorationLocation = 30; + +// Explicitly supported builtin types +const int _builtinFragCoord = 15; + +// Supported instructions +const int _opExtInstImport = 11; +const int _opExtInst = 12; +const int _opMemoryModel = 14; +const int _opEntryPoint = 15; +const int _opExecutionMode = 16; +const int _opCapability = 17; +const int _opTypeVoid = 19; +const int _opTypeBool = 20; +const int _opTypeFloat = 22; +const int _opTypeVector = 23; +const int _opTypeMatrix = 24; +const int _opTypePointer = 32; +const int _opTypeFunction = 33; +const int _opConstant = 43; +const int _opConstantComposite = 44; +const int _opFunction = 54; +const int _opFunctionParameter = 55; +const int _opFunctionEnd = 56; +const int _opFunctionCall = 57; +const int _opVariable = 59; +const int _opLoad = 61; +const int _opStore = 62; +const int _opAccessChain = 65; +const int _opDecorate = 71; +const int _opVectorShuffle = 79; +const int _opCompositeConstruct = 80; +const int _opCompositeExtract = 81; +const int _opFNegate = 127; +const int _opFAdd = 129; +const int _opFSub = 131; +const int _opFMul = 133; +const int _opFDiv = 136; +const int _opFMod = 141; +const int _opVectorTimesScalar = 142; +const int _opMatrixTimesScalar = 143; +const int _opVectorTimesMatrix = 144; +const int _opMatrixTimesVector = 145; +const int _opMatrixTimesMatrix = 146; +const int _opDot = 148; +const int _opLabel = 248; +const int _opReturn = 253; +const int _opReturnValue = 254; + +// GLSL extension constants defined at +// https://www.khronos.org/registry/spir-v/specs/unified1/GLSL.std.450.html + +// Supported GLSL extension name +const String _glslStd450 = 'GLSL.std.450'; + +// Supported GLSL ops +const int _glslStd450Trunc = 3; +const int _glslStd450FAbs = 4; +const int _glslStd450FSign = 6; +const int _glslStd450Floor = 8; +const int _glslStd450Ceil = 9; +const int _glslStd450Fract = 10; +const int _glslStd450Radians = 11; +const int _glslStd450Degrees = 12; +const int _glslStd450Sin = 13; +const int _glslStd450Cos = 14; +const int _glslStd450Tan = 15; +const int _glslStd450Asin = 16; +const int _glslStd450Acos = 17; +const int _glslStd450Atan = 18; +const int _glslStd450Atan2 = 25; +const int _glslStd450Pow = 26; +const int _glslStd450Exp = 27; +const int _glslStd450Log = 28; +const int _glslStd450Exp2 = 29; +const int _glslStd450Log2 = 30; +const int _glslStd450Sqrt = 31; +const int _glslStd450InverseSqrt = 32; +const int _glslStd450FMin = 37; +const int _glslStd450FMax = 40; +const int _glslStd450FClamp = 43; +const int _glslStd450FMix = 46; +const int _glslStd450Step = 48; +const int _glslStd450SmoothStep = 49; +const int _glslStd450Length = 66; +const int _glslStd450Distance = 67; +const int _glslStd450Cross = 68; +const int _glslStd450Normalize = 69; +const int _glslStd450FaceForward = 70; +const int _glslStd450Reflect = 71; + +const Map _glslStd450OpNames = { + _glslStd450Trunc: 'trunc', + _glslStd450FAbs: 'abs', + _glslStd450FSign: 'sign', + _glslStd450Floor: 'floor', + _glslStd450Ceil: 'ceil', + _glslStd450Fract: 'fract', + _glslStd450Radians: 'radians', + _glslStd450Degrees: 'degrees', + _glslStd450Sin: 'sin', + _glslStd450Cos: 'cos', + _glslStd450Tan: 'tan', + _glslStd450Asin: 'asin', + _glslStd450Acos: 'acos', + _glslStd450Atan: 'atan', + _glslStd450Atan2: 'atan2', + _glslStd450Pow: 'pow', + _glslStd450Exp: 'exp', + _glslStd450Log: 'log', + _glslStd450Exp2: 'exp2', + _glslStd450Log2: 'log2', + _glslStd450Sqrt: 'sqrt', + _glslStd450InverseSqrt: 'inversesqrt', + _glslStd450FMin: 'min', + _glslStd450FMax: 'max', + _glslStd450FClamp: 'clamp', + _glslStd450FMix: 'mix', + _glslStd450Step: 'step', + _glslStd450SmoothStep: 'smoothstep', + _glslStd450Length: 'length', + _glslStd450Distance: 'distance', + _glslStd450Cross: 'cross', + _glslStd450Normalize: 'normalize', + _glslStd450FaceForward: 'faceforward', + _glslStd450Reflect: 'reflect', +}; + +const Map _glslStd450OpArgc = { + _glslStd450Trunc: 1, + _glslStd450FAbs: 1, + _glslStd450FSign: 1, + _glslStd450Floor: 1, + _glslStd450Ceil: 1, + _glslStd450Fract: 1, + _glslStd450Radians: 1, + _glslStd450Degrees: 1, + _glslStd450Sin: 1, + _glslStd450Cos: 1, + _glslStd450Tan: 1, + _glslStd450Asin: 1, + _glslStd450Acos: 1, + _glslStd450Atan: 1, + _glslStd450Atan2: 2, + _glslStd450Pow: 2, + _glslStd450Exp: 2, + _glslStd450Log: 1, + _glslStd450Exp2: 1, + _glslStd450Log2: 1, + _glslStd450Sqrt: 1, + _glslStd450InverseSqrt: 1, + _glslStd450FMin: 2, + _glslStd450FMax: 2, + _glslStd450FClamp: 3, + _glslStd450FMix: 3, + _glslStd450Step: 2, + _glslStd450SmoothStep: 3, + _glslStd450Length: 1, + _glslStd450Distance: 2, + _glslStd450Cross: 2, + _glslStd450Normalize: 1, + _glslStd450FaceForward: 3, + _glslStd450Reflect: 2, +}; diff --git a/lib/spirv/lib/src/transpiler.dart b/lib/spirv/lib/src/transpiler.dart new file mode 100644 index 0000000000000..d256366a800b3 --- /dev/null +++ b/lib/spirv/lib/src/transpiler.dart @@ -0,0 +1,840 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.12 +part of spirv; + +/// The name of the fragment-coordinate parameter when generating SkSL. +const String _fragParamName = 'iFragCoord'; + +/// The name of a local variable in the main function of an SkSL shader. +/// It will be assigned in place of an output variable at location 0. +/// This will be returned at the end of the main function. +const String _colorVariableName = 'oColor'; + +/// The name of the color-output variable in GLSL ES 100. +const String _glslESColorName = 'gl_FragColor'; + +/// The name of the fragment-coordinate value in GLSL. +const String _glslFragCoord = 'gl_FragCoord'; + +const String _mainFunctionName = 'main'; + +/// State machine for transpiling SPIR-V to the target language. +/// +/// SPIR-V is specified as a sequence of 32-bit values called words. +/// The first words are the header, and the rest are a sequence of +/// instructions. Instructions begin with one word that includes +/// an opcode and the number of words contained by the instruction. +/// +/// This transpiler works by maintaining a read position, [position], +/// which is advanced by methods with names beginning in "read", "parse", +/// or "op". State is written to member variables as the read position +/// advances, this will become more complex with a larger supported +/// subset of SPIR-V, and with more optimized output. It is currently +/// designed only for simplicity and speed, as the resuling code +/// will be compiled and optimized before making it to the GPU. +/// +/// The main method for the class is [transpile]. +/// +/// The list of supported SPIR-V operands is specified by the switch +/// statement in [parseInstruction] and the accompanying constants +/// in `src/constants.dart`. +/// +/// The methods beginning with `op` correspond to a specific opcode in SPIR-V. +/// The accompanying documentation is at +/// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html +/// +/// For the spec of a specific instruction, navigate to the above url with +/// the capitalized name of the operator appended. For example, for +/// [opConstant] append `#OpConstant`, like the following: +/// https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpConstant +class _Transpiler { + _Transpiler(this.spirv, this.target) { + out = src; + } + + final Uint32List spirv; + final TargetLanguage target; + + /// The resulting source code of the target language is written to src. + final StringBuffer src = StringBuffer(); + + /// ID mapped to numerical types. + final Map types = {}; + + /// ID mapped to function types. + final Map functionTypes = {}; + + /// Function ID mapped to source-code definition. + final Map functionDefs = {}; + + /// ID mapped to location decorator. + /// See [opDecorate] for more information. + final Map locations = {}; + + /// ID mapped to ID. Used by [OpLoad]. + final Map alias = {}; + + /// The current word-index in the SPIR-V buffer. + int position = 0; + + /// The word-index of the next unread instruction. + int nextPosition = 0; + + /// The current op code being handled, or 0 if none. + int currentOp = 0; + + /// The ID of the GLSL.std.450 instruction set. + /// See [opExtInstImport] for more information. + int glslExtImport = 0; + + /// The ID of the shader's entry point. + /// See [opEntryPoint]. + int entryPoint = 0; + + /// The ID of a 32-bit float type. + /// See [opTypeFloat]. + int floatType = 0; + + /// The ID of the function that is currently being defined. + /// Set by [opFunction] and unset by [opFunctionEnd]. + int currentFunction = 0; + + /// The type of [currentFunction], or null. + _FunctionType? currentFunctionType; + + /// Count of parameters declared so far for the [currentFunction]. + int declaredParams = 0; + + /// The ID for the color output variable. + /// Set by [opVariable]. + int colorOutput = 0; + + /// The ID for the fragment coordinate builtin. + /// Set by [opDecorate]. + int fragCoord = 0; + + /// The number of floats used by uniforms. + int uniformFloatCount = 0; + + /// Current indentation to prepend to new lines. + String indent = ''; + + /// Points to the source of the [currentFunction], or to [src] as a fallback. + /// The source of [currentFunction] is stored in [functionDefs]. + late StringBuffer out; + + /// Scan through all the words and populate [out] with source code, + /// or throw an exception. Calls to [parseInstruction] will affect + /// the state of the transpiler, including [position] and [out]. + void transpile() { + parseHeader(); + writeHeader(); + while (position < spirv.length) { + final int lastPosition = position; + parseInstruction(); + // If position is the same or smaller, the while loop may repeat forever. + assert(position > lastPosition); + } + + src.writeln(); + for (final StringBuffer def in functionDefs.values) { + src.write(def.toString()); + } + } + + TranspileException failure(String why) => + TranspileException._(currentOp, why); + + void writeHeader() { + switch (target) { + case TargetLanguage.glslES: + src.writeln('#version 100\n'); + src.writeln('precision mediump float;\n'); + break; + case TargetLanguage.glslES300: + src.writeln('#version 300 es\n'); + src.writeln('precision mediump float;\n'); + src.writeln('layout ( location = 0 ) out vec4 $_colorVariableName;\n'); + break; + default: + break; + } + } + + String resolveName(int id) { + if (alias.containsKey(id)) { + return resolveName(alias[id]!); + } + if (id == colorOutput) { + if (target == TargetLanguage.glslES) { + return _glslESColorName; + } else { + return _colorVariableName; + } + } else if (id == entryPoint) { + return _mainFunctionName; + } else if (id == fragCoord && target != TargetLanguage.sksl) { + return _glslFragCoord; + } + return 'i$id'; + } + + String resolveType(int type) { + final _Type? t = types[type]; + if (t == null) { + throw failure('The id "$type" has not been asgined a type'); + } + return _typeName(t, target); + } + + int readWord() { + if (nextPosition != 0 && position > nextPosition) { + throw failure('Read past the current instruction.'); + } + final int word = spirv[position]; + position++; + return word; + } + + void parseHeader() { + if (spirv[0] != _magicNumber) { + throw failure('Magic number not detected in the header'); + } + // Skip version, generator's magic word, bound, and reserved word. + position = 5; + } + + String readStringLiteral() { + final List literal = []; + while (position < nextPosition) { + final int word = readWord(); + for (int i = 0; i < 4; i++) { + final int octet = (word >> (i * 8)) & 0xFF; + if (octet == 0) { + return utf8.decode(literal); + } + literal.add(octet); + } + } + // Null terminating character not found. + throw failure('No null-terminating character found for string literal'); + } + + /// Read an instruction word, and handle the operation. + /// + /// SPIR-V instructions contain an op-code as well as a + /// word-size. This method parses both, calls the appropriate + /// operation-handler method, and then advances [position] + /// to the next instruction. + void parseInstruction() { + final int word = readWord(); + currentOp = word & 0xFFFF; + nextPosition = position + (word >> 16) - 1; + switch (currentOp) { + case _opExtInstImport: + opExtInstImport(); + break; + case _opExtInst: + opExtInst(); + break; + case _opMemoryModel: + opMemoryModel(); + break; + case _opEntryPoint: + opEntryPoint(); + break; + case _opExecutionMode: + opExecutionMode(); + break; + case _opCapability: + opCapability(); + break; + case _opTypeVoid: + opTypeVoid(); + break; + case _opTypeBool: + opTypeBool(); + break; + case _opTypeFloat: + opTypeFloat(); + break; + case _opTypeVector: + opTypeVector(); + break; + case _opTypeMatrix: + opTypeMatrix(); + break; + case _opTypePointer: + opTypePointer(); + break; + case _opTypeFunction: + opTypeFunction(); + break; + case _opConstant: + opConstant(); + break; + case _opConstantComposite: + opConstantComposite(); + break; + case _opFunction: + opFunction(); + break; + case _opFunctionParameter: + opFunctionParameter(); + break; + case _opFunctionEnd: + opFunctionEnd(); + break; + case _opFunctionCall: + opFunctionCall(); + break; + case _opVariable: + opVariable(); + break; + case _opLoad: + opLoad(); + break; + case _opStore: + opStore(); + break; + case _opAccessChain: + opAccessChain(); + break; + case _opDecorate: + opDecorate(); + break; + case _opVectorShuffle: + opVectorShuffle(); + break; + case _opCompositeConstruct: + opCompositeConstruct(); + break; + case _opCompositeExtract: + opCompositeExtract(); + break; + case _opFNegate: + opFNegate(); + break; + case _opFAdd: + parseOperatorInst('+'); + break; + case _opFSub: + parseOperatorInst('-'); + break; + case _opFMul: + parseOperatorInst('*'); + break; + case _opFDiv: + parseOperatorInst('/'); + break; + case _opFMod: + parseBuiltinFunction('mod'); + break; + case _opVectorTimesScalar: + case _opMatrixTimesScalar: + case _opVectorTimesMatrix: + case _opMatrixTimesVector: + case _opMatrixTimesMatrix: + parseOperatorInst('*'); + break; + case _opDot: + parseBuiltinFunction('dot'); + break; + case _opLabel: + opLabel(); + break; + case _opReturn: + opReturn(); + break; + case _opReturnValue: + opReturnValue(); + break; + default: + throw failure('Not a supported op.'); + } + position = nextPosition; + } + + void opExtInstImport() { + glslExtImport = readWord(); + final String ext = readStringLiteral(); + if (ext != _glslStd450) { + throw failure('only "$_glslStd450" is supported. Got "$ext".'); + } + } + + void opExtInst() { + final int type = readWord(); + final int id = readWord(); + final int set = readWord(); + if (set != glslExtImport) { + throw failure('only imported glsl instructions are supported'); + } + parseGLSLInst(id, type); + } + + void opMemoryModel() { + // addressing model + if (readWord() != _addressingModelLogical) { + throw failure('only the logical addressing model is supported'); + } + // memory model + if (readWord() != _memoryModelGLSL450) { + throw failure('only the GLSL450 memory model is supported'); + } + } + + void opEntryPoint() { + // skip execution model + position++; + entryPoint = readWord(); + } + + void opExecutionMode() { + // Skip entry point + position++; + final int executionMode = readWord(); + if (executionMode != _originLowerLeft) { + throw failure('only OriginLowerLeft is supported as an execution mode'); + } + } + + void opCapability() { + final int capability = readWord(); + switch (capability) { + case _capabilityMatrix: + case _capabilityShader: + return; + default: + throw failure('$capability is not a supported capability'); + } + } + + void opTypeVoid() { + types[readWord()] = _Type._void; + } + + void opTypeBool() { + types[readWord()] = _Type._bool; + } + + void opTypeFloat() { + final int id = readWord(); + types[id] = _Type.float; + floatType = id; + final int width = readWord(); + if (width != 32) { + throw failure('float width must be 32'); + } + } + + void opTypeVector() { + final int id = readWord(); + _Type t; + final int componentType = readWord(); + if (componentType != floatType) { + throw failure('only float vectors are supported'); + } + final int componentCount = readWord(); + switch (componentCount) { + case 2: + t = _Type.float2; + break; + case 3: + t = _Type.float3; + break; + case 4: + t = _Type.float4; + break; + default: + throw failure('$componentCount not a supported component count.'); + } + types[id] = t; + } + + void opTypeMatrix() { + final int id = readWord(); + _Type t; + final int columnType = readWord(); + final int columnCount = readWord(); + _Type expected = _Type.float2; + switch (columnCount) { + case 2: + t = _Type.float2x2; + break; + case 3: + t = _Type.float3x3; + expected = _Type.float3; + break; + case 4: + t = _Type.float4x4; + expected = _Type.float4; + break; + default: + throw failure('$columnCount is not a supported column count'); + } + if (types[columnType] != expected) { + throw failure('Only square matrix dimensions are supported'); + } + types[id] = t; + } + + void opTypePointer() { + final int id = readWord(); + // ignore storage class + position++; + final _Type? t = types[readWord()]; + if (t == null) { + throw failure('$t is not a registered type'); + } + types[id] = t; + } + + void opTypeFunction() { + final int id = readWord(); + final int returnType = readWord(); + final int paramCount = nextPosition - position; + final List params = List.filled(paramCount, 0); + for (int i = 0; i < paramCount; i++) { + params[i] = readWord(); + } + functionTypes[id] = _FunctionType(returnType, params); + } + + void opConstant() { + final int type = readWord(); + final String id = resolveName(readWord()); + final int value = readWord(); + String valueString = '$value'; + if (types[type] == _Type.float) { + final double v = Int32List.fromList([value]) + .buffer + .asByteData() + .getFloat32(0, Endian.little); + valueString = '$v'; + } + final String typeName = resolveType(type); + src.writeln('const $typeName $id = $valueString;'); + } + + void opConstantComposite() { + final String type = resolveType(readWord()); + final String id = resolveName(readWord()); + src.write('const $type $id = $type('); + final int count = nextPosition - position; + for (int i = 0; i < count; i++) { + src.write(resolveName(readWord())); + if (i < count - 1) { + src.write(', '); + } + } + src.writeln(');'); + } + + void opFunction() { + String returnType = resolveType(readWord()); + final int id = readWord(); + + if (target == TargetLanguage.sksl && id == entryPoint) { + returnType = 'half4'; + } + + // ignore function control + position++; + + final String name = resolveName(id); + final String opening = '$returnType $name('; + final StringBuffer def = StringBuffer(); + def.write(opening); + src.write(opening); + + if (target == TargetLanguage.sksl && id == entryPoint) { + const String fragParam = 'float2 $_fragParamName'; + def.write(fragParam); + src.write(fragParam); + } + + final int typeIndex = readWord(); + final _FunctionType? functionType = functionTypes[typeIndex]; + if (functionType == null) { + throw failure('$typeIndex is not a registered function type'); + } + + if (functionType.params.isEmpty) { + def.write(') '); + src.writeln(');'); + } + + currentFunction = id; + currentFunctionType = functionType; + declaredParams = 0; + out = def; + functionDefs[id] = def; + } + + void opFunctionParameter() { + if (declaredParams > 0) { + out.write(', '); + src.write(', '); + } + + final int type = readWord(); + final int id = readWord(); + final String decl = resolveType(type) + ' ' + resolveName(id); + out.write(decl); + src.write(decl); + declaredParams++; + + if (declaredParams == currentFunctionType?.params.length) { + out.write(') '); + src.writeln(');'); + } + } + + void opFunctionEnd() { + if (target == TargetLanguage.sksl && currentFunction == entryPoint) { + out.writeln('${indent}return $_colorVariableName;'); + } + out.writeln('}'); + out.writeln(); + // Remove trailing two space characters, if present. + indent = indent.substring(0, max(0, indent.length - 2)); + currentFunction = 0; + out = src; + currentFunctionType = null; + } + + void opFunctionCall() { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + final String functionName = resolveName(readWord()); + final List args = + List.generate(nextPosition - position, (int i) { + return resolveName(readWord()); + }); + out.write('$indent$type $name = $functionName('); + for (int i = 0; i < args.length; i++) { + out.write(args[i]); + if (i < args.length - 1) { + out.write(', '); + } + } + out.writeln(');'); + } + + void opVariable() { + final int typeId = readWord(); + final String type = resolveType(typeId); + final int id = readWord(); + final String name = resolveName(id); + final int storageClass = readWord(); + + switch (storageClass) { + case _storageClassUniformConstant: + if (target == TargetLanguage.glslES300) { + final String location = locations[id].toString(); + src.write('layout ( location = $location ) '); + } + src.writeln('uniform $type $name;'); + final _Type? t = types[typeId]; + if (t == null) { + throw failure('$typeId is not a defined type'); + } + uniformFloatCount += _typeFloatCounts[t]!; + return; + case _storageClassInput: + return; + case _storageClassOutput: + if (locations[id] == 0) { + colorOutput = id; + } + return; + case _storageClassFunction: + out.writeln('$indent$type $name;'); + return; + default: + throw failure('$storageClass is an unsupported Storage Class'); + } + } + + void opLoad() { + // ignore type + position++; + final int id = readWord(); + final int pointer = readWord(); + alias[id] = pointer; + } + + void opStore() { + final String pointer = resolveName(readWord()); + final String object = resolveName(readWord()); + out.writeln('$indent$pointer = $object;'); + } + + void opAccessChain() { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + final String base = resolveName(readWord()); + + // opAccessChain currently only supports indexed access. + // Once struct support is added, this will need to be updated. + // Currently, structs will be caught before this method is called, + // since using the instruction to define a struct type will throw + // an exception. + out.write('$indent$type $name = $base'); + final int count = nextPosition - position; + for (int i = 0; i < count; i++) { + final String index = resolveName(readWord()); + out.write('[$index]'); + } + out.writeln(';'); + } + + void opDecorate() { + final int target = readWord(); + final int decoration = readWord(); + switch (decoration) { + case _decorationBuiltIn: + if (readWord() == _builtinFragCoord) { + fragCoord = target; + } + return; + case _decorationLocation: + locations[target] = readWord(); + return; + default: + return; + } + } + + void opVectorShuffle() { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + final String vector1Name = resolveName(readWord()); + // ignore second vector + position++; + + out.write('$indent$type $name = $type('); + + final int count = nextPosition - position; + for (int i = 0; i < count; i++) { + final int index = readWord(); + out.write('$vector1Name[$index]'); + if (i < count - 1) { + out.write(', '); + } + } + out.writeln(');'); + } + + void opCompositeConstruct() { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + out.write('$indent$type $name = $type('); + final int count = nextPosition - position; + for (int i = 0; i < count; i++) { + out.write(resolveName(readWord())); + if (i < count - 1) { + out.write(', '); + } + } + out.writeln(');'); + } + + void opCompositeExtract() { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + final String src = resolveName(readWord()); + out.write('$indent$type $name = $src'); + final int count = nextPosition - position; + for (int i = 0; i < count; i++) { + final int index = readWord(); + out.write('[$index]'); + } + out.writeln(';'); + } + + void opFNegate() { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + final String operand = resolveName(readWord()); + out.writeln('$indent$type $name = -$operand;'); + } + + void opLabel() { + out.writeln('{'); + indent = indent + ' '; + if (target == TargetLanguage.sksl && currentFunction == entryPoint) { + final String ind = indent; + if (fragCoord > 0) { + final String fragName = resolveName(fragCoord); + out + ..write(ind) + ..writeln('float4 $fragName = float4($_fragParamName, 0, 0);'); + } + out + ..write(ind) + ..writeln('float4 $_colorVariableName;'); + } + } + + void opReturn() { + if (currentFunction == entryPoint) { + return; + } + out.writeln(indent + 'return;'); + } + + void opReturnValue() { + final String name = resolveName(readWord()); + out.writeln(indent + 'return $name;'); + } + + void parseOperatorInst(String op) { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + final String a = resolveName(readWord()); + final String b = resolveName(readWord()); + out.writeln('$indent$type $name = $a $op $b;'); + } + + void parseBuiltinFunction(String functionName) { + final String type = resolveType(readWord()); + final String name = resolveName(readWord()); + out.write('$indent$type $name = $functionName('); + final int count = nextPosition - position; + for (int i = 0; i < count; i++) { + out.write(resolveName(readWord())); + if (i < count - 1) { + out.write(', '); + } + } + out.writeln(');'); + } + + void parseGLSLInst(int id, int type) { + final int inst = readWord(); + final String? opName = _glslStd450OpNames[inst]; + if (opName == null) { + throw failure('$id is not a supported GLSL instruction.'); + } + final int argc = _glslStd450OpArgc[inst]!; + parseGLSLOp(id, type, opName, argc); + } + + void parseGLSLOp(int id, int type, String name, int argCount) { + final String resultName = resolveName(id); + final String typeName = resolveType(type); + out.write('$indent$typeName $resultName = $name('); + for (int i = 0; i < argCount; i++) { + out.write(resolveName(readWord())); + if (i < argCount - 1) { + out.write(', '); + } + } + out.writeln(');'); + } +} diff --git a/lib/spirv/lib/src/types.dart b/lib/spirv/lib/src/types.dart new file mode 100644 index 0000000000000..e951ca0ef9c72 --- /dev/null +++ b/lib/spirv/lib/src/types.dart @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// @dart = 2.12 + +part of spirv; + +enum _Type { + _void, + _bool, + _int, + float, + float2, + float3, + float4, + float2x2, + float3x3, + float4x4, +} + +class _FunctionType { + /// Result-id of the return type. + final int returnType; + + /// Type-id for each parameter. + final List params; + + _FunctionType(this.returnType, this.params); +} + +String _typeName(_Type t, TargetLanguage target) { + switch (target) { + case TargetLanguage.sksl: + return _skslTypeNames[t]!; + default: + return _glslTypeNames[t]!; + } +} + +const Map<_Type, String> _skslTypeNames = <_Type, String>{ + _Type._void: 'void', + _Type._bool: 'bool', + _Type._int: 'int', + _Type.float: 'float', + _Type.float2: 'float2', + _Type.float3: 'float3', + _Type.float4: 'float4', + _Type.float2x2: 'float2x2', + _Type.float3x3: 'float3x3', + _Type.float4x4: 'float4x4', +}; + +const Map<_Type, String> _glslTypeNames = <_Type, String>{ + _Type._void: 'void', + _Type._bool: 'bool', + _Type._int: 'int', + _Type.float: 'float', + _Type.float2: 'vec2', + _Type.float3: 'vec3 ', + _Type.float4: 'vec4', + _Type.float2x2: 'mat2', + _Type.float3x3: 'mat3', + _Type.float4x4: 'mat4', +}; + +const Map<_Type, int> _typeFloatCounts = <_Type, int>{ + _Type.float: 1, + _Type.float2: 2, + _Type.float3: 3, + _Type.float4: 4, + _Type.float2x2: 4, + _Type.float3x3: 9, + _Type.float4x4: 16, +}; diff --git a/lib/spirv/pubspec.yaml b/lib/spirv/pubspec.yaml new file mode 100644 index 0000000000000..763c976f5e7dc --- /dev/null +++ b/lib/spirv/pubspec.yaml @@ -0,0 +1,9 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +name: spirv +publish_to: none + +environment: + sdk: '>=2.11.0 <3.0.0' diff --git a/lib/spirv/test/BUILD.gn b/lib/spirv/test/BUILD.gn new file mode 100644 index 0000000000000..ab3ba5b008486 --- /dev/null +++ b/lib/spirv/test/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +import("//flutter/testing/testing.gni") + +if (enable_unittests) { + executable("spirv_assembler") { + sources = [ "spirv_assembler.cc" ] + + configs += [ "//third_party/swiftshader_flutter:spvtools_public_config" ] + + deps = [ + "//third_party/swiftshader_flutter:spvtools", + "//third_party/swiftshader_flutter:spvtools_val", + ] + } +} diff --git a/lib/spirv/test/exception_shaders/BUILD.gn b/lib/spirv/test/exception_shaders/BUILD.gn new file mode 100644 index 0000000000000..17e371e5f3261 --- /dev/null +++ b/lib/spirv/test/exception_shaders/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +import("//build/compiled_action.gni") +import("//flutter/testing/testing.gni") + +if (enable_unittests) { + compiled_action_foreach("spirv_compile_exception_shaders") { + tool = "//flutter/lib/spirv/test:spirv_assembler" + + sources = [ + "unassigned_function_type.spvasm", + "unassigned_pointer_type.spvasm", + "unassigned_type.spvasm", + "unassigned_variable_type.spvasm", + "unsupported_addressing_model.spvasm", + "unsupported_capability.spvasm", + "unsupported_execution_mode.spvasm", + "unsupported_ext_inst_import.spvasm", + "unsupported_float_width.spvasm", + "unsupported_glsl_inst.spvasm", + "unsupported_matrix_column_count.spvasm", + "unsupported_matrix_non_square.spvasm", + "unsupported_memory_model.spvasm", + "unsupported_variable_storage_class.spvasm", + "unsupported_vector_component_count.spvasm", + "unsupported_vector_type.spvasm", + ] + + outputs = [ "$target_gen_dir/{{source_name_part}}.spv" ] + + args = [ + "{{source}}", + rebase_path(target_gen_dir, root_build_dir) + "/{{source_name_part}}.spv", + ] + } +} diff --git a/lib/spirv/test/exception_shaders/assemble.py b/lib/spirv/test/exception_shaders/assemble.py new file mode 100644 index 0000000000000..80b4092a65e78 --- /dev/null +++ b/lib/spirv/test/exception_shaders/assemble.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import subprocess + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + 'input', + type=pathlib.Path, + required=True, + help='path to input SPIR-V assembly file') + parser.add_argument( + 'output', + type=pathlib.Path, + required=True, + help='path to output SPIR-V binary file') + args = parser.parse_args() + subprocess.run([ + 'spirv-as', + '-o', + args.output, + args.input, + ]) + +if __name__ == '__main__': + main() + diff --git a/lib/spirv/test/exception_shaders/unassigned_function_type.spvasm b/lib/spirv/test/exception_shaders/unassigned_function_type.spvasm new file mode 100644 index 0000000000000..a757977bcd9d8 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unassigned_function_type.spvasm @@ -0,0 +1,31 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 13 +; Schema: 0 +; +; OpFunction is called with an unassigned type on line 27. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %13 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unassigned_pointer_type.spvasm b/lib/spirv/test/exception_shaders/unassigned_pointer_type.spvasm new file mode 100644 index 0000000000000..35b644e8e6342 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unassigned_pointer_type.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 14 +; Schema: 0 +; +; ID %13 is assigned a pointer type with an undefined underlying +; type %14. Line 20. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %13 = OpTypePointer Output %14 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unassigned_type.spvasm b/lib/spirv/test/exception_shaders/unassigned_type.spvasm new file mode 100644 index 0000000000000..c9bf556c135c0 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unassigned_type.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 13 +; Schema: 0 +; +; In the definition of %main, %void has been replaced with an undefined +; ID, representing an undefined type. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %13 None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unassigned_variable_type.spvasm b/lib/spirv/test/exception_shaders/unassigned_variable_type.spvasm new file mode 100644 index 0000000000000..5905010d615a8 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unassigned_variable_type.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; Line 24 calls OpVariable to create a UniformConstant variable with an +; undefined type. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %14 = OpVariable %13 UniformConstant + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_addressing_model.spvasm b/lib/spirv/test/exception_shaders/unsupported_addressing_model.spvasm new file mode 100644 index 0000000000000..42db7acd64fdb --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_addressing_model.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; OpMemoryModel is called with a non 'Logical' value, here +; Physical32 is used instead. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Physical32 GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_capability.spvasm b/lib/spirv/test/exception_shaders/unsupported_capability.spvasm new file mode 100644 index 0000000000000..3653af6987844 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_capability.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; A capability not in the set of {Matrix, Shader} is used. +; + OpCapability Shader + OpCapability Geometry + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_execution_mode.spvasm b/lib/spirv/test/exception_shaders/unsupported_execution_mode.spvasm new file mode 100644 index 0000000000000..00abb481689b8 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_execution_mode.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; OpExecutionMode is called with a non 'OriginLowerLeft' value, here +; PointMode is used instead. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main PointMode + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_ext_inst_import.spvasm b/lib/spirv/test/exception_shaders/unsupported_ext_inst_import.spvasm new file mode 100644 index 0000000000000..722ff319bdfeb --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_ext_inst_import.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 13 +; Schema: 0 +; +; ID %13 is assigned an unsupported external instruction set. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + %13 = OpExtInstImport "OpenCL.std" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_float_width.spvasm b/lib/spirv/test/exception_shaders/unsupported_float_width.spvasm new file mode 100644 index 0000000000000..4fabcfbf2e779 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_float_width.spvasm @@ -0,0 +1,31 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; A non-32 float width is used in OpTypeFloat. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 16 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_glsl_inst.spvasm b/lib/spirv/test/exception_shaders/unsupported_glsl_inst.spvasm new file mode 100644 index 0000000000000..23e3d1a0fdea6 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_glsl_inst.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 13 +; Schema: 0 +; +; A GLSL instruction not in the set defined in `constants.dart` is used +; on line 30. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + %13 = OpExtInst %v4float %1 Round %11 + OpStore %oColor %13 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_matrix_column_count.spvasm b/lib/spirv/test/exception_shaders/unsupported_matrix_column_count.spvasm new file mode 100644 index 0000000000000..b773de2b1cf66 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_matrix_column_count.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; A matrix type is defined with a component count not in the set of +; {2, 3, 4}, on line 24. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %13 = OpTypeMatrix %v4float 5 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_matrix_non_square.spvasm b/lib/spirv/test/exception_shaders/unsupported_matrix_non_square.spvasm new file mode 100644 index 0000000000000..d5ff3c432d432 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_matrix_non_square.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; A matrix type is defined with a non-square matrix size. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %13 = OpTypeMatrix %v4float 3 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_memory_model.spvasm b/lib/spirv/test/exception_shaders/unsupported_memory_model.spvasm new file mode 100644 index 0000000000000..ae6e51e97cbd7 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_memory_model.spvasm @@ -0,0 +1,32 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; OpMemoryModel is called with a non 'GLSL450' value, here +; 'Simple' is used instead. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical Simple + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_variable_storage_class.spvasm b/lib/spirv/test/exception_shaders/unsupported_variable_storage_class.spvasm new file mode 100644 index 0000000000000..fa8574767c903 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_variable_storage_class.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; OpVariable uses a Storage Class not in the set +; {UniformConstant, Input, Output, Function}, on line 25. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %13 = OpVariable %_ptr_Output_v4float Image + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_vector_component_count.spvasm b/lib/spirv/test/exception_shaders/unsupported_vector_component_count.spvasm new file mode 100644 index 0000000000000..317ec226ee716 --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_vector_component_count.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 12 +; Schema: 0 +; +; A vector type is defined with a component count not in the set of +; {2, 3, 4}, on line 24. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %v8float = OpTypeVector %float 8 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/exception_shaders/unsupported_vector_type.spvasm b/lib/spirv/test/exception_shaders/unsupported_vector_type.spvasm new file mode 100644 index 0000000000000..8126cec49cd4b --- /dev/null +++ b/lib/spirv/test/exception_shaders/unsupported_vector_type.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 8 +; Bound: 14 +; Schema: 0 +; +; OpTypeVector is called with a base type that is not Float, +; here an undefined ID %13 is used, on line 24. +; + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %oColor + OpExecutionMode %main OriginLowerLeft + OpSource ESSL 320 + OpName %main "main" + OpName %oColor "oColor" + OpDecorate %oColor RelaxedPrecision + OpDecorate %oColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %14 = OpTypeVector %13 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %oColor = OpVariable %_ptr_Output_v4float Output + %float_0 = OpConstant %float 0 + %11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %oColor %11 + OpReturn + OpFunctionEnd diff --git a/lib/spirv/test/spirv_assembler.cc b/lib/spirv/test/spirv_assembler.cc new file mode 100644 index 0000000000000..e6a11c9c6b764 --- /dev/null +++ b/lib/spirv/test/spirv_assembler.cc @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include "third_party/swiftshader/third_party/SPIRV-Tools/include/spirv-tools/libspirv.hpp" + +namespace fs = std::filesystem; + +int main(int argc, const char* argv[]) { + if (argc != 3) { + std::cerr << "Invalid argument count." << std::endl; + return -1; + } + + fs::path path(argv[1]); + if (!fs::exists(path)) { + std::cerr << "File does not exist." << std::endl; + return -1; + } + + std::fstream input; + input.open(argv[1]); + input.seekg(0, std::ios::end); + std::streampos size = input.tellg(); + input.seekg(0, std::ios::beg); + std::vector buf(size); + input.read(buf.data(), size); + input.close(); + + spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::vector assembled_spirv; + if (!tools.Assemble(buf.data(), size, &assembled_spirv)) { + std::cerr << "Failed to assemble " << argv[1] << std::endl; + return -1; + } + + std::fstream output; + output.open(argv[2], std::fstream::out | std::fstream::trunc); + if (!output.is_open()) { + output.close(); + std::cerr << "failed to open output file" << std::endl; + std::abort(); + } + + output.write(reinterpret_cast(assembled_spirv.data()), + sizeof(uint32_t) * assembled_spirv.size()); + output.close(); + return 0; +} diff --git a/testing/dart/pubspec.yaml b/testing/dart/pubspec.yaml index fa09d954abf87..1d125350b69c8 100644 --- a/testing/dart/pubspec.yaml +++ b/testing/dart/pubspec.yaml @@ -32,6 +32,8 @@ dependency_overrides: path: ../../../third_party/dart/pkg/meta path: path: ../../../third_party/dart/third_party/pkg/path + spirv: + path: ../../lib/spirv sky_engine: path: ../../../out/host_debug_unopt/gen/dart-pkg/sky_engine sky_services: diff --git a/testing/dart/spirv_exception_test.dart b/testing/dart/spirv_exception_test.dart new file mode 100644 index 0000000000000..7f6823386b14d --- /dev/null +++ b/testing/dart/spirv_exception_test.dart @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:spirv/spirv.dart' as spirv; +import 'package:path/path.dart' as path; +import 'package:litetest/litetest.dart'; + +const List targets = [ + spirv.TargetLanguage.sksl, + spirv.TargetLanguage.glslES, + spirv.TargetLanguage.glslES300, +]; + +void main() { + test('spirv transpiler throws exceptions', () async { + int count = 0; + await for (final Uint8List shader in exceptionShaders()) { + for (final spirv.TargetLanguage target in targets) { + expect(() => spirv.transpile(shader.buffer, target), throwsException); + } + count++; + } + // If the SPIR-V assembly step silently fails, make sure this test fails + // too. + expect(count, greaterThan(0)); + }); +} + +Stream exceptionShaders() async* { + final Directory dir = Directory(path.joinAll([ + 'out', + 'host_debug_unopt', + 'gen', + 'flutter', + 'lib', + 'spirv', + 'test', + 'exception_shaders', + ])); + await for (final FileSystemEntity entry in dir.list()) { + if (entry is! File) { + continue; + } + final File file = entry as File; + if (path.extension(file.path) != '.spv') { + continue; + } + yield file.readAsBytesSync(); + } +} +