blob: 999ba82a34507b55f3bbe810d49332199534d123 [file] [log] [blame]
// 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.
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 = header;
}
final Uint32List spirv;
final TargetLanguage target;
/// The resulting source code of the target language is written to src.
final StringBuffer src = StringBuffer();
/// Contains shader header, constants, and uniforms.
final StringBuffer header = StringBuffer();
/// The main body of code, contains function definitions.
final StringBuffer body = StringBuffer();
/// Uniform declarations.
final Map<int, String> uniformDeclarations = <int, String>{};
/// Declarations for sampler sizes in SkSL.
///
/// This is because the SkSL eval function uses texel coordinates when
/// sampling an ImageShader, and SkSL does not support the textureSize
/// function. These uniforms allow adding support for normalized
/// coordinates for [opImageSampleImplicitLod].
final Map<int, String> samplerSizeDeclarations = <int, String>{};
/// ID mapped to numerical types.
final Map<int, _Type> types = <int, _Type>{};
/// ID mapped to function types.
final Map<int, _FunctionType> functionTypes = <int, _FunctionType>{};
/// Function ID mapped to source-code definition.
final Map<int, StringBuffer> functionDefs = <int, StringBuffer>{};
/// Function ID mapped to referenced functions.
/// This is used to ensure they are written in the right order.
final Map<int, List<int>> functionDeps = <int, List<int>>{};
/// ID mapped to location decorator.
/// See [opDecorate] for more information.
final Map<int, int> locations = <int, int>{};
/// ID mapped to ID. Used by [OpLoad].
final Map<int, int> alias = <int, int>{};
/// ID mapped to a string to use instead of a generated name.
final Map<int, String> nameOverloads = <int, String>{};
/// The ID for a constant true value.
/// See [opConstantTrue].
int constantTrue = 0;
/// The ID for a constant false value.
/// See [opConstantFalse].
int constantFalse = 0;
/// 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 int type.
/// See [opTypeInt].
int intType = 0;
/// The ID of a 32-bit float type.
/// See [opTypeFloat].
int floatType = 0;
/// The ID of the image type.
/// See [opTypeImage].
int imageType = 0;
/// The ID of the sampledImage type.
/// See [opTypeSampledImage].
int sampledImageType = 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;
/// The number of samplers used by uniforms.
int samplerCount = 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();
// Parse instructions and write to body.
out = body;
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);
}
// TODO(antrob): Investigate if `List<bool>.filled(maxFunctionId, false)` can be used here instead.
final Set<int> visited = <int>{};
writeFunctionAndDeps(visited, entryPoint);
// Add uniform declarations to header.
if (uniformDeclarations.isNotEmpty) {
header.writeln();
final List<int> locations = uniformDeclarations.keys.toList();
locations.sort((int a, int b) => a - b);
for (final int location in locations) {
header.writeln(uniformDeclarations[location]);
}
}
// Add SkSL sampler size declarations to header.
if (samplerSizeDeclarations.isNotEmpty) {
header.writeln();
final List<int> locations = samplerSizeDeclarations.keys.toList();
locations.sort((int a, int b) => a - b);
for (final int location in locations) {
header.writeln(samplerSizeDeclarations[location]);
}
}
src.write(header);
src.writeln();
src.write(body);
}
TranspileException failure(String why) =>
TranspileException._(currentOp, why);
void writeFunctionAndDeps(Set<int> visited, int function) {
if (visited.contains(function)) {
return;
}
visited.add(function);
for (final int dep in functionDeps[function]!) {
writeFunctionAndDeps(visited, dep);
}
out.write(functionDefs[function]!.toString());
}
void writeHeader() {
switch (target) {
case TargetLanguage.glslES:
out.writeln('#version 100\n');
out.writeln('precision mediump float;\n');
break;
case TargetLanguage.glslES300:
out.writeln('#version 300 es\n');
out.writeln('precision mediump float;\n');
out.writeln('layout ( location = 0 ) out vec4 $_colorVariableName;\n');
break;
default:
break;
}
}
String resolveName(int id) {
if (alias.containsKey(id)) {
return resolveName(alias[id]!);
}
if (nameOverloads.containsKey(id)) {
return nameOverloads[id]!;
} else if (constantTrue > 0 && id == constantTrue) {
return 'true';
} else if (constantFalse > 0 && id == constantFalse) {
return 'false';
} 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<int> literal = <int>[];
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');
}
void typeCast() {
final String type = resolveType(readWord());
final String name = resolveName(readWord());
final String value = resolveName(readWord());
out.writeln('$indent$type $name = $type($value);');
}
/// 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 _opConvertSToF:
typeCast();
break;
case _opTypeVoid:
opTypeVoid();
break;
case _opTypeBool:
opTypeBool();
break;
case _opTypeInt:
opTypeInt();
break;
case _opTypeFloat:
opTypeFloat();
break;
case _opTypeVector:
opTypeVector();
break;
case _opTypeMatrix:
opTypeMatrix();
break;
case _opTypeImage:
opTypeImage();
break;
case _opTypeSampledImage:
opTypeSampledImage();
break;
case _opTypePointer:
opTypePointer();
break;
case _opTypeFunction:
opTypeFunction();
break;
case _opConstantTrue:
opConstantTrue();
break;
case _opConstantFalse:
opConstantFalse();
break;
case _opConstant:
opConstant();
break;
case _opConstantComposite:
opConstantComposite();
break;
case _opConvertFToS:
typeCast();
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 _opSelect:
opSelect();
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 _opImageSampleImplicitLod:
opImageSampleImplicitLod();
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 _opFUnordNotEqual:
parseOperatorInst('!=');
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;
// Unsupported ops with no semantic meaning.
case _opSource:
case _opSourceExtension:
case _opName:
case _opMemberName:
case _opString:
case _opLine:
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 opTypeInt() {
final int id = readWord();
types[id] = _Type._int;
intType = id;
final int width = readWord();
if (width != 32) {
throw failure('int width must be 32');
}
}
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 opTypeImage() {
if (imageType != 0) {
throw failure('Image type was previously declared.');
}
final int id = readWord();
final int sampledType = readWord();
if (types[sampledType] != _Type.float) {
throw failure('Sampled type must be float.');
}
final int dimensionality = readWord();
if (dimensionality != _dim2D) {
throw failure('Dimensionality must be 2D.');
}
final int depth = readWord();
if (depth != 0) {
throw failure('Depth must be 0.');
}
final int arrayed = readWord();
if (arrayed != 0) {
throw failure('Arrayed must be 0.');
}
final int multisampled = readWord();
if (multisampled != 0) {
throw failure('Multisampled must be 0.');
}
final int sampled = readWord();
if (sampled != 1) {
throw failure('Sampled must be 1.');
}
imageType = id;
}
void opTypeSampledImage() {
if (sampledImageType != 0) {
throw failure('imageSampledType was previously declared.');
}
if (imageType == 0) {
throw failure('imageType has not yet been declared.');
}
final int id = readWord();
final int imgType = readWord();
if (imgType != imageType) {
throw failure('Invalid image type.');
}
sampledImageType = id;
types[id] = _Type.sampledImage;
}
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<int> params = List<int>.filled(paramCount, 0);
for (int i = 0; i < paramCount; i++) {
params[i] = readWord();
}
functionTypes[id] = _FunctionType(returnType, params);
}
void opConstantTrue() {
position++; // Skip type operand.
constantTrue = readWord();
}
void opConstantFalse() {
position++; // Skip type operand.
constantFalse = readWord();
}
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(<int>[value])
.buffer
.asByteData()
.getFloat32(0, Endian.little);
valueString = '$v';
}
final String typeName = resolveType(type);
header.writeln('const $typeName $id = $valueString;');
}
void opConstantComposite() {
final String type = resolveType(readWord());
final String id = resolveName(readWord());
header.write('const $type $id = $type(');
final int count = nextPosition - position;
for (int i = 0; i < count; i++) {
header.write(resolveName(readWord()));
if (i < count - 1) {
header.write(', ');
}
}
header.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);
if (target == TargetLanguage.sksl && id == entryPoint) {
const String fragParam = 'float2 $_fragParamName';
def.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(') ');
}
currentFunction = id;
currentFunctionType = functionType;
declaredParams = 0;
out = def;
functionDefs[id] = def;
functionDeps[id] = <int>[];
}
void opFunctionParameter() {
if (declaredParams > 0) {
out.write(', ');
}
final int type = readWord();
final int id = readWord();
final String decl = resolveType(type) + ' ' + resolveName(id);
out.write(decl);
declaredParams++;
if (declaredParams == currentFunctionType?.params.length) {
out.write(') ');
}
}
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 = body;
currentFunctionType = null;
}
void opFunctionCall() {
final String type = resolveType(readWord());
final String name = resolveName(readWord());
final int functionId = readWord();
final String functionName = resolveName(functionId);
// Make the current function depend on this function.
functionDeps[currentFunction]!.add(functionId);
final List<String> args =
List<String>.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:
int? location = locations[id];
if (location == null) {
throw failure('$id had no location specified');
}
String prefix = '';
if (target == TargetLanguage.glslES300) {
prefix = 'layout ( location = $location ) ';
}
uniformDeclarations[location] = '${prefix}uniform $type $name;';
final _Type? t = types[typeId];
if (t == null) {
throw failure('$typeId is not a defined type');
}
if (t == _Type.sampledImage) {
samplerCount++;
if (target == TargetLanguage.sksl) {
samplerSizeDeclarations[location] = 'uniform half2 ${name}_size;';
}
} else {
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 opSelect() {
final String type = resolveType(readWord());
final String name = resolveName(readWord());
final String condition = resolveName(readWord());
final String a = resolveName(readWord());
final String b = resolveName(readWord());
out.writeln('$indent$type $name = mix($b, $a, $type($condition));');
}
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 int id = 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.
String overload = base;
final int count = nextPosition - position;
for (int i = 0; i < count; i++) {
final String index = resolveName(readWord());
overload += '[$index]';
}
nameOverloads[id] = overload;
}
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 opImageSampleImplicitLod() {
final String type = resolveType(readWord());
final String name = resolveName(readWord());
final String sampledImage = resolveName(readWord());
final String coordinate = resolveName(readWord());
if (target == TargetLanguage.sksl) {
out.writeln('$indent$type $name = $sampledImage.eval(${sampledImage}_size * $coordinate);');
} else {
out.writeln('$indent$type $name = texture($sampledImage, $coordinate);');
}
}
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) {
int inst = readWord();
if (inst == _glslStd450Atan2 && target == TargetLanguage.sksl) {
inst = _glslStd450Atan;
}
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(');');
}
}