// Copyright (C) 2020 Jérôme Leclercq // This file is part of the "Nazara Engine - Shader generator" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include #include #include namespace Nz { namespace { static const char* flipYUniformName = "_NzFlipValue"; static const char* overridenMain = "_NzMain"; //FIXME: Have this only once std::unordered_map s_entryPoints = { { "frag", ShaderStageType::Fragment }, { "vert", ShaderStageType::Vertex }, }; struct PreVisitor : ShaderAst::AstCloner { using AstCloner::Clone; ShaderAst::StatementPtr Clone(ShaderAst::DeclareFunctionStatement& node) override { auto clone = AstCloner::Clone(node); assert(clone->GetType() == ShaderAst::NodeType::DeclareFunctionStatement); ShaderAst::DeclareFunctionStatement* func = static_cast(clone.get()); bool hasEntryPoint = false; for (auto& attribute : func->attributes) { if (attribute.type == ShaderAst::AttributeType::Entry) { auto it = s_entryPoints.find(std::get(attribute.args)); assert(it != s_entryPoints.end()); if (it->second == selectedEntryPoint) { hasEntryPoint = true; break; } } } if (!hasEntryPoint) return ShaderBuilder::NoOp(); entryPoint = func; if (func->name == "main") func->name = "_NzMain"; return clone; } ShaderStageType selectedEntryPoint; ShaderAst::DeclareFunctionStatement* entryPoint = nullptr; }; struct EntryFuncResolver : ShaderAst::AstScopedVisitor { void Visit(ShaderAst::DeclareFunctionStatement& node) override { if (&node != entryPoint) return; assert(node.parameters.size() == 1); const ShaderAst::ExpressionType& inputType = node.parameters.front().type; const ShaderAst::ExpressionType& outputType = node.returnType; const Identifier* identifier; assert(IsIdentifierType(node.parameters.front().type)); identifier = FindIdentifier(std::get(inputType).name); assert(identifier); inputIdentifier = *identifier; assert(IsIdentifierType(outputType)); identifier = FindIdentifier(std::get(outputType).name); assert(identifier); outputIdentifier = *identifier; } Identifier inputIdentifier; Identifier outputIdentifier; ShaderAst::DeclareFunctionStatement* entryPoint; }; struct Builtin { std::string identifier; ShaderStageTypeFlags stageFlags; }; std::unordered_map builtinMapping = { { "position", { "gl_Position", ShaderStageType::Vertex } } }; } struct GlslWriter::State { const States* states = nullptr; ShaderAst::DeclareFunctionStatement* entryFunc = nullptr; std::stringstream stream; unsigned int indentLevel = 0; }; GlslWriter::GlslWriter() : m_currentState(nullptr) { } std::string GlslWriter::Generate(ShaderStageType shaderStage, ShaderAst::StatementPtr& shader, const States& conditions) { State state; m_currentState = &state; CallOnExit onExit([this]() { m_currentState = nullptr; }); std::string error; if (!ShaderAst::ValidateAst(shader, &error)) throw std::runtime_error("Invalid shader AST: " + error); PreVisitor previsitor; previsitor.selectedEntryPoint = shaderStage; ShaderAst::StatementPtr adaptedShader = previsitor.Clone(shader); if (!previsitor.entryPoint) throw std::runtime_error("missing entry point"); state.entryFunc = previsitor.entryPoint; unsigned int glslVersion; if (m_environment.glES) { if (m_environment.glMajorVersion >= 3 && m_environment.glMinorVersion >= 2) glslVersion = 320; else if (m_environment.glMajorVersion >= 3 && m_environment.glMinorVersion >= 1) glslVersion = 310; else if (m_environment.glMajorVersion >= 3) glslVersion = 300; else if (m_environment.glMajorVersion >= 2) glslVersion = 100; else throw std::runtime_error("This version of OpenGL ES does not support shaders"); } else { if (m_environment.glMajorVersion >= 3 && m_environment.glMinorVersion >= 3) glslVersion = m_environment.glMajorVersion * 100 + m_environment.glMinorVersion * 10; else if (m_environment.glMajorVersion >= 3 && m_environment.glMinorVersion >= 2) glslVersion = 150; else if (m_environment.glMajorVersion >= 3 && m_environment.glMinorVersion >= 1) glslVersion = 140; else if (m_environment.glMajorVersion >= 3) glslVersion = 130; else if (m_environment.glMajorVersion >= 2 && m_environment.glMinorVersion >= 1) glslVersion = 120; else if (m_environment.glMajorVersion >= 2) glslVersion = 110; else throw std::runtime_error("This version of OpenGL does not support shaders"); } // Header Append("#version "); Append(glslVersion); if (m_environment.glES) Append(" es"); AppendLine(); AppendLine(); // Extensions std::vector requiredExtensions; if (!m_environment.glES && m_environment.extCallback) { // GL_ARB_shading_language_420pack (required for layout(binding = X)) if (glslVersion < 420 && HasExplicitBinding(adaptedShader)) { if (m_environment.extCallback("GL_ARB_shading_language_420pack")) requiredExtensions.emplace_back("GL_ARB_shading_language_420pack"); } // GL_ARB_separate_shader_objects (required for layout(location = X)) if (glslVersion < 410 && HasExplicitLocation(adaptedShader)) { if (m_environment.extCallback("GL_ARB_separate_shader_objects")) requiredExtensions.emplace_back("GL_ARB_separate_shader_objects"); } } if (!requiredExtensions.empty()) { for (const std::string& ext : requiredExtensions) AppendLine("#extension " + ext + " : require"); AppendLine(); } if (m_environment.glES) { AppendLine("#if GL_FRAGMENT_PRECISION_HIGH"); AppendLine("precision highp float;"); AppendLine("#else"); AppendLine("precision mediump float;"); AppendLine("#endif"); AppendLine(); } PushScope(); { adaptedShader->Visit(*this); // Append true GLSL entry point AppendEntryPoint(shaderStage, adaptedShader); } PopScope(); return state.stream.str(); } void GlslWriter::SetEnv(Environment environment) { m_environment = std::move(environment); } const char* GlslWriter::GetFlipYUniformName() { return flipYUniformName; } void GlslWriter::Append(const ShaderAst::ExpressionType& type) { std::visit([&](auto&& arg) { Append(arg); }, type); } void GlslWriter::Append(ShaderAst::BuiltinEntry builtin) { switch (builtin) { case ShaderAst::BuiltinEntry::VertexPosition: Append("gl_Position"); break; } } void GlslWriter::Append(const ShaderAst::IdentifierType& identifierType) { Append(identifierType.name); } void GlslWriter::Append(const ShaderAst::MatrixType& matrixType) { if (matrixType.columnCount == matrixType.rowCount) { Append("mat"); Append(matrixType.columnCount); } else { Append("mat"); Append(matrixType.columnCount); Append("x"); Append(matrixType.rowCount); } } void GlslWriter::Append(ShaderAst::PrimitiveType type) { switch (type) { case ShaderAst::PrimitiveType::Boolean: return Append("bool"); case ShaderAst::PrimitiveType::Float32: return Append("float"); case ShaderAst::PrimitiveType::Int32: return Append("ivec2"); case ShaderAst::PrimitiveType::UInt32: return Append("uint"); } } void GlslWriter::Append(const ShaderAst::SamplerType& samplerType) { switch (samplerType.sampledType) { case ShaderAst::PrimitiveType::Boolean: case ShaderAst::PrimitiveType::Float32: break; case ShaderAst::PrimitiveType::Int32: Append("i"); break; case ShaderAst::PrimitiveType::UInt32: Append("u"); break; } Append("sampler"); switch (samplerType.dim) { case ImageType_1D: Append("1D"); break; case ImageType_1D_Array: Append("1DArray"); break; case ImageType_2D: Append("2D"); break; case ImageType_2D_Array: Append("2DArray"); break; case ImageType_3D: Append("3D"); break; case ImageType_Cubemap: Append("Cube"); break; } } void GlslWriter::Append(const ShaderAst::StructType& structType) { throw std::runtime_error("unexpected struct type"); } void GlslWriter::Append(const ShaderAst::UniformType& uniformType) { /* TODO */ } void GlslWriter::Append(const ShaderAst::VectorType& vecType) { switch (vecType.type) { case ShaderAst::PrimitiveType::Boolean: Append("b"); break; case ShaderAst::PrimitiveType::Float32: break; case ShaderAst::PrimitiveType::Int32: Append("i"); break; case ShaderAst::PrimitiveType::UInt32: Append("u"); break; } Append("vec"); Append(vecType.componentCount); } void GlslWriter::Append(ShaderAst::MemoryLayout layout) { switch (layout) { case ShaderAst::MemoryLayout::Std140: Append("std140"); break; } } void GlslWriter::Append(ShaderAst::NoType) { return Append("void"); } template void GlslWriter::Append(const T& param) { NazaraAssert(m_currentState, "This function should only be called while processing an AST"); m_currentState->stream << param; } template void GlslWriter::Append(const T1& firstParam, const T2& secondParam, Args&&... params) { Append(firstParam); Append(secondParam, std::forward(params)...); } void GlslWriter::AppendCommentSection(const std::string& section) { NazaraAssert(m_currentState, "This function should only be called while processing an AST"); std::string stars((section.size() < 33) ? (36 - section.size()) / 2 : 3, '*'); m_currentState->stream << "/*" << stars << ' ' << section << ' ' << stars << "*/"; AppendLine(); } void GlslWriter::AppendEntryPoint(ShaderStageType shaderStage, ShaderAst::StatementPtr& shader) { EntryFuncResolver entryResolver; entryResolver.entryPoint = m_currentState->entryFunc; entryResolver.ScopedVisit(shader); AppendLine(); AppendLine("// Entry point handling"); struct InOutField { std::string name; std::string targetName; }; std::vector inputFields; const ShaderAst::StructDescription* inputStruct = nullptr; auto HandleInOutStructs = [this, shaderStage](const Identifier& identifier, std::vector& fields, const char* keyword, const char* fromPrefix, const char* targetPrefix) -> const ShaderAst::StructDescription* { assert(std::holds_alternative(identifier.value)); const auto& s = std::get(identifier.value); for (const auto& member : s.members) { bool skip = false; std::optional builtinName; std::optional attributeLocation; for (const auto& [attributeType, attributeParam] : member.attributes) { if (attributeType == ShaderAst::AttributeType::Builtin) { auto it = builtinMapping.find(std::get(attributeParam)); if (it != builtinMapping.end()) { const Builtin& builtin = it->second; if (!builtin.stageFlags.Test(shaderStage)) { skip = true; break; } builtinName = builtin.identifier; break; } } else if (attributeType == ShaderAst::AttributeType::Location) { attributeLocation = std::get(attributeParam); break; } } if (!skip && attributeLocation) { Append("layout(location = "); Append(*attributeLocation); Append(") "); Append(keyword); Append(" "); Append(member.type); Append(" "); Append(targetPrefix); Append(member.name); AppendLine(";"); fields.push_back({ fromPrefix + member.name, targetPrefix + member.name }); } else if (builtinName) { fields.push_back({ fromPrefix + member.name, *builtinName }); } } AppendLine(); return &s; }; if (!m_currentState->entryFunc->parameters.empty()) inputStruct = HandleInOutStructs(entryResolver.inputIdentifier, inputFields, "in", "_nzInput.", "_NzIn_"); std::vector outputFields; const ShaderAst::StructDescription* outputStruct = nullptr; if (!IsNoType(m_currentState->entryFunc->returnType)) outputStruct = HandleInOutStructs(entryResolver.outputIdentifier, outputFields, "out", "_nzOutput.", "_NzOut_"); if (shaderStage == ShaderStageType::Vertex && m_environment.flipYPosition) AppendLine("uniform float ", flipYUniformName, ";"); AppendLine("void main()"); EnterScope(); { if (inputStruct) { Append(inputStruct->name); AppendLine(" _nzInput;"); for (const auto& [name, targetName] : inputFields) { AppendLine(name, " = ", targetName, ";"); } AppendLine(); } if (outputStruct) Append(outputStruct->name, " _nzOutput = "); Append(m_currentState->entryFunc->name); Append("("); if (m_currentState->entryFunc) Append("_nzInput"); Append(");"); if (outputStruct) { AppendLine(); for (const auto& [name, targetName] : outputFields) { bool isOutputPosition = (shaderStage == ShaderStageType::Vertex && m_environment.flipYPosition && targetName == "gl_Position"); AppendLine(); Append(targetName, " = ", name); if (isOutputPosition) Append(" * vec4(1.0, ", flipYUniformName, ", 1.0, 1.0)"); Append(";"); } } } LeaveScope(); } void GlslWriter::AppendField(const std::string& structName, const std::string* memberIdentifier, std::size_t remainingMembers) { Append("."); Append(memberIdentifier[0]); const Identifier* identifier = FindIdentifier(structName); assert(identifier); assert(std::holds_alternative(identifier->value)); const auto& s = std::get(identifier->value); auto memberIt = std::find_if(s.members.begin(), s.members.begin(), [&](const auto& field) { return field.name == memberIdentifier[0]; }); assert(memberIt != s.members.end()); const auto& member = *memberIt; if (remainingMembers > 1) AppendField(std::get(member.type).name, memberIdentifier + 1, remainingMembers - 1); } void GlslWriter::AppendLine(const std::string& txt) { NazaraAssert(m_currentState, "This function should only be called while processing an AST"); m_currentState->stream << txt << '\n' << std::string(m_currentState->indentLevel, '\t'); } template void GlslWriter::AppendLine(Args&&... params) { (Append(std::forward(params)), ...); AppendLine(); } void GlslWriter::EnterScope() { NazaraAssert(m_currentState, "This function should only be called while processing an AST"); m_currentState->indentLevel++; AppendLine("{"); } void GlslWriter::LeaveScope(bool skipLine) { NazaraAssert(m_currentState, "This function should only be called while processing an AST"); m_currentState->indentLevel--; AppendLine(); if (skipLine) AppendLine("}"); else Append("}"); } void GlslWriter::Visit(ShaderAst::ExpressionPtr& expr, bool encloseIfRequired) { bool enclose = encloseIfRequired && (GetExpressionCategory(*expr) != ShaderAst::ExpressionCategory::LValue); if (enclose) Append("("); expr->Visit(*this); if (enclose) Append(")"); } void GlslWriter::Visit(ShaderAst::AccessMemberIdentifierExpression& node) { Visit(node.structExpr, true); const ShaderAst::ExpressionType& exprType = node.structExpr->cachedExpressionType.value(); assert(IsIdentifierType(exprType)); AppendField(std::get(exprType).name, node.memberIdentifiers.data(), node.memberIdentifiers.size()); } void GlslWriter::Visit(ShaderAst::AssignExpression& node) { node.left->Visit(*this); switch (node.op) { case ShaderAst::AssignType::Simple: Append(" = "); break; } node.right->Visit(*this); } void GlslWriter::Visit(ShaderAst::BranchStatement& node) { bool first = true; for (const auto& statement : node.condStatements) { if (!first) Append("else "); Append("if ("); statement.condition->Visit(*this); AppendLine(")"); EnterScope(); PushScope(); statement.statement->Visit(*this); PopScope(); LeaveScope(); first = false; } if (node.elseStatement) { AppendLine("else"); EnterScope(); PushScope(); node.elseStatement->Visit(*this); PopScope(); LeaveScope(); } } void GlslWriter::Visit(ShaderAst::BinaryExpression& node) { Visit(node.left, true); switch (node.op) { case ShaderAst::BinaryType::Add: Append(" + "); break; case ShaderAst::BinaryType::Subtract: Append(" - "); break; case ShaderAst::BinaryType::Multiply: Append(" * "); break; case ShaderAst::BinaryType::Divide: Append(" / "); break; case ShaderAst::BinaryType::CompEq: Append(" == "); break; case ShaderAst::BinaryType::CompGe: Append(" >= "); break; case ShaderAst::BinaryType::CompGt: Append(" > "); break; case ShaderAst::BinaryType::CompLe: Append(" <= "); break; case ShaderAst::BinaryType::CompLt: Append(" < "); break; case ShaderAst::BinaryType::CompNe: Append(" != "); break; } Visit(node.right, true); } void GlslWriter::Visit(ShaderAst::CastExpression& node) { Append(node.targetType); Append("("); bool first = true; for (const auto& exprPtr : node.expressions) { if (!exprPtr) break; if (!first) m_currentState->stream << ", "; exprPtr->Visit(*this); first = false; } Append(")"); } void GlslWriter::Visit(ShaderAst::ConditionalExpression& node) { /*std::size_t conditionIndex = m_context.shader->FindConditionByName(node.conditionName); assert(conditionIndex != ShaderAst::InvalidCondition); if (TestBit(m_context.states->enabledConditions, conditionIndex)) Visit(node.truePath); else Visit(node.falsePath);*/ } void GlslWriter::Visit(ShaderAst::ConditionalStatement& node) { /*std::size_t conditionIndex = m_context.shader->FindConditionByName(node.conditionName); assert(conditionIndex != ShaderAst::InvalidCondition); if (TestBit(m_context.states->enabledConditions, conditionIndex)) Visit(node.statement);*/ } void GlslWriter::Visit(ShaderAst::ConstantExpression& node) { std::visit([&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) Append("i"); //< for ivec if constexpr (std::is_same_v) Append((arg) ? "true" : "false"); else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) Append(std::to_string(arg)); else if constexpr (std::is_same_v || std::is_same_v) Append("vec2(" + std::to_string(arg.x) + ", " + std::to_string(arg.y) + ")"); else if constexpr (std::is_same_v || std::is_same_v) Append("vec3(" + std::to_string(arg.x) + ", " + std::to_string(arg.y) + ", " + std::to_string(arg.z) + ")"); else if constexpr (std::is_same_v || std::is_same_v) Append("vec4(" + std::to_string(arg.x) + ", " + std::to_string(arg.y) + ", " + std::to_string(arg.z) + ", " + std::to_string(arg.w) + ")"); else static_assert(AlwaysFalse::value, "non-exhaustive visitor"); }, node.value); } void GlslWriter::Visit(ShaderAst::DeclareExternalStatement& node) { for (const auto& externalVar : node.externalVars) { std::optional bindingIndex; bool isStd140 = false; for (const auto& [attributeType, attributeParam] : externalVar.attributes) { if (attributeType == ShaderAst::AttributeType::Binding) bindingIndex = std::get(attributeParam); else if (attributeType == ShaderAst::AttributeType::Layout) { if (std::get(attributeParam) == "std140") isStd140 = true; } } if (bindingIndex) { Append("layout(binding = "); Append(*bindingIndex); if (isStd140) Append(", std140"); Append(") uniform "); if (IsUniformType(externalVar.type)) { Append("_NzBinding_"); AppendLine(externalVar.name); EnterScope(); { const Identifier* identifier = FindIdentifier(std::get(std::get(externalVar.type).containedType).name); assert(identifier); assert(std::holds_alternative(identifier->value)); const auto& s = std::get(identifier->value); bool first = true; for (const auto& [name, attribute, type] : s.members) { if (!first) AppendLine(); first = false; Append(type); Append(" "); Append(name); Append(";"); } } LeaveScope(false); } else Append(externalVar.type); Append(" "); Append(externalVar.name); AppendLine(";"); } } } void GlslWriter::Visit(ShaderAst::DeclareFunctionStatement& node) { NazaraAssert(m_currentState, "This function should only be called while processing an AST"); Append(node.returnType); Append(" "); Append(node.name); Append("("); for (std::size_t i = 0; i < node.parameters.size(); ++i) { if (i != 0) Append(", "); Append(node.parameters[i].type); Append(" "); Append(node.parameters[i].name); } Append(")\n"); EnterScope(); PushScope(); { for (auto& statement : node.statements) statement->Visit(*this); } PopScope(); LeaveScope(); } void GlslWriter::Visit(ShaderAst::DeclareStructStatement& node) { RegisterStruct(node.description); Append("struct "); AppendLine(node.description.name); EnterScope(); { bool first = true; for (const auto& [name, attribute, type] : node.description.members) { if (!first) AppendLine(); first = false; Append(type); Append(" "); Append(name); Append(";"); } } LeaveScope(false); AppendLine(";"); } void GlslWriter::Visit(ShaderAst::DeclareVariableStatement& node) { RegisterVariable(node.varName, node.varType); Append(node.varType); Append(" "); Append(node.varName); if (node.initialExpression) { Append(" = "); node.initialExpression->Visit(*this); } AppendLine(";"); } void GlslWriter::Visit(ShaderAst::DiscardStatement& /*node*/) { Append("discard;"); } void GlslWriter::Visit(ShaderAst::ExpressionStatement& node) { node.expression->Visit(*this); AppendLine(";"); } void GlslWriter::Visit(ShaderAst::IdentifierExpression& node) { Append(node.identifier); } void GlslWriter::Visit(ShaderAst::IntrinsicExpression& node) { switch (node.intrinsic) { case ShaderAst::IntrinsicType::CrossProduct: Append("cross"); break; case ShaderAst::IntrinsicType::DotProduct: Append("dot"); break; case ShaderAst::IntrinsicType::SampleTexture: Append("texture"); break; } Append("("); for (std::size_t i = 0; i < node.parameters.size(); ++i) { if (i != 0) Append(", "); node.parameters[i]->Visit(*this); } Append(")"); } void GlslWriter::Visit(ShaderAst::MultiStatement& node) { PushScope(); bool first = true; for (const ShaderAst::StatementPtr& statement : node.statements) { if (!first && statement->GetType() != ShaderAst::NodeType::NoOpStatement) AppendLine(); statement->Visit(*this); first = false; } PopScope(); } void GlslWriter::Visit(ShaderAst::NoOpStatement& /*node*/) { /* nothing to do */ } void GlslWriter::Visit(ShaderAst::ReturnStatement& node) { if (node.returnExpr) { Append("return "); node.returnExpr->Visit(*this); Append(";"); } else Append("return;"); } void GlslWriter::Visit(ShaderAst::SwizzleExpression& node) { Visit(node.expression, true); Append("."); for (std::size_t i = 0; i < node.componentCount; ++i) { switch (node.components[i]) { case ShaderAst::SwizzleComponent::First: Append("x"); break; case ShaderAst::SwizzleComponent::Second: Append("y"); break; case ShaderAst::SwizzleComponent::Third: Append("z"); break; case ShaderAst::SwizzleComponent::Fourth: Append("w"); break; } } } bool GlslWriter::HasExplicitBinding(ShaderAst::StatementPtr& shader) { /*for (const auto& uniform : shader.GetUniforms()) { if (uniform.bindingIndex.has_value()) return true; }*/ return false; } bool GlslWriter::HasExplicitLocation(ShaderAst::StatementPtr& shader) { /*for (const auto& input : shader.GetInputs()) { if (input.locationIndex.has_value()) return true; } for (const auto& output : shader.GetOutputs()) { if (output.locationIndex.has_value()) return true; }*/ return false; } }