Selaa lähdekoodia

Add some code for the shader pre-processor

Panagiotis Christopoulos Charitos 7 vuotta sitten
vanhempi
sitoutus
58ba98775f

+ 723 - 0
src/anki/resource/ShaderProgramPreProcessor.cpp

@@ -0,0 +1,723 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/resource/ShaderProgramPreProcessor.h>
+#include <anki/resource/ResourceFilesystem.h>
+
+namespace anki
+{
+
+#define ANKI_MALFORMED() \
+	ANKI_RESOURCE_LOGE("%s: Malformed expression: %s", fname.cstr(), line.cstr()); \
+	return Error::USER_DATA
+
+static ANKI_USE_RESULT Error computeShaderVariableDataType(const CString& str, ShaderVariableDataType& out)
+{
+	Error err = Error::NONE;
+
+	if(str == "I32")
+	{
+		out = ShaderVariableDataType::INT;
+	}
+	else if(str == "IVec2")
+	{
+		out = ShaderVariableDataType::IVEC2;
+	}
+	else if(str == "IVec3")
+	{
+		out = ShaderVariableDataType::IVEC3;
+	}
+	else if(str == "IVec4")
+	{
+		out = ShaderVariableDataType::IVEC4;
+	}
+	else if(str == "U32")
+	{
+		out = ShaderVariableDataType::UINT;
+	}
+	else if(str == "UVec2")
+	{
+		out = ShaderVariableDataType::UVEC2;
+	}
+	else if(str == "UVec3")
+	{
+		out = ShaderVariableDataType::UVEC3;
+	}
+	else if(str == "UVec4")
+	{
+		out = ShaderVariableDataType::UVEC4;
+	}
+	else if(str == "F32")
+	{
+		out = ShaderVariableDataType::FLOAT;
+	}
+	else if(str == "Vec2")
+	{
+		out = ShaderVariableDataType::VEC2;
+	}
+	else if(str == "Vec3")
+	{
+		out = ShaderVariableDataType::VEC3;
+	}
+	else if(str == "Vec4")
+	{
+		out = ShaderVariableDataType::VEC4;
+	}
+	else if(str == "Mat3")
+	{
+		out = ShaderVariableDataType::MAT3;
+	}
+	else if(str == "Mat4")
+	{
+		out = ShaderVariableDataType::MAT4;
+	}
+	else if(str == "sampler2D")
+	{
+		out = ShaderVariableDataType::SAMPLER_2D;
+	}
+	else if(str == "sampler2DArray")
+	{
+		out = ShaderVariableDataType::SAMPLER_2D_ARRAY;
+	}
+	else if(str == "samplerCube")
+	{
+		out = ShaderVariableDataType::SAMPLER_CUBE;
+	}
+	else
+	{
+		ANKI_RESOURCE_LOGE("Incorrect variable type %s", &str[0]);
+		err = Error::USER_DATA;
+	}
+
+	return err;
+}
+
+Error ShaderProgramPrePreprocessor::parse()
+{
+	ANKI_ASSERT(!m_fname.isEmpty());
+	ANKI_ASSERT(m_lines.isEmpty());
+
+	// Parse recursively
+	ANKI_CHECK(parseFile(m_fname.toCString(), 0));
+
+	// Checks
+	if(m_foundInstancedInput != m_foundInstancedMutator)
+	{
+		ANKI_RESOURCE_LOGE(
+			"%s: If there is an instanced mutator there should be at least one instanced input", m_fname.cstr());
+		return Error::USER_DATA;
+	}
+
+	// Compute the final source
+	{
+		// First the globals
+		if(m_globalsLines.getSize() > 0)
+		{
+			StringAuto globals(m_alloc);
+			m_globalsLines.join("\n", globals);
+
+			m_finalSource.append(globals.toCString());
+		}
+
+		// Then the UBO
+		if(m_uboStructLines.getSize() > 0)
+		{
+			m_uboStructLines.pushFront("struct GenUniforms_ {");
+			m_uboStructLines.pushBack("};");
+
+			m_uboStructLines.pushBack("#if USE_PUSH_CONSTANTS");
+			m_uboStructLines.pushBackSprintf("ANKI_PUSH_CONSTANTS(GenUniforms_, gen_unis_);");
+			m_uboStructLines.pushBack("#else");
+			m_uboStructLines.pushBackSprintf(
+				"layout(ANKI_UBO_BINDING(%u, 0)) uniform genubo_ {GenUniforms_ gen_unis_;};", m_set);
+			m_uboStructLines.pushBack("#endif");
+
+			StringAuto ubo(m_alloc);
+			m_uboStructLines.join("\n", ubo);
+			m_finalSource.append(ubo.toCString());
+		}
+
+		// Last is the source
+		{
+			StringAuto code(m_alloc);
+			m_lines.join("\n", code);
+			m_finalSource.append(code.toCString());
+		}
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramPrePreprocessor::parseFile(CString filename, U32 depth)
+{
+	// First check the depth
+	if(depth > MAX_INCLUDE_DEPTH)
+	{
+		ANKI_RESOURCE_LOGE("The include depth is too high. Probably circular includance");
+		return Error::USER_DATA;
+	}
+
+	Bool foundPragmaOnce = false;
+
+	// Load file in lines
+	ResourceFilePtr file;
+	ANKI_CHECK(m_fsystem->openFile(filename, file));
+	StringAuto txt(m_alloc);
+	ANKI_CHECK(file->readAllText(m_alloc, txt));
+
+	StringListAuto lines(m_alloc);
+	lines.splitString(txt.toCString(), '\n');
+	if(lines.getSize() < 1)
+	{
+		ANKI_RESOURCE_LOGE("Source is empty");
+		return Error::USER_DATA;
+	}
+
+	// Parse lines
+	for(const String& line : lines)
+	{
+		if(line.find("pragma") != CString::NPOS || line.find("include") != CString::NPOS)
+		{
+			// Possibly a preprocessor directive we care
+			ANKI_CHECK(parseLine(line.toCString(), filename, foundPragmaOnce, depth));
+		}
+		else
+		{
+			// Just append the line
+			m_lines.pushBack(line.toCString());
+		}
+	}
+
+	if(foundPragmaOnce)
+	{
+		// Append the guard
+		m_lines.pushFront("#endf // End guard");
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramPrePreprocessor::parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth)
+{
+	// Tokenize
+	DynamicArrayAuto<StringAuto> tokens(m_alloc);
+	tokenizeLine(line, tokens);
+	ANKI_ASSERT(tokens.getSize() > 0);
+
+	const StringAuto* token = tokens.getBegin();
+	const StringAuto* end = tokens.getEnd();
+
+	// Skip the hash
+	if(*token == "#")
+	{
+		++token;
+	}
+
+	if((token < end) && (*token == "include" || *token == "#include"))
+	{
+		// We _must_ have an #include
+
+		++token;
+
+		if(token < end && token->getLength() >= 3)
+		{
+			const char firstChar = (*token)[0];
+			const char lastChar = (*token)[token->getLength() - 1];
+
+			if((firstChar == '\"' && lastChar == '\"') || (firstChar == '<' && lastChar == '>'))
+			{
+				StringAuto fname(m_alloc);
+				fname.create(line.begin() + 1, line.begin() + line.getLength() - 1);
+
+				ANKI_CHECK(parseFile(fname.toCString(), depth + 1));
+				return Error::NONE;
+			}
+		}
+
+		// We have an error
+		ANKI_MALFORMED();
+	}
+	else if((token < end) && (*token == "pragma" || *token == "#pragma"))
+	{
+		// We may have a #pragma once or a #pragma anki or something else
+
+		++token;
+
+		if(*token == "once")
+		{
+			// Pragma once
+
+			if(foundPragmaOnce)
+			{
+				ANKI_RESOURCE_LOGE("%s: Can't have more than one #pragma once per file", fname.cstr());
+				return Error::USER_DATA;
+			}
+
+			ANKI_MALFORMED();
+
+			// Add the guard unique for this file
+			foundPragmaOnce = true;
+			const U64 hash = fname.computeHash();
+			m_lines.pushBackSprintf("#ifndef GEN_FILE_GUARD_%llu\n#define GEN_FILE_GUARD_%llu 1", hash, hash);
+		}
+		else if(*token == "anki")
+		{
+			// Must be a #pragma anki
+
+			++token;
+
+			if(token + 1 >= end)
+			{
+				ANKI_MALFORMED();
+			}
+
+			if(*token == "mutator")
+			{
+				ANKI_CHECK(parsePragmaMutator(token + 1, end, line, fname));
+			}
+			else if(*token == "input")
+			{
+				ANKI_CHECK(parsePragmaInput(token + 1, end, line, fname));
+			}
+			else if(*token == "start")
+			{
+				ANKI_CHECK(parsePragmaStart(token + 1, end, line, fname));
+			}
+			else if(*token == "end")
+			{
+				ANKI_CHECK(parsePragmaEnd(token + 1, end, line, fname));
+			}
+			else
+			{
+				ANKI_MALFORMED();
+			}
+		}
+		else
+		{
+			// Ignore
+		}
+	}
+	else
+	{
+		// Ignore
+	}
+
+	// Ignored
+	m_lines.pushBack(line);
+	return Error::NONE;
+}
+
+Error ShaderProgramPrePreprocessor::parsePragmaMutator(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+	ANKI_ASSERT(begin < end);
+
+	m_mutators.emplaceBack(m_alloc);
+	Mutator& mutator = m_mutators.getBack();
+
+	// Instanced
+	{
+		if(*begin == "instanced")
+		{
+			mutator.m_instanced = true;
+
+			// Check
+			if(m_foundInstancedMutator)
+			{
+				ANKI_RESOURCE_LOGE("%s: Can't have more than one instanced mutators", fname.cstr());
+				return Error::USER_DATA;
+			}
+
+			m_foundInstancedMutator = true;
+			++begin;
+		}
+		else
+		{
+			mutator.m_instanced = false;
+		}
+	}
+
+	// Name
+	{
+		if(begin >= end)
+		{
+			// Need to have a name
+			ANKI_MALFORMED();
+		}
+
+		// Check for duplicate mutators
+		for(U i = 0; i < m_mutators.getSize() - 1; ++i)
+		{
+			if(m_mutators[i].m_name == *begin)
+			{
+				ANKI_RESOURCE_LOGE("%s: Duplicate mutator %s", fname.cstr(), begin->cstr());
+				return Error::USER_DATA;
+			}
+		}
+
+		mutator.m_name.create(begin->toCString());
+		++begin;
+	}
+
+	// Values
+	{
+		// Gather them
+		for(; begin < end; ++begin)
+		{
+			U32 value = 0;
+			if(begin->toNumber(value))
+			{
+				ANKI_MALFORMED();
+			}
+
+			mutator.m_values.emplaceBack(value);
+		}
+
+		// Check for correct count
+		if(mutator.m_values.getSize() < 2)
+		{
+			// Mutator with less that 2 values doesn't make sense
+			ANKI_MALFORMED();
+		}
+
+		std::sort(mutator.m_values.getBegin(), mutator.m_values.getEnd());
+
+		// Check for duplicates
+		for(U i = 1; i < mutator.m_values.getSize(); ++i)
+		{
+			if(mutator.m_values[i - 1] == mutator.m_values[i])
+			{
+				// Can't have the same value
+				ANKI_MALFORMED();
+			}
+		}
+	}
+
+	if(mutator.m_instanced)
+	{
+		m_globalsLines.pushFrontSprintf("#define GEN_INSTANCED_MUTATOR_ %s", mutator.m_name.cstr());
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramPrePreprocessor::parsePragmaInput(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+	ANKI_ASSERT(begin < end);
+
+	m_inputs.emplaceBack(m_alloc);
+	Input& input = m_inputs.getBack();
+
+	// const
+	{
+		input.m_consts = false;
+		if(*begin == "const")
+		{
+			input.m_consts = true;
+			++begin;
+		}
+	}
+
+	// instanced
+	{
+		if(begin >= end)
+		{
+			ANKI_MALFORMED();
+		}
+
+		input.m_instanced = false;
+		if(*begin == "instanced")
+		{
+			input.m_instanced = true;
+			m_foundInstancedInput = true;
+			++begin;
+		}
+	}
+
+	// type
+	const StringAuto& dataTypeStr = *begin;
+	{
+		if(begin >= end)
+		{
+			ANKI_MALFORMED();
+		}
+
+		if(computeShaderVariableDataType(begin->toCString(), input.m_dataType))
+		{
+			ANKI_MALFORMED();
+		}
+		++begin;
+	}
+
+	// name
+	{
+		if(begin >= end)
+		{
+			ANKI_MALFORMED();
+		}
+
+		// Check if there are duplicates
+		for(U i = 0; i < m_inputs.getSize() - 1; ++i)
+		{
+			if(m_inputs[i].m_name == *begin)
+			{
+				ANKI_MALFORMED();
+			}
+		}
+
+		input.m_name.create(begin->toCString());
+		++begin;
+	}
+
+	// Preprocessor expression
+	StringAuto preproc(m_alloc);
+	{
+		// Create the string
+		for(; begin < end; ++begin)
+		{
+			preproc.append(begin->toCString());
+		}
+
+		if(!preproc.isEmpty())
+		{
+			if(preproc.getLength() < 3)
+			{
+				// Too small
+				ANKI_MALFORMED();
+			}
+
+			if(preproc[0] != '\"')
+			{
+				// Should start with "
+				ANKI_MALFORMED();
+			}
+
+			preproc[0] = ' ';
+
+			if(preproc[preproc.getLength() - 1] != '\"')
+			{
+				// Should end with "
+				ANKI_MALFORMED();
+			}
+
+			preproc[preproc.getLength() - 1] = ' ';
+
+			// Only now create it
+			input.m_preproc.create(preproc.toCString());
+		}
+		else
+		{
+			preproc.append("1");
+		}
+	}
+
+	// Append to source
+
+	const Bool isSampler = input.m_dataType >= ShaderVariableDataType::SAMPLERS_FIRST
+						   && input.m_dataType <= ShaderVariableDataType::SAMPLERS_LAST;
+
+	if(input.m_consts)
+	{
+		// Const
+
+		if(isSampler)
+		{
+			// No const samplers
+			ANKI_MALFORMED();
+		}
+
+		m_globalsLines.pushBackSprintf("#if %s", preproc.cstr());
+		m_globalsLines.pushBackSprintf("const %s %s = %s(%s_CONSTVAL);",
+			dataTypeStr.cstr(),
+			input.m_name.cstr(),
+			dataTypeStr.cstr(),
+			input.m_name.cstr());
+		m_globalsLines.pushBackSprintf("#endif");
+	}
+	else if(isSampler)
+	{
+		// Sampler
+
+		if(input.m_instanced)
+		{
+			// Samplers can't be instanced
+			ANKI_MALFORMED();
+		}
+
+		m_globalsLines.pushBackSprintf("#if %s", preproc.cstr());
+
+		m_globalsLines.pushBackSprintf("layout(ANKI_TEX_BINDING(%u, %u)) uniform %s %s;",
+			m_set,
+			m_lastTexBinding,
+			dataTypeStr.cstr(),
+			input.m_name.cstr());
+		input.m_texBinding = m_lastTexBinding;
+		++m_lastTexBinding;
+
+		m_globalsLines.pushBackSprintf("#endif");
+	}
+	else
+	{
+		// UBO
+
+		const char* name = input.m_name.cstr();
+		const char* type = dataTypeStr.cstr();
+
+		m_uboStructLines.pushBackSprintf("#if %s", preproc.cstr());
+		m_globalsLines.pushBackSprintf("#if %s", preproc.cstr());
+
+		if(input.m_instanced)
+		{
+			m_uboStructLines.pushBackSprintf("#if GEN_INSTANCED_MUTATOR_ > 1");
+			m_uboStructLines.pushBackSprintf("%s gen_uni_%s[GEN_INSTANCED_MUTATOR_];", type, name);
+			m_uboStructLines.pushBackSprintf("#else");
+			m_uboStructLines.pushBackSprintf("%s gen_uni_%s;", type, name);
+			m_uboStructLines.pushBackSprintf("#endif");
+
+			m_globalsLines.pushBackSprintf("#if GEN_INSTANCED_MUTATOR_ > 1");
+			m_globalsLines.pushBackSprintf("%s %s = gen_unis_.gen_uni_%s[gl_InstanceID];", type, name, name);
+			m_globalsLines.pushBackSprintf("#else");
+			m_globalsLines.pushBackSprintf("%s %s = gen_unis_.gen_uni_%s;", type, name, name);
+			m_globalsLines.pushBackSprintf("#endif");
+		}
+		else
+		{
+			m_uboStructLines.pushBackSprintf("%s gen_uni_%s;", type, name);
+
+			m_globalsLines.pushBackSprintf("%s %s = gen_unis_.gen_uni_%s;", type, name, name);
+		}
+
+		m_uboStructLines.pushBackSprintf("#endif");
+		m_globalsLines.pushBackSprintf("#endif");
+	}
+
+	return Error::NONE;
+}
+
+Error ShaderProgramPrePreprocessor::parsePragmaStart(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+	ANKI_ASSERT(begin < end);
+
+	ShaderType shaderType = ShaderType::COUNT;
+	if(*begin == "vert")
+	{
+		shaderType = ShaderType::VERTEX;
+		m_lines.pushBack("#ifdef ANKI_VERTEX_SHADER");
+	}
+	else if(*begin == "tessc")
+	{
+		shaderType = ShaderType::TESSELLATION_CONTROL;
+		m_lines.pushBack("#ifdef ANKI_TESSELATION_CONTROL_SHADER");
+	}
+	else if(*begin == "tesse")
+	{
+		shaderType = ShaderType::TESSELLATION_EVALUATION;
+		m_lines.pushBack("#ifdef ANKI_TESSELLATION_EVALUATION_SHADER");
+	}
+	else if(*begin == "geom")
+	{
+		shaderType = ShaderType::GEOMETRY;
+		m_lines.pushBack("#ifdef ANKI_GEOMETRY_SHADER");
+	}
+	else if(*begin == "frag")
+	{
+		shaderType = ShaderType::FRAGMENT;
+		m_lines.pushBack("#ifdef ANKI_FRAGMENT_SHADER");
+	}
+	else if(*begin == "comp")
+	{
+		shaderType = ShaderType::COMPUTE;
+		m_lines.pushBack("#ifdef ANKI_COMPUTE_SHADER");
+	}
+	else
+	{
+		ANKI_MALFORMED();
+	}
+
+	++begin;
+	if(begin != end)
+	{
+		// Should be the last token
+		ANKI_MALFORMED();
+	}
+
+	// Set the mask
+	ShaderTypeBit mask = ShaderTypeBit(1 << U(shaderType));
+	if(!!(mask & m_shaderTypes))
+	{
+		ANKI_RESOURCE_LOGE("%s: Can't have #pragma start <shader> appearing more than once", fname.cstr());
+		return Error::USER_DATA;
+	}
+	m_shaderTypes |= mask;
+
+	// Check bounds
+	if(m_insideShader)
+	{
+		ANKI_RESOURCE_LOGE("%s: Can't have #pragma start before you close the previous pragma start", fname.cstr());
+		return Error::USER_DATA;
+	}
+	m_insideShader = true;
+
+	return Error::NONE;
+}
+
+Error ShaderProgramPrePreprocessor::parsePragmaEnd(
+	const StringAuto* begin, const StringAuto* end, CString line, CString fname)
+{
+	ANKI_ASSERT(begin && end);
+
+	// Check tokens
+	if(begin != end)
+	{
+		ANKI_MALFORMED();
+	}
+
+	// Check bounds
+	if(!m_insideShader)
+	{
+		ANKI_RESOURCE_LOGE("%s: Can't have #pragma end before you open with a pragma start", fname.cstr());
+		return Error::USER_DATA;
+	}
+	m_insideShader = false;
+
+	// Write code
+	m_lines.pushBack("#end // Shader guard");
+
+	return Error::NONE;
+}
+
+void ShaderProgramPrePreprocessor::tokenizeLine(CString line, DynamicArrayAuto<StringAuto>& tokens)
+{
+	ANKI_ASSERT(line.getLength() > 0);
+
+	StringAuto l(m_alloc);
+	l.create(line);
+
+	// Replace all tabs with spaces
+	for(char& c : l)
+	{
+		if(c == '\t')
+		{
+			c = '\n';
+		}
+	}
+
+	// Split
+	StringListAuto spaceTokens(m_alloc);
+	spaceTokens.splitString(l.toCString(), ' ', false);
+
+	// Create the array
+	for(const String& s : spaceTokens)
+	{
+		tokens.emplaceBack(m_alloc, s.toCString());
+	}
+}
+
+} // end namespace anki

+ 140 - 0
src/anki/resource/ShaderProgramPreProcessor.h

@@ -0,0 +1,140 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/resource/Common.h>
+#include <anki/util/StringList.h>
+#include <anki/util/WeakArray.h>
+#include <anki/gr/Common.h>
+
+namespace anki
+{
+
+// Forward
+class ShaderProgramPrePreprocessor;
+
+/// @addtogroup resource
+/// @{
+
+/// @memberof ShaderProgramPrePreprocessor
+class ShaderProgramPrePreprocessorMutator
+{
+	friend ShaderProgramPrePreprocessor;
+
+public:
+	ShaderProgramPrePreprocessorMutator(GenericMemoryPoolAllocator<U8> alloc)
+		: m_name(alloc)
+		, m_values(alloc)
+	{
+	}
+
+	CString getName() const
+	{
+		return m_name.toCString();
+	}
+
+	ConstWeakArray<U32> getValues() const
+	{
+		return m_values;
+	}
+
+private:
+	StringAuto m_name;
+	DynamicArrayAuto<U32> m_values;
+	Bool8 m_instanced = false;
+	Bool8 m_const = false;
+};
+
+/// @memberof ShaderProgramPrePreprocessor
+class ShaderProgramPrePreprocessorInput
+{
+	friend ShaderProgramPrePreprocessor;
+
+public:
+	ShaderProgramPrePreprocessorInput(GenericMemoryPoolAllocator<U8> alloc)
+		: m_name(alloc)
+		, m_preproc(alloc)
+	{
+	}
+
+	CString getName() const
+	{
+		return m_name.toCString();
+	}
+
+	ShaderVariableDataType getDataType() const
+	{
+		return m_dataType;
+	}
+
+	CString getPreprocessorCondition() const
+	{
+		return m_preproc.toCString();
+	}
+
+private:
+	StringAuto m_name;
+	StringAuto m_preproc;
+	ShaderVariableDataType m_dataType = ShaderVariableDataType::NONE;
+	Bool8 m_consts = false;
+	Bool8 m_instanced = false;
+	U8 m_texBinding = MAX_U8;
+};
+
+/// XXX
+/// #include {<> | ""}
+/// #pragma once
+/// #pragma anki mutator [instanced] NAME VALUE0 [VALUE1 [VALUE2] ...]
+/// #pragma anki input [const | instanced] TYPE NAME ["preprocessor expression"]
+/// #pragma anki start {vert | tessc | tesse | geom | frag | comp}
+/// #pragma anki end
+class ShaderProgramPrePreprocessor : public NonCopyable
+{
+public:
+	ShaderProgramPrePreprocessor(CString fname, ResourceFilesystem* fsystem, GenericMemoryPoolAllocator<U8> alloc);
+
+	~ShaderProgramPrePreprocessor();
+
+	ANKI_USE_RESULT Error parse();
+
+private:
+	using Mutator = ShaderProgramPrePreprocessorMutator;
+	using Input = ShaderProgramPrePreprocessorInput;
+
+	static const U32 MAX_INCLUDE_DEPTH = 8;
+
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	StringAuto m_fname;
+	ResourceFilesystem* m_fsystem = nullptr;
+
+	StringListAuto m_lines; ///< The code.
+	StringListAuto m_globalsLines;
+	StringListAuto m_uboStructLines;
+	StringAuto m_finalSource;
+
+	DynamicArrayAuto<Mutator> m_mutators;
+	DynamicArrayAuto<Input> m_inputs;
+
+	ShaderTypeBit m_shaderTypes;
+	Bool8 m_insideShader = false;
+	U32 m_lastTexBinding = 0;
+	U32 m_set = 0; // TODO
+	Bool8 m_foundInstancedMutator = false;
+	Bool8 m_foundInstancedInput = false;
+
+	ANKI_USE_RESULT Error parseFile(CString fname, U32 depth);
+	ANKI_USE_RESULT Error parseLine(CString line, CString fname, Bool& foundPragmaOnce, U32 depth);
+	ANKI_USE_RESULT Error parsePragmaMutator(
+		const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaInput(const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaStart(const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+	ANKI_USE_RESULT Error parsePragmaEnd(const StringAuto* begin, const StringAuto* end, CString line, CString fname);
+
+	void tokenizeLine(CString line, DynamicArrayAuto<StringAuto>& tokens);
+};
+/// @}
+
+} // end namespace anki