| // 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.10 |
| part of engine; |
| |
| /// Creates shader program for target webgl version. |
| /// |
| /// See spec at https://www.khronos.org/registry/webgl/specs/latest/1.0/. |
| /// |
| /// Differences in WebGL2 vs WebGL1. |
| /// - WebGL2 needs '#version 300 es' to enable the new shading language |
| /// - vertex attributes have the qualifier 'in' instead of 'attribute' |
| /// - GLSL 3.00 defines texture and other new and future reserved words. |
| /// - varying is now called `in`. |
| /// - GLSL 1.00 has a predefined variable gl_FragColor which now needs to be |
| /// defined as `out vec4 fragmentColor`. |
| /// - Texture lookup functions texture2D and textureCube have now been |
| /// replaced with texture. |
| /// |
| /// Example usage: |
| /// ShaderBuilder builder = ShaderBuilder(WebGlVersion.webgl2); |
| /// ShaderDeclaration u1 = builder.addUniform(ShaderType.kVec4); |
| /// ShaderMethod method = builder.addMethod('main'); |
| /// method.addStatement('${u1.name} = vec4(1.0, 1.0, 1.0, 0.0);'); |
| /// source = builder.build(); |
| class ShaderBuilder { |
| /// WebGL version. |
| final int version; |
| final List<ShaderDeclaration> declarations = []; |
| final List<ShaderMethod> _methods = []; |
| |
| /// Precision for integer variables. |
| int? integerPrecision; |
| |
| /// Precision floating point variables. |
| int? floatPrecision; |
| |
| /// Counter for generating unique name if name is not specified for attribute. |
| int _attribCounter = 0; |
| |
| /// Counter for generating unique name if name is not specified for varying. |
| int _varyingCounter = 0; |
| |
| /// Counter for generating unique name if name is not specified for uniform. |
| int _uniformCounter = 0; |
| |
| /// Counter for generating unique name if name is not specified for constant. |
| int _constCounter = 0; |
| |
| final bool isWebGl2; |
| final bool _isFragmentShader; |
| |
| static const String kOpenGlEs3Header = '#version 300 es'; |
| |
| /// Lazily allocated fragment color output. |
| ShaderDeclaration? _fragmentColorDeclaration; |
| |
| ShaderBuilder(this.version) : isWebGl2 = version == WebGLVersion.webgl2, |
| _isFragmentShader = false; |
| |
| ShaderBuilder.fragment(this.version) : |
| isWebGl2 = version == WebGLVersion.webgl2, |
| _isFragmentShader = true; |
| |
| /// Returns fragment color declaration for fragment shader. |
| /// |
| /// This is hard coded for webgl1 as gl_FragColor. |
| ShaderDeclaration get fragmentColor { |
| _fragmentColorDeclaration ??= ShaderDeclaration( |
| isWebGl2 ? 'gFragColor' : 'gl_FragColor', |
| ShaderType.kVec4, |
| ShaderStorageQualifier.kVarying); |
| return _fragmentColorDeclaration!; |
| } |
| |
| /// Adds an attribute. |
| /// |
| /// The attribute variable is assigned a value from a object buffer as a |
| /// series of graphics primitives are rendered. The value is only accessible |
| /// in the vertex shader. |
| ShaderDeclaration addIn(int dataType, {String? name}) { |
| ShaderDeclaration attrib = ShaderDeclaration( |
| name ?? 'attr_${_attribCounter++}', |
| dataType, |
| ShaderStorageQualifier.kAttribute); |
| declarations.add(attrib); |
| return attrib; |
| } |
| |
| /// Adds a constant. |
| ShaderDeclaration addConst(int dataType, String value, {String? name}) { |
| ShaderDeclaration declaration = ShaderDeclaration.constant( |
| name ?? 'c_${_constCounter++}', dataType, value); |
| declarations.add(declaration); |
| return declaration; |
| } |
| |
| /// Adds a uniform variable. |
| /// |
| /// The variable is assigned a value before a gl.draw call. |
| /// It is accessible in both the vertex and fragment shaders. |
| /// |
| ShaderDeclaration addUniform(int dataType, {String? name}) { |
| ShaderDeclaration uniform = ShaderDeclaration( |
| name ?? 'uni_${_uniformCounter++}', |
| dataType, |
| ShaderStorageQualifier.kUniform); |
| declarations.add(uniform); |
| return uniform; |
| } |
| |
| /// Adds a varying variable. |
| /// |
| /// The variable is assigned a value by a vertex shader and |
| /// interpolated across the surface of a graphics primitive for each |
| /// input to a fragment shader. |
| /// It can be used in a fragment shader, but not changed. |
| ShaderDeclaration addOut(int dataType, {String? name}) { |
| ShaderDeclaration varying = ShaderDeclaration( |
| name ?? 'output_${_varyingCounter++}', |
| dataType, |
| ShaderStorageQualifier.kVarying); |
| declarations.add(varying); |
| return varying; |
| } |
| |
| void _writeVariableDeclaration(StringBuffer sb, ShaderDeclaration variable) { |
| switch (variable.storage) { |
| case ShaderStorageQualifier.kConst: |
| _buffer.write('const '); |
| break; |
| case ShaderStorageQualifier.kAttribute: |
| _buffer.write(isWebGl2 ? 'in ' |
| : _isFragmentShader ? 'varying ' : 'attribute '); |
| break; |
| case ShaderStorageQualifier.kUniform: |
| _buffer.write('uniform '); |
| break; |
| case ShaderStorageQualifier.kVarying: |
| _buffer.write(isWebGl2 ? 'out ' : 'varying '); |
| break; |
| } |
| _buffer.write('${typeToString(variable.dataType)} ${variable.name}'); |
| if (variable.storage == ShaderStorageQualifier.kConst) { |
| _buffer.write(' = ${variable.constValue}'); |
| } |
| _buffer.writeln(';'); |
| } |
| |
| final StringBuffer _buffer = StringBuffer(); |
| |
| static String typeToString(int dataType) { |
| switch (dataType) { |
| case ShaderType.kBool: |
| return 'bool'; |
| case ShaderType.kInt: |
| return 'int'; |
| case ShaderType.kFloat: |
| return 'float'; |
| case ShaderType.kBVec2: |
| return 'bvec2'; |
| case ShaderType.kBVec3: |
| return 'bvec3'; |
| case ShaderType.kBVec4: |
| return 'bvec4'; |
| case ShaderType.kIVec2: |
| return 'ivec2'; |
| case ShaderType.kIVec3: |
| return 'ivec3'; |
| case ShaderType.kIVec4: |
| return 'ivec4'; |
| case ShaderType.kVec2: |
| return 'vec2'; |
| case ShaderType.kVec3: |
| return 'vec3'; |
| case ShaderType.kVec4: |
| return 'vec4'; |
| case ShaderType.kMat2: |
| return 'mat2'; |
| case ShaderType.kMat3: |
| return 'mat3'; |
| case ShaderType.kMat4: |
| return 'mat4'; |
| case ShaderType.kSampler1D: |
| return 'sampler1D'; |
| case ShaderType.kSampler2D: |
| return 'sampler2D'; |
| case ShaderType.kSampler3D: |
| return 'sampler3D'; |
| case ShaderType.kVoid: |
| return 'void'; |
| } |
| throw ArgumentError(); |
| } |
| |
| ShaderMethod addMethod(String name) { |
| final ShaderMethod method = ShaderMethod(name); |
| _methods.add(method); |
| return method; |
| } |
| |
| String build() { |
| // Write header. |
| if (isWebGl2) { |
| _buffer.writeln(kOpenGlEs3Header); |
| } |
| // Write optional precision. |
| if (integerPrecision != null) { |
| _buffer |
| .writeln('precision ${_precisionToString(integerPrecision!)} int;'); |
| } |
| if (floatPrecision != null) { |
| _buffer |
| .writeln('precision ${_precisionToString(floatPrecision!)} float;'); |
| } |
| if (isWebGl2 && _fragmentColorDeclaration != null) { |
| _writeVariableDeclaration(_buffer, _fragmentColorDeclaration!); |
| } |
| for (ShaderDeclaration decl in declarations) { |
| _writeVariableDeclaration(_buffer, decl); |
| } |
| for (ShaderMethod method in _methods) { |
| method.write(_buffer); |
| } |
| return _buffer.toString(); |
| } |
| |
| String _precisionToString(int precision) => precision == ShaderPrecision.kLow |
| ? 'lowp' |
| : precision == ShaderPrecision.kMedium ? 'mediump' : 'highp'; |
| } |
| |
| class ShaderMethod { |
| ShaderMethod(this.name); |
| |
| final String returnType = 'void'; |
| final String name; |
| final List<String> _statements = []; |
| int _indentLevel = 1; |
| |
| void indent() { |
| ++_indentLevel; |
| } |
| |
| void unindent() { |
| assert(_indentLevel != 1); |
| --_indentLevel; |
| } |
| |
| void addStatement(String statement) { |
| if (assertionsEnabled) { |
| _statements.add(' ' * _indentLevel + statement); |
| } else { |
| _statements.add(statement); |
| } |
| } |
| |
| void write(StringBuffer buffer) { |
| buffer.writeln('$returnType $name() {'); |
| for (String statement in _statements) { |
| buffer.writeln(statement); |
| } |
| buffer.writeln('}'); |
| } |
| } |
| |
| /// html webgl version qualifier constants. |
| abstract class WebGLVersion { |
| // WebGL 1.0 is based on OpenGL ES 2.0 / GLSL 1.00 |
| static const int webgl1 = 1; |
| // WebGL 2.0 is based on OpenGL ES 3.0 / GLSL 3.00 |
| static const int webgl2 = 2; |
| } |
| |
| /// WebGl Shader data types. |
| abstract class ShaderType { |
| // Basic types. |
| static const int kBool = 0; |
| static const int kInt = 1; |
| static const int kFloat = 2; |
| // Vector types. |
| static const int kBVec2 = 3; |
| static const int kBVec3 = 4; |
| static const int kBVec4 = 5; |
| static const int kIVec2 = 6; |
| static const int kIVec3 = 7; |
| static const int kIVec4 = 8; |
| static const int kVec2 = 9; |
| static const int kVec3 = 10; |
| static const int kVec4 = 11; |
| static const int kMat2 = 12; |
| static const int kMat3 = 13; |
| static const int kMat4 = 14; |
| // Textures. |
| static const int kSampler1D = 15; |
| static const int kSampler2D = 16; |
| static const int kSampler3D = 17; |
| // Other. |
| static const int kVoid = 18; |
| } |
| |
| /// Precision of int and float types. |
| /// |
| /// Integers: 8 bit, 10 bit and 16 bits. |
| /// Float: 8 bit. 14 bit and 62 bits. |
| abstract class ShaderPrecision { |
| static const int kLow = 0; |
| static const int kMedium = 1; |
| static const int kHigh = 2; |
| } |
| |
| /// GL Variable storage qualifiers. |
| abstract class ShaderStorageQualifier { |
| static const int kConst = 0; |
| static const int kAttribute = 1; |
| static const int kUniform = 2; |
| static const int kVarying = 3; |
| } |
| |
| /// Shader variable and constant declaration. |
| class ShaderDeclaration { |
| final String name; |
| final int dataType; |
| final int storage; |
| final String constValue; |
| ShaderDeclaration(this.name, this.dataType, this.storage) |
| : assert(!_isGLSLReservedWord(name)), |
| constValue = ''; |
| |
| /// Constructs a constant. |
| ShaderDeclaration.constant(this.name, this.dataType, this.constValue) |
| : storage = ShaderStorageQualifier.kConst; |
| } |
| |
| // These are used only in debug mode to assert if used as variable name. |
| // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.10.pdf |
| const List<String> _kReservedWords = [ |
| 'attribute', |
| 'const', |
| 'uniform', |
| 'varying', |
| 'layout', |
| 'centroid', |
| 'flat', |
| 'smooth', |
| 'noperspective', |
| 'patch', 'sample', |
| 'break', 'continue', |
| 'do', 'for', 'while', 'switch', 'case', 'default', 'if', 'else', |
| 'subroutine', |
| 'in', 'out', 'inout', 'float', 'double', 'int', |
| 'void', |
| 'bool', 'true', 'false', |
| 'invariant', |
| 'discard', 'return', |
| 'mat2', 'mat3', 'mat4', 'dmat2', 'dmat3', 'dmat4', |
| 'mat2x2', 'mat2x3', 'mat2x4', 'dmat2x2', 'dmat2x3', 'dmat2x4', |
| 'mat3x2', 'mat3x3', 'mat3x4', 'dmat3x2', 'dmat3x3', 'dmat3x4', |
| 'mat4x2', 'mat4x3', 'mat4x4', 'dmat4x2', 'dmat4x3', 'dmat4x4', |
| 'vec2', 'vec3', 'vec4', 'ivec2', 'ivec3', 'ivec4', 'bvec2', 'bvec3', 'bvec4', |
| 'dvec2', 'dvec3', 'dvec4', |
| 'uint', 'uvec2', 'uvec3', 'uvec4', |
| 'lowp', 'mediump', 'highp', 'precision', |
| 'sampler1D', 'sampler2D', 'sampler3D', 'samplerCube', |
| 'sampler1DShadow', 'sampler2DShadow', 'samplerCubeShadow', |
| 'sampler1DArray', 'sampler2DArray', |
| 'sampler1DArrayShadow', 'sampler2DArrayShadow', |
| 'isampler1D', 'isampler2D', 'isampler3D', 'isamplerCube', |
| 'isampler1DArray', 'isampler2DArray', |
| 'usampler1D', 'usampler2D', 'usampler3D', 'usamplerCube', |
| 'usampler1DArray', 'usampler2DArray', |
| 'sampler2DRect', 'sampler2DRectShadow', 'isampler2DRect', 'usampler2DRect', |
| 'samplerBuffer', 'isamplerBuffer', 'usamplerBuffer', |
| 'sampler2DMS', 'isampler2DMS', 'usampler2DMS', |
| 'sampler2DMSArray', 'isampler2DMSArray', 'usampler2DMSArray', |
| 'samplerCubeArray', 'samplerCubeArrayShadow', 'isamplerCubeArray', |
| 'usamplerCubeArray', |
| 'struct', |
| 'texture', |
| |
| // Reserved for future use, see |
| // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.10.pdf |
| 'active', 'asm', 'cast', 'class', 'common', 'enum', 'extern', 'external', |
| 'filter', 'fixed', 'fvec2', 'fvec3', 'fvec4', 'goto', 'half', 'hvec2', |
| 'hvec3', 'hvec4', 'iimage1D', 'iimage1DArray', 'iimage2D', 'iimage2DArray', |
| 'iimage3D', 'iimageBuffer', 'iimageCube', 'image1D', 'image1DArray', |
| 'image1DArrayShadow', 'image1DShadow', 'image2D', 'image2DArray', |
| 'image2DArrayShadow', 'image2DShadow', 'image3D', 'imageBuffer', |
| 'imageCube', 'inline', 'input', 'interface', 'long', |
| 'namespace', 'noinline', 'output', 'packed', 'partition', 'public', |
| 'row_majo', 'short', 'sizeof', 'static', 'superp', 'template', 'this', |
| 'typedef', 'uimage1D', 'uimage1DArray', 'uimage2D', 'uimage2DArray', |
| 'uimage3D', 'uimageBuffer', 'uimageCube', 'union', 'unsigned', |
| 'using', 'volatile', |
| ]; |
| |
| bool _isGLSLReservedWord(String name) { |
| return _kReservedWords.contains(name); |
| } |