/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "CodeGenerator.h" #include "generated/shaders.h" #include #include namespace filamat { // From driverEnum namespace using namespace filament; using namespace backend; using namespace utils; io::sstream& CodeGenerator::generateSeparator(io::sstream& out) const { out << '\n'; return out; } io::sstream& CodeGenerator::generateProlog(io::sstream& out, ShaderType type, bool hasExternalSamplers) const { assert(mShaderModel != ShaderModel::UNKNOWN); switch (mShaderModel) { case ShaderModel::UNKNOWN: break; case ShaderModel::GL_ES_30: // Vulkan requires version 310 or higher if (mTargetLanguage == TargetLanguage::SPIRV) { // Vulkan requires layout locations on ins and outs, which were not supported // in the OpenGL 4.1 GLSL profile. out << "#version 310 es\n\n"; } else { out << "#version 300 es\n\n"; } if (hasExternalSamplers) { out << "#extension GL_OES_EGL_image_external_essl3 : require\n\n"; } out << "#define TARGET_MOBILE\n"; break; case ShaderModel::GL_CORE_41: if (mTargetLanguage == TargetLanguage::SPIRV) { // Vulkan requires binding specifiers on uniforms and samplers, which were not // supported in the OpenGL 4.1 GLSL profile. out << "#version 450 core\n\n"; } else { out << "#version 410 core\n\n"; } break; } // This allows our includer system to use the #line directive to denote the source file for // #included code. This way, glslang reports errors more accurately. out << "#extension GL_GOOGLE_cpp_style_line_directive : enable\n\n"; if (mTargetApi == TargetApi::VULKAN) { out << "#define TARGET_VULKAN_ENVIRONMENT\n"; } if (mTargetApi == TargetApi::METAL) { out << "#define TARGET_METAL_ENVIRONMENT\n"; } if (mTargetLanguage == TargetLanguage::SPIRV) { out << "#define TARGET_LANGUAGE_SPIRV\n"; } out << '\n'; out << "#define FILAMENT_QUALITY_LOW 0\n"; out << "#define FILAMENT_QUALITY_HIGH 1\n"; out << "#if defined(TARGET_MOBILE)\n"; out << "#define FILAMENT_QUALITY FILAMENT_QUALITY_LOW\n"; out << "#else\n"; out << "#define FILAMENT_QUALITY FILAMENT_QUALITY_HIGH\n"; out << "#endif\n"; out << '\n'; Precision defaultPrecision = getDefaultPrecision(type); const char* precision = getPrecisionQualifier(defaultPrecision, Precision::DEFAULT); out << "precision " << precision << " float;\n"; out << "precision " << precision << " int;\n"; out << "precision lowp sampler2DArray;\n"; out << "precision lowp sampler3D;\n"; out << SHADERS_COMMON_TYPES_FS_DATA; out << "\n"; return out; } Precision CodeGenerator::getDefaultPrecision(ShaderType type) const { if (type == ShaderType::VERTEX) { return Precision::HIGH; } else if (type == ShaderType::FRAGMENT) { if (mShaderModel < ShaderModel::GL_CORE_41) { return Precision::MEDIUM; } else { return Precision::HIGH; } } return Precision::HIGH; } Precision CodeGenerator::getDefaultUniformPrecision() const { if (mShaderModel < ShaderModel::GL_CORE_41) { return Precision::MEDIUM; } else { return Precision::HIGH; } } io::sstream& CodeGenerator::generateEpilog(io::sstream& out) const { out << "\n"; // For line compression all shaders finish with a newline character. return out; } io::sstream& CodeGenerator::generateShaderMain(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { out << SHADERS_MAIN_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_MAIN_FS_DATA; } return out; } io::sstream& CodeGenerator::generatePostProcessMain(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { out << SHADERS_POST_PROCESS_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_POST_PROCESS_FS_DATA; } return out; } io::sstream& CodeGenerator::generateVariable(io::sstream& out, ShaderType type, const CString& name, size_t index) const { if (!name.empty()) { if (type == ShaderType::VERTEX) { out << "\n#define VARIABLE_CUSTOM" << index << " " << name.c_str() << "\n"; out << "\n#define VARIABLE_CUSTOM_AT" << index << " variable_" << name.c_str() << "\n"; out << "LAYOUT_LOCATION(" << index << ") out vec4 variable_" << name.c_str() << ";\n"; } else if (type == ShaderType::FRAGMENT) { out << "\nLAYOUT_LOCATION(" << index << ") in highp vec4 variable_" << name.c_str() << ";\n"; } } return out; } io::sstream& CodeGenerator::generateShaderInputs(io::sstream& out, ShaderType type, const AttributeBitset& attributes, Interpolation interpolation) const { const char* shading = getInterpolationQualifier(interpolation); out << "#define SHADING_INTERPOLATION " << shading << "\n"; bool hasTangents = attributes.test(VertexAttribute::TANGENTS); generateDefine(out, "HAS_ATTRIBUTE_TANGENTS", hasTangents); bool hasColor = attributes.test(VertexAttribute::COLOR); generateDefine(out, "HAS_ATTRIBUTE_COLOR", hasColor); bool hasUV0 = attributes.test(VertexAttribute::UV0); generateDefine(out, "HAS_ATTRIBUTE_UV0", hasUV0); bool hasUV1 = attributes.test(VertexAttribute::UV1); generateDefine(out, "HAS_ATTRIBUTE_UV1", hasUV1); bool hasBoneIndices = attributes.test(VertexAttribute::BONE_INDICES); generateDefine(out, "HAS_ATTRIBUTE_BONE_INDICES", hasBoneIndices); bool hasBoneWeights = attributes.test(VertexAttribute::BONE_WEIGHTS); generateDefine(out, "HAS_ATTRIBUTE_BONE_WEIGHTS", hasBoneWeights); for (int i = 0; i < MAX_CUSTOM_ATTRIBUTES; i++) { bool hasCustom = attributes.test(VertexAttribute::CUSTOM0 + i); if (hasCustom) { generateIndexedDefine(out, "HAS_ATTRIBUTE_CUSTOM", i, 1); } } if (type == ShaderType::VERTEX) { out << "\n"; generateDefine(out, "LOCATION_POSITION", uint32_t(VertexAttribute::POSITION)); if (hasTangents) { generateDefine(out, "LOCATION_TANGENTS", uint32_t(VertexAttribute::TANGENTS)); } if (hasUV0) { generateDefine(out, "LOCATION_UV0", uint32_t(VertexAttribute::UV0)); } if (hasUV1) { generateDefine(out, "LOCATION_UV1", uint32_t(VertexAttribute::UV1)); } if (hasColor) { generateDefine(out, "LOCATION_COLOR", uint32_t(VertexAttribute::COLOR)); } if (hasBoneIndices) { generateDefine(out, "LOCATION_BONE_INDICES", uint32_t(VertexAttribute::BONE_INDICES)); } if (hasBoneWeights) { generateDefine(out, "LOCATION_BONE_WEIGHTS", uint32_t(VertexAttribute::BONE_WEIGHTS)); } for (int i = 0; i < MAX_CUSTOM_ATTRIBUTES; i++) { if (attributes.test(VertexAttribute::CUSTOM0 + i)) { generateIndexedDefine(out, "LOCATION_CUSTOM", i, uint32_t(VertexAttribute::CUSTOM0) + i); } } out << SHADERS_INPUTS_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_INPUTS_FS_DATA; } return out; } utils::io::sstream& CodeGenerator::generateOutput(utils::io::sstream& out, ShaderType type, const utils::CString& name, size_t index, MaterialBuilder::VariableQualifier qualifier, MaterialBuilder::OutputType outputType) const { if (name.empty() || type == ShaderType::VERTEX) { return out; } // TODO: add and support additional variable qualifiers (void) qualifier; assert(qualifier == MaterialBuilder::VariableQualifier::OUT); const char* typeString = getOutputTypeName(outputType); out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str() << "\n"; out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str() << "\n"; out << "\n#define FRAG_OUTPUT_TYPE" << index << " " << typeString << "\n"; out << "layout(location=" << index << ") out " << typeString << " output_" << name.c_str() << ";\n"; return out; } io::sstream& CodeGenerator::generateDepthShaderMain(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { out << SHADERS_DEPTH_MAIN_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_DEPTH_MAIN_FS_DATA; } return out; } const char* CodeGenerator::getUniformPrecisionQualifier(UniformType type, Precision precision, Precision uniformPrecision, Precision defaultPrecision) const noexcept { if (!hasPrecision(type)) { return ""; } if (precision == Precision::DEFAULT) { precision = uniformPrecision; } return getPrecisionQualifier(precision, defaultPrecision); } io::sstream& CodeGenerator::generateUniforms(io::sstream& out, ShaderType shaderType, uint8_t binding, const UniformInterfaceBlock& uib) const { auto const& infos = uib.getUniformInfoList(); if (infos.empty()) { return out; } const CString& blockName = uib.getName(); std::string instanceName(uib.getName().c_str()); instanceName.front() = char(std::tolower((unsigned char)instanceName.front())); Precision uniformPrecision = getDefaultUniformPrecision(); Precision defaultPrecision = getDefaultPrecision(shaderType); out << "\nlayout("; if (mTargetLanguage == TargetLanguage::SPIRV) { uint32_t bindingIndex = (uint32_t) binding; // avoid char output out << "binding = " << bindingIndex << ", "; } out << "std140) uniform " << blockName.c_str() << " {\n"; for (auto const& info : infos) { char const* const type = getUniformTypeName(info.type); char const* const precision = getUniformPrecisionQualifier(info.type, info.precision, uniformPrecision, defaultPrecision); out << " " << precision; if (precision[0] != '\0') out << " "; out << type << " " << info.name.c_str(); if (info.size > 1) { out << "[" << info.size << "]"; } out << ";\n"; } out << "} " << instanceName << ";\n"; return out; } io::sstream& CodeGenerator::generateSamplers( io::sstream& out, uint8_t firstBinding, const SamplerInterfaceBlock& sib) const { auto const& infos = sib.getSamplerInfoList(); if (infos.empty()) { return out; } for (auto const& info : infos) { CString uniformName = SamplerInterfaceBlock::getUniformName( sib.getName().c_str(), info.name.c_str()); auto type = info.type; if (type == SamplerType::SAMPLER_EXTERNAL && mShaderModel != ShaderModel::GL_ES_30) { // we're generating the shader for the desktop, where we assume external textures // are not supported, in which case we revert to texture2d type = SamplerType::SAMPLER_2D; } char const* const typeName = getSamplerTypeName(type, info.format, info.multisample); char const* const precision = getPrecisionQualifier(info.precision, Precision::DEFAULT); if (mTargetLanguage == TargetLanguage::SPIRV) { const uint32_t bindingIndex = (uint32_t) firstBinding + info.offset; out << "layout(binding = " << bindingIndex; // For Vulkan, we place uniforms in set 0 (the default set) and samplers in set 1. This // allows the sampler bindings to live in a separate "namespace" that starts at zero. // Note that the set specifier is not covered by the desktop GLSL spec, including // recent versions. It is only documented in the GL_KHR_vulkan_glsl extension. if (mTargetApi == TargetApi::VULKAN) { out << ", set = 1"; } out << ") "; } out << "uniform " << precision << " " << typeName << " " << uniformName.c_str(); out << ";\n"; } out << "\n"; return out; } utils::io::sstream& CodeGenerator::generateSubpass(utils::io::sstream& out, SubpassInfo subpass) const { if (!subpass.isValid) { return out; } CString subpassName = SamplerInterfaceBlock::getUniformName(subpass.block.c_str(), subpass.name.c_str()); char const* const typeName = "subpassInput"; // In our Vulkan backend, subpass inputs always live in descriptor set 2. (ignored for GLES) char const* const precision = getPrecisionQualifier(subpass.precision, Precision::DEFAULT); out << "layout(input_attachment_index = " << (int) subpass.attachmentIndex << ", set = 2, binding = " << (int) subpass.binding << ") "; out << "uniform " << precision << " " << typeName << " " << subpassName.c_str(); out << ";\n"; out << "\n"; return out; } void CodeGenerator::fixupExternalSamplers( std::string& shader, SamplerInterfaceBlock const& sib) noexcept { auto const& infos = sib.getSamplerInfoList(); if (infos.empty()) { return; } bool hasExternalSampler = false; // Replace sampler2D declarations by samplerExternal declarations as they may have // been swapped during a previous optimization step for (auto const& info : infos) { if (info.type == SamplerType::SAMPLER_EXTERNAL) { CString uniformName = SamplerInterfaceBlock::getUniformName( sib.getName().c_str(), info.name.c_str()); auto name = std::string("sampler2D ") + uniformName.c_str(); size_t index = shader.find(name); if (index != std::string::npos) { hasExternalSampler = true; auto newName = std::string("samplerExternalOES ") + uniformName.c_str(); shader.replace(index, name.size(), newName); } } } // This method should only be called on shaders that have external samplers but since // they may have been removed by previous optimization steps, we check again here if (hasExternalSampler) { // Find the #version line so we can insert the #extension directive size_t index = shader.find("#version"); index += 8; // Find the end of the line and skip the line return while (shader[index] != '\n') index++; index++; shader.insert(index, "#extension GL_OES_EGL_image_external_essl3 : require\n"); } } io::sstream& CodeGenerator::generateDefine(io::sstream& out, const char* name, bool value) const { if (value) { out << "#define " << name << "\n"; } return out; } io::sstream& CodeGenerator::generateDefine(io::sstream& out, const char* name, uint32_t value) const { out << "#define " << name << " " << value << "\n"; return out; } io::sstream& CodeGenerator::generateDefine(io::sstream& out, const char* name, const char* string) const { out << "#define " << name << " " << string << "\n"; return out; } io::sstream& CodeGenerator::generateIndexedDefine(io::sstream& out, const char* name, uint32_t index, uint32_t value) const { out << "#define " << name << index << " " << value << "\n"; return out; } io::sstream& CodeGenerator::generateMaterialProperty(io::sstream& out, MaterialBuilder::Property property, bool isSet) const { if (isSet) { out << "#define " << "MATERIAL_HAS_" << getConstantName(property) << "\n"; } return out; } io::sstream& CodeGenerator::generateCommon(io::sstream& out, ShaderType type) const { out << SHADERS_COMMON_MATH_FS_DATA; out << SHADERS_COMMON_SHADOWING_FS_DATA; if (type == ShaderType::VERTEX) { } else if (type == ShaderType::FRAGMENT) { out << SHADERS_COMMON_SHADING_FS_DATA; out << SHADERS_COMMON_GRAPHICS_FS_DATA; out << SHADERS_COMMON_MATERIAL_FS_DATA; } return out; } io::sstream& CodeGenerator::generateFog(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { } else if (type == ShaderType::FRAGMENT) { out << SHADERS_FOG_FS_DATA; } return out; } io::sstream& CodeGenerator::generateCommonMaterial(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { out << SHADERS_MATERIAL_INPUTS_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_MATERIAL_INPUTS_FS_DATA; } return out; } io::sstream& CodeGenerator::generatePostProcessInputs(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { out << SHADERS_POST_PROCESS_INPUTS_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_POST_PROCESS_INPUTS_FS_DATA; } return out; } utils::io::sstream& CodeGenerator::generatePostProcessGetters(utils::io::sstream& out, ShaderType type) const { out << SHADERS_COMMON_GETTERS_FS_DATA; if (type == ShaderType::VERTEX) { out << SHADERS_POST_PROCESS_GETTERS_VS_DATA; } return out; } io::sstream& CodeGenerator::generateGetters(io::sstream& out, ShaderType type) const { out << SHADERS_COMMON_GETTERS_FS_DATA; if (type == ShaderType::VERTEX) { out << SHADERS_GETTERS_VS_DATA; } else if (type == ShaderType::FRAGMENT) { out << SHADERS_GETTERS_FS_DATA; } return out; } io::sstream& CodeGenerator::generateParameters(io::sstream& out, ShaderType type) const { if (type == ShaderType::VERTEX) { } else if (type == ShaderType::FRAGMENT) { out << SHADERS_SHADING_PARAMETERS_FS_DATA; } return out; } io::sstream& CodeGenerator::generateShaderLit(io::sstream& out, ShaderType type, filament::Variant variant, filament::Shading shading) const { if (type == ShaderType::VERTEX) { } else if (type == ShaderType::FRAGMENT) { out << SHADERS_COMMON_LIGHTING_FS_DATA; if (variant.hasShadowReceiver()) { out << SHADERS_SHADOWING_FS_DATA; } out << SHADERS_BRDF_FS_DATA; switch (shading) { case Shading::UNLIT: assert("Lit shader generated with unlit shading model"); break; case Shading::SPECULAR_GLOSSINESS: case Shading::LIT: out << SHADERS_SHADING_MODEL_STANDARD_FS_DATA; break; case Shading::SUBSURFACE: out << SHADERS_SHADING_MODEL_SUBSURFACE_FS_DATA; break; case Shading::CLOTH: out << SHADERS_SHADING_MODEL_CLOTH_FS_DATA; break; } if (shading != Shading::UNLIT) { out << SHADERS_AMBIENT_OCCLUSION_FS_DATA; out << SHADERS_LIGHT_INDIRECT_FS_DATA; } if (variant.hasDirectionalLighting()) { out << SHADERS_LIGHT_DIRECTIONAL_FS_DATA; } if (variant.hasDynamicLighting()) { out << SHADERS_LIGHT_PUNCTUAL_FS_DATA; } out << SHADERS_SHADING_LIT_FS_DATA; } return out; } io::sstream& CodeGenerator::generateShaderUnlit(io::sstream& out, ShaderType type, filament::Variant variant, bool hasShadowMultiplier) const { if (type == ShaderType::VERTEX) { } else if (type == ShaderType::FRAGMENT) { if (hasShadowMultiplier) { if (variant.hasShadowReceiver()) { out << SHADERS_SHADOWING_FS_DATA; } } out << SHADERS_SHADING_UNLIT_FS_DATA; } return out; } /* static */ char const* CodeGenerator::getConstantName(MaterialBuilder::Property property) noexcept { using Property = MaterialBuilder::Property; switch (property) { case Property::BASE_COLOR: return "BASE_COLOR"; case Property::ROUGHNESS: return "ROUGHNESS"; case Property::METALLIC: return "METALLIC"; case Property::REFLECTANCE: return "REFLECTANCE"; case Property::AMBIENT_OCCLUSION: return "AMBIENT_OCCLUSION"; case Property::CLEAR_COAT: return "CLEAR_COAT"; case Property::CLEAR_COAT_ROUGHNESS: return "CLEAR_COAT_ROUGHNESS"; case Property::CLEAR_COAT_NORMAL: return "CLEAR_COAT_NORMAL"; case Property::ANISOTROPY: return "ANISOTROPY"; case Property::ANISOTROPY_DIRECTION: return "ANISOTROPY_DIRECTION"; case Property::THICKNESS: return "THICKNESS"; case Property::SUBSURFACE_POWER: return "SUBSURFACE_POWER"; case Property::SUBSURFACE_COLOR: return "SUBSURFACE_COLOR"; case Property::SHEEN_COLOR: return "SHEEN_COLOR"; case Property::SHEEN_ROUGHNESS: return "SHEEN_ROUGHNESS"; case Property::GLOSSINESS: return "GLOSSINESS"; case Property::SPECULAR_COLOR: return "SPECULAR_COLOR"; case Property::EMISSIVE: return "EMISSIVE"; case Property::NORMAL: return "NORMAL"; case Property::POST_LIGHTING_COLOR: return "POST_LIGHTING_COLOR"; case Property::CLIP_SPACE_TRANSFORM: return "CLIP_SPACE_TRANSFORM"; case Property::ABSORPTION: return "ABSORPTION"; case Property::TRANSMISSION: return "TRANSMISSION"; case Property::IOR: return "IOR"; case Property::MICRO_THICKNESS: return "MICRO_THICKNESS"; case Property::BENT_NORMAL: return "BENT_NORMAL"; } } char const* CodeGenerator::getUniformTypeName(UniformInterfaceBlock::Type type) noexcept { using Type = UniformInterfaceBlock::Type; switch (type) { case Type::BOOL: return "bool"; case Type::BOOL2: return "bvec2"; case Type::BOOL3: return "bvec3"; case Type::BOOL4: return "bvec4"; case Type::FLOAT: return "float"; case Type::FLOAT2: return "vec2"; case Type::FLOAT3: return "vec3"; case Type::FLOAT4: return "vec4"; case Type::INT: return "int"; case Type::INT2: return "ivec2"; case Type::INT3: return "ivec3"; case Type::INT4: return "ivec4"; case Type::UINT: return "uint"; case Type::UINT2: return "uvec2"; case Type::UINT3: return "uvec3"; case Type::UINT4: return "uvec4"; case Type::MAT3: return "mat3"; case Type::MAT4: return "mat4"; } } char const* CodeGenerator::getOutputTypeName(MaterialBuilder::OutputType type) noexcept { using Type = UniformInterfaceBlock::Type; switch (type) { case MaterialBuilder::OutputType::FLOAT: return "float"; case MaterialBuilder::OutputType::FLOAT2: return "float2"; case MaterialBuilder::OutputType::FLOAT3: return "float3"; case MaterialBuilder::OutputType::FLOAT4: return "float4"; } } char const* CodeGenerator::getSamplerTypeName(SamplerType type, SamplerFormat format, bool multisample) const noexcept { assert(!multisample); // multisample samplers not yet supported. switch (type) { case SamplerType::SAMPLER_2D: switch (format) { case SamplerFormat::INT: return "isampler2D"; case SamplerFormat::UINT: return "usampler2D"; case SamplerFormat::FLOAT: return "sampler2D"; case SamplerFormat::SHADOW: return "sampler2DShadow"; } case SamplerType::SAMPLER_3D: assert(format != SamplerFormat::SHADOW); switch (format) { case SamplerFormat::INT: return "isampler3D"; case SamplerFormat::UINT: return "usampler3D"; case SamplerFormat::FLOAT: return "sampler3D"; case SamplerFormat::SHADOW: return nullptr; } case SamplerType::SAMPLER_2D_ARRAY: switch (format) { case SamplerFormat::INT: return "isampler2DArray"; case SamplerFormat::UINT: return "usampler2DArray"; case SamplerFormat::FLOAT: return "sampler2DArray"; case SamplerFormat::SHADOW: return "sampler2DArrayShadow"; } case SamplerType::SAMPLER_CUBEMAP: switch (format) { case SamplerFormat::INT: return "isamplerCube"; case SamplerFormat::UINT: return "usamplerCube"; case SamplerFormat::FLOAT: return "samplerCube"; case SamplerFormat::SHADOW: return "samplerCubeShadow"; } case SamplerType::SAMPLER_EXTERNAL: assert(format != SamplerFormat::SHADOW); // Vulkan doesn't have external textures in the sense as GL. Vulkan external textures // are created via VK_ANDROID_external_memory_android_hardware_buffer, but they are // backed by VkImage just like a normal texture, and sampled from normally. return (mTargetLanguage == TargetLanguage::SPIRV) ? "sampler2D" : "samplerExternalOES"; } } char const* CodeGenerator::getInterpolationQualifier(Interpolation interpolation) noexcept { switch (interpolation) { case Interpolation::SMOOTH: return ""; case Interpolation::FLAT: return "flat "; } } /* static */ char const* CodeGenerator::getPrecisionQualifier(Precision precision, Precision defaultPrecision) noexcept { if (precision == defaultPrecision) { return ""; } switch (precision) { case Precision::LOW: return "lowp"; case Precision::MEDIUM: return "mediump"; case Precision::HIGH: return "highp"; case Precision::DEFAULT: return "ERROR"; } } /* static */ bool CodeGenerator::hasPrecision(UniformInterfaceBlock::Type type) noexcept { switch (type) { case UniformType::BOOL: case UniformType::BOOL2: case UniformType::BOOL3: case UniformType::BOOL4: return false; default: return true; } } } // namespace filamat