Index: ps/trunk/source/ps/PreprocessorWrapper.cpp =================================================================== --- ps/trunk/source/ps/PreprocessorWrapper.cpp (revision 23214) +++ ps/trunk/source/ps/PreprocessorWrapper.cpp (nonexistent) @@ -1,86 +0,0 @@ -/* Copyright (C) 2012 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#include "precompiled.h" - -#include "PreprocessorWrapper.h" - -#include "graphics/ShaderDefines.h" -#include "ps/CLogger.h" - -void CPreprocessorWrapper::AddDefine(const char* name, const char* value) -{ - m_Preprocessor.Define(name, value); -} - -void CPreprocessorWrapper::AddDefines(const CShaderDefines& defines) -{ - std::map map = defines.GetMap(); - for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) - m_Preprocessor.Define(it->first.c_str(), it->first.length(), it->second.c_str(), it->second.length()); -} - -bool CPreprocessorWrapper::TestConditional(const CStr& expr) -{ - // Construct a dummy program so we can trigger the preprocessor's expression - // code without modifying its public API. - // Be careful that the API buggily returns a statically allocated pointer - // (which we will try to free()) if the input just causes it to append a single - // sequence of newlines to the output; the "\n" after the "#endif" is enough - // to avoid this case. - CStr input = "#if "; - input += expr; - input += "\n1\n#endif\n"; - - size_t len = 0; - char* output = m_Preprocessor.Parse(input.c_str(), input.size(), len); - - if (!output) - { - LOGERROR("Failed to parse conditional expression '%s'", expr.c_str()); - return false; - } - - bool ret = (memchr(output, '1', len) != NULL); - - // Free output if it's not inside the source string - if (!(output >= input.c_str() && output < input.c_str() + input.size())) - free(output); - - return ret; - -} - -CStr CPreprocessorWrapper::Preprocess(const CStr& input) -{ - size_t len = 0; - char* output = m_Preprocessor.Parse(input.c_str(), input.size(), len); - - if (!output) - { - LOGERROR("Shader preprocessing failed"); - return ""; - } - - CStr ret(output, len); - - // Free output if it's not inside the source string - if (!(output >= input.c_str() && output < input.c_str() + input.size())) - free(output); - - return ret; -} Property changes on: ps/trunk/source/ps/PreprocessorWrapper.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/ps/Preprocessor.cpp =================================================================== --- ps/trunk/source/ps/Preprocessor.cpp (revision 23214) +++ ps/trunk/source/ps/Preprocessor.cpp (nonexistent) @@ -1,1316 +0,0 @@ -/* - * This source file originally came from OGRE v1.7.2 - http://www.ogre3d.org/ - * with some tweaks as part of 0 A.D. - * All changes are released under the original license, as follows: - */ - -/* ------------------------------------------------------------------------------ -This source file is part of OGRE -(Object-oriented Graphics Rendering Engine) -For the latest info, see http://www.ogre3d.org/ - -Copyright (c) 2000-2009 Torus Knot Software Ltd - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. ------------------------------------------------------------------------------ -*/ - -#include "precompiled.h" - -#include "Preprocessor.h" - -#include "ps/CLogger.h" - -// Limit max number of macro arguments to this -#define MAX_MACRO_ARGS 16 - -//---------------------------------------------------------------------------// - -/// Return closest power of two not smaller than given number -static size_t ClosestPow2 (size_t x) -{ - if (!(x & (x - 1))) - return x; - while (x & (x + 1)) - x |= (x + 1); - return x + 1; -} - -void CPreprocessor::Token::Append (const char *iString, size_t iLength) -{ - Token t (Token::TK_TEXT, iString, iLength); - Append (t); -} - -void CPreprocessor::Token::Append (const Token &iOther) -{ - if (!iOther.String) - return; - - if (!String) - { - String = iOther.String; - Length = iOther.Length; - Allocated = iOther.Allocated; - iOther.Allocated = 0; // !!! not quite correct but effective - return; - } - - if (Allocated) - { - size_t new_alloc = ClosestPow2 (Length + iOther.Length); - if (new_alloc < 64) - new_alloc = 64; - if (new_alloc != Allocated) - { - Allocated = new_alloc; - Buffer = (char *)realloc (Buffer, Allocated); - } - } - else if (String + Length != iOther.String) - { - Allocated = ClosestPow2 (Length + iOther.Length); - if (Allocated < 64) - Allocated = 64; - char *newstr = (char *)malloc (Allocated); - memcpy (newstr, String, Length); - Buffer = newstr; - } - - if (Allocated) - memcpy (Buffer + Length, iOther.String, iOther.Length); - Length += iOther.Length; -} - -bool CPreprocessor::Token::GetValue (long &oValue) const -{ - long val = 0; - size_t i = 0; - - while (isspace (String [i])) - i++; - - long base = 10; - if (String [i] == '0') - { - if (Length > i + 1 && String [i + 1] == 'x') - base = 16, i += 2; - else - base = 8; - } - - for (; i < Length; i++) - { - long c = long (String [i]); - if (isspace (c)) - // Possible end of number - break; - - if (c >= 'a' && c <= 'z') - c -= ('a' - 'A'); - - c -= '0'; - if (c < 0) - return false; - - if (c > 9) - c -= ('A' - '9' - 1); - - if (c >= base) - return false; - - val = (val * base) + c; - } - - // Check that all other characters are just spaces - for (; i < Length; i++) - if (!isspace (String [i])) - return false; - - oValue = val; - return true; -} - -void CPreprocessor::Token::SetValue (long iValue) -{ - char tmp [21]; - int len = snprintf (tmp, sizeof (tmp), "%ld", iValue); - Length = 0; - Append (tmp, len); - Type = TK_NUMBER; -} - -void CPreprocessor::Token::AppendNL (int iCount) -{ - static const char newlines [8] = - { '\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n' }; - - while (iCount > 8) - { - Append (newlines, 8); - iCount -= 8; - } - if (iCount > 0) - Append (newlines, iCount); -} - -int CPreprocessor::Token::CountNL () -{ - if (Type == TK_EOS || Type == TK_ERROR) - return 0; - - const char *s = String; - int l = Length; - int c = 0; - while (l > 0) - { - const char *n = (const char *)memchr (s, '\n', l); - if (!n) - return c; - c++; - l -= (n - s + 1); - s = n + 1; - } - return c; -} - -//---------------------------------------------------------------------------// - -CPreprocessor::Token CPreprocessor::Macro::Expand ( - int iNumArgs, CPreprocessor::Token *iArgs, Macro *iMacros) -{ - Expanding = true; - - CPreprocessor cpp; - cpp.MacroList = iMacros; - - // Define a new macro for every argument - int i; - for (i = 0; i < iNumArgs; i++) - cpp.Define (Args [i].String, Args [i].Length, - iArgs [i].String, iArgs [i].Length); - // The rest arguments are empty - for (; i < NumArgs; i++) - cpp.Define (Args [i].String, Args [i].Length, "", 0); - - // Now run the macro expansion through the supplimentary preprocessor - Token xt = cpp.Parse (Value); - - Expanding = false; - - // Remove the extra macros we have defined - for (int j = NumArgs - 1; j >= 0; j--) - cpp.Undef (Args [j].String, Args [j].Length); - - cpp.MacroList = NULL; - - return xt; -} - -//---------------------------------------------------------------------------// - -static void DefaultError (void *iData, int iLine, const char *iError, - const char *iToken, size_t iTokenLen) -{ - (void)iData; - if (iToken) - LOGERROR("Preprocessor error: line %d: %s: '%s'\n", - iLine, iError, std::string (iToken, iTokenLen)); - else - LOGERROR("Preprocessor error: line %d: %s\n", iLine, iError); -} - -//---------------------------------------------------------------------------// - -CPreprocessor::ErrorHandlerFunc CPreprocessor::ErrorHandler = DefaultError; - -CPreprocessor::CPreprocessor (const Token &iToken, int iLine) : MacroList (NULL) -{ - Source = iToken.String; - SourceEnd = iToken.String + iToken.Length; - EnableOutput = 1; - Line = iLine; - BOL = true; -} - -CPreprocessor::~CPreprocessor () -{ - delete MacroList; -} - -void CPreprocessor::Error (int iLine, const char *iError, const Token *iToken) -{ - if (iToken) - ErrorHandler (ErrorData, iLine, iError, iToken->String, iToken->Length); - else - ErrorHandler (ErrorData, iLine, iError, NULL, 0); -} - -CPreprocessor::Token CPreprocessor::GetToken (bool iExpand) -{ - if (Source >= SourceEnd) - return Token (Token::TK_EOS); - - const char *begin = Source; - char c = *Source++; - - - if (c == '\n' || (c == '\r' && *Source == '\n')) - { - Line++; - BOL = true; - if (c == '\r') - Source++; - return Token (Token::TK_NEWLINE, begin, Source - begin); - } - else if (isspace (c)) - { - while (Source < SourceEnd && - *Source != '\r' && - *Source != '\n' && - isspace (*Source)) - Source++; - - return Token (Token::TK_WHITESPACE, begin, Source - begin); - } - else if (isdigit (c)) - { - BOL = false; - if (c == '0' && Source < SourceEnd && Source [0] == 'x') // hex numbers - { - Source++; - while (Source < SourceEnd && isxdigit (*Source)) - Source++; - } - else - while (Source < SourceEnd && isdigit (*Source)) - Source++; - return Token (Token::TK_NUMBER, begin, Source - begin); - } - else if (c == '_' || isalnum (c)) - { - BOL = false; - while (Source < SourceEnd && (*Source == '_' || isalnum (*Source))) - Source++; - Token t (Token::TK_KEYWORD, begin, Source - begin); - if (iExpand) - t = ExpandMacro (t); - return t; - } - else if (c == '"' || c == '\'') - { - BOL = false; - while (Source < SourceEnd && *Source != c) - { - if (*Source == '\\') - { - Source++; - if (Source >= SourceEnd) - break; - } - if (*Source == '\n') - Line++; - Source++; - } - if (Source < SourceEnd) - Source++; - return Token (Token::TK_STRING, begin, Source - begin); - } - else if (c == '/' && *Source == '/') - { - BOL = false; - Source++; - while (Source < SourceEnd && *Source != '\r' && *Source != '\n') - Source++; - return Token (Token::TK_LINECOMMENT, begin, Source - begin); - } - else if (c == '/' && *Source == '*') - { - BOL = false; - Source++; - while (Source < SourceEnd && (Source [0] != '*' || Source [1] != '/')) - { - if (*Source == '\n') - Line++; - Source++; - } - if (Source < SourceEnd && *Source == '*') - Source++; - if (Source < SourceEnd && *Source == '/') - Source++; - return Token (Token::TK_COMMENT, begin, Source - begin); - } - else if (c == '#' && BOL) - { - // Skip all whitespaces after '#' - while (Source < SourceEnd && isspace (*Source)) - Source++; - while (Source < SourceEnd && !isspace (*Source)) - Source++; - return Token (Token::TK_DIRECTIVE, begin, Source - begin); - } - else if (c == '\\' && Source < SourceEnd && (*Source == '\r' || *Source == '\n')) - { - // Treat backslash-newline as a whole token - if (*Source == '\r') - Source++; - if (*Source == '\n') - Source++; - Line++; - BOL = true; - return Token (Token::TK_LINECONT, begin, Source - begin); - } - else - { - BOL = false; - // Handle double-char operators here - if (c == '>' && (*Source == '>' || *Source == '=')) - Source++; - else if (c == '<' && (*Source == '<' || *Source == '=')) - Source++; - else if (c == '!' && *Source == '=') - Source++; - else if (c == '=' && *Source == '=') - Source++; - else if ((c == '|' || c == '&' || c == '^') && *Source == c) - Source++; - return Token (Token::TK_PUNCTUATION, begin, Source - begin); - } -} - -CPreprocessor::Macro *CPreprocessor::IsDefined (const Token &iToken) -{ - for (Macro *cur = MacroList; cur; cur = cur->Next) - if (cur->Name == iToken) - return cur; - - return NULL; -} - -CPreprocessor::Token CPreprocessor::ExpandMacro (const Token &iToken) -{ - Macro *cur = IsDefined (iToken); - if (cur && !cur->Expanding) - { - Token *args = NULL; - int nargs = 0; - int old_line = Line; - - if (cur->NumArgs != 0) - { - Token t = GetArguments (nargs, args, cur->ExpandFunc ? false : true); - if (t.Type == Token::TK_ERROR) - { - delete [] args; - return t; - } - - // Put the token back into the source pool; we'll handle it later - if (t.String) - { - // Returned token should never be allocated on heap - ENSURE (t.Allocated == 0); - Source = t.String; - Line -= t.CountNL (); - } - } - - if (nargs > cur->NumArgs) - { - char tmp [60]; - snprintf (tmp, sizeof (tmp), "Macro `%.*s' passed %d arguments, but takes just %d", - int (cur->Name.Length), cur->Name.String, - nargs, cur->NumArgs); - Error (old_line, tmp); - return Token (Token::TK_ERROR); - } - - Token t = cur->ExpandFunc ? - cur->ExpandFunc (this, nargs, args) : - cur->Expand (nargs, args, MacroList); - t.AppendNL (Line - old_line); - - delete [] args; - - return t; - } - - return iToken; -} - -/** - * Operator priority: - * 0: Whole expression - * 1: '(' ')' - * 2: || - * 3: && - * 4: | - * 5: ^ - * 6: & - * 7: '==' '!=' - * 8: '<' '<=' '>' '>=' - * 9: '<<' '>>' - * 10: '+' '-' - * 11: '*' '/' '%' - * 12: unary '+' '-' '!' '~' - */ -CPreprocessor::Token CPreprocessor::GetExpression ( - Token &oResult, int iLine, int iOpPriority) -{ - char tmp [40]; - - do - { - oResult = GetToken (true); - } while (oResult.Type == Token::TK_WHITESPACE || - oResult.Type == Token::TK_NEWLINE || - oResult.Type == Token::TK_COMMENT || - oResult.Type == Token::TK_LINECOMMENT || - oResult.Type == Token::TK_LINECONT); - - Token op (Token::TK_WHITESPACE, "", 0); - - // Handle unary operators here - if (oResult.Type == Token::TK_PUNCTUATION && oResult.Length == 1) - { - if (strchr ("+-!~", oResult.String [0])) - { - char uop = oResult.String [0]; - op = GetExpression (oResult, iLine, 12); - long val; - if (!GetValue (oResult, val, iLine)) - { - snprintf (tmp, sizeof (tmp), "Unary '%c' not applicable", uop); - Error (iLine, tmp, &oResult); - return Token (Token::TK_ERROR); - } - - if (uop == '-') - oResult.SetValue (-val); - else if (uop == '!') - oResult.SetValue (!val); - else if (uop == '~') - oResult.SetValue (~val); - } - else if (oResult.String [0] == '(') - { - op = GetExpression (oResult, iLine, 1); - if (op.Type == Token::TK_ERROR) - return op; - if (op.Type == Token::TK_EOS) - { - Error (iLine, "Unclosed parenthesis in #if expression"); - return Token (Token::TK_ERROR); - } - - ENSURE (op.Type == Token::TK_PUNCTUATION && - op.Length == 1 && - op.String [0] == ')'); - op = GetToken (true); - } - } - - while (op.Type == Token::TK_WHITESPACE || - op.Type == Token::TK_NEWLINE || - op.Type == Token::TK_COMMENT || - op.Type == Token::TK_LINECOMMENT || - op.Type == Token::TK_LINECONT) - op = GetToken (true); - - while (true) - { - if (op.Type != Token::TK_PUNCTUATION) - return op; - - int prio = 0; - if (op.Length == 1) - switch (op.String [0]) - { - case ')': return op; - case '|': prio = 4; break; - case '^': prio = 5; break; - case '&': prio = 6; break; - case '<': - case '>': prio = 8; break; - case '+': - case '-': prio = 10; break; - case '*': - case '/': - case '%': prio = 11; break; - } - else if (op.Length == 2) - switch (op.String [0]) - { - case '|': if (op.String [1] == '|') prio = 2; break; - case '&': if (op.String [1] == '&') prio = 3; break; - case '=': if (op.String [1] == '=') prio = 7; break; - case '!': if (op.String [1] == '=') prio = 7; break; - case '<': - if (op.String [1] == '=') - prio = 8; - else if (op.String [1] == '<') - prio = 9; - break; - case '>': - if (op.String [1] == '=') - prio = 8; - else if (op.String [1] == '>') - prio = 9; - break; - } - - if (!prio) - { - Error (iLine, "Expecting operator, got", &op); - return Token (Token::TK_ERROR); - } - - if (iOpPriority >= prio) - return op; - - Token rop; - Token nextop = GetExpression (rop, iLine, prio); - long vlop, vrop; - if (!GetValue (oResult, vlop, iLine)) - { - snprintf (tmp, sizeof (tmp), "Left operand of '%.*s' is not a number", - int (op.Length), op.String); - Error (iLine, tmp, &oResult); - return Token (Token::TK_ERROR); - } - if (!GetValue (rop, vrop, iLine)) - { - snprintf (tmp, sizeof (tmp), "Right operand of '%.*s' is not a number", - int (op.Length), op.String); - Error (iLine, tmp, &rop); - return Token (Token::TK_ERROR); - } - - switch (op.String [0]) - { - case '|': - if (prio == 2) - oResult.SetValue (vlop || vrop); - else - oResult.SetValue (vlop | vrop); - break; - case '&': - if (prio == 3) - oResult.SetValue (vlop && vrop); - else - oResult.SetValue (vlop & vrop); - break; - case '<': - if (op.Length == 1) - oResult.SetValue (vlop < vrop); - else if (prio == 8) - oResult.SetValue (vlop <= vrop); - else if (prio == 9) - oResult.SetValue (vlop << vrop); - break; - case '>': - if (op.Length == 1) - oResult.SetValue (vlop > vrop); - else if (prio == 8) - oResult.SetValue (vlop >= vrop); - else if (prio == 9) - oResult.SetValue (vlop >> vrop); - break; - case '^': oResult.SetValue (vlop ^ vrop); break; - case '!': oResult.SetValue (vlop != vrop); break; - case '=': oResult.SetValue (vlop == vrop); break; - case '+': oResult.SetValue (vlop + vrop); break; - case '-': oResult.SetValue (vlop - vrop); break; - case '*': oResult.SetValue (vlop * vrop); break; - case '/': - case '%': - if (vrop == 0) - { - Error (iLine, "Division by zero"); - return Token (Token::TK_ERROR); - } - if (op.String [0] == '/') - oResult.SetValue (vlop / vrop); - else - oResult.SetValue (vlop % vrop); - break; - } - - op = nextop; - } -} - -bool CPreprocessor::GetValue (const Token &iToken, long &oValue, int iLine) -{ - Token r; - const Token *vt = &iToken; - - if ((vt->Type == Token::TK_KEYWORD || - vt->Type == Token::TK_TEXT || - vt->Type == Token::TK_NUMBER) && - !vt->String) - { - Error (iLine, "Trying to evaluate an empty expression"); - return false; - } - - if (vt->Type == Token::TK_TEXT) - { - CPreprocessor cpp (iToken, iLine); - cpp.MacroList = MacroList; - - Token t; - t = cpp.GetExpression (r, iLine); - - cpp.MacroList = NULL; - - if (t.Type == Token::TK_ERROR) - return false; - - if (t.Type != Token::TK_EOS) - { - Error (iLine, "Garbage after expression", &t); - return false; - } - - vt = &r; - } - - switch (vt->Type) - { - case Token::TK_EOS: - case Token::TK_ERROR: - return false; - - case Token::TK_KEYWORD: - { - // Try to expand the macro - Macro *m = IsDefined (*vt); - if (m != NULL && !m->Expanding) - { - Token x = ExpandMacro (*vt); - m->Expanding = true; - bool rc = GetValue (x, oValue, iLine); - m->Expanding = false; - return rc; - } - - // Undefined macro, "expand" to 0 (mimic cpp behaviour) - oValue = 0; - break; - } - case Token::TK_TEXT: - case Token::TK_NUMBER: - if (!vt->GetValue (oValue)) - { - Error (iLine, "Not a numeric expression", vt); - return false; - } - break; - - default: - Error (iLine, "Unexpected token", vt); - return false; - } - - return true; -} - -CPreprocessor::Token CPreprocessor::GetArgument (Token &oArg, bool iExpand) -{ - do - { - oArg = GetToken (iExpand); - } while (oArg.Type == Token::TK_WHITESPACE || - oArg.Type == Token::TK_NEWLINE || - oArg.Type == Token::TK_COMMENT || - oArg.Type == Token::TK_LINECOMMENT || - oArg.Type == Token::TK_LINECONT); - - if (!iExpand) - { - if (oArg.Type == Token::TK_EOS) - return oArg; - else if (oArg.Type == Token::TK_PUNCTUATION && - (oArg.String [0] == ',' || - oArg.String [0] == ')')) - { - Token t = oArg; - oArg = Token (Token::TK_TEXT, "", 0); - return t; - } - else if (oArg.Type != Token::TK_KEYWORD) - { - Error (Line, "Unexpected token", &oArg); - return Token (Token::TK_ERROR); - } - } - - unsigned int len = oArg.Length; - while (true) - { - Token t = GetToken (iExpand); - switch (t.Type) - { - case Token::TK_EOS: - Error (Line, "Unfinished list of arguments"); - FALLTHROUGH; - case Token::TK_ERROR: - return Token (Token::TK_ERROR); - case Token::TK_PUNCTUATION: - if (t.String [0] == ',' || - t.String [0] == ')') - { - // Trim whitespaces at the end - oArg.Length = len; - return t; - } - break; - case Token::TK_LINECONT: - case Token::TK_COMMENT: - case Token::TK_LINECOMMENT: - case Token::TK_NEWLINE: - // ignore these tokens - continue; - default: - break; - } - - if (!iExpand && t.Type != Token::TK_WHITESPACE) - { - Error (Line, "Unexpected token", &oArg); - return Token (Token::TK_ERROR); - } - - oArg.Append (t); - - if (t.Type != Token::TK_WHITESPACE) - len = oArg.Length; - } -} - -CPreprocessor::Token CPreprocessor::GetArguments (int &oNumArgs, Token *&oArgs, - bool iExpand) -{ - Token args [MAX_MACRO_ARGS]; - int nargs = 0; - - // Suppose we'll leave by the wrong path - oNumArgs = 0; - oArgs = NULL; - - Token t; - do - { - t = GetToken (iExpand); - } while (t.Type == Token::TK_WHITESPACE || - t.Type == Token::TK_COMMENT || - t.Type == Token::TK_LINECOMMENT); - - if (t.Type != Token::TK_PUNCTUATION || t.String [0] != '(') - { - oNumArgs = 0; - oArgs = NULL; - return t; - } - - while (true) - { - if (nargs == MAX_MACRO_ARGS) - { - Error (Line, "Too many arguments to macro"); - return Token (Token::TK_ERROR); - } - - t = GetArgument (args [nargs++], iExpand); - - switch (t.Type) - { - case Token::TK_EOS: - Error (Line, "Unfinished list of arguments"); - FALLTHROUGH; - case Token::TK_ERROR: - return Token (Token::TK_ERROR); - - case Token::TK_PUNCTUATION: - if (t.String [0] == ')') - { - t = GetToken (iExpand); - goto Done; - } // otherwise we've got a ',' - break; - - default: - Error (Line, "Unexpected token", &t); - break; - } - } - -Done: - oNumArgs = nargs; - oArgs = new Token [nargs]; - for (int i = 0; i < nargs; i++) - oArgs [i] = args [i]; - return t; -} - -bool CPreprocessor::HandleDefine (Token &iBody, int iLine) -{ - // Create an additional preprocessor to process macro body - CPreprocessor cpp (iBody, iLine); - - Token t = cpp.GetToken (false); - if (t.Type != Token::TK_KEYWORD) - { - Error (iLine, "Macro name expected after #define"); - return false; - } - - bool output_enabled = ((EnableOutput & (EnableOutput + 1)) == 0); - if (!output_enabled) - return true; - - Macro *m = new Macro (t); - m->Body = iBody; - t = cpp.GetArguments (m->NumArgs, m->Args, false); - while (t.Type == Token::TK_WHITESPACE) - t = cpp.GetToken (false); - - switch (t.Type) - { - case Token::TK_NEWLINE: - case Token::TK_EOS: - // Assign "" to token - t = Token (Token::TK_TEXT, "", 0); - break; - - case Token::TK_ERROR: - delete m; - return false; - - default: - t.Type = Token::TK_TEXT; - ENSURE (t.String + t.Length == cpp.Source); - t.Length = cpp.SourceEnd - t.String; - break; - } - - m->Value = t; - m->Next = MacroList; - MacroList = m; - return true; -} - -bool CPreprocessor::HandleUnDef (Token &iBody, int iLine) -{ - CPreprocessor cpp (iBody, iLine); - - Token t = cpp.GetToken (false); - - if (t.Type != Token::TK_KEYWORD) - { - Error (iLine, "Expecting a macro name after #undef, got", &t); - return false; - } - - // Don't barf if macro does not exist - standard C behaviour - Undef (t.String, t.Length); - - do - { - t = cpp.GetToken (false); - } while (t.Type == Token::TK_WHITESPACE || - t.Type == Token::TK_COMMENT || - t.Type == Token::TK_LINECOMMENT); - - if (t.Type != Token::TK_EOS) - Error (iLine, "Warning: Ignoring garbage after directive", &t); - - return true; -} - -bool CPreprocessor::HandleIfDef (Token &iBody, int iLine) -{ - if (EnableOutput & (1 << 31)) - { - Error (iLine, "Too many embedded #if directives"); - return false; - } - - CPreprocessor cpp (iBody, iLine); - - Token t = cpp.GetToken (false); - - if (t.Type != Token::TK_KEYWORD) - { - Error (iLine, "Expecting a macro name after #ifdef, got", &t); - return false; - } - - EnableOutput <<= 1; - if (IsDefined (t)) - EnableOutput |= 1; - - do - { - t = cpp.GetToken (false); - } while (t.Type == Token::TK_WHITESPACE || - t.Type == Token::TK_COMMENT || - t.Type == Token::TK_LINECOMMENT); - - if (t.Type != Token::TK_EOS) - Error (iLine, "Warning: Ignoring garbage after directive", &t); - - return true; -} - -CPreprocessor::Token CPreprocessor::ExpandDefined (CPreprocessor *iParent, int iNumArgs, Token *iArgs) -{ - if (iNumArgs != 1) - { - iParent->Error (iParent->Line, "The defined() function takes exactly one argument"); - return Token (Token::TK_ERROR); - } - - const char *v = iParent->IsDefined (iArgs [0]) ? "1" : "0"; - return Token (Token::TK_NUMBER, v, 1); -} - -bool CPreprocessor::HandleIf (Token &iBody, int iLine) -{ - Macro defined (Token (Token::TK_KEYWORD, "defined", 7)); - defined.Next = MacroList; - defined.ExpandFunc = ExpandDefined; - defined.NumArgs = 1; - - // Temporary add the defined() function to the macro list - MacroList = &defined; - - long val; - bool rc = GetValue (iBody, val, iLine); - - // Restore the macro list - MacroList = defined.Next; - defined.Next = NULL; - - if (!rc) - return false; - - EnableOutput <<= 1; - if (val) - EnableOutput |= 1; - - return true; -} - -bool CPreprocessor::HandleElse (Token &iBody, int iLine) -{ - if (EnableOutput == 1) - { - Error (iLine, "#else without #if"); - return false; - } - - // Negate the result of last #if - EnableOutput ^= 1; - - if (iBody.Length) - Error (iLine, "Warning: Ignoring garbage after #else", &iBody); - - return true; -} - -bool CPreprocessor::HandleEndIf (Token &iBody, int iLine) -{ - EnableOutput >>= 1; - if (EnableOutput == 0) - { - Error (iLine, "#endif without #if"); - return false; - } - - if (iBody.Length) - Error (iLine, "Warning: Ignoring garbage after #endif", &iBody); - - return true; -} - -CPreprocessor::Token CPreprocessor::HandleDirective (Token &iToken, int iLine) -{ - // Analyze preprocessor directive - const char *directive = iToken.String + 1; - int dirlen = iToken.Length - 1; - while (dirlen && isspace (*directive)) - dirlen--, directive++; - - int old_line = Line; - - // Collect the remaining part of the directive until EOL - Token t, last; - do - { - t = GetToken (false); - if (t.Type == Token::TK_NEWLINE) - { - // No directive arguments - last = t; - t.Length = 0; - goto Done; - } - } while (t.Type == Token::TK_WHITESPACE || - t.Type == Token::TK_LINECONT || - t.Type == Token::TK_COMMENT || - t.Type == Token::TK_LINECOMMENT); - - for (;;) - { - last = GetToken (false); - switch (last.Type) - { - case Token::TK_EOS: - // Can happen and is not an error - goto Done; - - case Token::TK_LINECOMMENT: - case Token::TK_COMMENT: - // Skip comments in macros - continue; - - case Token::TK_ERROR: - return last; - - case Token::TK_LINECONT: - continue; - - case Token::TK_NEWLINE: - goto Done; - - default: - break; - } - - t.Append (last); - t.Type = Token::TK_TEXT; - } -Done: - -#define IS_DIRECTIVE(s) \ - ((dirlen == sizeof (s) - 1) && (strncmp (directive, s, sizeof (s) - 1) == 0)) - - bool rc; - if (IS_DIRECTIVE ("define")) - rc = HandleDefine (t, iLine); - else if (IS_DIRECTIVE ("undef")) - rc = HandleUnDef (t, iLine); - else if (IS_DIRECTIVE ("ifdef")) - rc = HandleIfDef (t, iLine); - else if (IS_DIRECTIVE ("ifndef")) - { - rc = HandleIfDef (t, iLine); - if (rc) - EnableOutput ^= 1; - } - else if (IS_DIRECTIVE ("if")) - rc = HandleIf (t, iLine); - else if (IS_DIRECTIVE ("else")) - rc = HandleElse (t, iLine); - else if (IS_DIRECTIVE ("endif")) - rc = HandleEndIf (t, iLine); - - else - { - // elif is tricky to support because the EnableOutput stack doesn't - // contain enough data to tell whether any previous branch matched - if (IS_DIRECTIVE ("elif")) - Error (iLine, "Unsupported preprocessor directive #elif"); - - //Error (iLine, "Unknown preprocessor directive", &iToken); - //return Token (Token::TK_ERROR); - - // Unknown preprocessor directive, roll back and pass through - Line = old_line; - Source = iToken.String + iToken.Length; - iToken.Type = Token::TK_TEXT; - return iToken; - } - -#undef IS_DIRECTIVE - - if (!rc) - return Token (Token::TK_ERROR); - return last; -} - -void CPreprocessor::Define (const char *iMacroName, size_t iMacroNameLen, - const char *iMacroValue, size_t iMacroValueLen) -{ - Macro *m = new Macro (Token (Token::TK_KEYWORD, iMacroName, iMacroNameLen)); - m->Value = Token (Token::TK_TEXT, iMacroValue, iMacroValueLen); - m->Next = MacroList; - MacroList = m; -} - -void CPreprocessor::Define (const char *iMacroName, size_t iMacroNameLen, - long iMacroValue) -{ - Macro *m = new Macro (Token (Token::TK_KEYWORD, iMacroName, iMacroNameLen)); - m->Value.SetValue (iMacroValue); - m->Next = MacroList; - MacroList = m; -} - -void CPreprocessor::Define (const char *iMacroName, const char *iMacroValue) -{ - Define (iMacroName, strlen(iMacroName), iMacroValue, strlen(iMacroValue)); -} - -void CPreprocessor::Define (const char *iMacroName, long iMacroValue) -{ - Define (iMacroName, strlen(iMacroName), iMacroValue); -} - -bool CPreprocessor::Undef (const char *iMacroName, size_t iMacroNameLen) -{ - Macro **cur = &MacroList; - Token name (Token::TK_KEYWORD, iMacroName, iMacroNameLen); - while (*cur) - { - if ((*cur)->Name == name) - { - Macro *next = (*cur)->Next; - (*cur)->Next = NULL; - delete (*cur); - *cur = next; - return true; - } - - cur = &(*cur)->Next; - } - - return false; -} - -CPreprocessor::Token CPreprocessor::Parse (const Token &iSource) -{ - Source = iSource.String; - SourceEnd = Source + iSource.Length; - Line = 1; - BOL = true; - EnableOutput = 1; - - // Accumulate output into this token - Token output (Token::TK_TEXT); - int empty_lines = 0; - - // Enable output only if all embedded #if's were true - bool old_output_enabled = true; - bool output_enabled = true; - int output_disabled_line = 0; - - while (Source < SourceEnd) - { - int old_line = Line; - Token t = GetToken (true); - - NextToken: - switch (t.Type) - { - case Token::TK_ERROR: - return t; - - case Token::TK_EOS: - return output; // Force termination - - case Token::TK_COMMENT: - // C comments are replaced with single spaces. - if (output_enabled) - { - output.Append (" ", 1); - output.AppendNL (Line - old_line); - } - break; - - case Token::TK_LINECOMMENT: - // C++ comments are ignored - continue; - - case Token::TK_DIRECTIVE: - // Handle preprocessor directives - t = HandleDirective (t, old_line); - - output_enabled = ((EnableOutput & (EnableOutput + 1)) == 0); - if (output_enabled != old_output_enabled) - { - if (output_enabled) - output.AppendNL (old_line - output_disabled_line); - else - output_disabled_line = old_line; - old_output_enabled = output_enabled; - } - - if (output_enabled) - output.AppendNL (Line - old_line - t.CountNL ()); - goto NextToken; - - case Token::TK_LINECONT: - // Backslash-Newline sequences are deleted, no matter where. - empty_lines++; - break; - - case Token::TK_NEWLINE: - if (empty_lines) - { - // Compensate for the backslash-newline combinations - // we have encountered, otherwise line numeration is broken - if (output_enabled) - output.AppendNL (empty_lines); - empty_lines = 0; - } - // Fallthrough to default - FALLTHROUGH; - case Token::TK_WHITESPACE: - // Fallthrough to default - default: - // Passthrough all other tokens - if (output_enabled) - output.Append (t); - break; - } - } - - if (EnableOutput != 1) - { - Error (Line, "Unclosed #if at end of source"); - return Token (Token::TK_ERROR); - } - - return output; -} - -char *CPreprocessor::Parse (const char *iSource, size_t iLength, size_t &oLength) -{ - Token retval = Parse (Token (Token::TK_TEXT, iSource, iLength)); - if (retval.Type == Token::TK_ERROR) - return NULL; - - oLength = retval.Length; - retval.Allocated = 0; - return retval.Buffer; -} Property changes on: ps/trunk/source/ps/Preprocessor.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/ps/PreprocessorWrapper.h =================================================================== --- ps/trunk/source/ps/PreprocessorWrapper.h (revision 23214) +++ ps/trunk/source/ps/PreprocessorWrapper.h (nonexistent) @@ -1,44 +0,0 @@ -/* Copyright (C) 2012 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_PREPROCESSORWRAPPER -#define INCLUDED_PREPROCESSORWRAPPER - -#include "ps/Preprocessor.h" -#include "ps/CStr.h" - -class CShaderDefines; - -/** - * Convenience wrapper around CPreprocessor. - */ -class CPreprocessorWrapper -{ -public: - void AddDefine(const char* name, const char* value); - - void AddDefines(const CShaderDefines& defines); - - bool TestConditional(const CStr& expr); - - CStr Preprocess(const CStr& input); - -private: - CPreprocessor m_Preprocessor; -}; - -#endif // INCLUDED_PREPROCESSORWRAPPER Property changes on: ps/trunk/source/ps/PreprocessorWrapper.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/ps/Preprocessor.h =================================================================== --- ps/trunk/source/ps/Preprocessor.h (revision 23214) +++ ps/trunk/source/ps/Preprocessor.h (nonexistent) @@ -1,541 +0,0 @@ -/* - * This source file originally came from OGRE v1.7.2 - http://www.ogre3d.org/ - * with some tweaks as part of 0 A.D. - * All changes are released under the original license, as follows: - */ - -/* ------------------------------------------------------------------------------ -This source file is part of OGRE - (Object-oriented Graphics Rendering Engine) -For the latest info, see http://www.ogre3d.org/ - -Copyright (c) 2000-2009 Torus Knot Software Ltd - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. ------------------------------------------------------------------------------ -*/ - -#ifndef INCLUDED_CPREPROCESSOR -#define INCLUDED_CPREPROCESSOR - -/** - * This is a simplistic C/C++-like preprocessor. - * It takes an non-zero-terminated string on input and outputs a - * non-zero-terminated string buffer. - * - * This preprocessor was designed specifically for GLSL shaders, so - * if you want to use it for other purposes you might want to check - * if the feature set it provides is enough for you. - * - * Here's a list of supported features: - *
    - *
  • Fast memory allocation-less operation (mostly).
  • - *
  • Line continuation (backslash-newline) is swallowed.
  • - *
  • Line numeration is fully preserved by inserting empty lines where - * required. This is crucial if, say, GLSL compiler reports you an error - * with a line number.
  • - *
  • \#define: Parametrized and non-parametrized macros. Invoking a macro with - * less arguments than it takes assignes empty values to missing arguments.
  • - *
  • \#undef: Forget defined macros
  • - *
  • \#ifdef/\#ifndef/\#else/\#endif: Conditional suppression of parts of code.
  • - *
  • \#if: Supports numeric expression of any complexity, also supports the - * defined() pseudo-function.
  • - *
- */ -class CPreprocessor -{ - /** - * A input token. - * - * For performance reasons most tokens will point to portions of the - * input stream, so no unneeded memory allocation is done. However, - * in some cases we must allocate different memory for token storage, - * in this case this is signalled by setting the Allocated member - * to non-zero in which case the destructor will know that it must - * free memory on object destruction. - * - * Again for performance reasons we use malloc/realloc/free here because - * C++-style new[] lacks the realloc() counterpart. - */ - class Token - { - public: - enum Kind - { - TK_EOS, // End of input stream - TK_ERROR, // An error has been encountered - TK_WHITESPACE, // A whitespace span (but not newline) - TK_NEWLINE, // A single newline (CR & LF) - TK_LINECONT, // Line continuation ('\' followed by LF) - TK_NUMBER, // A number - TK_KEYWORD, // A keyword - TK_PUNCTUATION, // A punctuation character - TK_DIRECTIVE, // A preprocessor directive - TK_STRING, // A string - TK_COMMENT, // A block comment - TK_LINECOMMENT, // A line comment - TK_TEXT, // An unparsed text (cannot be returned from GetToken()) - }; - - /// Token type - Kind Type; - /// True if string was allocated (and must be freed) - mutable size_t Allocated; - union - { - /// A pointer somewhere into the input buffer - const char *String; - /// A memory-allocated string - char *Buffer; - }; - /// Token length in bytes - size_t Length; - - Token () : Type (TK_ERROR), Allocated (0), String (NULL), Length (0) - { } - - Token (Kind iType) : Type (iType), Allocated (0), String (NULL), Length (0) - { } - - Token (Kind iType, const char *iString, size_t iLength) : - Type (iType), Allocated (0), String (iString), Length (iLength) - { } - - Token (const Token &iOther) - { - Type = iOther.Type; - Allocated = iOther.Allocated; - iOther.Allocated = 0; // !!! not quite correct but effective - String = iOther.String; - Length = iOther.Length; - } - - ~Token () - { if (Allocated) free (Buffer); } - - /// Assignment operator - Token &operator = (const Token &iOther) - { - if (Allocated) free (Buffer); - Type = iOther.Type; - Allocated = iOther.Allocated; - iOther.Allocated = 0; // !!! not quite correct but effective - String = iOther.String; - Length = iOther.Length; - return *this; - } - - /// Append a string to this token - void Append (const char *iString, size_t iLength); - - /// Append a token to this token - void Append (const Token &iOther); - - /// Append given number of newlines to this token - void AppendNL (int iCount); - - /// Count number of newlines in this token - int CountNL (); - - /// Get the numeric value of the token - bool GetValue (long &oValue) const; - - /// Set the numeric value of the token - void SetValue (long iValue); - - /// Test two tokens for equality - bool operator == (const Token &iOther) - { - if (iOther.Length != Length) - return false; - return (memcmp (String, iOther.String, Length) == 0); - } - }; - - /// A macro definition - class Macro - { - public: - /// Macro name - Token Name; - /// Number of arguments - int NumArgs; - /// The names of the arguments - Token *Args; - /// The macro value - Token Value; - /// Unparsed macro body (keeps the whole raw unparsed macro body) - Token Body; - /// Next macro in chained list - Macro *Next; - /// A pointer to function implementation (if macro is really a func) - Token (*ExpandFunc) (CPreprocessor *iParent, int iNumArgs, Token *iArgs); - /// true if macro expansion is in progress - bool Expanding; - - Macro (const Token &iName) : - Name (iName), NumArgs (0), Args (NULL), Next (NULL), - ExpandFunc (NULL), Expanding (false) - { } - - ~Macro () - { delete [] Args; delete Next; } - - /// Expand the macro value (will not work for functions) - Token Expand (int iNumArgs, Token *iArgs, Macro *iMacros); - }; - - friend class CPreprocessor::Macro; - - /// The current source text input - const char *Source; - /// The end of the source text - const char *SourceEnd; - /// Current line number - int Line; - /// True if we are at beginning of line - bool BOL; - /// A stack of 32 booleans packed into one value :) - unsigned EnableOutput; - /// The list of macros defined so far - Macro *MacroList; - - /** - * Private constructor to re-parse a single token. - */ - CPreprocessor (const Token &iToken, int iLine); - - /** - * Stateless tokenizer: Parse the input text and return the next token. - * @param iExpand - * If true, macros will be expanded to their values - * @return - * The next token from the input stream - */ - Token GetToken (bool iExpand); - - /** - * Handle a preprocessor directive. - * @param iToken - * The whole preprocessor directive line (until EOL) - * @param iLine - * The line where the directive begins (for error reports) - * @return - * The last input token that was not proceeded. - */ - Token HandleDirective (Token &iToken, int iLine); - - /** - * Handle a \#define directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleDefine (Token &iBody, int iLine); - - /** - * Undefine a previously defined macro - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleUnDef (Token &iBody, int iLine); - - /** - * Handle an \#ifdef directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleIfDef (Token &iBody, int iLine); - - /** - * Handle an \#if directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleIf (Token &iBody, int iLine); - - /** - * Handle an \#else directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleElse (Token &iBody, int iLine); - - /** - * Handle an \#endif directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleEndIf (Token &iBody, int iLine); - - /** - * Get a single function argument until next ',' or ')'. - * @param oArg - * The argument is returned in this variable. - * @param iExpand - * If false, parameters are not expanded and no expressions are - * allowed; only a single keyword is expected per argument. - * @return - * The first unhandled token after argument. - */ - Token GetArgument (Token &oArg, bool iExpand); - - /** - * Get all the arguments of a macro: '(' arg1 { ',' arg2 { ',' ... }} ')' - * @param oNumArgs - * Number of parsed arguments is stored into this variable. - * @param oArgs - * This is set to a pointer to an array of parsed arguments. - * @param iExpand - * If false, parameters are not expanded and no expressions are - * allowed; only a single keyword is expected per argument. - */ - Token GetArguments (int &oNumArgs, Token *&oArgs, bool iExpand); - - /** - * Parse an expression, compute it and return the result. - * @param oResult - * A token containing the result of expression - * @param iLine - * The line at which the expression starts (for error reports) - * @param iOpPriority - * Operator priority (at which operator we will stop if - * proceeding recursively -- used internally. Parser stops - * when it encounters an operator with higher or equal priority). - * @return - * The last unhandled token after the expression - */ - Token GetExpression (Token &oResult, int iLine, int iOpPriority = 0); - - /** - * Get the numeric value of a token. - * If the token was produced by expanding a macro, we will get - * an TEXT token which can contain a whole expression; in this - * case we will call GetExpression to parse it. Otherwise we - * just call the token's GetValue() method. - * @param iToken - * The token to get the numeric value of - * @param oValue - * The variable to put the value into - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if ok, false if not - */ - bool GetValue (const Token &iToken, long &oValue, int iLine); - - /** - * Expand the given macro, if it exists. - * If macro has arguments, they are collected from source stream. - * @param iToken - * A KEYWORD token containing the (possible) macro name. - * @return - * The expanded token or iToken if it is not a macro - */ - Token ExpandMacro (const Token &iToken); - - /** - * Check if a macro is defined, and if so, return it - * @param iToken - * Macro name - * @return - * The macro object or NULL if a macro with this name does not exist - */ - Macro *IsDefined (const Token &iToken); - - /** - * The implementation of the defined() preprocessor function - * @param iParent - * The parent preprocessor object - * @param iNumArgs - * Number of arguments - * @param iArgs - * The arguments themselves - * @return - * The return value encapsulated in a token - */ - static Token ExpandDefined (CPreprocessor *iParent, int iNumArgs, Token *iArgs); - - /** - * Parse the input string and return a token containing the whole output. - * @param iSource - * The source text enclosed in a token - * @return - * The output text enclosed in a token - */ - Token Parse (const Token &iSource); - - /** - * Call the error handler - * @param iLine - * The line at which the error happened. - * @param iError - * The error string. - * @param iToken - * If not NULL contains the erroneous token - */ - void Error (int iLine, const char *iError, const Token *iToken = NULL); - -public: - /// Create an empty preprocessor object - CPreprocessor () : MacroList (NULL) - { } - - /// Destroy the preprocessor object - virtual ~CPreprocessor (); - - /** - * Define a macro without parameters. - * @param iMacroName - * The name of the defined macro - * @param iMacroNameLen - * The length of the name of the defined macro - * @param iMacroValue - * The value of the defined macro - * @param iMacroValueLen - * The length of the value of the defined macro - */ - void Define (const char *iMacroName, size_t iMacroNameLen, - const char *iMacroValue, size_t iMacroValueLen); - - /** - * Define a numerical macro. - * @param iMacroName - * The name of the defined macro - * @param iMacroNameLen - * The length of the name of the defined macro - * @param iMacroValue - * The value of the defined macro - */ - void Define (const char *iMacroName, size_t iMacroNameLen, long iMacroValue); - - /** - * Define a macro without parameters. - * @param iMacroName - * The name of the defined macro - * @param iMacroValue - * The value of the defined macro - */ - void Define (const char *iMacroName, const char *iMacroValue); - - /** - * Define a numerical macro. - * @param iMacroName - * The name of the defined macro - * @param iMacroValue - * The value of the defined macro - */ - void Define (const char *iMacroName, long iMacroValue); - - /** - * Undefine a macro. - * @param iMacroName - * The name of the macro to undefine - * @param iMacroNameLen - * The length of the name of the macro to undefine - * @return - * true if the macro has been undefined, false if macro doesn't exist - */ - bool Undef (const char *iMacroName, size_t iMacroNameLen); - - /** - * Parse the input string and return a newly-allocated output string. - * @note - * The returned preprocessed string is NOT zero-terminated - * (just like the input string). - * @param iSource - * The source text - * @param iLength - * The length of the source text in characters - * @param oLength - * The length of the output string. - * @return - * The output from preprocessor, allocated with malloc(). - * The parser can actually allocate more than needed for performance - * reasons, but this should not be a problem unless you will want - * to store the returned pointer for long time in which case you - * might want to realloc() it. - * If an error has been encountered, the function returns NULL. - * In some cases the function may return an unallocated address - * that's *inside* the source buffer. You must free() the result - * string only if the returned address is not inside the source text. - */ - char *Parse (const char *iSource, size_t iLength, size_t &oLength); - - /** - * An error handler function type. - * The default implementation just drops a note to stderr and - * then the parser ends, returning NULL. - * @param iData - * User-specific pointer from the corresponding CPreprocessor object. - * @param iLine - * The line at which the error happened. - * @param iError - * The error string. - * @param iToken - * If not NULL contains the erroneous token - * @param iTokenLen - * The length of iToken. iToken is never zero-terminated! - */ - typedef void (*ErrorHandlerFunc) ( - void *iData, int iLine, const char *iError, - const char *iToken, size_t iTokenLen); - - /** - * A pointer to the preprocessor's error handler. - * You can assign the address of your own function to this variable - * and implement your own error handling (e.g. throwing an exception etc). - */ - static ErrorHandlerFunc ErrorHandler; - - /// User-specific storage, passed to Error() - void *ErrorData; -}; - -#endif // INCLUDED_CPREPROCESSOR Property changes on: ps/trunk/source/ps/Preprocessor.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/ps/tests/test_Preprocessor.h =================================================================== --- ps/trunk/source/ps/tests/test_Preprocessor.h (revision 23214) +++ ps/trunk/source/ps/tests/test_Preprocessor.h (nonexistent) @@ -1,54 +0,0 @@ -/* Copyright (C) 2016 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#include "lib/self_test.h" - -#include "ps/Preprocessor.h" - -class TestPreprocessor : public CxxTest::TestSuite -{ -public: - void test_basic() - { - CPreprocessor preproc; - const char* in = "#define TEST 2\n1+1=TEST\n"; - size_t len = 0; - char* out = preproc.Parse(in, strlen(in), len); - TS_ASSERT_EQUALS(std::string(out, len), "\n1+1=2\n"); - - // Free output if it's not inside the source string - if (!(out >= in && out < in + strlen(in))) - free(out); - } - - void test_error() - { - TestLogger logger; - - CPreprocessor preproc; - const char* in = "foo\n#if ()\nbar\n"; - size_t len = 0; - char* out = preproc.Parse(in, strlen(in), len); - TS_ASSERT_EQUALS(std::string(out, len), ""); - - TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "ERROR: Preprocessor error: line 2: Unclosed parenthesis in #if expression\n"); - - // Free output if it's not inside the source string - if (!(out >= in && out < in + strlen(in))) - free(out); - } -}; Property changes on: ps/trunk/source/ps/tests/test_Preprocessor.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/build/premake/premake5.lua =================================================================== --- ps/trunk/build/premake/premake5.lua (revision 23214) +++ ps/trunk/build/premake/premake5.lua (revision 23215) @@ -1,1446 +1,1447 @@ newoption { trigger = "android", description = "Use non-working Android cross-compiling mode" } newoption { trigger = "atlas", description = "Include Atlas scenario editor projects" } newoption { trigger = "coverage", description = "Enable code coverage data collection (GCC only)" } newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" } newoption { trigger = "icc", description = "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)" } newoption { trigger = "jenkins-tests", description = "Configure CxxTest to use the XmlPrinter runner which produces Jenkins-compatible output" } newoption { trigger = "minimal-flags", description = "Only set compiler/linker flags that are really needed. Has no effect on Windows builds" } newoption { trigger = "outpath", description = "Location for generated project files" } newoption { trigger = "with-system-mozjs45", description = "Search standard paths for libmozjs45, instead of using bundled copy" } newoption { trigger = "with-system-nvtt", description = "Search standard paths for nvidia-texture-tools library, instead of using bundled copy" } newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" } newoption { trigger = "without-lobby", description = "Disable the use of gloox and the multiplayer lobby" } newoption { trigger = "without-miniupnpc", description = "Disable use of miniupnpc for port forwarding" } newoption { trigger = "without-nvtt", description = "Disable use of NVTT" } newoption { trigger = "without-pch", description = "Disable generation and usage of precompiled headers" } newoption { trigger = "without-tests", description = "Disable generation of test projects" } -- Linux/BSD specific options newoption { trigger = "prefer-local-libs", description = "Prefer locally built libs. Any local libraries used must also be listed within a file within /etc/ld.so.conf.d so the dynamic linker can find them at runtime." } -- OS X specific options newoption { trigger = "macosx-bundle", description = "Enable OSX bundle, the argument is the bundle identifier string (e.g. com.wildfiregames.0ad)" } newoption { trigger = "macosx-version-min", description = "Set minimum required version of the OS X API, the build will possibly fail if an older SDK is used, while newer API functions will be weakly linked (i.e. resolved at runtime)" } newoption { trigger = "sysroot", description = "Set compiler system root path, used for building against a non-system SDK. For example /usr/local becomes SYSROOT/user/local" } -- Windows specific options newoption { trigger = "build-shared-glooxwrapper", description = "Rebuild glooxwrapper DLL for Windows. Requires the same compiler version that gloox was built with" } newoption { trigger = "use-shared-glooxwrapper", description = "Use prebuilt glooxwrapper DLL for Windows" } newoption { trigger = "large-address-aware", description = "Make the executable large address aware. Do not use for development, in order to spot memory issues easily" } -- Install options newoption { trigger = "bindir", description = "Directory for executables (typically '/usr/games'); default is to be relocatable" } newoption { trigger = "datadir", description = "Directory for data files (typically '/usr/share/games/0ad'); default is ../data/ relative to executable" } newoption { trigger = "libdir", description = "Directory for libraries (typically '/usr/lib/games/0ad'); default is ./ relative to executable" } -- Root directory of project checkout relative to this .lua file rootdir = "../.." dofile("extern_libs5.lua") -- detect compiler for non-Windows if os.istarget("macosx") then cc = "clang" elseif os.istarget("linux") and _OPTIONS["icc"] then cc = "icc" elseif not os.istarget("windows") then cc = os.getenv("CC") if cc == nil or cc == "" then local hasgcc = os.execute("which gcc > .gccpath") local f = io.open(".gccpath", "r") local gccpath = f:read("*line") f:close() os.execute("rm .gccpath") if gccpath == nil then cc = "clang" else cc = "gcc" end end end -- detect CPU architecture (simplistic, currently only supports x86, amd64 and ARM) arch = "x86" if _OPTIONS["android"] then arch = "arm" elseif os.istarget("windows") then if os.getenv("PROCESSOR_ARCHITECTURE") == "amd64" or os.getenv("PROCESSOR_ARCHITEW6432") == "amd64" then arch = "amd64" end else arch = os.getenv("HOSTTYPE") if arch == "x86_64" or arch == "amd64" then arch = "amd64" else os.execute(cc .. " -dumpmachine > .gccmachine.tmp") local f = io.open(".gccmachine.tmp", "r") local machine = f:read("*line") f:close() if string.find(machine, "x86_64") == 1 or string.find(machine, "amd64") == 1 then arch = "amd64" elseif string.find(machine, "i.86") == 1 then arch = "x86" elseif string.find(machine, "arm") == 1 then arch = "arm" elseif string.find(machine, "aarch64") == 1 then arch = "aarch64" else print("WARNING: Cannot determine architecture from GCC, assuming x86") end end end -- Set up the Workspace workspace "pyrogenesis" targetdir(rootdir.."/binaries/system") libdirs(rootdir.."/binaries/system") if not _OPTIONS["outpath"] then error("You must specify the 'outpath' parameter") end location(_OPTIONS["outpath"]) configurations { "Release", "Debug" } source_root = rootdir.."/source/" -- default for most projects - overridden by local in others -- Rationale: projects should not have any additional include paths except for -- those required by external libraries. Instead, we should always write the -- full relative path, e.g. #include "maths/Vector3d.h". This avoids confusion -- ("which file is meant?") and avoids enormous include path lists. -- projects: engine static libs, main exe, atlas, atlas frontends, test. -------------------------------------------------------------------------------- -- project helper functions -------------------------------------------------------------------------------- function project_set_target(project_name) -- Note: On Windows, ".exe" is added on the end, on unices the name is used directly local obj_dir_prefix = _OPTIONS["outpath"].."/obj/"..project_name.."_" filter "Debug" objdir(obj_dir_prefix.."Debug") targetsuffix("_dbg") filter "Release" objdir(obj_dir_prefix.."Release") filter { } end function project_set_build_flags() editandcontinue "Off" if not _OPTIONS["minimal-flags"] then symbols "On" end if cc ~= "icc" and (os.istarget("windows") or not _OPTIONS["minimal-flags"]) then -- adds the -Wall compiler flag warnings "Extra" -- this causes far too many warnings/remarks on ICC end -- disable Windows debug heap, since it makes malloc/free hugely slower when -- running inside a debugger if os.istarget("windows") then debugenvs { "_NO_DEBUG_HEAP=1" } end filter "Debug" defines { "DEBUG" } filter "Release" if os.istarget("windows") or not _OPTIONS["minimal-flags"] then optimize "Speed" end defines { "NDEBUG", "CONFIG_FINAL=1" } filter { } if _OPTIONS["gles"] then defines { "CONFIG2_GLES=1" } end if _OPTIONS["without-audio"] then defines { "CONFIG2_AUDIO=0" } end if _OPTIONS["without-nvtt"] then defines { "CONFIG2_NVTT=0" } end if _OPTIONS["without-lobby"] then defines { "CONFIG2_LOBBY=0" } end if _OPTIONS["without-miniupnpc"] then defines { "CONFIG2_MINIUPNPC=0" } end -- required for the lowlevel library. must be set from all projects that use it, otherwise it assumes it is -- being used as a DLL (which is currently not the case in 0ad) defines { "LIB_STATIC_LINK" } -- various platform-specific build flags if os.istarget("windows") then flags { "MultiProcessorCompile" } -- use native wchar_t type (not typedef to unsigned short) nativewchar "on" else -- *nix -- TODO, FIXME: This check is incorrect because it means that some additional flags will be added inside the "else" branch if the -- compiler is ICC and minimal-flags is specified (ticket: #2994) if cc == "icc" and not _OPTIONS["minimal-flags"] then buildoptions { "-w1", -- "-Wabi", -- "-Wp64", -- complains about OBJECT_TO_JSVAL which is annoying "-Wpointer-arith", "-Wreturn-type", -- "-Wshadow", "-Wuninitialized", "-Wunknown-pragmas", "-Wunused-function", "-wd1292" -- avoid lots of 'attribute "__nonnull__" ignored' } filter "Debug" buildoptions { "-O0" } -- ICC defaults to -O2 filter { } if os.istarget("macosx") then linkoptions { "-multiply_defined","suppress" } end else -- exclude most non-essential build options for minimal-flags if not _OPTIONS["minimal-flags"] then buildoptions { -- enable most of the standard warnings "-Wno-switch", -- enumeration value not handled in switch (this is sometimes useful, but results in lots of noise) "-Wno-reorder", -- order of initialization list in constructors (lots of noise) "-Wno-invalid-offsetof", -- offsetof on non-POD types (see comment in renderer/PatchRData.cpp) "-Wextra", "-Wno-missing-field-initializers", -- (this is common in external headers we can't fix) -- add some other useful warnings that need to be enabled explicitly "-Wunused-parameter", "-Wredundant-decls", -- (useful for finding some multiply-included header files) -- "-Wformat=2", -- (useful sometimes, but a bit noisy, so skip it by default) -- "-Wcast-qual", -- (useful for checking const-correctness, but a bit noisy, so skip it by default) "-Wnon-virtual-dtor", -- (sometimes noisy but finds real bugs) "-Wundef", -- (useful for finding macro name typos) -- enable security features (stack checking etc) that shouldn't have -- a significant effect on performance and can catch bugs "-fstack-protector-all", "-U_FORTIFY_SOURCE", -- (avoid redefinition warning if already defined) "-D_FORTIFY_SOURCE=2", -- always enable strict aliasing (useful in debug builds because of the warnings) "-fstrict-aliasing", -- don't omit frame pointers (for now), because performance will be impacted -- negatively by the way this breaks profilers more than it will be impacted -- positively by the optimisation "-fno-omit-frame-pointer" } if not _OPTIONS["without-pch"] then buildoptions { -- do something (?) so that ccache can handle compilation with PCH enabled -- (ccache 3.1+ also requires CCACHE_SLOPPINESS=time_macros for this to work) "-fpch-preprocess" } end if os.istarget("linux") or os.istarget("bsd") then buildoptions { "-fPIC" } linkoptions { "-Wl,--no-undefined", "-Wl,--as-needed", "-Wl,-z,relro" } end if arch == "x86" then buildoptions { -- To support intrinsics like __sync_bool_compare_and_swap on x86 -- we need to set -march to something that supports them (i686). -- We use pentium3 to also enable other features like mmx and sse, -- while tuning for generic to have good performance on every -- supported CPU. -- Note that all these features are already supported on amd64. "-march=pentium3 -mtune=generic" } end end buildoptions { -- Enable C++11 standard. "-std=c++0x" } if arch == "arm" then -- disable warnings about va_list ABI change and use -- compile-time flags for futher configuration. buildoptions { "-Wno-psabi" } if _OPTIONS["android"] then -- Android uses softfp, so we should too. buildoptions { "-mfloat-abi=softfp" } end end if _OPTIONS["coverage"] then buildoptions { "-fprofile-arcs", "-ftest-coverage" } links { "gcov" } end -- We don't want to require SSE2 everywhere yet, but OS X headers do -- require it (and Intel Macs always have it) so enable it here if os.istarget("macosx") then buildoptions { "-msse2" } end -- Check if SDK path should be used if _OPTIONS["sysroot"] then buildoptions { "-isysroot " .. _OPTIONS["sysroot"] } linkoptions { "-Wl,-syslibroot," .. _OPTIONS["sysroot"] } end -- On OS X, sometimes we need to specify the minimum API version to use if _OPTIONS["macosx-version-min"] then buildoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } -- clang and llvm-gcc look at mmacosx-version-min to determine link target -- and CRT version, and use it to set the macosx_version_min linker flag linkoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } end -- Check if we're building a bundle if _OPTIONS["macosx-bundle"] then defines { "BUNDLE_IDENTIFIER=" .. _OPTIONS["macosx-bundle"] } end -- On OS X, force using libc++ since it has better C++11 support, -- now required by the game if os.istarget("macosx") then buildoptions { "-stdlib=libc++" } linkoptions { "-stdlib=libc++" } end end buildoptions { -- Hide symbols in dynamic shared objects by default, for efficiency and for equivalence with -- Windows - they should be exported explicitly with __attribute__ ((visibility ("default"))) "-fvisibility=hidden" } if _OPTIONS["bindir"] then defines { "INSTALLED_BINDIR=" .. _OPTIONS["bindir"] } end if _OPTIONS["datadir"] then defines { "INSTALLED_DATADIR=" .. _OPTIONS["datadir"] } end if _OPTIONS["libdir"] then defines { "INSTALLED_LIBDIR=" .. _OPTIONS["libdir"] } end if os.istarget("linux") or os.istarget("bsd") then if _OPTIONS["prefer-local-libs"] then libdirs { "/usr/local/lib" } end -- To use our local shared libraries, they need to be found in the -- runtime dynamic linker path. Add their path to -rpath. if _OPTIONS["libdir"] then linkoptions {"-Wl,-rpath," .. _OPTIONS["libdir"] } else -- On FreeBSD we need to allow use of $ORIGIN if os.istarget("bsd") then linkoptions { "-Wl,-z,origin" } end -- Adding the executable path and taking care of correct escaping if _ACTION == "gmake" then linkoptions { "-Wl,-rpath,'$$ORIGIN'" } elseif _ACTION == "codeblocks" then linkoptions { "-Wl,-R\\\\$$$ORIGIN" } end end end end end -- create a project and set the attributes that are common to all projects. function project_create(project_name, target_type) project(project_name) language "C++" kind(target_type) filter "action:vs2013" toolset "v120_xp" filter "action:vs2015" toolset "v140_xp" filter {} project_set_target(project_name) project_set_build_flags() end -- OSX creates a .app bundle if the project type of the main application is set to "WindowedApp". -- We don't want this because this bundle would be broken (it lacks all the resources and external dependencies, Info.plist etc...) -- Windows opens a console in the background if it's set to ConsoleApp, which is not what we want. -- I didn't check if this setting matters for linux, but WindowedApp works there. function get_main_project_target_type() if _OPTIONS["android"] then return "SharedLib" elseif os.istarget("macosx") then return "ConsoleApp" else return "WindowedApp" end end -- source_root: rel_source_dirs and rel_include_dirs are relative to this directory -- rel_source_dirs: A table of subdirectories. All source files in these directories are added. -- rel_include_dirs: A table of subdirectories to be included. -- extra_params: table including zero or more of the following: -- * no_pch: If specified, no precompiled headers are used for this project. -- * pch_dir: If specified, this directory will be used for precompiled headers instead of the default -- /pch//. -- * extra_files: table of filenames (relative to source_root) to add to project -- * extra_links: table of library names to add to link step function project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) for i,v in pairs(rel_source_dirs) do local prefix = source_root..v.."/" files { prefix.."*.cpp", prefix.."*.h", prefix.."*.inl", prefix.."*.js", prefix.."*.asm", prefix.."*.mm" } end -- Put the project-specific PCH directory at the start of the -- include path, so '#include "precompiled.h"' will look in -- there first local pch_dir if not extra_params["pch_dir"] then pch_dir = source_root .. "pch/" .. project().name .. "/" else pch_dir = extra_params["pch_dir"] end includedirs { pch_dir } -- Precompiled Headers -- rationale: we need one PCH per static lib, since one global header would -- increase dependencies. To that end, we can either include them as -- "projectdir/precompiled.h", or add "source/PCH/projectdir" to the -- include path and put the PCH there. The latter is better because -- many projects contain several dirs and it's unclear where there the -- PCH should be stored. This way is also a bit easier to use in that -- source files always include "precompiled.h". -- Notes: -- * Visual Assist manages to use the project include path and can -- correctly open these files from the IDE. -- * precompiled.cpp (needed to "Create" the PCH) also goes in -- the abovementioned dir. if (not _OPTIONS["without-pch"] and not extra_params["no_pch"]) then filter "action:vs*" pchheader("precompiled.h") filter "action:xcode*" pchheader("../"..pch_dir.."precompiled.h") filter { "action:not vs*", "action:not xcode*" } pchheader(pch_dir.."precompiled.h") filter {} pchsource(pch_dir.."precompiled.cpp") defines { "USING_PCH" } files { pch_dir.."precompiled.h", pch_dir.."precompiled.cpp" } else flags { "NoPCH" } end -- next is source root dir, for absolute (nonrelative) includes -- (e.g. "lib/precompiled.h") includedirs { source_root } for i,v in pairs(rel_include_dirs) do includedirs { source_root .. v } end if extra_params["extra_files"] then for i,v in pairs(extra_params["extra_files"]) do -- .rc files are only needed on Windows if path.getextension(v) ~= ".rc" or os.istarget("windows") then files { source_root .. v } end end end if extra_params["extra_links"] then links { extra_params["extra_links"] } end end -- Add command-line options to set up the manifest dependencies for Windows -- (See lib/sysdep/os/win/manifest.cpp) function project_add_manifest() linkoptions { "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df'\"" } end -------------------------------------------------------------------------------- -- engine static libraries -------------------------------------------------------------------------------- -- the engine is split up into several static libraries. this eases separate -- distribution of those components, reduces dependencies a bit, and can -- also speed up builds. -- more to the point, it is necessary to efficiently support a separate -- test executable that also includes much of the game code. -- names of all static libs created. automatically added to the -- main app project later (see explanation at end of this file) static_lib_names = {} static_lib_names_debug = {} static_lib_names_release = {} -- set up one of the static libraries into which the main engine code is split. -- extra_params: -- no_default_link: If specified, linking won't be done by default. -- For the rest of extra_params, see project_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. function setup_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "StaticLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then rtti "off" elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end function setup_third_party_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) setup_static_lib_project(project_name, rel_source_dirs, extern_libs, extra_params) includedirs { source_root .. "third_party/" .. project_name .. "/include/" } end function setup_shared_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "SharedLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then rtti "off" links { "delayimp" } elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end -- this is where the source tree is chopped up into static libs. -- can be changed very easily; just copy+paste a new setup_static_lib_project, -- or remove existing ones. static libs are automagically added to -- main_exe link step. function setup_all_libs () -- relative to global source_root. local source_dirs = {} -- names of external libraries used (see libraries_dir comment) local extern_libs = {} source_dirs = { "network", } extern_libs = { "spidermonkey", "enet", "boost", -- dragged in via server->simulation.h->random } if not _OPTIONS["without-miniupnpc"] then table.insert(extern_libs, "miniupnpc") end setup_static_lib_project("network", source_dirs, extern_libs, {}) source_dirs = { "third_party/tinygettext/src", } extern_libs = { "iconv", "boost", } setup_third_party_static_lib_project("tinygettext", source_dirs, extern_libs, { } ) -- it's an external library and we don't want to modify its source to fix warnings, so we just disable them to avoid noise in the compile output filter "action:vs*" buildoptions { "/wd4127", "/wd4309", "/wd4800", "/wd4100", "/wd4996", "/wd4099", "/wd4503" } filter {} if not _OPTIONS["without-lobby"] then source_dirs = { "lobby", "lobby/scripting", "i18n", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "enet", "gloox", "icu", "iconv", "libsodium", "tinygettext" } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) if _OPTIONS["use-shared-glooxwrapper"] and not _OPTIONS["build-shared-glooxwrapper"] then table.insert(static_lib_names_debug, "glooxwrapper_dbg") table.insert(static_lib_names_release, "glooxwrapper") else source_dirs = { "lobby/glooxwrapper", } extern_libs = { "boost", "gloox", } if _OPTIONS["build-shared-glooxwrapper"] then setup_shared_lib_project("glooxwrapper", source_dirs, extern_libs, {}) else setup_static_lib_project("glooxwrapper", source_dirs, extern_libs, {}) end end else source_dirs = { "lobby/scripting", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "libsodium" } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) files { source_root.."lobby/Globals.cpp" } end source_dirs = { "simulation2", "simulation2/components", "simulation2/helpers", "simulation2/scripting", "simulation2/serialization", "simulation2/system", "simulation2/testcomponents", } extern_libs = { "boost", "opengl", "spidermonkey", } setup_static_lib_project("simulation2", source_dirs, extern_libs, {}) source_dirs = { "scriptinterface", "scriptinterface/third_party" } extern_libs = { "boost", "spidermonkey", "valgrind", "sdl", } setup_static_lib_project("scriptinterface", source_dirs, extern_libs, {}) source_dirs = { "ps", "ps/scripting", "network/scripting", "ps/GameSetup", "ps/XML", "soundmanager", "soundmanager/data", "soundmanager/items", "soundmanager/scripting", "maths", "maths/scripting", "i18n", "i18n/scripting", "third_party/cppformat", } extern_libs = { "spidermonkey", "sdl", -- key definitions "libxml2", "opengl", "zlib", "boost", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") table.insert(extern_libs, "vorbis") end setup_static_lib_project("engine", source_dirs, extern_libs, {}) source_dirs = { "graphics", "graphics/scripting", "renderer", "renderer/scripting", - "third_party/mikktspace" + "third_party/mikktspace", + "third_party/ogre3d_preprocessor" } extern_libs = { "opengl", "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost" } if not _OPTIONS["without-nvtt"] then table.insert(extern_libs, "nvtt") end setup_static_lib_project("graphics", source_dirs, extern_libs, {}) source_dirs = { "tools/atlas/GameInterface", "tools/atlas/GameInterface/Handlers" } extern_libs = { "boost", "sdl", -- key definitions "opengl", "spidermonkey" } setup_static_lib_project("atlas", source_dirs, extern_libs, {}) source_dirs = { "gui", "gui/ObjectTypes", "gui/ObjectBases", "gui/Scripting", "gui/SettingTypes", "i18n" } extern_libs = { "spidermonkey", "sdl", -- key definitions "opengl", "boost", "enet", "tinygettext", "icu", "iconv", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") end setup_static_lib_project("gui", source_dirs, extern_libs, {}) source_dirs = { "lib", "lib/adts", "lib/allocators", "lib/external_libraries", "lib/file", "lib/file/archive", "lib/file/common", "lib/file/io", "lib/file/vfs", "lib/pch", "lib/posix", "lib/res", "lib/res/graphics", "lib/sysdep", "lib/tex" } extern_libs = { "boost", "sdl", "openal", "opengl", "libpng", "zlib", "valgrind", "cxxtest", } -- CPU architecture-specific if arch == "amd64" then table.insert(source_dirs, "lib/sysdep/arch/amd64"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "x86" then table.insert(source_dirs, "lib/sysdep/arch/ia32"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "arm" then table.insert(source_dirs, "lib/sysdep/arch/arm"); elseif arch == "aarch64" then table.insert(source_dirs, "lib/sysdep/arch/aarch64"); end -- OS-specific sysdep_dirs = { linux = { "lib/sysdep/os/linux", "lib/sysdep/os/unix" }, -- note: RC file must be added to main_exe project. -- note: don't add "lib/sysdep/os/win/aken.cpp" because that must be compiled with the DDK. windows = { "lib/sysdep/os/win", "lib/sysdep/os/win/wposix", "lib/sysdep/os/win/whrt" }, macosx = { "lib/sysdep/os/osx", "lib/sysdep/os/unix" }, bsd = { "lib/sysdep/os/bsd", "lib/sysdep/os/unix", "lib/sysdep/os/unix/x" }, } for i,v in pairs(sysdep_dirs[os.target()]) do table.insert(source_dirs, v); end if os.istarget("linux") then if _OPTIONS["android"] then table.insert(source_dirs, "lib/sysdep/os/android") else table.insert(source_dirs, "lib/sysdep/os/unix/x") end end -- On OSX, disable precompiled headers because C++ files and Objective-C++ files are -- mixed in this project. To fix that, we would need per-file basis configuration which -- is not yet supported by the gmake action in premake. We should look into using gmake2. extra_params = {} if os.istarget("macosx") then extra_params = { no_pch = 1 } end -- runtime-library-specific if _ACTION == "vs2013" or _ACTION == "vs2015" then table.insert(source_dirs, "lib/sysdep/rtl/msc"); else table.insert(source_dirs, "lib/sysdep/rtl/gcc"); end setup_static_lib_project("lowlevel", source_dirs, extern_libs, extra_params) -- Third-party libraries that are built as part of the main project, -- not built externally and then linked source_dirs = { "third_party/mongoose", } extern_libs = { } setup_static_lib_project("mongoose", source_dirs, extern_libs, { no_pch = 1 }) -- CxxTest mock function support extern_libs = { "boost", "cxxtest", } -- 'real' implementations, to be linked against the main executable -- (files are added manually and not with setup_static_lib_project -- because not all files in the directory are included) setup_static_lib_project("mocks_real", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { "mocks/*.h", source_root.."mocks/*_real.cpp" } -- 'test' implementations, to be linked against the test executable setup_static_lib_project("mocks_test", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { source_root.."mocks/*.h", source_root.."mocks/*_test.cpp" } end -------------------------------------------------------------------------------- -- main EXE -------------------------------------------------------------------------------- -- used for main EXE as well as test used_extern_libs = { "opengl", "sdl", "libpng", "zlib", "spidermonkey", "libxml2", "boost", "cxxtest", "comsuppw", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", "valgrind", } if not os.istarget("windows") and not _OPTIONS["android"] and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(used_extern_libs, "x11") table.insert(used_extern_libs, "xcursor") end if not _OPTIONS["without-audio"] then table.insert(used_extern_libs, "openal") table.insert(used_extern_libs, "vorbis") end if not _OPTIONS["without-nvtt"] then table.insert(used_extern_libs, "nvtt") end if not _OPTIONS["without-lobby"] then table.insert(used_extern_libs, "gloox") end if not _OPTIONS["without-miniupnpc"] then table.insert(used_extern_libs, "miniupnpc") end -- Bundles static libs together with main.cpp and builds game executable. function setup_main_exe () local target_type = get_main_project_target_type() project_create("pyrogenesis", target_type) filter "system:not macosx" linkgroups 'On' filter {} links { "mocks_real" } local extra_params = { extra_files = { "main.cpp" }, no_pch = 1 } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } -- Platform Specifics if os.istarget("windows") then files { source_root.."lib/sysdep/os/win/icon.rc" } -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } rtti "off" linkoptions { -- wraps main thread in a __try block(see wseh.cpp). replace with mainCRTStartup if that's undesired. "/ENTRY:wseh_EntryPoint", -- see wstartup.h "/INCLUDE:_wstartup_InitAndRegisterShutdown", -- allow manual unload of delay-loaded DLLs "/DELAY:UNLOAD", } -- allow the executable to use more than 2GB of RAM. -- this should not be enabled during development, so that memory issues are easily spotted. if _OPTIONS["large-address-aware"] then linkoptions { "/LARGEADDRESSAWARE" } end -- see manifest.cpp project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } links { "log" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } elseif os.istarget("bsd") then links { -- Needed for backtrace* on BSDs "execinfo", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } elseif os.istarget("macosx") then links { "pthread" } links { "ApplicationServices.framework", "Cocoa.framework", "CoreFoundation.framework" } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end -------------------------------------------------------------------------------- -- atlas -------------------------------------------------------------------------------- -- setup a typical Atlas component project -- extra_params, rel_source_dirs and rel_include_dirs: as in project_add_contents; function setup_atlas_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = rootdir.."/source/tools/atlas/" .. project_name .. "/" project_create(project_name, target_type) -- if not specified, the default for atlas pch files is in the project root. if not extra_params["pch_dir"] then extra_params["pch_dir"] = source_root end project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then -- Link to required libraries links { "winmm", "comctl32", "rpcrt4", "delayimp", "ws2_32" } elseif os.istarget("linux") or os.istarget("bsd") then buildoptions { "-rdynamic", "-fPIC" } linkoptions { "-fPIC", "-rdynamic" } -- warnings triggered by wxWidgets buildoptions { "-Wno-unused-local-typedefs" } elseif os.istarget("macosx") then -- install_name settings aren't really supported yet by premake, but there are plans for the future. -- we currently use this hack to work around some bugs with wrong install_names. if target_type == "SharedLib" then if _OPTIONS["macosx-bundle"] then -- If we're building a bundle, it will be in ../Frameworks filter "Debug" linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name.."_dbg.dylib" } filter "Release" linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name..".dylib" } filter { } else filter "Debug" linkoptions { "-install_name @executable_path/lib"..project_name.."_dbg.dylib" } filter "Release" linkoptions { "-install_name @executable_path/lib"..project_name..".dylib" } filter { } end end end end -- build all Atlas component projects function setup_atlas_projects() setup_atlas_project("AtlasObject", "StaticLib", { -- src ".", "../../../third_party/jsonspirit" },{ -- include "../../../third_party/jsonspirit" },{ -- extern_libs "boost", "iconv", "libxml2" },{ -- extra_params no_pch = 1 }) atlas_src = { "ActorEditor", "CustomControls/Buttons", "CustomControls/Canvas", "CustomControls/ColorDialog", "CustomControls/DraggableListCtrl", "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", "General", "General/VideoRecorder", "Misc", "ScenarioEditor", "ScenarioEditor/Sections/Common", "ScenarioEditor/Sections/Cinema", "ScenarioEditor/Sections/Environment", "ScenarioEditor/Sections/Map", "ScenarioEditor/Sections/Object", "ScenarioEditor/Sections/Player", "ScenarioEditor/Sections/Terrain", "ScenarioEditor/Tools", "ScenarioEditor/Tools/Common", } atlas_extra_links = { "AtlasObject" } atlas_extern_libs = { "boost", "comsuppw", "iconv", "libxml2", "sdl", -- key definitions "wxwidgets", "zlib", } if not os.istarget("windows") and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(atlas_extern_libs, "x11") end setup_atlas_project("AtlasUI", "SharedLib", atlas_src, { -- include "..", "CustomControls", "Misc" }, atlas_extern_libs, { -- extra_params pch_dir = rootdir.."/source/tools/atlas/AtlasUI/Misc/", no_pch = false, extra_links = atlas_extra_links, extra_files = { "Misc/atlas.rc" } }) end -- Atlas 'frontend' tool-launching projects function setup_atlas_frontend_project (project_name) local target_type = get_main_project_target_type() project_create(project_name, target_type) local source_root = rootdir.."/source/tools/atlas/AtlasFrontends/" files { source_root..project_name..".cpp" } if os.istarget("windows") then files { source_root..project_name..".rc" } end includedirs { source_root .. ".." } -- Platform Specifics if os.istarget("windows") then -- see manifest.cpp project_add_manifest() else -- Non-Windows, = Unix links { "AtlasObject" } end links { "AtlasUI" } end function setup_atlas_frontends() setup_atlas_frontend_project("ActorEditor") end -------------------------------------------------------------------------------- -- collada -------------------------------------------------------------------------------- function setup_collada_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) project_create(project_name, target_type) local source_root = source_root.."collada/" extra_params["pch_dir"] = source_root project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then characterset "MBCS" elseif os.istarget("linux") then defines { "LINUX" } links { "dl", } -- FCollada is not aliasing-safe, so disallow dangerous optimisations -- (TODO: It'd be nice to fix FCollada, but that looks hard) buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("bsd") then if os.getversion().description == "OpenBSD" then links { "c", } end if os.getversion().description == "GNU/kFreeBSD" then links { "dl", } end buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("macosx") then -- define MACOS-something? -- install_name settings aren't really supported yet by premake, but there are plans for the future. -- we currently use this hack to work around some bugs with wrong install_names. if target_type == "SharedLib" then if _OPTIONS["macosx-bundle"] then -- If we're building a bundle, it will be in ../Frameworks linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name..".dylib" } else linkoptions { "-install_name @executable_path/lib"..project_name..".dylib" } end end buildoptions { "-fno-strict-aliasing" } -- On OSX, fcollada uses a few utility functions from coreservices links { "CoreServices.framework" } end end -- build all Collada component projects function setup_collada_projects() setup_collada_project("Collada", "SharedLib", { -- src "." },{ -- include },{ -- extern_libs "fcollada", "iconv", "libxml2" },{ -- extra_params }) end -------------------------------------------------------------------------------- -- tests -------------------------------------------------------------------------------- function setup_tests() local cxxtest = require "cxxtest" if os.istarget("windows") then cxxtest.setpath(rootdir.."/build/bin/cxxtestgen.exe") else cxxtest.setpath(rootdir.."/libraries/source/cxxtest-4.4/bin/cxxtestgen") end local runner = "ErrorPrinter" if _OPTIONS["jenkins-tests"] then runner = "XmlPrinter" end local includefiles = { -- Precompiled headers - the header is added to all generated .cpp files -- note that the header isn't actually precompiled here, only #included -- so that the build stage can use it as a precompiled header. "precompiled.h", -- This is required to build against SDL 2.0.4 on Windows. "lib/external_libraries/libsdl.h", } cxxtest.init(source_root, true, runner, includefiles) local target_type = get_main_project_target_type() project_create("test", target_type) -- Find header files in 'test' subdirectories local all_files = os.matchfiles(source_root .. "**/tests/*.h") local test_files = {} for i,v in pairs(all_files) do -- Don't include sysdep tests on the wrong sys -- Don't include Atlas tests unless Atlas is being built if not (string.find(v, "/sysdep/os/win/") and not os.istarget("windows")) and not (string.find(v, "/tools/atlas/") and not _OPTIONS["atlas"]) and not (string.find(v, "/sysdep/arch/x86_x64/") and ((arch ~= "amd64") or (arch ~= "x86"))) then table.insert(test_files, v) end end cxxtest.configure_project(test_files) filter "system:not macosx" linkgroups 'On' filter {} links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } links { "mocks_test" } if _OPTIONS["atlas"] then links { "AtlasObject" } end extra_params = { extra_files = { "test_setup.cpp" }, } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } -- TODO: should fix the duplication between this OS-specific linking -- code, and the similar version in setup_main_exe if os.istarget("windows") then -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } rtti "off" -- see wstartup.h linkoptions { "/INCLUDE:_wstartup_InitAndRegisterShutdown" } -- Enables console for the TEST project on Windows linkoptions { "/SUBSYSTEM:CONSOLE" } project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } elseif os.istarget("bsd") then links { -- Needed for backtrace* on BSDs "execinfo", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } includedirs { source_root .. "pch/test/" } elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end -- must come first, so that VC sets it as the default project and therefore -- allows running via F5 without the "where is the EXE" dialog. setup_main_exe() setup_all_libs() -- add the static libs to the main EXE project. only now (after -- setup_all_libs has run) are the lib names known. cannot move -- setup_main_exe to run after setup_all_libs (see comment above). -- we also don't want to hardcode the names - that would require more -- work when changing the static lib breakdown. project("pyrogenesis") -- Set the main project active links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } if _OPTIONS["atlas"] then setup_atlas_projects() setup_atlas_frontends() end setup_collada_projects() if not _OPTIONS["without-tests"] then setup_tests() end Index: ps/trunk/source/graphics/MaterialManager.cpp =================================================================== --- ps/trunk/source/graphics/MaterialManager.cpp (revision 23214) +++ ps/trunk/source/graphics/MaterialManager.cpp (revision 23215) @@ -1,197 +1,197 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MaterialManager.h" +#include "graphics/PreprocessorWrapper.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" -#include "ps/PreprocessorWrapper.h" #include "ps/XML/Xeromyces.h" #include "renderer/RenderingOptions.h" #include CMaterialManager::CMaterialManager() { qualityLevel = 5.0; CFG_GET_VAL("materialmgr.quality", qualityLevel); qualityLevel = Clamp(qualityLevel, 0.0f, 10.0f); if (VfsDirectoryExists(L"art/materials/") && !CXeromyces::AddValidator(g_VFS, "material", "art/materials/material.rng")) LOGERROR("CMaterialManager: failed to load grammar file 'art/materials/material.rng'"); } CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) { if (pathname.empty()) return CMaterial(); std::map::iterator iter = m_Materials.find(pathname); if (iter != m_Materials.end()) return iter->second; CXeromyces xeroFile; if (xeroFile.Load(g_VFS, pathname, "material") != PSRETURN_OK) return CMaterial(); #define EL(x) int el_##x = xeroFile.GetElementID(#x) #define AT(x) int at_##x = xeroFile.GetAttributeID(#x) EL(alpha_blending); EL(alternative); EL(define); EL(shader); EL(uniform); EL(renderquery); EL(required_texture); EL(conditional_define); AT(effect); AT(if); AT(define); AT(quality); AT(material); AT(name); AT(value); AT(type); AT(min); AT(max); AT(conf); #undef AT #undef EL CPreprocessorWrapper preprocessor; preprocessor.AddDefine("CFG_FORCE_ALPHATEST", g_RenderingOptions.GetForceAlphaTest() ? "1" : "0"); CMaterial material; material.AddStaticUniform("qualityLevel", CVector4D(qualityLevel, 0, 0, 0)); XMBElement root = xeroFile.GetRoot(); XERO_ITER_EL(root, node) { int token = node.GetNodeName(); XMBAttributeList attrs = node.GetAttributes(); if (token == el_alternative) { CStr cond = attrs.GetNamedItem(at_if); if (cond.empty() || !preprocessor.TestConditional(cond)) { cond = attrs.GetNamedItem(at_quality); if (cond.empty()) continue; else { if (cond.ToFloat() <= qualityLevel) continue; } } material = LoadMaterial(VfsPath("art/materials") / attrs.GetNamedItem(at_material).FromUTF8()); break; } else if (token == el_alpha_blending) { material.SetUsesAlphaBlending(true); } else if (token == el_shader) { material.SetShaderEffect(attrs.GetNamedItem(at_effect)); } else if (token == el_define) { material.AddShaderDefine(CStrIntern(attrs.GetNamedItem(at_name)), CStrIntern(attrs.GetNamedItem(at_value))); } else if (token == el_conditional_define) { std::vector args; CStr type = attrs.GetNamedItem(at_type).c_str(); int typeID = -1; if (type == CStr("draw_range")) { typeID = DCOND_DISTANCE; float valmin = -1.0f; float valmax = -1.0f; CStr conf = attrs.GetNamedItem(at_conf); if (!conf.empty()) { CFG_GET_VAL("materialmgr." + conf + ".min", valmin); CFG_GET_VAL("materialmgr." + conf + ".max", valmax); } else { CStr dmin = attrs.GetNamedItem(at_min); if (!dmin.empty()) valmin = attrs.GetNamedItem(at_min).ToFloat(); CStr dmax = attrs.GetNamedItem(at_max); if (!dmax.empty()) valmax = attrs.GetNamedItem(at_max).ToFloat(); } args.push_back(valmin); args.push_back(valmax); if (valmin >= 0.0f) { std::stringstream sstr; sstr << valmin; material.AddShaderDefine(CStrIntern(conf + "_MIN"), CStrIntern(sstr.str())); } if (valmax >= 0.0f) { std::stringstream sstr; sstr << valmax; material.AddShaderDefine(CStrIntern(conf + "_MAX"), CStrIntern(sstr.str())); } } material.AddConditionalDefine(attrs.GetNamedItem(at_name).c_str(), attrs.GetNamedItem(at_value).c_str(), typeID, args); } else if (token == el_uniform) { std::stringstream str(attrs.GetNamedItem(at_value)); CVector4D vec; str >> vec.X >> vec.Y >> vec.Z >> vec.W; material.AddStaticUniform(attrs.GetNamedItem(at_name).c_str(), vec); } else if (token == el_renderquery) { material.AddRenderQuery(attrs.GetNamedItem(at_name).c_str()); } else if (token == el_required_texture) { material.AddRequiredSampler(attrs.GetNamedItem(at_name)); if (!attrs.GetNamedItem(at_define).empty()) material.AddShaderDefine(CStrIntern(attrs.GetNamedItem(at_define)), str_1); } } material.RecomputeCombinedShaderDefines(); m_Materials[pathname] = material; return material; } Index: ps/trunk/source/graphics/PreprocessorWrapper.cpp =================================================================== --- ps/trunk/source/graphics/PreprocessorWrapper.cpp (nonexistent) +++ ps/trunk/source/graphics/PreprocessorWrapper.cpp (revision 23215) @@ -0,0 +1,99 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "PreprocessorWrapper.h" + +#include "graphics/ShaderDefines.h" +#include "ps/CLogger.h" + +void CPreprocessorWrapper::PyrogenesisShaderError(void* UNUSED(iData), int iLine, const char* iError, const char* iToken, size_t iTokenLen) +{ + if (iToken) + LOGERROR("Preprocessor error: line %d: %s: '%s'\n", iLine, iError, std::string(iToken, iTokenLen).c_str()); + else + LOGERROR("Preprocessor error: line %d: %s\n", iLine, iError); +} + +CPreprocessorWrapper::CPreprocessorWrapper() +{ + CPreprocessor::ErrorHandler = CPreprocessorWrapper::PyrogenesisShaderError; +} + +void CPreprocessorWrapper::AddDefine(const char* name, const char* value) +{ + m_Preprocessor.Define(name, value); +} + +void CPreprocessorWrapper::AddDefines(const CShaderDefines& defines) +{ + std::map map = defines.GetMap(); + for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) + m_Preprocessor.Define(it->first.c_str(), it->first.length(), it->second.c_str(), it->second.length()); +} + +bool CPreprocessorWrapper::TestConditional(const CStr& expr) +{ + // Construct a dummy program so we can trigger the preprocessor's expression + // code without modifying its public API. + // Be careful that the API buggily returns a statically allocated pointer + // (which we will try to free()) if the input just causes it to append a single + // sequence of newlines to the output; the "\n" after the "#endif" is enough + // to avoid this case. + CStr input = "#if "; + input += expr; + input += "\n1\n#endif\n"; + + size_t len = 0; + char* output = m_Preprocessor.Parse(input.c_str(), input.size(), len); + + if (!output) + { + LOGERROR("Failed to parse conditional expression '%s'", expr.c_str()); + return false; + } + + bool ret = (memchr(output, '1', len) != NULL); + + // Free output if it's not inside the source string + if (!(output >= input.c_str() && output < input.c_str() + input.size())) + free(output); + + return ret; + +} + +CStr CPreprocessorWrapper::Preprocess(const CStr& input) +{ + size_t len = 0; + char* output = m_Preprocessor.Parse(input.c_str(), input.size(), len); + + if (!output) + { + LOGERROR("Shader preprocessing failed"); + return ""; + } + + CStr ret(output, len); + + // Free output if it's not inside the source string + if (!(output >= input.c_str() && output < input.c_str() + input.size())) + free(output); + + return ret; +} Property changes on: ps/trunk/source/graphics/PreprocessorWrapper.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/graphics/PreprocessorWrapper.h =================================================================== --- ps/trunk/source/graphics/PreprocessorWrapper.h (nonexistent) +++ ps/trunk/source/graphics/PreprocessorWrapper.h (revision 23215) @@ -0,0 +1,48 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_PREPROCESSORWRAPPER +#define INCLUDED_PREPROCESSORWRAPPER + +#include "ps/CStr.h" +#include "third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h" + +class CShaderDefines; + +/** + * Convenience wrapper around CPreprocessor. + */ +class CPreprocessorWrapper +{ +public: + CPreprocessorWrapper(); + + void AddDefine(const char* name, const char* value); + + void AddDefines(const CShaderDefines& defines); + + bool TestConditional(const CStr& expr); + + CStr Preprocess(const CStr& input); + + static void PyrogenesisShaderError(void* UNUSED(iData), int iLine, const char* iError, const char* iToken, size_t iTokenLen); + +private: + CPreprocessor m_Preprocessor; +}; + +#endif // INCLUDED_PREPROCESSORWRAPPER Property changes on: ps/trunk/source/graphics/PreprocessorWrapper.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/graphics/ShaderManager.cpp =================================================================== --- ps/trunk/source/graphics/ShaderManager.cpp (revision 23214) +++ ps/trunk/source/graphics/ShaderManager.cpp (revision 23215) @@ -1,582 +1,582 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ShaderManager.h" +#include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderTechnique.h" #include "lib/config2.h" #include "lib/hash.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/CStrIntern.h" #include "ps/Filesystem.h" -#include "ps/PreprocessorWrapper.h" #include "ps/Profile.h" #if USE_SHADER_XML_VALIDATION # include "ps/XML/RelaxNG.h" #endif #include "ps/XML/Xeromyces.h" #include "ps/XML/XMLWriter.h" #include "renderer/Renderer.h" TIMER_ADD_CLIENT(tc_ShaderValidation); CShaderManager::CShaderManager() { #if USE_SHADER_XML_VALIDATION { TIMER_ACCRUE(tc_ShaderValidation); if (!CXeromyces::AddValidator(g_VFS, "shader", "shaders/program.rng")) LOGERROR("CShaderManager: failed to load grammar shaders/program.rng"); } #endif // Allow hotloading of textures RegisterFileReloadFunc(ReloadChangedFileCB, this); } CShaderManager::~CShaderManager() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } CShaderProgramPtr CShaderManager::LoadProgram(const char* name, const CShaderDefines& defines) { CacheKey key = { name, defines }; std::map::iterator it = m_ProgramCache.find(key); if (it != m_ProgramCache.end()) return it->second; CShaderProgramPtr program; if (!NewProgram(name, defines, program)) { LOGERROR("Failed to load shader '%s'", name); program = CShaderProgramPtr(); } m_ProgramCache[key] = program; return program; } static GLenum ParseAttribSemantics(const CStr& str) { // Map known semantics onto the attribute locations documented by NVIDIA if (str == "gl_Vertex") return 0; if (str == "gl_Normal") return 2; if (str == "gl_Color") return 3; if (str == "gl_SecondaryColor") return 4; if (str == "gl_FogCoord") return 5; if (str == "gl_MultiTexCoord0") return 8; if (str == "gl_MultiTexCoord1") return 9; if (str == "gl_MultiTexCoord2") return 10; if (str == "gl_MultiTexCoord3") return 11; if (str == "gl_MultiTexCoord4") return 12; if (str == "gl_MultiTexCoord5") return 13; if (str == "gl_MultiTexCoord6") return 14; if (str == "gl_MultiTexCoord7") return 15; // Define some arbitrary names for user-defined attribute locations // that won't conflict with any standard semantics if (str == "CustomAttribute0") return 1; if (str == "CustomAttribute1") return 6; if (str == "CustomAttribute2") return 7; debug_warn("Invalid attribute semantics"); return 0; } bool CShaderManager::NewProgram(const char* name, const CShaderDefines& baseDefines, CShaderProgramPtr& program) { PROFILE2("loading shader"); PROFILE2_ATTR("name: %s", name); if (strncmp(name, "fixed:", 6) == 0) { program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines)); if (!program) return false; program->Reload(); return true; } VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return false; #if USE_SHADER_XML_VALIDATION { TIMER_ACCRUE(tc_ShaderValidation); // Serialize the XMB data and pass it to the validator XMLWriter_File shaderFile; shaderFile.SetPrettyPrint(false); shaderFile.XMB(XeroFile); bool ok = CXeromyces::ValidateEncoded("shader", wstring_from_utf8(name), shaderFile.GetOutput()); if (!ok) return false; } #endif // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(attrib); EL(define); EL(fragment); EL(stream); EL(uniform); EL(vertex); AT(file); AT(if); AT(loc); AT(name); AT(semantics); AT(type); AT(value); #undef AT #undef EL CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement Root = XeroFile.GetRoot(); bool isGLSL = (Root.GetAttributes().GetNamedItem(at_type) == "glsl"); VfsPath vertexFile; VfsPath fragmentFile; CShaderDefines defines = baseDefines; std::map vertexUniforms; std::map fragmentUniforms; std::map vertexAttribs; int streamFlags = 0; XERO_ITER_EL(Root, Child) { if (Child.GetNodeName() == el_define) { defines.Add(CStrIntern(Child.GetAttributes().GetNamedItem(at_name)), CStrIntern(Child.GetAttributes().GetNamedItem(at_value))); } else if (Child.GetNodeName() == el_vertex) { vertexFile = L"shaders/" + Child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(Child, Param) { XMBAttributeList Attrs = Param.GetAttributes(); CStr cond = Attrs.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (Param.GetNodeName() == el_uniform) { vertexUniforms[CStrIntern(Attrs.GetNamedItem(at_name))] = Attrs.GetNamedItem(at_loc).ToInt(); } else if (Param.GetNodeName() == el_stream) { CStr StreamName = Attrs.GetNamedItem(at_name); if (StreamName == "pos") streamFlags |= STREAM_POS; else if (StreamName == "normal") streamFlags |= STREAM_NORMAL; else if (StreamName == "color") streamFlags |= STREAM_COLOR; else if (StreamName == "uv0") streamFlags |= STREAM_UV0; else if (StreamName == "uv1") streamFlags |= STREAM_UV1; else if (StreamName == "uv2") streamFlags |= STREAM_UV2; else if (StreamName == "uv3") streamFlags |= STREAM_UV3; } else if (Param.GetNodeName() == el_attrib) { int attribLoc = ParseAttribSemantics(Attrs.GetNamedItem(at_semantics)); vertexAttribs[CStrIntern(Attrs.GetNamedItem(at_name))] = attribLoc; } } } else if (Child.GetNodeName() == el_fragment) { fragmentFile = L"shaders/" + Child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(Child, Param) { XMBAttributeList Attrs = Param.GetAttributes(); CStr cond = Attrs.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (Param.GetNodeName() == el_uniform) { // A somewhat incomplete listing, missing "shadow" and "rect" versions // which are interpreted as 2D (NB: our shadowmaps may change // type based on user config). GLenum type = GL_TEXTURE_2D; CStr t = Attrs.GetNamedItem(at_type); if (t == "sampler1D") #if CONFIG2_GLES debug_warn(L"sampler1D not implemented on GLES"); #else type = GL_TEXTURE_1D; #endif else if (t == "sampler2D") type = GL_TEXTURE_2D; else if (t == "sampler3D") #if CONFIG2_GLES debug_warn(L"sampler3D not implemented on GLES"); #else type = GL_TEXTURE_3D; #endif else if (t == "samplerCube") type = GL_TEXTURE_CUBE_MAP; fragmentUniforms[CStrIntern(Attrs.GetNamedItem(at_name))] = std::make_pair(Attrs.GetNamedItem(at_loc).ToInt(), type); } } } } if (isGLSL) program = CShaderProgramPtr(CShaderProgram::ConstructGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamFlags)); else program = CShaderProgramPtr(CShaderProgram::ConstructARB(vertexFile, fragmentFile, defines, vertexUniforms, fragmentUniforms, streamFlags)); program->Reload(); // m_HotloadFiles[xmlFilename].insert(program); // TODO: should reload somehow when the XML changes m_HotloadFiles[vertexFile].insert(program); m_HotloadFiles[fragmentFile].insert(program); return true; } static GLenum ParseComparisonFunc(const CStr& str) { if (str == "never") return GL_NEVER; if (str == "always") return GL_ALWAYS; if (str == "less") return GL_LESS; if (str == "lequal") return GL_LEQUAL; if (str == "equal") return GL_EQUAL; if (str == "gequal") return GL_GEQUAL; if (str == "greater") return GL_GREATER; if (str == "notequal") return GL_NOTEQUAL; debug_warn("Invalid comparison func"); return GL_ALWAYS; } static GLenum ParseBlendFunc(const CStr& str) { if (str == "zero") return GL_ZERO; if (str == "one") return GL_ONE; if (str == "src_color") return GL_SRC_COLOR; if (str == "one_minus_src_color") return GL_ONE_MINUS_SRC_COLOR; if (str == "dst_color") return GL_DST_COLOR; if (str == "one_minus_dst_color") return GL_ONE_MINUS_DST_COLOR; if (str == "src_alpha") return GL_SRC_ALPHA; if (str == "one_minus_src_alpha") return GL_ONE_MINUS_SRC_ALPHA; if (str == "dst_alpha") return GL_DST_ALPHA; if (str == "one_minus_dst_alpha") return GL_ONE_MINUS_DST_ALPHA; if (str == "constant_color") return GL_CONSTANT_COLOR; if (str == "one_minus_constant_color") return GL_ONE_MINUS_CONSTANT_COLOR; if (str == "constant_alpha") return GL_CONSTANT_ALPHA; if (str == "one_minus_constant_alpha") return GL_ONE_MINUS_CONSTANT_ALPHA; if (str == "src_alpha_saturate") return GL_SRC_ALPHA_SATURATE; debug_warn("Invalid blend func"); return GL_ZERO; } size_t CShaderManager::EffectCacheKeyHash::operator()(const EffectCacheKey& key) const { size_t hash = 0; hash_combine(hash, key.name.GetHash()); hash_combine(hash, key.defines1.GetHash()); hash_combine(hash, key.defines2.GetHash()); return hash; } bool CShaderManager::EffectCacheKey::operator==(const EffectCacheKey& b) const { return (name == b.name && defines1 == b.defines1 && defines2 == b.defines2); } CShaderTechniquePtr CShaderManager::LoadEffect(CStrIntern name) { return LoadEffect(name, g_Renderer.GetSystemShaderDefines(), CShaderDefines()); } CShaderTechniquePtr CShaderManager::LoadEffect(CStrIntern name, const CShaderDefines& defines1, const CShaderDefines& defines2) { // Return the cached effect, if there is one EffectCacheKey key = { name, defines1, defines2 }; EffectCacheMap::iterator it = m_EffectCache.find(key); if (it != m_EffectCache.end()) return it->second; // First time we've seen this key, so construct a new effect: // Merge the two sets of defines, so NewEffect doesn't have to care about the split CShaderDefines defines(defines1); defines.SetMany(defines2); CShaderTechniquePtr tech(new CShaderTechnique()); if (!NewEffect(name.c_str(), defines, tech)) { LOGERROR("Failed to load effect '%s'", name.c_str()); tech = CShaderTechniquePtr(); } m_EffectCache[key] = tech; return tech; } bool CShaderManager::NewEffect(const char* name, const CShaderDefines& baseDefines, CShaderTechniquePtr& tech) { PROFILE2("loading effect"); PROFILE2_ATTR("name: %s", name); // Shortcut syntax for effects that just contain a single shader if (strncmp(name, "shader:", 7) == 0) { CShaderProgramPtr program = LoadProgram(name+7, baseDefines); if (!program) return false; CShaderPass pass; pass.SetShader(program); tech->AddPass(pass); return true; } VfsPath xmlFilename = L"shaders/effects/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return false; // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(alpha); EL(blend); EL(define); EL(depth); EL(pass); EL(require); EL(sort_by_distance); AT(context); AT(dst); AT(func); AT(ref); AT(shader); AT(shaders); AT(src); AT(mask); AT(name); AT(value); #undef AT #undef EL // Read some defines that influence how we pick techniques bool hasARB = (baseDefines.GetInt("SYS_HAS_ARB") != 0); bool hasGLSL = (baseDefines.GetInt("SYS_HAS_GLSL") != 0); bool preferGLSL = (baseDefines.GetInt("SYS_PREFER_GLSL") != 0); // Prepare the preprocessor for conditional tests CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement Root = XeroFile.GetRoot(); // Find all the techniques that we can use, and their preference std::vector > usableTechs; XERO_ITER_EL(Root, Technique) { int preference = 0; bool isUsable = true; XERO_ITER_EL(Technique, Child) { XMBAttributeList Attrs = Child.GetAttributes(); if (Child.GetNodeName() == el_require) { if (Attrs.GetNamedItem(at_shaders) == "fixed") { // FFP not supported by OpenGL ES #if CONFIG2_GLES isUsable = false; #endif } else if (Attrs.GetNamedItem(at_shaders) == "arb") { if (!hasARB) isUsable = false; } else if (Attrs.GetNamedItem(at_shaders) == "glsl") { if (!hasGLSL) isUsable = false; if (preferGLSL) preference += 100; else preference -= 100; } else if (!Attrs.GetNamedItem(at_context).empty()) { CStr cond = Attrs.GetNamedItem(at_context); if (!preprocessor.TestConditional(cond)) isUsable = false; } } } if (isUsable) usableTechs.emplace_back(Technique, preference); } if (usableTechs.empty()) { debug_warn(L"Can't find a usable technique"); return false; } // Sort by preference, tie-break on order of specification std::stable_sort(usableTechs.begin(), usableTechs.end(), [](const std::pair& a, const std::pair& b) { return b.second < a.second; }); CShaderDefines techDefines = baseDefines; XERO_ITER_EL(usableTechs[0].first, Child) { if (Child.GetNodeName() == el_define) { techDefines.Add(CStrIntern(Child.GetAttributes().GetNamedItem(at_name)), CStrIntern(Child.GetAttributes().GetNamedItem(at_value))); } else if (Child.GetNodeName() == el_sort_by_distance) { tech->SetSortByDistance(true); } else if (Child.GetNodeName() == el_pass) { CShaderDefines passDefines = techDefines; CShaderPass pass; XERO_ITER_EL(Child, Element) { if (Element.GetNodeName() == el_define) { passDefines.Add(CStrIntern(Element.GetAttributes().GetNamedItem(at_name)), CStrIntern(Element.GetAttributes().GetNamedItem(at_value))); } else if (Element.GetNodeName() == el_alpha) { GLenum func = ParseComparisonFunc(Element.GetAttributes().GetNamedItem(at_func)); float ref = Element.GetAttributes().GetNamedItem(at_ref).ToFloat(); pass.AlphaFunc(func, ref); } else if (Element.GetNodeName() == el_blend) { GLenum src = ParseBlendFunc(Element.GetAttributes().GetNamedItem(at_src)); GLenum dst = ParseBlendFunc(Element.GetAttributes().GetNamedItem(at_dst)); pass.BlendFunc(src, dst); } else if (Element.GetNodeName() == el_depth) { if (!Element.GetAttributes().GetNamedItem(at_func).empty()) pass.DepthFunc(ParseComparisonFunc(Element.GetAttributes().GetNamedItem(at_func))); if (!Element.GetAttributes().GetNamedItem(at_mask).empty()) pass.DepthMask(Element.GetAttributes().GetNamedItem(at_mask) == "true" ? 1 : 0); } } // Load the shader program after we've read all the possibly-relevant s pass.SetShader(LoadProgram(Child.GetAttributes().GetNamedItem(at_shader).c_str(), passDefines)); tech->AddPass(pass); } } return true; } size_t CShaderManager::GetNumEffectsLoaded() { return m_EffectCache.size(); } /*static*/ Status CShaderManager::ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } Status CShaderManager::ReloadChangedFile(const VfsPath& path) { // Find all shaders using this file HotloadFilesMap::iterator files = m_HotloadFiles.find(path); if (files != m_HotloadFiles.end()) { // Reload all shaders using this file for (std::set >::iterator it = files->second.begin(); it != files->second.end(); ++it) { if (std::shared_ptr program = it->lock()) program->Reload(); } } // TODO: hotloading changes to shader XML files and effect XML files would be nice return INFO::OK; } Index: ps/trunk/source/graphics/ShaderProgram.cpp =================================================================== --- ps/trunk/source/graphics/ShaderProgram.cpp (revision 23214) +++ ps/trunk/source/graphics/ShaderProgram.cpp (revision 23215) @@ -1,892 +1,892 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ShaderProgram.h" #include "graphics/Color.h" +#include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "lib/res/graphics/ogl_tex.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" -#include "ps/PreprocessorWrapper.h" #if !CONFIG2_GLES class CShaderProgramARB : public CShaderProgram { public: CShaderProgramARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) : CShaderProgram(streamflags), m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), m_Defines(defines), m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes) { pglGenProgramsARB(1, &m_VertexProgram); pglGenProgramsARB(1, &m_FragmentProgram); } ~CShaderProgramARB() { Unload(); pglDeleteProgramsARB(1, &m_VertexProgram); pglDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); pglBindProgramARB(target, program); ogl_WarnIfError(); pglProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); if (ogl_SquelchError(GL_INVALID_OPERATION)) { GLint errPos = 0; glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1; char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr); return false; } pglBindProgramARB(target, 0); ogl_WarnIfError(); return true; } virtual void Reload() { Unload(); CVFSFile vertexFile; if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) return; CVFSFile fragmentFile; if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) return; CPreprocessorWrapper preprocessor; preprocessor.AddDefines(m_Defines); CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString()); CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString()); // printf(">>>\n%s<<<\n", vertexCode.c_str()); // printf(">>>\n%s<<<\n", fragmentCode.c_str()); if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, m_VertexFile, vertexCode)) return; if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, m_FragmentFile, fragmentCode)) return; m_IsValid = true; } void Unload() { m_IsValid = false; } virtual void Bind() { glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); BindClientStates(); } virtual void Unbind() { glDisable(GL_VERTEX_PROGRAM_ARB); glDisable(GL_FRAGMENT_PROGRAM_ARB); pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); UnbindClientStates(); // TODO: should unbind textures, probably } int GetUniformVertexIndex(CStrIntern id) { std::map::iterator it = m_VertexIndexes.find(id); if (it == m_VertexIndexes.end()) return -1; return it->second; } frag_index_pair_t GetUniformFragmentIndex(CStrIntern id) { std::map::iterator it = m_FragmentIndexes.find(id); if (it == m_FragmentIndexes.end()) return std::make_pair(-1, 0); return it->second; } virtual Binding GetTextureBinding(texture_id_t id) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index == -1) return Binding(); else return Binding((int)fPair.second, index); } virtual void BindTexture(texture_id_t id, Handle tex) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0+index); glBindTexture(fPair.second, h); } } virtual void BindTexture(texture_id_t id, GLuint tex) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { pglActiveTextureARB(GL_TEXTURE0+index); glBindTexture(fPair.second, tex); } } virtual void BindTexture(Binding id, Handle tex) { int index = id.second; if (index != -1) ogl_tex_bind(tex, index); } virtual Binding GetUniformBinding(uniform_id_t id) { return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first); } virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) { if (id.first != -1) pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3); if (id.second != -1) pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3); } virtual void Uniform(Binding id, const CMatrix3D& v) { if (id.first != -1) { pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44); } if (id.second != -1) { pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44); } } virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) { ENSURE(count == 1); Uniform(id, v[0]); } private: VfsPath m_VertexFile; VfsPath m_FragmentFile; CShaderDefines m_Defines; GLuint m_VertexProgram; GLuint m_FragmentProgram; std::map m_VertexIndexes; // pair contains std::map m_FragmentIndexes; }; #endif // #if !CONFIG2_GLES ////////////////////////////////////////////////////////////////////////// TIMER_ADD_CLIENT(tc_ShaderGLSLCompile); TIMER_ADD_CLIENT(tc_ShaderGLSLLink); class CShaderProgramGLSL : public CShaderProgram { public: CShaderProgramGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) : CShaderProgram(streamflags), m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), m_Defines(defines), m_VertexAttribs(vertexAttribs) { m_Program = 0; m_VertexShader = pglCreateShaderObjectARB(GL_VERTEX_SHADER); m_FragmentShader = pglCreateShaderObjectARB(GL_FRAGMENT_SHADER); } ~CShaderProgramGLSL() { Unload(); pglDeleteShader(m_VertexShader); pglDeleteShader(m_FragmentShader); } bool Compile(GLhandleARB shader, const VfsPath& file, const CStr& code) { TIMER_ACCRUE(tc_ShaderGLSLCompile); ogl_WarnIfError(); const char* code_string = code.c_str(); GLint code_length = code.length(); pglShaderSourceARB(shader, 1, &code_string, &code_length); pglCompileShaderARB(shader); GLint ok = 0; pglGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; pglGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); // Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0 // (http://code.google.com/p/android/issues/detail?id=9953) if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; pglGetShaderInfoLog(shader, length, NULL, infolog); if (ok) LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog); else LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); return (ok ? true : false); } bool Link() { TIMER_ACCRUE(tc_ShaderGLSLLink); ENSURE(!m_Program); m_Program = pglCreateProgramObjectARB(); pglAttachObjectARB(m_Program, m_VertexShader); ogl_WarnIfError(); pglAttachObjectARB(m_Program, m_FragmentShader); ogl_WarnIfError(); // Set up the attribute bindings explicitly, since apparently drivers // don't always pick the most efficient bindings automatically, // and also this lets us hardcode indexes into VertexPointer etc for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglBindAttribLocationARB(m_Program, it->second, it->first.c_str()); pglLinkProgramARB(m_Program); GLint ok = 0; pglGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; pglGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; pglGetProgramInfoLog(m_Program, length, NULL, infolog); if (ok) LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog); else LOGERROR("Failed to link program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); if (!ok) return false; m_Uniforms.clear(); m_Samplers.clear(); Bind(); ogl_WarnIfError(); GLint numUniforms = 0; pglGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms); ogl_WarnIfError(); for (GLint i = 0; i < numUniforms; ++i) { char name[256] = {0}; GLsizei nameLength = 0; GLint size = 0; GLenum type = 0; pglGetActiveUniformARB(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); GLint loc = pglGetUniformLocationARB(m_Program, name); CStrIntern nameIntern(name); m_Uniforms[nameIntern] = std::make_pair(loc, type); // Assign sampler uniforms to sequential texture units if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE #if !CONFIG2_GLES || type == GL_SAMPLER_2D_SHADOW #endif ) { int unit = (int)m_Samplers.size(); m_Samplers[nameIntern].first = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D); m_Samplers[nameIntern].second = unit; pglUniform1iARB(loc, unit); // link uniform to unit ogl_WarnIfError(); } } // TODO: verify that we're not using more samplers than is supported Unbind(); ogl_WarnIfError(); return true; } virtual void Reload() { Unload(); CVFSFile vertexFile; if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) return; CVFSFile fragmentFile; if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) return; CPreprocessorWrapper preprocessor; preprocessor.AddDefines(m_Defines); #if CONFIG2_GLES // GLES defines the macro "GL_ES" in its GLSL preprocessor, // but since we run our own preprocessor first, we need to explicitly // define it here preprocessor.AddDefine("GL_ES", "1"); #endif CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString()); CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString()); #if CONFIG2_GLES // Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00, // and also to set default float precision for fragment shaders vertexCode.Replace("#version 110\n", "#version 100\n"); vertexCode.Replace("#version 110\r\n", "#version 100\n"); vertexCode.Replace("#version 120\n", "#version 100\n"); vertexCode.Replace("#version 120\r\n", "#version 100\n"); fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n"); #endif if (!Compile(m_VertexShader, m_VertexFile, vertexCode)) return; if (!Compile(m_FragmentShader, m_FragmentFile, fragmentCode)) return; if (!Link()) return; m_IsValid = true; } void Unload() { m_IsValid = false; if (m_Program) pglDeleteProgram(m_Program); m_Program = 0; // The shader objects can be reused and don't need to be deleted here } virtual void Bind() { pglUseProgramObjectARB(m_Program); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglEnableVertexAttribArrayARB(it->second); } virtual void Unbind() { pglUseProgramObjectARB(0); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglDisableVertexAttribArrayARB(it->second); // TODO: should unbind textures, probably } virtual Binding GetTextureBinding(texture_id_t id) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return Binding(); else return Binding((int)it->second.first, it->second.second); } virtual void BindTexture(texture_id_t id, Handle tex) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, h); } virtual void BindTexture(texture_id_t id, GLuint tex) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; pglActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, tex); } virtual void BindTexture(Binding id, Handle tex) { if (id.second == -1) return; GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0 + id.second); glBindTexture(id.first, h); } virtual Binding GetUniformBinding(uniform_id_t id) { std::map >::iterator it = m_Uniforms.find(id); if (it == m_Uniforms.end()) return Binding(); else return Binding(it->second.first, (int)it->second.second); } virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) { if (id.first != -1) { if (id.second == GL_FLOAT) pglUniform1fARB(id.first, v0); else if (id.second == GL_FLOAT_VEC2) pglUniform2fARB(id.first, v0, v1); else if (id.second == GL_FLOAT_VEC3) pglUniform3fARB(id.first, v0, v1, v2); else if (id.second == GL_FLOAT_VEC4) pglUniform4fARB(id.first, v0, v1, v2, v3); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)"); } } virtual void Uniform(Binding id, const CMatrix3D& v) { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) pglUniformMatrix4fvARB(id.first, 1, GL_FALSE, &v._11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) pglUniformMatrix4fvARB(id.first, count, GL_FALSE, &v->_11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } // Map the various fixed-function Pointer functions onto generic vertex attributes // (matching the attribute indexes from ShaderManager's ParseAttribSemantics): virtual void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_POS; } virtual void NormalPointer(GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(2, 3, type, GL_TRUE, stride, pointer); m_ValidStreams |= STREAM_NORMAL; } virtual void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(3, size, type, GL_TRUE, stride, pointer); m_ValidStreams |= STREAM_COLOR; } virtual void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(8 + texture - GL_TEXTURE0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } virtual void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { pglVertexAttribPointerARB(it->second, size, type, normalized, stride, pointer); } } virtual void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer) { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { #if CONFIG2_GLES debug_warn(L"glVertexAttribIPointer not supported on GLES"); #else pglVertexAttribIPointerEXT(it->second, size, type, stride, pointer); #endif } } private: VfsPath m_VertexFile; VfsPath m_FragmentFile; CShaderDefines m_Defines; std::map m_VertexAttribs; GLhandleARB m_Program; GLhandleARB m_VertexShader; GLhandleARB m_FragmentShader; std::map > m_Uniforms; std::map > m_Samplers; // texture target & unit chosen for each uniform sampler }; ////////////////////////////////////////////////////////////////////////// CShaderProgram::CShaderProgram(int streamflags) : m_IsValid(false), m_StreamFlags(streamflags), m_ValidStreams(0) { } #if CONFIG2_GLES /*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& UNUSED(defines), const std::map& UNUSED(vertexIndexes), const std::map& UNUSED(fragmentIndexes), int UNUSED(streamflags)) { LOGERROR("CShaderProgram::ConstructARB: '%s'+'%s': ARB shaders not supported on this device", vertexFile.string8(), fragmentFile.string8()); return NULL; } #else /*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) { return new CShaderProgramARB(vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags); } #endif /*static*/ CShaderProgram* CShaderProgram::ConstructGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) { return new CShaderProgramGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamflags); } bool CShaderProgram::IsValid() const { return m_IsValid; } int CShaderProgram::GetStreamFlags() const { return m_StreamFlags; } void CShaderProgram::BindTexture(texture_id_t id, CTexturePtr tex) { BindTexture(id, tex->GetHandle()); } void CShaderProgram::Uniform(Binding id, int v) { Uniform(id, (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(Binding id, float v) { Uniform(id, v, v, v, v); } void CShaderProgram::Uniform(Binding id, float v0, float v1) { Uniform(id, v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(Binding id, const CVector3D& v) { Uniform(id, v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(Binding id, const CColor& v) { Uniform(id, v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, int v) { Uniform(GetUniformBinding(id), (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(uniform_id_t id, float v) { Uniform(GetUniformBinding(id), v, v, v, v); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1) { Uniform(GetUniformBinding(id), v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CVector3D& v) { Uniform(GetUniformBinding(id), v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CColor& v) { Uniform(GetUniformBinding(id), v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1, float v2, float v3) { Uniform(GetUniformBinding(id), v0, v1, v2, v3); } void CShaderProgram::Uniform(uniform_id_t id, const CMatrix3D& v) { Uniform(GetUniformBinding(id), v); } void CShaderProgram::Uniform(uniform_id_t id, size_t count, const CMatrix3D* v) { Uniform(GetUniformBinding(id), count, v); } // These should all be overridden by CShaderProgramGLSL, and not used // if a non-GLSL shader was loaded instead: void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type), GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribPointer"); } void CShaderProgram::VertexAttribIPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribIPointer"); } #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL // (GLES doesn't support any other types of shader program): void CShaderProgram::VertexPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::VertexPointer should be overridden"); } void CShaderProgram::NormalPointer(GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::NormalPointer should be overridden"); } void CShaderProgram::ColorPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::ColorPointer should be overridden"); } void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::TexCoordPointer should be overridden"); } #else // These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders // both use the fixed-function vertex attribute pointers so we'll share their // definitions here: void CShaderProgram::VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { glVertexPointer(size, type, stride, pointer); m_ValidStreams |= STREAM_POS; } void CShaderProgram::NormalPointer(GLenum type, GLsizei stride, const void* pointer) { glNormalPointer(type, stride, pointer); m_ValidStreams |= STREAM_NORMAL; } void CShaderProgram::ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { glColorPointer(size, type, stride, pointer); m_ValidStreams |= STREAM_COLOR; } void CShaderProgram::TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) { pglClientActiveTextureARB(texture); glTexCoordPointer(size, type, stride, pointer); pglClientActiveTextureARB(GL_TEXTURE0); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } void CShaderProgram::BindClientStates() { ENSURE(m_StreamFlags == (m_StreamFlags & (STREAM_POS|STREAM_NORMAL|STREAM_COLOR|STREAM_UV0|STREAM_UV1))); // Enable all the desired client states for non-GLSL rendering if (m_StreamFlags & STREAM_POS) glEnableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & STREAM_NORMAL) glEnableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & STREAM_COLOR) glEnableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & STREAM_UV0) { pglClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { pglClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); pglClientActiveTextureARB(GL_TEXTURE0); } // Rendering code must subsequently call VertexPointer etc for all of the streams // that were activated in this function, else AssertPointersBound will complain // that some arrays were unspecified m_ValidStreams = 0; } void CShaderProgram::UnbindClientStates() { if (m_StreamFlags & STREAM_POS) glDisableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & STREAM_NORMAL) glDisableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & STREAM_COLOR) glDisableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & STREAM_UV0) { pglClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { pglClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); pglClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } Index: ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp =================================================================== --- ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp (nonexistent) +++ ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp (revision 23215) @@ -0,0 +1,1315 @@ +/* + * This source file originally came from OGRE v1.7.2 - http://www.ogre3d.org/ + * with some tweaks as part of 0 A.D. + * All changes are released under the original license, as follows: + */ + +/* +----------------------------------------------------------------------------- +This source file is part of OGRE +(Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org/ + +Copyright (c) 2000-2009 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + +#include "precompiled.h" + +#include "OgreGLSLPreprocessor.h" + +// Limit max number of macro arguments to this +#define MAX_MACRO_ARGS 16 + +//---------------------------------------------------------------------------// + +/// Return closest power of two not smaller than given number +static size_t ClosestPow2 (size_t x) +{ + if (!(x & (x - 1))) + return x; + while (x & (x + 1)) + x |= (x + 1); + return x + 1; +} + +void CPreprocessor::Token::Append (const char *iString, size_t iLength) +{ + Token t (Token::TK_TEXT, iString, iLength); + Append (t); +} + +void CPreprocessor::Token::Append (const Token &iOther) +{ + if (!iOther.String) + return; + + if (!String) + { + String = iOther.String; + Length = iOther.Length; + Allocated = iOther.Allocated; + iOther.Allocated = 0; // !!! not quite correct but effective + return; + } + + if (Allocated) + { + size_t new_alloc = ClosestPow2 (Length + iOther.Length); + if (new_alloc < 64) + new_alloc = 64; + if (new_alloc != Allocated) + { + Allocated = new_alloc; + Buffer = (char *)realloc (Buffer, Allocated); + } + } + else if (String + Length != iOther.String) + { + Allocated = ClosestPow2 (Length + iOther.Length); + if (Allocated < 64) + Allocated = 64; + char *newstr = (char *)malloc (Allocated); + memcpy (newstr, String, Length); + Buffer = newstr; + } + + if (Allocated) + memcpy (Buffer + Length, iOther.String, iOther.Length); + Length += iOther.Length; +} + +bool CPreprocessor::Token::GetValue (long &oValue) const +{ + long val = 0; + size_t i = 0; + + while (isspace (String [i])) + i++; + + long base = 10; + if (String [i] == '0') + { + if (Length > i + 1 && String [i + 1] == 'x') + base = 16, i += 2; + else + base = 8; + } + + for (; i < Length; i++) + { + long c = long (String [i]); + if (isspace (c)) + // Possible end of number + break; + + if (c >= 'a' && c <= 'z') + c -= ('a' - 'A'); + + c -= '0'; + if (c < 0) + return false; + + if (c > 9) + c -= ('A' - '9' - 1); + + if (c >= base) + return false; + + val = (val * base) + c; + } + + // Check that all other characters are just spaces + for (; i < Length; i++) + if (!isspace (String [i])) + return false; + + oValue = val; + return true; +} + +void CPreprocessor::Token::SetValue (long iValue) +{ + char tmp [21]; + int len = snprintf (tmp, sizeof (tmp), "%ld", iValue); + Length = 0; + Append (tmp, len); + Type = TK_NUMBER; +} + +void CPreprocessor::Token::AppendNL (int iCount) +{ + static const char newlines [8] = + { '\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n' }; + + while (iCount > 8) + { + Append (newlines, 8); + iCount -= 8; + } + if (iCount > 0) + Append (newlines, iCount); +} + +int CPreprocessor::Token::CountNL () +{ + if (Type == TK_EOS || Type == TK_ERROR) + return 0; + + const char *s = String; + int l = Length; + int c = 0; + while (l > 0) + { + const char *n = (const char *)memchr (s, '\n', l); + if (!n) + return c; + c++; + l -= (n - s + 1); + s = n + 1; + } + return c; +} + +//---------------------------------------------------------------------------// + +CPreprocessor::Token CPreprocessor::Macro::Expand ( + int iNumArgs, CPreprocessor::Token *iArgs, Macro *iMacros) +{ + Expanding = true; + + CPreprocessor cpp; + cpp.MacroList = iMacros; + + // Define a new macro for every argument + int i; + for (i = 0; i < iNumArgs; i++) + cpp.Define (Args [i].String, Args [i].Length, + iArgs [i].String, iArgs [i].Length); + // The rest arguments are empty + for (; i < NumArgs; i++) + cpp.Define (Args [i].String, Args [i].Length, "", 0); + + // Now run the macro expansion through the supplimentary preprocessor + Token xt = cpp.Parse (Value); + + Expanding = false; + + // Remove the extra macros we have defined + for (int j = NumArgs - 1; j >= 0; j--) + cpp.Undef (Args [j].String, Args [j].Length); + + cpp.MacroList = NULL; + + return xt; +} + +//---------------------------------------------------------------------------// + +static void DefaultError (void *iData, int iLine, const char *iError, + const char *iToken, size_t iTokenLen) +{ + (void)iData; + char line [1000]; + if (iToken) + snprintf (line, sizeof (line), "line %d: %s: `%.*s'\n", + iLine, iError, int (iTokenLen), iToken); + else + snprintf (line, sizeof (line), "line %d: %s\n", iLine, iError); +} + +//---------------------------------------------------------------------------// + +CPreprocessor::ErrorHandlerFunc CPreprocessor::ErrorHandler = DefaultError; + +CPreprocessor::CPreprocessor (const Token &iToken, int iLine) : MacroList (NULL) +{ + Source = iToken.String; + SourceEnd = iToken.String + iToken.Length; + EnableOutput = 1; + Line = iLine; + BOL = true; +} + +CPreprocessor::~CPreprocessor () +{ + delete MacroList; +} + +void CPreprocessor::Error (int iLine, const char *iError, const Token *iToken) +{ + if (iToken) + ErrorHandler (ErrorData, iLine, iError, iToken->String, iToken->Length); + else + ErrorHandler (ErrorData, iLine, iError, NULL, 0); +} + +CPreprocessor::Token CPreprocessor::GetToken (bool iExpand) +{ + if (Source >= SourceEnd) + return Token (Token::TK_EOS); + + const char *begin = Source; + char c = *Source++; + + + if (c == '\n' || (c == '\r' && *Source == '\n')) + { + Line++; + BOL = true; + if (c == '\r') + Source++; + return Token (Token::TK_NEWLINE, begin, Source - begin); + } + else if (isspace (c)) + { + while (Source < SourceEnd && + *Source != '\r' && + *Source != '\n' && + isspace (*Source)) + Source++; + + return Token (Token::TK_WHITESPACE, begin, Source - begin); + } + else if (isdigit (c)) + { + BOL = false; + if (c == '0' && Source < SourceEnd && Source [0] == 'x') // hex numbers + { + Source++; + while (Source < SourceEnd && isxdigit (*Source)) + Source++; + } + else + while (Source < SourceEnd && isdigit (*Source)) + Source++; + return Token (Token::TK_NUMBER, begin, Source - begin); + } + else if (c == '_' || isalnum (c)) + { + BOL = false; + while (Source < SourceEnd && (*Source == '_' || isalnum (*Source))) + Source++; + Token t (Token::TK_KEYWORD, begin, Source - begin); + if (iExpand) + t = ExpandMacro (t); + return t; + } + else if (c == '"' || c == '\'') + { + BOL = false; + while (Source < SourceEnd && *Source != c) + { + if (*Source == '\\') + { + Source++; + if (Source >= SourceEnd) + break; + } + if (*Source == '\n') + Line++; + Source++; + } + if (Source < SourceEnd) + Source++; + return Token (Token::TK_STRING, begin, Source - begin); + } + else if (c == '/' && *Source == '/') + { + BOL = false; + Source++; + while (Source < SourceEnd && *Source != '\r' && *Source != '\n') + Source++; + return Token (Token::TK_LINECOMMENT, begin, Source - begin); + } + else if (c == '/' && *Source == '*') + { + BOL = false; + Source++; + while (Source < SourceEnd && (Source [0] != '*' || Source [1] != '/')) + { + if (*Source == '\n') + Line++; + Source++; + } + if (Source < SourceEnd && *Source == '*') + Source++; + if (Source < SourceEnd && *Source == '/') + Source++; + return Token (Token::TK_COMMENT, begin, Source - begin); + } + else if (c == '#' && BOL) + { + // Skip all whitespaces after '#' + while (Source < SourceEnd && isspace (*Source)) + Source++; + while (Source < SourceEnd && !isspace (*Source)) + Source++; + return Token (Token::TK_DIRECTIVE, begin, Source - begin); + } + else if (c == '\\' && Source < SourceEnd && (*Source == '\r' || *Source == '\n')) + { + // Treat backslash-newline as a whole token + if (*Source == '\r') + Source++; + if (*Source == '\n') + Source++; + Line++; + BOL = true; + return Token (Token::TK_LINECONT, begin, Source - begin); + } + else + { + BOL = false; + // Handle double-char operators here + if (c == '>' && (*Source == '>' || *Source == '=')) + Source++; + else if (c == '<' && (*Source == '<' || *Source == '=')) + Source++; + else if (c == '!' && *Source == '=') + Source++; + else if (c == '=' && *Source == '=') + Source++; + else if ((c == '|' || c == '&' || c == '^') && *Source == c) + Source++; + return Token (Token::TK_PUNCTUATION, begin, Source - begin); + } +} + +CPreprocessor::Macro *CPreprocessor::IsDefined (const Token &iToken) +{ + for (Macro *cur = MacroList; cur; cur = cur->Next) + if (cur->Name == iToken) + return cur; + + return NULL; +} + +CPreprocessor::Token CPreprocessor::ExpandMacro (const Token &iToken) +{ + Macro *cur = IsDefined (iToken); + if (cur && !cur->Expanding) + { + Token *args = NULL; + int nargs = 0; + int old_line = Line; + + if (cur->NumArgs != 0) + { + Token t = GetArguments (nargs, args, cur->ExpandFunc ? false : true); + if (t.Type == Token::TK_ERROR) + { + delete [] args; + return t; + } + + // Put the token back into the source pool; we'll handle it later + if (t.String) + { + // Returned token should never be allocated on heap + ENSURE (t.Allocated == 0); + Source = t.String; + Line -= t.CountNL (); + } + } + + if (nargs > cur->NumArgs) + { + char tmp [60]; + snprintf (tmp, sizeof (tmp), "Macro `%.*s' passed %d arguments, but takes just %d", + int (cur->Name.Length), cur->Name.String, + nargs, cur->NumArgs); + Error (old_line, tmp); + return Token (Token::TK_ERROR); + } + + Token t = cur->ExpandFunc ? + cur->ExpandFunc (this, nargs, args) : + cur->Expand (nargs, args, MacroList); + t.AppendNL (Line - old_line); + + delete [] args; + + return t; + } + + return iToken; +} + +/** + * Operator priority: + * 0: Whole expression + * 1: '(' ')' + * 2: || + * 3: && + * 4: | + * 5: ^ + * 6: & + * 7: '==' '!=' + * 8: '<' '<=' '>' '>=' + * 9: '<<' '>>' + * 10: '+' '-' + * 11: '*' '/' '%' + * 12: unary '+' '-' '!' '~' + */ +CPreprocessor::Token CPreprocessor::GetExpression ( + Token &oResult, int iLine, int iOpPriority) +{ + char tmp [40]; + + do + { + oResult = GetToken (true); + } while (oResult.Type == Token::TK_WHITESPACE || + oResult.Type == Token::TK_NEWLINE || + oResult.Type == Token::TK_COMMENT || + oResult.Type == Token::TK_LINECOMMENT || + oResult.Type == Token::TK_LINECONT); + + Token op (Token::TK_WHITESPACE, "", 0); + + // Handle unary operators here + if (oResult.Type == Token::TK_PUNCTUATION && oResult.Length == 1) + { + if (strchr ("+-!~", oResult.String [0])) + { + char uop = oResult.String [0]; + op = GetExpression (oResult, iLine, 12); + long val; + if (!GetValue (oResult, val, iLine)) + { + snprintf (tmp, sizeof (tmp), "Unary '%c' not applicable", uop); + Error (iLine, tmp, &oResult); + return Token (Token::TK_ERROR); + } + + if (uop == '-') + oResult.SetValue (-val); + else if (uop == '!') + oResult.SetValue (!val); + else if (uop == '~') + oResult.SetValue (~val); + } + else if (oResult.String [0] == '(') + { + op = GetExpression (oResult, iLine, 1); + if (op.Type == Token::TK_ERROR) + return op; + if (op.Type == Token::TK_EOS) + { + Error (iLine, "Unclosed parenthesis in #if expression"); + return Token (Token::TK_ERROR); + } + + ENSURE (op.Type == Token::TK_PUNCTUATION && + op.Length == 1 && + op.String [0] == ')'); + op = GetToken (true); + } + } + + while (op.Type == Token::TK_WHITESPACE || + op.Type == Token::TK_NEWLINE || + op.Type == Token::TK_COMMENT || + op.Type == Token::TK_LINECOMMENT || + op.Type == Token::TK_LINECONT) + op = GetToken (true); + + while (true) + { + if (op.Type != Token::TK_PUNCTUATION) + return op; + + int prio = 0; + if (op.Length == 1) + switch (op.String [0]) + { + case ')': return op; + case '|': prio = 4; break; + case '^': prio = 5; break; + case '&': prio = 6; break; + case '<': + case '>': prio = 8; break; + case '+': + case '-': prio = 10; break; + case '*': + case '/': + case '%': prio = 11; break; + } + else if (op.Length == 2) + switch (op.String [0]) + { + case '|': if (op.String [1] == '|') prio = 2; break; + case '&': if (op.String [1] == '&') prio = 3; break; + case '=': if (op.String [1] == '=') prio = 7; break; + case '!': if (op.String [1] == '=') prio = 7; break; + case '<': + if (op.String [1] == '=') + prio = 8; + else if (op.String [1] == '<') + prio = 9; + break; + case '>': + if (op.String [1] == '=') + prio = 8; + else if (op.String [1] == '>') + prio = 9; + break; + } + + if (!prio) + { + Error (iLine, "Expecting operator, got", &op); + return Token (Token::TK_ERROR); + } + + if (iOpPriority >= prio) + return op; + + Token rop; + Token nextop = GetExpression (rop, iLine, prio); + long vlop, vrop; + if (!GetValue (oResult, vlop, iLine)) + { + snprintf (tmp, sizeof (tmp), "Left operand of '%.*s' is not a number", + int (op.Length), op.String); + Error (iLine, tmp, &oResult); + return Token (Token::TK_ERROR); + } + if (!GetValue (rop, vrop, iLine)) + { + snprintf (tmp, sizeof (tmp), "Right operand of '%.*s' is not a number", + int (op.Length), op.String); + Error (iLine, tmp, &rop); + return Token (Token::TK_ERROR); + } + + switch (op.String [0]) + { + case '|': + if (prio == 2) + oResult.SetValue (vlop || vrop); + else + oResult.SetValue (vlop | vrop); + break; + case '&': + if (prio == 3) + oResult.SetValue (vlop && vrop); + else + oResult.SetValue (vlop & vrop); + break; + case '<': + if (op.Length == 1) + oResult.SetValue (vlop < vrop); + else if (prio == 8) + oResult.SetValue (vlop <= vrop); + else if (prio == 9) + oResult.SetValue (vlop << vrop); + break; + case '>': + if (op.Length == 1) + oResult.SetValue (vlop > vrop); + else if (prio == 8) + oResult.SetValue (vlop >= vrop); + else if (prio == 9) + oResult.SetValue (vlop >> vrop); + break; + case '^': oResult.SetValue (vlop ^ vrop); break; + case '!': oResult.SetValue (vlop != vrop); break; + case '=': oResult.SetValue (vlop == vrop); break; + case '+': oResult.SetValue (vlop + vrop); break; + case '-': oResult.SetValue (vlop - vrop); break; + case '*': oResult.SetValue (vlop * vrop); break; + case '/': + case '%': + if (vrop == 0) + { + Error (iLine, "Division by zero"); + return Token (Token::TK_ERROR); + } + if (op.String [0] == '/') + oResult.SetValue (vlop / vrop); + else + oResult.SetValue (vlop % vrop); + break; + } + + op = nextop; + } +} + +bool CPreprocessor::GetValue (const Token &iToken, long &oValue, int iLine) +{ + Token r; + const Token *vt = &iToken; + + if ((vt->Type == Token::TK_KEYWORD || + vt->Type == Token::TK_TEXT || + vt->Type == Token::TK_NUMBER) && + !vt->String) + { + Error (iLine, "Trying to evaluate an empty expression"); + return false; + } + + if (vt->Type == Token::TK_TEXT) + { + CPreprocessor cpp (iToken, iLine); + cpp.MacroList = MacroList; + + Token t; + t = cpp.GetExpression (r, iLine); + + cpp.MacroList = NULL; + + if (t.Type == Token::TK_ERROR) + return false; + + if (t.Type != Token::TK_EOS) + { + Error (iLine, "Garbage after expression", &t); + return false; + } + + vt = &r; + } + + switch (vt->Type) + { + case Token::TK_EOS: + case Token::TK_ERROR: + return false; + + case Token::TK_KEYWORD: + { + // Try to expand the macro + Macro *m = IsDefined (*vt); + if (m != NULL && !m->Expanding) + { + Token x = ExpandMacro (*vt); + m->Expanding = true; + bool rc = GetValue (x, oValue, iLine); + m->Expanding = false; + return rc; + } + + // Undefined macro, "expand" to 0 (mimic cpp behaviour) + oValue = 0; + break; + } + case Token::TK_TEXT: + case Token::TK_NUMBER: + if (!vt->GetValue (oValue)) + { + Error (iLine, "Not a numeric expression", vt); + return false; + } + break; + + default: + Error (iLine, "Unexpected token", vt); + return false; + } + + return true; +} + +CPreprocessor::Token CPreprocessor::GetArgument (Token &oArg, bool iExpand) +{ + do + { + oArg = GetToken (iExpand); + } while (oArg.Type == Token::TK_WHITESPACE || + oArg.Type == Token::TK_NEWLINE || + oArg.Type == Token::TK_COMMENT || + oArg.Type == Token::TK_LINECOMMENT || + oArg.Type == Token::TK_LINECONT); + + if (!iExpand) + { + if (oArg.Type == Token::TK_EOS) + return oArg; + else if (oArg.Type == Token::TK_PUNCTUATION && + (oArg.String [0] == ',' || + oArg.String [0] == ')')) + { + Token t = oArg; + oArg = Token (Token::TK_TEXT, "", 0); + return t; + } + else if (oArg.Type != Token::TK_KEYWORD) + { + Error (Line, "Unexpected token", &oArg); + return Token (Token::TK_ERROR); + } + } + + unsigned int len = oArg.Length; + while (true) + { + Token t = GetToken (iExpand); + switch (t.Type) + { + case Token::TK_EOS: + Error (Line, "Unfinished list of arguments"); + FALLTHROUGH; + case Token::TK_ERROR: + return Token (Token::TK_ERROR); + case Token::TK_PUNCTUATION: + if (t.String [0] == ',' || + t.String [0] == ')') + { + // Trim whitespaces at the end + oArg.Length = len; + return t; + } + break; + case Token::TK_LINECONT: + case Token::TK_COMMENT: + case Token::TK_LINECOMMENT: + case Token::TK_NEWLINE: + // ignore these tokens + continue; + default: + break; + } + + if (!iExpand && t.Type != Token::TK_WHITESPACE) + { + Error (Line, "Unexpected token", &oArg); + return Token (Token::TK_ERROR); + } + + oArg.Append (t); + + if (t.Type != Token::TK_WHITESPACE) + len = oArg.Length; + } +} + +CPreprocessor::Token CPreprocessor::GetArguments (int &oNumArgs, Token *&oArgs, + bool iExpand) +{ + Token args [MAX_MACRO_ARGS]; + int nargs = 0; + + // Suppose we'll leave by the wrong path + oNumArgs = 0; + oArgs = NULL; + + Token t; + do + { + t = GetToken (iExpand); + } while (t.Type == Token::TK_WHITESPACE || + t.Type == Token::TK_COMMENT || + t.Type == Token::TK_LINECOMMENT); + + if (t.Type != Token::TK_PUNCTUATION || t.String [0] != '(') + { + oNumArgs = 0; + oArgs = NULL; + return t; + } + + while (true) + { + if (nargs == MAX_MACRO_ARGS) + { + Error (Line, "Too many arguments to macro"); + return Token (Token::TK_ERROR); + } + + t = GetArgument (args [nargs++], iExpand); + + switch (t.Type) + { + case Token::TK_EOS: + Error (Line, "Unfinished list of arguments"); + FALLTHROUGH; + case Token::TK_ERROR: + return Token (Token::TK_ERROR); + + case Token::TK_PUNCTUATION: + if (t.String [0] == ')') + { + t = GetToken (iExpand); + goto Done; + } // otherwise we've got a ',' + break; + + default: + Error (Line, "Unexpected token", &t); + break; + } + } + +Done: + oNumArgs = nargs; + oArgs = new Token [nargs]; + for (int i = 0; i < nargs; i++) + oArgs [i] = args [i]; + return t; +} + +bool CPreprocessor::HandleDefine (Token &iBody, int iLine) +{ + // Create an additional preprocessor to process macro body + CPreprocessor cpp (iBody, iLine); + + Token t = cpp.GetToken (false); + if (t.Type != Token::TK_KEYWORD) + { + Error (iLine, "Macro name expected after #define"); + return false; + } + + bool output_enabled = ((EnableOutput & (EnableOutput + 1)) == 0); + if (!output_enabled) + return true; + + Macro *m = new Macro (t); + m->Body = iBody; + t = cpp.GetArguments (m->NumArgs, m->Args, false); + while (t.Type == Token::TK_WHITESPACE) + t = cpp.GetToken (false); + + switch (t.Type) + { + case Token::TK_NEWLINE: + case Token::TK_EOS: + // Assign "" to token + t = Token (Token::TK_TEXT, "", 0); + break; + + case Token::TK_ERROR: + delete m; + return false; + + default: + t.Type = Token::TK_TEXT; + ENSURE (t.String + t.Length == cpp.Source); + t.Length = cpp.SourceEnd - t.String; + break; + } + + m->Value = t; + m->Next = MacroList; + MacroList = m; + return true; +} + +bool CPreprocessor::HandleUnDef (Token &iBody, int iLine) +{ + CPreprocessor cpp (iBody, iLine); + + Token t = cpp.GetToken (false); + + if (t.Type != Token::TK_KEYWORD) + { + Error (iLine, "Expecting a macro name after #undef, got", &t); + return false; + } + + // Don't barf if macro does not exist - standard C behaviour + Undef (t.String, t.Length); + + do + { + t = cpp.GetToken (false); + } while (t.Type == Token::TK_WHITESPACE || + t.Type == Token::TK_COMMENT || + t.Type == Token::TK_LINECOMMENT); + + if (t.Type != Token::TK_EOS) + Error (iLine, "Warning: Ignoring garbage after directive", &t); + + return true; +} + +bool CPreprocessor::HandleIfDef (Token &iBody, int iLine) +{ + if (EnableOutput & (1 << 31)) + { + Error (iLine, "Too many embedded #if directives"); + return false; + } + + CPreprocessor cpp (iBody, iLine); + + Token t = cpp.GetToken (false); + + if (t.Type != Token::TK_KEYWORD) + { + Error (iLine, "Expecting a macro name after #ifdef, got", &t); + return false; + } + + EnableOutput <<= 1; + if (IsDefined (t)) + EnableOutput |= 1; + + do + { + t = cpp.GetToken (false); + } while (t.Type == Token::TK_WHITESPACE || + t.Type == Token::TK_COMMENT || + t.Type == Token::TK_LINECOMMENT); + + if (t.Type != Token::TK_EOS) + Error (iLine, "Warning: Ignoring garbage after directive", &t); + + return true; +} + +CPreprocessor::Token CPreprocessor::ExpandDefined (CPreprocessor *iParent, int iNumArgs, Token *iArgs) +{ + if (iNumArgs != 1) + { + iParent->Error (iParent->Line, "The defined() function takes exactly one argument"); + return Token (Token::TK_ERROR); + } + + const char *v = iParent->IsDefined (iArgs [0]) ? "1" : "0"; + return Token (Token::TK_NUMBER, v, 1); +} + +bool CPreprocessor::HandleIf (Token &iBody, int iLine) +{ + Macro defined (Token (Token::TK_KEYWORD, "defined", 7)); + defined.Next = MacroList; + defined.ExpandFunc = ExpandDefined; + defined.NumArgs = 1; + + // Temporary add the defined() function to the macro list + MacroList = &defined; + + long val; + bool rc = GetValue (iBody, val, iLine); + + // Restore the macro list + MacroList = defined.Next; + defined.Next = NULL; + + if (!rc) + return false; + + EnableOutput <<= 1; + if (val) + EnableOutput |= 1; + + return true; +} + +bool CPreprocessor::HandleElse (Token &iBody, int iLine) +{ + if (EnableOutput == 1) + { + Error (iLine, "#else without #if"); + return false; + } + + // Negate the result of last #if + EnableOutput ^= 1; + + if (iBody.Length) + Error (iLine, "Warning: Ignoring garbage after #else", &iBody); + + return true; +} + +bool CPreprocessor::HandleEndIf (Token &iBody, int iLine) +{ + EnableOutput >>= 1; + if (EnableOutput == 0) + { + Error (iLine, "#endif without #if"); + return false; + } + + if (iBody.Length) + Error (iLine, "Warning: Ignoring garbage after #endif", &iBody); + + return true; +} + +CPreprocessor::Token CPreprocessor::HandleDirective (Token &iToken, int iLine) +{ + // Analyze preprocessor directive + const char *directive = iToken.String + 1; + int dirlen = iToken.Length - 1; + while (dirlen && isspace (*directive)) + dirlen--, directive++; + + int old_line = Line; + + // Collect the remaining part of the directive until EOL + Token t, last; + do + { + t = GetToken (false); + if (t.Type == Token::TK_NEWLINE) + { + // No directive arguments + last = t; + t.Length = 0; + goto Done; + } + } while (t.Type == Token::TK_WHITESPACE || + t.Type == Token::TK_LINECONT || + t.Type == Token::TK_COMMENT || + t.Type == Token::TK_LINECOMMENT); + + for (;;) + { + last = GetToken (false); + switch (last.Type) + { + case Token::TK_EOS: + // Can happen and is not an error + goto Done; + + case Token::TK_LINECOMMENT: + case Token::TK_COMMENT: + // Skip comments in macros + continue; + + case Token::TK_ERROR: + return last; + + case Token::TK_LINECONT: + continue; + + case Token::TK_NEWLINE: + goto Done; + + default: + break; + } + + t.Append (last); + t.Type = Token::TK_TEXT; + } +Done: + +#define IS_DIRECTIVE(s) \ + ((dirlen == sizeof (s) - 1) && (strncmp (directive, s, sizeof (s) - 1) == 0)) + + bool rc; + if (IS_DIRECTIVE ("define")) + rc = HandleDefine (t, iLine); + else if (IS_DIRECTIVE ("undef")) + rc = HandleUnDef (t, iLine); + else if (IS_DIRECTIVE ("ifdef")) + rc = HandleIfDef (t, iLine); + else if (IS_DIRECTIVE ("ifndef")) + { + rc = HandleIfDef (t, iLine); + if (rc) + EnableOutput ^= 1; + } + else if (IS_DIRECTIVE ("if")) + rc = HandleIf (t, iLine); + else if (IS_DIRECTIVE ("else")) + rc = HandleElse (t, iLine); + else if (IS_DIRECTIVE ("endif")) + rc = HandleEndIf (t, iLine); + + else + { + // elif is tricky to support because the EnableOutput stack doesn't + // contain enough data to tell whether any previous branch matched + if (IS_DIRECTIVE ("elif")) + Error (iLine, "Unsupported preprocessor directive #elif"); + + //Error (iLine, "Unknown preprocessor directive", &iToken); + //return Token (Token::TK_ERROR); + + // Unknown preprocessor directive, roll back and pass through + Line = old_line; + Source = iToken.String + iToken.Length; + iToken.Type = Token::TK_TEXT; + return iToken; + } + +#undef IS_DIRECTIVE + + if (!rc) + return Token (Token::TK_ERROR); + return last; +} + +void CPreprocessor::Define (const char *iMacroName, size_t iMacroNameLen, + const char *iMacroValue, size_t iMacroValueLen) +{ + Macro *m = new Macro (Token (Token::TK_KEYWORD, iMacroName, iMacroNameLen)); + m->Value = Token (Token::TK_TEXT, iMacroValue, iMacroValueLen); + m->Next = MacroList; + MacroList = m; +} + +void CPreprocessor::Define (const char *iMacroName, size_t iMacroNameLen, + long iMacroValue) +{ + Macro *m = new Macro (Token (Token::TK_KEYWORD, iMacroName, iMacroNameLen)); + m->Value.SetValue (iMacroValue); + m->Next = MacroList; + MacroList = m; +} + +void CPreprocessor::Define (const char *iMacroName, const char *iMacroValue) +{ + Define (iMacroName, strlen(iMacroName), iMacroValue, strlen(iMacroValue)); +} + +void CPreprocessor::Define (const char *iMacroName, long iMacroValue) +{ + Define (iMacroName, strlen(iMacroName), iMacroValue); +} + +bool CPreprocessor::Undef (const char *iMacroName, size_t iMacroNameLen) +{ + Macro **cur = &MacroList; + Token name (Token::TK_KEYWORD, iMacroName, iMacroNameLen); + while (*cur) + { + if ((*cur)->Name == name) + { + Macro *next = (*cur)->Next; + (*cur)->Next = NULL; + delete (*cur); + *cur = next; + return true; + } + + cur = &(*cur)->Next; + } + + return false; +} + +CPreprocessor::Token CPreprocessor::Parse (const Token &iSource) +{ + Source = iSource.String; + SourceEnd = Source + iSource.Length; + Line = 1; + BOL = true; + EnableOutput = 1; + + // Accumulate output into this token + Token output (Token::TK_TEXT); + int empty_lines = 0; + + // Enable output only if all embedded #if's were true + bool old_output_enabled = true; + bool output_enabled = true; + int output_disabled_line = 0; + + while (Source < SourceEnd) + { + int old_line = Line; + Token t = GetToken (true); + + NextToken: + switch (t.Type) + { + case Token::TK_ERROR: + return t; + + case Token::TK_EOS: + return output; // Force termination + + case Token::TK_COMMENT: + // C comments are replaced with single spaces. + if (output_enabled) + { + output.Append (" ", 1); + output.AppendNL (Line - old_line); + } + break; + + case Token::TK_LINECOMMENT: + // C++ comments are ignored + continue; + + case Token::TK_DIRECTIVE: + // Handle preprocessor directives + t = HandleDirective (t, old_line); + + output_enabled = ((EnableOutput & (EnableOutput + 1)) == 0); + if (output_enabled != old_output_enabled) + { + if (output_enabled) + output.AppendNL (old_line - output_disabled_line); + else + output_disabled_line = old_line; + old_output_enabled = output_enabled; + } + + if (output_enabled) + output.AppendNL (Line - old_line - t.CountNL ()); + goto NextToken; + + case Token::TK_LINECONT: + // Backslash-Newline sequences are deleted, no matter where. + empty_lines++; + break; + + case Token::TK_NEWLINE: + if (empty_lines) + { + // Compensate for the backslash-newline combinations + // we have encountered, otherwise line numeration is broken + if (output_enabled) + output.AppendNL (empty_lines); + empty_lines = 0; + } + // Fallthrough to default + FALLTHROUGH; + case Token::TK_WHITESPACE: + // Fallthrough to default + default: + // Passthrough all other tokens + if (output_enabled) + output.Append (t); + break; + } + } + + if (EnableOutput != 1) + { + Error (Line, "Unclosed #if at end of source"); + return Token (Token::TK_ERROR); + } + + return output; +} + +char *CPreprocessor::Parse (const char *iSource, size_t iLength, size_t &oLength) +{ + Token retval = Parse (Token (Token::TK_TEXT, iSource, iLength)); + if (retval.Type == Token::TK_ERROR) + return NULL; + + oLength = retval.Length; + retval.Allocated = 0; + return retval.Buffer; +} Property changes on: ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h =================================================================== --- ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h (nonexistent) +++ ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h (revision 23215) @@ -0,0 +1,541 @@ +/* + * This source file originally came from OGRE v1.7.2 - http://www.ogre3d.org/ + * with some tweaks as part of 0 A.D. + * All changes are released under the original license, as follows: + */ + +/* +----------------------------------------------------------------------------- +This source file is part of OGRE + (Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org/ + +Copyright (c) 2000-2009 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + +#ifndef INCLUDED_CPREPROCESSOR +#define INCLUDED_CPREPROCESSOR + +/** + * This is a simplistic C/C++-like preprocessor. + * It takes an non-zero-terminated string on input and outputs a + * non-zero-terminated string buffer. + * + * This preprocessor was designed specifically for GLSL shaders, so + * if you want to use it for other purposes you might want to check + * if the feature set it provides is enough for you. + * + * Here's a list of supported features: + *
    + *
  • Fast memory allocation-less operation (mostly).
  • + *
  • Line continuation (backslash-newline) is swallowed.
  • + *
  • Line numeration is fully preserved by inserting empty lines where + * required. This is crucial if, say, GLSL compiler reports you an error + * with a line number.
  • + *
  • \#define: Parametrized and non-parametrized macros. Invoking a macro with + * less arguments than it takes assignes empty values to missing arguments.
  • + *
  • \#undef: Forget defined macros
  • + *
  • \#ifdef/\#ifndef/\#else/\#endif: Conditional suppression of parts of code.
  • + *
  • \#if: Supports numeric expression of any complexity, also supports the + * defined() pseudo-function.
  • + *
+ */ +class CPreprocessor +{ + /** + * A input token. + * + * For performance reasons most tokens will point to portions of the + * input stream, so no unneeded memory allocation is done. However, + * in some cases we must allocate different memory for token storage, + * in this case this is signalled by setting the Allocated member + * to non-zero in which case the destructor will know that it must + * free memory on object destruction. + * + * Again for performance reasons we use malloc/realloc/free here because + * C++-style new[] lacks the realloc() counterpart. + */ + class Token + { + public: + enum Kind + { + TK_EOS, // End of input stream + TK_ERROR, // An error has been encountered + TK_WHITESPACE, // A whitespace span (but not newline) + TK_NEWLINE, // A single newline (CR & LF) + TK_LINECONT, // Line continuation ('\' followed by LF) + TK_NUMBER, // A number + TK_KEYWORD, // A keyword + TK_PUNCTUATION, // A punctuation character + TK_DIRECTIVE, // A preprocessor directive + TK_STRING, // A string + TK_COMMENT, // A block comment + TK_LINECOMMENT, // A line comment + TK_TEXT, // An unparsed text (cannot be returned from GetToken()) + }; + + /// Token type + Kind Type; + /// True if string was allocated (and must be freed) + mutable size_t Allocated; + union + { + /// A pointer somewhere into the input buffer + const char *String; + /// A memory-allocated string + char *Buffer; + }; + /// Token length in bytes + size_t Length; + + Token () : Type (TK_ERROR), Allocated (0), String (NULL), Length (0) + { } + + Token (Kind iType) : Type (iType), Allocated (0), String (NULL), Length (0) + { } + + Token (Kind iType, const char *iString, size_t iLength) : + Type (iType), Allocated (0), String (iString), Length (iLength) + { } + + Token (const Token &iOther) + { + Type = iOther.Type; + Allocated = iOther.Allocated; + iOther.Allocated = 0; // !!! not quite correct but effective + String = iOther.String; + Length = iOther.Length; + } + + ~Token () + { if (Allocated) free (Buffer); } + + /// Assignment operator + Token &operator = (const Token &iOther) + { + if (Allocated) free (Buffer); + Type = iOther.Type; + Allocated = iOther.Allocated; + iOther.Allocated = 0; // !!! not quite correct but effective + String = iOther.String; + Length = iOther.Length; + return *this; + } + + /// Append a string to this token + void Append (const char *iString, size_t iLength); + + /// Append a token to this token + void Append (const Token &iOther); + + /// Append given number of newlines to this token + void AppendNL (int iCount); + + /// Count number of newlines in this token + int CountNL (); + + /// Get the numeric value of the token + bool GetValue (long &oValue) const; + + /// Set the numeric value of the token + void SetValue (long iValue); + + /// Test two tokens for equality + bool operator == (const Token &iOther) + { + if (iOther.Length != Length) + return false; + return (memcmp (String, iOther.String, Length) == 0); + } + }; + + /// A macro definition + class Macro + { + public: + /// Macro name + Token Name; + /// Number of arguments + int NumArgs; + /// The names of the arguments + Token *Args; + /// The macro value + Token Value; + /// Unparsed macro body (keeps the whole raw unparsed macro body) + Token Body; + /// Next macro in chained list + Macro *Next; + /// A pointer to function implementation (if macro is really a func) + Token (*ExpandFunc) (CPreprocessor *iParent, int iNumArgs, Token *iArgs); + /// true if macro expansion is in progress + bool Expanding; + + Macro (const Token &iName) : + Name (iName), NumArgs (0), Args (NULL), Next (NULL), + ExpandFunc (NULL), Expanding (false) + { } + + ~Macro () + { delete [] Args; delete Next; } + + /// Expand the macro value (will not work for functions) + Token Expand (int iNumArgs, Token *iArgs, Macro *iMacros); + }; + + friend class CPreprocessor::Macro; + + /// The current source text input + const char *Source; + /// The end of the source text + const char *SourceEnd; + /// Current line number + int Line; + /// True if we are at beginning of line + bool BOL; + /// A stack of 32 booleans packed into one value :) + unsigned EnableOutput; + /// The list of macros defined so far + Macro *MacroList; + + /** + * Private constructor to re-parse a single token. + */ + CPreprocessor (const Token &iToken, int iLine); + + /** + * Stateless tokenizer: Parse the input text and return the next token. + * @param iExpand + * If true, macros will be expanded to their values + * @return + * The next token from the input stream + */ + Token GetToken (bool iExpand); + + /** + * Handle a preprocessor directive. + * @param iToken + * The whole preprocessor directive line (until EOL) + * @param iLine + * The line where the directive begins (for error reports) + * @return + * The last input token that was not proceeded. + */ + Token HandleDirective (Token &iToken, int iLine); + + /** + * Handle a \#define directive. + * @param iBody + * The body of the directive (everything after the directive + * until end of line). + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if everything went ok, false if not + */ + bool HandleDefine (Token &iBody, int iLine); + + /** + * Undefine a previously defined macro + * @param iBody + * The body of the directive (everything after the directive + * until end of line). + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if everything went ok, false if not + */ + bool HandleUnDef (Token &iBody, int iLine); + + /** + * Handle an \#ifdef directive. + * @param iBody + * The body of the directive (everything after the directive + * until end of line). + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if everything went ok, false if not + */ + bool HandleIfDef (Token &iBody, int iLine); + + /** + * Handle an \#if directive. + * @param iBody + * The body of the directive (everything after the directive + * until end of line). + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if everything went ok, false if not + */ + bool HandleIf (Token &iBody, int iLine); + + /** + * Handle an \#else directive. + * @param iBody + * The body of the directive (everything after the directive + * until end of line). + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if everything went ok, false if not + */ + bool HandleElse (Token &iBody, int iLine); + + /** + * Handle an \#endif directive. + * @param iBody + * The body of the directive (everything after the directive + * until end of line). + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if everything went ok, false if not + */ + bool HandleEndIf (Token &iBody, int iLine); + + /** + * Get a single function argument until next ',' or ')'. + * @param oArg + * The argument is returned in this variable. + * @param iExpand + * If false, parameters are not expanded and no expressions are + * allowed; only a single keyword is expected per argument. + * @return + * The first unhandled token after argument. + */ + Token GetArgument (Token &oArg, bool iExpand); + + /** + * Get all the arguments of a macro: '(' arg1 { ',' arg2 { ',' ... }} ')' + * @param oNumArgs + * Number of parsed arguments is stored into this variable. + * @param oArgs + * This is set to a pointer to an array of parsed arguments. + * @param iExpand + * If false, parameters are not expanded and no expressions are + * allowed; only a single keyword is expected per argument. + */ + Token GetArguments (int &oNumArgs, Token *&oArgs, bool iExpand); + + /** + * Parse an expression, compute it and return the result. + * @param oResult + * A token containing the result of expression + * @param iLine + * The line at which the expression starts (for error reports) + * @param iOpPriority + * Operator priority (at which operator we will stop if + * proceeding recursively -- used internally. Parser stops + * when it encounters an operator with higher or equal priority). + * @return + * The last unhandled token after the expression + */ + Token GetExpression (Token &oResult, int iLine, int iOpPriority = 0); + + /** + * Get the numeric value of a token. + * If the token was produced by expanding a macro, we will get + * an TEXT token which can contain a whole expression; in this + * case we will call GetExpression to parse it. Otherwise we + * just call the token's GetValue() method. + * @param iToken + * The token to get the numeric value of + * @param oValue + * The variable to put the value into + * @param iLine + * The line where the directive begins (for error reports) + * @return + * true if ok, false if not + */ + bool GetValue (const Token &iToken, long &oValue, int iLine); + + /** + * Expand the given macro, if it exists. + * If macro has arguments, they are collected from source stream. + * @param iToken + * A KEYWORD token containing the (possible) macro name. + * @return + * The expanded token or iToken if it is not a macro + */ + Token ExpandMacro (const Token &iToken); + + /** + * Check if a macro is defined, and if so, return it + * @param iToken + * Macro name + * @return + * The macro object or NULL if a macro with this name does not exist + */ + Macro *IsDefined (const Token &iToken); + + /** + * The implementation of the defined() preprocessor function + * @param iParent + * The parent preprocessor object + * @param iNumArgs + * Number of arguments + * @param iArgs + * The arguments themselves + * @return + * The return value encapsulated in a token + */ + static Token ExpandDefined (CPreprocessor *iParent, int iNumArgs, Token *iArgs); + + /** + * Parse the input string and return a token containing the whole output. + * @param iSource + * The source text enclosed in a token + * @return + * The output text enclosed in a token + */ + Token Parse (const Token &iSource); + + /** + * Call the error handler + * @param iLine + * The line at which the error happened. + * @param iError + * The error string. + * @param iToken + * If not NULL contains the erroneous token + */ + void Error (int iLine, const char *iError, const Token *iToken = NULL); + +public: + /// Create an empty preprocessor object + CPreprocessor () : MacroList (NULL) + { } + + /// Destroy the preprocessor object + virtual ~CPreprocessor (); + + /** + * Define a macro without parameters. + * @param iMacroName + * The name of the defined macro + * @param iMacroNameLen + * The length of the name of the defined macro + * @param iMacroValue + * The value of the defined macro + * @param iMacroValueLen + * The length of the value of the defined macro + */ + void Define (const char *iMacroName, size_t iMacroNameLen, + const char *iMacroValue, size_t iMacroValueLen); + + /** + * Define a numerical macro. + * @param iMacroName + * The name of the defined macro + * @param iMacroNameLen + * The length of the name of the defined macro + * @param iMacroValue + * The value of the defined macro + */ + void Define (const char *iMacroName, size_t iMacroNameLen, long iMacroValue); + + /** + * Define a macro without parameters. + * @param iMacroName + * The name of the defined macro + * @param iMacroValue + * The value of the defined macro + */ + void Define (const char *iMacroName, const char *iMacroValue); + + /** + * Define a numerical macro. + * @param iMacroName + * The name of the defined macro + * @param iMacroValue + * The value of the defined macro + */ + void Define (const char *iMacroName, long iMacroValue); + + /** + * Undefine a macro. + * @param iMacroName + * The name of the macro to undefine + * @param iMacroNameLen + * The length of the name of the macro to undefine + * @return + * true if the macro has been undefined, false if macro doesn't exist + */ + bool Undef (const char *iMacroName, size_t iMacroNameLen); + + /** + * Parse the input string and return a newly-allocated output string. + * @note + * The returned preprocessed string is NOT zero-terminated + * (just like the input string). + * @param iSource + * The source text + * @param iLength + * The length of the source text in characters + * @param oLength + * The length of the output string. + * @return + * The output from preprocessor, allocated with malloc(). + * The parser can actually allocate more than needed for performance + * reasons, but this should not be a problem unless you will want + * to store the returned pointer for long time in which case you + * might want to realloc() it. + * If an error has been encountered, the function returns NULL. + * In some cases the function may return an unallocated address + * that's *inside* the source buffer. You must free() the result + * string only if the returned address is not inside the source text. + */ + char *Parse (const char *iSource, size_t iLength, size_t &oLength); + + /** + * An error handler function type. + * The default implementation just drops a note to stderr and + * then the parser ends, returning NULL. + * @param iData + * User-specific pointer from the corresponding CPreprocessor object. + * @param iLine + * The line at which the error happened. + * @param iError + * The error string. + * @param iToken + * If not NULL contains the erroneous token + * @param iTokenLen + * The length of iToken. iToken is never zero-terminated! + */ + typedef void (*ErrorHandlerFunc) ( + void *iData, int iLine, const char *iError, + const char *iToken, size_t iTokenLen); + + /** + * A pointer to the preprocessor's error handler. + * You can assign the address of your own function to this variable + * and implement your own error handling (e.g. throwing an exception etc). + */ + static ErrorHandlerFunc ErrorHandler; + + /// User-specific storage, passed to Error() + void *ErrorData; +}; + +#endif // INCLUDED_CPREPROCESSOR Property changes on: ps/trunk/source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h =================================================================== --- ps/trunk/source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h (nonexistent) +++ ps/trunk/source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h (revision 23215) @@ -0,0 +1,60 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "lib/self_test.h" + +#include "graphics/PreprocessorWrapper.h" +#include "third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h" + +class TestPreprocessor : public CxxTest::TestSuite +{ +public: + void setUp() + { + CPreprocessor::ErrorHandler = CPreprocessorWrapper::PyrogenesisShaderError; + } + + void test_basic() + { + CPreprocessor preproc; + const char* in = "#define TEST 2\n1+1=TEST\n"; + size_t len = 0; + char* out = preproc.Parse(in, strlen(in), len); + TS_ASSERT_EQUALS(std::string(out, len), "\n1+1=2\n"); + + // Free output if it's not inside the source string + if (!(out >= in && out < in + strlen(in))) + free(out); + } + + void test_error() + { + TestLogger logger; + + CPreprocessor preproc; + const char* in = "foo\n#if ()\nbar\n"; + size_t len = 0; + char* out = preproc.Parse(in, strlen(in), len); + TS_ASSERT_EQUALS(std::string(out, len), ""); + + TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "ERROR: Preprocessor error: line 2: Unclosed parenthesis in #if expression\n"); + + // Free output if it's not inside the source string + if (!(out >= in && out < in + strlen(in))) + free(out); + } +}; Property changes on: ps/trunk/source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property