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