diff --git a/Graphics/HLSL2GLSLConverterLib/include/HLSL2GLSLConverterImpl.hpp b/Graphics/HLSL2GLSLConverterLib/include/HLSL2GLSLConverterImpl.hpp index 3f48e631e2..0b2d19114a 100644 --- a/Graphics/HLSL2GLSLConverterLib/include/HLSL2GLSLConverterImpl.hpp +++ b/Graphics/HLSL2GLSLConverterLib/include/HLSL2GLSLConverterImpl.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2024 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -257,6 +257,7 @@ class HLSL2GLSLConverterImpl TokenType Type = TokenType::Undefined; String Literal; String Delimiter; + size_t Idx = ~size_t{0}; void SetType(TokenType _Type) { @@ -303,19 +304,22 @@ class HLSL2GLSLConverterImpl const std::string::const_iterator& DelimStart, const std::string::const_iterator& DelimEnd, const std::string::const_iterator& LiteralStart, - const std::string::const_iterator& LiteralEnd) + const std::string::const_iterator& LiteralEnd, + size_t Idx) { - return TokenInfo{_Type, std::string{LiteralStart, LiteralEnd}, std::string{DelimStart, DelimEnd}}; + return TokenInfo{_Type, std::string{LiteralStart, LiteralEnd}, std::string{DelimStart, DelimEnd}, Idx}; } TokenInfo() {} TokenInfo(TokenType _Type, std::string _Literal, - std::string _Delimiter = "") : + std::string _Delimiter = "", + size_t _Idx = ~size_t{0}) : Type{_Type}, Literal{std::move(_Literal)}, - Delimiter{std::move(_Delimiter)} + Delimiter{std::move(_Delimiter)}, + Idx{_Idx} {} size_t GetDelimiterLen() const @@ -402,6 +406,8 @@ class HLSL2GLSLConverterImpl const HLSLObjectInfo* FindHLSLObject(const String& Name); + void ParseGlobalPreprocessorDefines(); + void ProcessShaderDeclaration(TokenListType::iterator EntryPointToken, SHADER_TYPE ShaderType); void ProcessObjectMethods(const TokenListType::iterator& ScopeStart, const TokenListType::iterator& ScopeEnd); @@ -586,6 +592,9 @@ class HLSL2GLSLConverterImpl // List of tokens defining structs std::unordered_map m_StructDefinitions; + // List of preprocessor macro definitions in global scope + std::unordered_map m_PreprocessorDefinitions; + // Stack of parsed objects, for every scope level. // There are currently only two levels: // level 0 - global scope, contains all global objects diff --git a/Graphics/HLSL2GLSLConverterLib/src/HLSL2GLSLConverterImpl.cpp b/Graphics/HLSL2GLSLConverterLib/src/HLSL2GLSLConverterImpl.cpp index afebe40807..41f85abfda 100644 --- a/Graphics/HLSL2GLSLConverterLib/src/HLSL2GLSLConverterImpl.cpp +++ b/Graphics/HLSL2GLSLConverterLib/src/HLSL2GLSLConverterImpl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2024 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -813,8 +813,18 @@ void HLSL2GLSLConverterImpl::ConversionStream::InsertIncludes(String& GLSLSource // The function converts source code into a token list void HLSL2GLSLConverterImpl::ConversionStream::Tokenize(const String& Source) { + size_t TokenIdx = 0; + m_Tokens = Parsing::Tokenize( - Source.begin(), Source.end(), TokenInfo::Create, + Source.begin(), Source.end(), + [&TokenIdx](TokenType Type, + const std::string::const_iterator& DelimStart, + const std::string::const_iterator& DelimEnd, + const std::string::const_iterator& LiteralStart, + const std::string::const_iterator& LiteralEnd) // + { + return TokenInfo::Create(Type, DelimStart, DelimEnd, LiteralStart, LiteralEnd, TokenIdx++); + }, [&](const std::string::const_iterator& Start, const std::string::const_iterator& End) // { auto KeywordIt = m_Converter.m_HLSLKeywords.find(HashMapStringKey{std::string{Start, End}}); @@ -827,6 +837,71 @@ void HLSL2GLSLConverterImpl::ConversionStream::Tokenize(const String& Source) }); } +void HLSL2GLSLConverterImpl::ConversionStream::ParseGlobalPreprocessorDefines() +{ + auto Token = m_Tokens.begin(); + + // Collect global-scope preprocessor definitions + int PreprocessorScopeLevel = 0; + while (Token != m_Tokens.end()) + { + if (Token->Type != TokenType::PreprocessorDirective) + { + ++Token; + continue; + } + + const auto Directive = RefinePreprocessorDirective(Token->Literal); + + if (Directive == "if" || + Directive == "ifdef" || + Directive == "ifndef") + { + ++PreprocessorScopeLevel; + } + else if (Directive == "endif") + { + if (PreprocessorScopeLevel > 0) + { + --PreprocessorScopeLevel; + } + else + { + LOG_ERROR_MESSAGE("No matching #if directive\n", PrintTokenContext(Token, 4)); + } + } + else if (Directive == "define") + { + // #define MACRO + // ^ + + // Only process macros in the global scope as we don't + // handle conditional compilation + if (PreprocessorScopeLevel == 0) + { + auto MacroNameToken = Token; + ++MacroNameToken; + // #define MACRO + // ^ + if (MacroNameToken != m_Tokens.end() && + // The name should not be empty + !MacroNameToken->Literal.empty() && + // Check that the name is on the same line + MacroNameToken->Delimiter.find_first_of("\r\n") == std::string::npos) + { + m_PreprocessorDefinitions.emplace(MacroNameToken->Literal, Token); + } + } + } + + ++Token; + } + + if (PreprocessorScopeLevel > 0) + { + LOG_ERROR_MESSAGE("Missing #endif directive at the end of the file. Current preprocessor scope level: ", PreprocessorScopeLevel); + } +} // The function replaces cbuffer with uniform and adds semicolon if it is missing after the closing brace: // cbuffer @@ -2270,60 +2345,63 @@ void HLSL2GLSLConverterImpl::ConversionStream::ParseShaderParameter(TokenListTyp auto TypeToken = Token; ParamInfo.Type = Token->Literal; - ++Token; - // out float4 Color : SV_Target, - // ^ - VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF while parsing argument list"); - VERIFY_PARSER_STATE(Token, Token->Type == TokenType::Identifier, "Missing argument name after ", ParamInfo.Type); - ParamInfo.Name = Token->Literal; - - ++Token; - VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF"); - - if (Token->Type == TokenType::OpenSquareBracket) + if (ParamInfo.storageQualifier != ShaderParameterInfo::StorageQualifier::Ret) { - // triangle VSOut In[3] - // ^ - ProcessScope( - Token, m_Tokens.end(), TokenType::OpenSquareBracket, TokenType::ClosingSquareBracket, - [&](TokenListType::iterator& tkn, int) { - ParamInfo.ArraySize.append(tkn->Delimiter); - ParamInfo.ArraySize.append(tkn->Literal); - ++tkn; - } // - ); - VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF"); - // triangle VSOut In[3], - // ^ - VERIFY_PARSER_STATE(Token, Token->Type == TokenType::ClosingSquareBracket, "Closing staple expected"); + ++Token; + // out float4 Color : SV_Target, + // ^ + VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF while parsing argument list"); + VERIFY_PARSER_STATE(Token, Token->Type == TokenType::Identifier, "Missing argument name after ", ParamInfo.Type); + ParamInfo.Name = Token->Literal; ++Token; VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF"); - VERIFY_PARSER_STATE(Token, Token->Type != TokenType::OpenSquareBracket, "Multi-dimensional arrays are not supported"); - } - - if (TypeToken->IsBuiltInType()) - { - // out float4 Color : SV_Target, - // ^ - VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected end of file after argument \"", ParamInfo.Name, '\"'); - if (Token->Literal == ":") + if (Token->Type == TokenType::OpenSquareBracket) { - ++Token; - // out float4 Color : SV_Target, - // ^ - VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected end of file while looking for semantic for argument \"", ParamInfo.Name, '\"'); - VERIFY_PARSER_STATE(Token, Token->Type == TokenType::Identifier, "Missing semantic for argument \"", ParamInfo.Name, '\"'); - // Transform to lower case - semantics are case-insensitive - ParamInfo.Semantic = StrToLower(Token->Literal); + // triangle VSOut In[3] + // ^ + ProcessScope( + Token, m_Tokens.end(), TokenType::OpenSquareBracket, TokenType::ClosingSquareBracket, + [&](TokenListType::iterator& tkn, int) { + ParamInfo.ArraySize.append(tkn->Delimiter); + ParamInfo.ArraySize.append(tkn->Literal); + ++tkn; + } // + ); + VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF"); + // triangle VSOut In[3], + // ^ + VERIFY_PARSER_STATE(Token, Token->Type == TokenType::ClosingSquareBracket, "Closing staple expected"); ++Token; + VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected EOF"); + VERIFY_PARSER_STATE(Token, Token->Type != TokenType::OpenSquareBracket, "Multi-dimensional arrays are not supported"); + } + + if (TypeToken->IsBuiltInType()) + { // out float4 Color : SV_Target, - // ^ + // ^ + VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected end of file after argument \"", ParamInfo.Name, '\"'); + if (Token->Literal == ":") + { + ++Token; + // out float4 Color : SV_Target, + // ^ + VERIFY_PARSER_STATE(Token, Token != m_Tokens.end(), "Unexpected end of file while looking for semantic for argument \"", ParamInfo.Name, '\"'); + VERIFY_PARSER_STATE(Token, Token->Type == TokenType::Identifier, "Missing semantic for argument \"", ParamInfo.Name, '\"'); + // Transform to lower case - semantics are case-insensitive + ParamInfo.Semantic = StrToLower(Token->Literal); + + ++Token; + // out float4 Color : SV_Target, + // ^ + } } } - else + + if (!TypeToken->IsBuiltInType()) { const auto& StructName = TypeToken->Literal; auto it = m_StructDefinitions.find(StructName.c_str()); @@ -2349,8 +2427,11 @@ void HLSL2GLSLConverterImpl::ConversionStream::ParseShaderParameter(TokenListTyp while (TypeToken != m_Tokens.end() && TypeToken->Type != TokenType::ClosingBrace) { ShaderParameterInfo MemberInfo; - MemberInfo.storageQualifier = ParamInfo.storageQualifier; ParseShaderParameter(TypeToken, MemberInfo); + // Set storage qualifier after we process the member as otherwise + // members of a struct with the StorageQualifier::Ret qualifier + // will not be processed. + MemberInfo.storageQualifier = ParamInfo.storageQualifier; ParamInfo.members.emplace_back(std::move(MemberInfo)); // struct VSOutput // { @@ -2376,11 +2457,43 @@ void HLSL2GLSLConverterImpl::ConversionStream::ProcessFunctionParameters(TokenLi // ^ auto FuncNameToken = Token; - bIsVoid = TypeToken->Type == TokenType::kw_void; + auto ActualTypeToken = TypeToken; + { + // PS_OUTPUT TestPS ( in VSOutput In, + + auto define_it = m_PreprocessorDefinitions.find(ActualTypeToken->Literal.c_str()); + if (define_it != m_PreprocessorDefinitions.end()) + { + auto DefinedTypeToken = define_it->second; + // #define PS_OUTPUT PSOutput + // ^ + // DefinedTypeToken + + // Check that the define directive is before the type token + if (DefinedTypeToken->Idx < TypeToken->Idx) + { + ++DefinedTypeToken; + // #define PS_OUTPUT PSOutput + // ^ + // DefinedTypeToken + if (DefinedTypeToken != m_Tokens.end() && DefinedTypeToken->Literal == TypeToken->Literal) + { + ++DefinedTypeToken; + // #define PS_OUTPUT PSOutput + // ^ + // DefinedTypeToken + if (DefinedTypeToken != m_Tokens.end()) + ActualTypeToken = DefinedTypeToken; + } + } + } + } + + bIsVoid = ActualTypeToken->Type == TokenType::kw_void; if (!bIsVoid) { ShaderParameterInfo RetParam; - RetParam.Type = TypeToken->Literal; + RetParam.Type = ActualTypeToken->Literal; RetParam.Name = FuncNameToken->Literal; RetParam.storageQualifier = ShaderParameterInfo::StorageQualifier::Ret; Params.emplace_back(std::move(RetParam)); @@ -2637,7 +2750,7 @@ void HLSL2GLSLConverterImpl::ConversionStream::ProcessFunctionParameters(TokenLi else { // VSOut TestVS () - auto TmpTypeToken = TypeToken; + auto TmpTypeToken = ActualTypeToken; ParseShaderParameter(TmpTypeToken, RetParam); } TypeToken->Type = TokenType::Identifier; @@ -4482,6 +4595,8 @@ String HLSL2GLSLConverterImpl::ConversionStream::Convert(const Char* EntryPoint, } } + ParseGlobalPreprocessorDefines(); + auto ShaderEntryPointToken = m_Tokens.end(); // Process textures and search for the shader entry point. // GLSL does not allow local variables of sampler type, so the @@ -4665,6 +4780,7 @@ String HLSL2GLSLConverterImpl::ConversionStream::Convert(const Char* EntryPoint, { m_Tokens.swap(TokensCopy); m_StructDefinitions.clear(); + m_PreprocessorDefinitions.clear(); m_Objects.clear(); } diff --git a/Tests/DiligentCoreAPITest/assets/shaders/HLSL2GLSLConverter/PreprocessorTest.hlsl b/Tests/DiligentCoreAPITest/assets/shaders/HLSL2GLSLConverter/PreprocessorTest.hlsl new file mode 100644 index 0000000000..9f34a771fc --- /dev/null +++ b/Tests/DiligentCoreAPITest/assets/shaders/HLSL2GLSLConverter/PreprocessorTest.hlsl @@ -0,0 +1,25 @@ +struct PSOutput +{ + float4 Color : SV_Target0; +}; + +struct PSInput +{ + float4 Pos : SV_Position; +}; + +# define PS_OUTPUT PSOutput +# define PS_INPUT PSInput + +#ifdef MACRO0 +# define PS_OUTPUT abc +#elif defined(MACRO1) +# define PS_OUTPUT xyz +#endif + +PS_OUTPUT main(in PSInput PSIn) +{ + PS_OUTPUT PSOut; + PSOut.Color = float4(PSIn.Pos.xy, 0.0, 0.0); + return PSOut; +} diff --git a/Tests/DiligentCoreAPITest/src/HLSL2GLSLConverterTest.cpp b/Tests/DiligentCoreAPITest/src/HLSL2GLSLConverterTest.cpp index e5e64b5395..8d77697476 100644 --- a/Tests/DiligentCoreAPITest/src/HLSL2GLSLConverterTest.cpp +++ b/Tests/DiligentCoreAPITest/src/HLSL2GLSLConverterTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Diligent Graphics LLC + * Copyright 2019-2024 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -142,4 +142,12 @@ TEST(HLSL2GLSLConverterTest, GS) EXPECT_NE(pGS, nullptr); } +TEST(HLSL2GLSLConverterTest, Preprocessor) +{ + GPUTestingEnvironment::ScopedReset EnvironmentAutoReset; + + auto pPS = CreateTestShader("PreprocessorTest.hlsl", "main", SHADER_TYPE_PIXEL); + EXPECT_NE(pPS, nullptr); +} + } // namespace