Browse Source

RenderGraph: More work

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
c32929b1f8

+ 4 - 1
.appveyor.yml

@@ -44,5 +44,8 @@ build_script:
   - mingw32-make -j 2
   - mingw32-make -j 2
 
 
 notifications:
 notifications:
-  email: false
+  - provider: Email
+    to:
+      - '{{commitAuthorEmail}}'
+    on_build_failure: true
 
 

+ 99 - 64
src/anki/gr/RenderGraph.cpp

@@ -13,29 +13,69 @@
 namespace anki
 namespace anki
 {
 {
 
 
-class RenderGraph::PassBatch
+/// Render pass or compute job.
+class RenderGraph::Pass
 {
 {
 public:
 public:
-	DynamicArrayAuto<Pass*> m_passes;
+	FramebufferPtr m_framebuffer;
 
 
-	PassBatch(const StackAllocator<U8>& alloc)
-		: m_passes(alloc)
-	{
-	}
+	Storage<RenderGraphDependency> m_consumers;
+	Storage<RenderGraphDependency> m_producers;
+
+	Storage<Pass*> m_dependsOn;
+
+	U32 m_index;
+	Array<char, MAX_GR_OBJECT_NAME_LENGTH + 1> m_name;
+};
+
+/// Render target.
+class RenderGraph::RenderTarget
+{
+public:
+	TexturePtr m_tex;
+	Bool8 m_imported = false;
+	Array<char, MAX_GR_OBJECT_NAME_LENGTH + 1> m_name;
+};
+
+/// A collection of passes that can execute in parallel.
+class RenderGraph::PassBatch
+{
+public:
+	Storage<Pass*> m_passes;
 };
 };
 
 
 class RenderGraph::BakeContext
 class RenderGraph::BakeContext
 {
 {
 public:
 public:
-	BakeContext(const StackAllocator<U8>& alloc)
-		: m_batches(alloc)
-	{
-	}
+	Storage<PassBatch> m_batches;
 
 
-	DynamicArrayAuto<PassBatch*> m_batches;
 	BitSet<MAX_PASSES> m_passIsInBatch = {false};
 	BitSet<MAX_PASSES> m_passIsInBatch = {false};
 };
 };
 
 
+template<typename T>
+void RenderGraph::Storage<T>::pushBack(StackAllocator<U8>& alloc, T&& x)
+{
+	if(m_count == m_storage)
+	{
+		m_storage = max<U32>(2, m_storage * 2);
+		T* newStorage = alloc.newArray<T>(m_storage);
+		for(U i = 0; i < m_count; ++i)
+		{
+			newStorage[i] = std::move(m_arr[i]);
+		}
+
+		if(m_count)
+		{
+			alloc.deleteArray(m_arr, m_count);
+		}
+
+		m_arr = newStorage;
+	}
+
+	m_arr[m_count] = std::move(x);
+	++m_count;
+}
+
 RenderGraph::RenderGraph(GrManager* manager, U64 hash, GrObjectCache* cache)
 RenderGraph::RenderGraph(GrManager* manager, U64 hash, GrObjectCache* cache)
 	: GrObject(manager, CLASS_TYPE, hash, cache)
 	: GrObject(manager, CLASS_TYPE, hash, cache)
 {
 {
@@ -51,38 +91,19 @@ RenderGraph::~RenderGraph()
 	// TODO
 	// TODO
 }
 }
 
 
-template<typename T>
-void RenderGraph::increaseStorage(T*& oldStorage, U32& count, U32& storage)
+void RenderGraph::reset()
 {
 {
-	T* newStorage;
-	if(count == storage)
+	for(RenderTarget& rt : m_renderTargets)
 	{
 	{
-		storage = max<U32>(2, storage * 2);
-		newStorage = m_tmpAlloc.newArray<T>(storage);
-		for(U i = 0; i < count; ++i)
-		{
-			newStorage[i] = oldStorage[i];
-		}
+		rt.m_tex.reset(nullptr);
 	}
 	}
-	else
-	{
-		newStorage = oldStorage;
-	}
-
-	++count;
-	oldStorage = newStorage;
-}
+	m_renderTargets.reset();
 
 
-void RenderGraph::reset()
-{
-	for(U i = 0; i < m_renderTargetsCount; ++i)
+	for(Pass& pass : m_passes)
 	{
 	{
-		m_renderTargets[i].m_tex.reset(nullptr);
+		pass.m_framebuffer.reset(nullptr);
 	}
 	}
-	m_renderTargetsCount = 0;
-	m_renderTargetsStorage = 0;
-
-	// for(Pass* pass : m_passes)
+	m_passes.reset();
 }
 }
 
 
 Error RenderGraph::dumpDependencyDotFile(const BakeContext& ctx, CString path) const
 Error RenderGraph::dumpDependencyDotFile(const BakeContext& ctx, CString path) const
@@ -97,7 +118,7 @@ Error RenderGraph::dumpDependencyDotFile(const BakeContext& ctx, CString path) c
 		ANKI_CHECK(file.writeText("\tsubgraph cluster_%u {\n", batchIdx));
 		ANKI_CHECK(file.writeText("\tsubgraph cluster_%u {\n", batchIdx));
 		ANKI_CHECK(file.writeText("\t\tlabel=\"batch_%u\";\n", batchIdx));
 		ANKI_CHECK(file.writeText("\t\tlabel=\"batch_%u\";\n", batchIdx));
 
 
-		for(const Pass* pass : ctx.m_batches[batchIdx]->m_passes)
+		for(const Pass* pass : ctx.m_batches[batchIdx].m_passes)
 		{
 		{
 			for(const Pass* dep : pass->m_dependsOn)
 			for(const Pass* dep : pass->m_dependsOn)
 			{
 			{
@@ -114,9 +135,7 @@ Error RenderGraph::dumpDependencyDotFile(const BakeContext& ctx, CString path) c
 
 
 RenderGraphHandle RenderGraph::pushRenderTarget(CString name, TexturePtr tex, Bool imported)
 RenderGraphHandle RenderGraph::pushRenderTarget(CString name, TexturePtr tex, Bool imported)
 {
 {
-	increaseStorage(m_renderTargets, m_renderTargetsCount, m_renderTargetsStorage);
-
-	RenderTarget& target = m_renderTargets[m_renderTargetsCount - 1];
+	RenderTarget target;
 	target.m_imported = imported;
 	target.m_imported = imported;
 	target.m_tex = tex;
 	target.m_tex = tex;
 
 
@@ -131,7 +150,8 @@ RenderGraphHandle RenderGraph::pushRenderTarget(CString name, TexturePtr tex, Bo
 		strcpy(&target.m_name[0], NA);
 		strcpy(&target.m_name[0], NA);
 	}
 	}
 
 
-	return (m_renderTargetsCount - 1) & TEXTURE_TYPE;
+	m_renderTargets.pushBack(m_tmpAlloc, std::move(target));
+	return (m_renderTargets.getSize() - 1) & TEXTURE_TYPE;
 }
 }
 
 
 RenderGraphHandle RenderGraph::importRenderTarget(CString name, TexturePtr tex)
 RenderGraphHandle RenderGraph::importRenderTarget(CString name, TexturePtr tex)
@@ -139,7 +159,7 @@ RenderGraphHandle RenderGraph::importRenderTarget(CString name, TexturePtr tex)
 	return pushRenderTarget(name, tex, true);
 	return pushRenderTarget(name, tex, true);
 }
 }
 
 
-RenderGraphHandle RenderGraph::newRenderTarget(CString name, const TextureInitInfo& texInf)
+RenderGraphHandle RenderGraph::newRenderTarget(const TextureInitInfo& texInf)
 {
 {
 	auto alloc = m_gr->getAllocator();
 	auto alloc = m_gr->getAllocator();
 
 
@@ -181,14 +201,14 @@ RenderGraphHandle RenderGraph::newRenderTarget(CString name, const TextureInitIn
 	}
 	}
 
 
 	// Create the render target
 	// Create the render target
-	return pushRenderTarget(name, tex, false);
+	return pushRenderTarget(texInf.getName(), tex, false);
 }
 }
 
 
 Bool RenderGraph::passADependsOnB(const Pass& a, const Pass& b)
 Bool RenderGraph::passADependsOnB(const Pass& a, const Pass& b)
 {
 {
 	for(const RenderGraphDependency& consumer : a.m_consumers)
 	for(const RenderGraphDependency& consumer : a.m_consumers)
 	{
 	{
-		for(const RenderGraphDependency& producer : b.m_producers)
+		for(const RenderGraphDependency& producer : a.m_producers)
 		{
 		{
 			if(consumer.m_handle == producer.m_handle)
 			if(consumer.m_handle == producer.m_handle)
 			{
 			{
@@ -209,38 +229,55 @@ RenderGraphHandle RenderGraph::registerRenderPass(CString name,
 	void* userData,
 	void* userData,
 	U32 secondLevelCmdbsCount)
 	U32 secondLevelCmdbsCount)
 {
 {
-	// Allocate the new pass
-	Pass* newPass = m_tmpAlloc.newInstance<Pass>();
-	newPass->m_index = m_passes.getSize();
+	Pass newPass;
+	newPass.m_index = m_passes.getSize();
 
 
 	// Set name
 	// Set name
 	if(name.getLength())
 	if(name.getLength())
 	{
 	{
 		ANKI_ASSERT(name.getLength() <= MAX_GR_OBJECT_NAME_LENGTH);
 		ANKI_ASSERT(name.getLength() <= MAX_GR_OBJECT_NAME_LENGTH);
-		strcpy(&newPass->m_name[0], &name[0]);
+		strcpy(&newPass.m_name[0], &name[0]);
 	}
 	}
 	else
 	else
 	{
 	{
 		static const char* NA = "N/A";
 		static const char* NA = "N/A";
-		strcpy(&newPass->m_name[0], NA);
+		strcpy(&newPass.m_name[0], NA);
 	}
 	}
 
 
 	// Set the dependencies
 	// Set the dependencies
-	// XXX
+	if(consumers.getSize())
+	{
+		newPass.m_consumers.m_arr = m_tmpAlloc.newArray<RenderGraphDependency>(consumers.getSize());
+		for(U i = 0; i < consumers.getSize(); ++i)
+		{
+			newPass.m_consumers.m_arr[i] = consumers[i];
+		}
+
+		newPass.m_consumers.m_count = newPass.m_consumers.m_storage = consumers.getSize();
+	}
+
+	if(producers.getSize())
+	{
+		newPass.m_producers.m_arr = m_tmpAlloc.newArray<RenderGraphDependency>(producers.getSize());
+		for(U i = 0; i < producers.getSize(); ++i)
+		{
+			newPass.m_producers.m_arr[i] = producers[i];
+		}
+
+		newPass.m_producers.m_count = newPass.m_producers.m_storage = producers.getSize();
+	}
 
 
 	// Find the dependencies
 	// Find the dependencies
-	for(Pass* prevPass : m_passes)
+	for(Pass& otherPass : m_passes)
 	{
 	{
-		if(passADependsOnB(*newPass, *prevPass))
+		if(passADependsOnB(newPass, otherPass))
 		{
 		{
-			newPass->m_dependsOn.resize(m_tmpAlloc, newPass->m_dependsOn.getSize() + 1);
-			newPass->m_dependsOn.getBack() = prevPass;
+			newPass.m_dependsOn.pushBack(m_tmpAlloc, &otherPass);
 		}
 		}
 	}
 	}
 
 
 	// Push pass to the passes
 	// Push pass to the passes
-	m_passes.resize(m_tmpAlloc, m_passes.getSize() + 1);
-	m_passes[m_passes.getSize() - 1] = newPass;
+	m_passes.pushBack(m_tmpAlloc, std::move(newPass));
 	return (m_passes.getSize() - 1) & RT_TYPE;
 	return (m_passes.getSize() - 1) & RT_TYPE;
 }
 }
 
 
@@ -274,27 +311,25 @@ Bool RenderGraph::passHasUnmetDependencies(const BakeContext& ctx, const Pass& p
 
 
 void RenderGraph::bake()
 void RenderGraph::bake()
 {
 {
-	BakeContext ctx(m_tmpAlloc);
+	BakeContext ctx;
 
 
 	// Walk the graph and create pass batches
 	// Walk the graph and create pass batches
 	U passesInBatchCount = 0;
 	U passesInBatchCount = 0;
 	while(passesInBatchCount < m_passes.getSize())
 	while(passesInBatchCount < m_passes.getSize())
 	{
 	{
-		PassBatch& batch = *m_tmpAlloc.newInstance<PassBatch>(m_tmpAlloc);
+		PassBatch batch;
 
 
 		for(U i = 0; i < m_passes.getSize(); ++i)
 		for(U i = 0; i < m_passes.getSize(); ++i)
 		{
 		{
-			if(!ctx.m_passIsInBatch.get(i) && !passHasUnmetDependencies(ctx, *m_passes[i]))
+			if(!ctx.m_passIsInBatch.get(i) && !passHasUnmetDependencies(ctx, m_passes[i]))
 			{
 			{
 				// Add to the batch
 				// Add to the batch
-				batch.m_passes.resize(batch.m_passes.getSize() + 1);
-				batch.m_passes.getBack() = m_passes[i];
+				batch.m_passes.pushBack(m_tmpAlloc, &m_passes[i]);
 			}
 			}
 		}
 		}
 
 
 		// Push back batch
 		// Push back batch
-		ctx.m_batches.resize(ctx.m_batches.getSize() + 1);
-		ctx.m_batches.getBack() = &batch;
+		ctx.m_batches.pushBack(m_tmpAlloc, std::move(batch));
 
 
 		// Mark batch's passes done
 		// Mark batch's passes done
 		for(const Pass* pass : batch.m_passes)
 		for(const Pass* pass : batch.m_passes)

+ 62 - 29
src/anki/gr/RenderGraph.h

@@ -77,7 +77,7 @@ public:
 	/// @{
 	/// @{
 	RenderGraphHandle importRenderTarget(CString name, TexturePtr tex);
 	RenderGraphHandle importRenderTarget(CString name, TexturePtr tex);
 
 
-	RenderGraphHandle newRenderTarget(CString name, const TextureInitInfo& texInf);
+	RenderGraphHandle newRenderTarget(const TextureInitInfo& texInf);
 
 
 	RenderGraphHandle importBuffer(CString name, BufferPtr buff);
 	RenderGraphHandle importBuffer(CString name, BufferPtr buff);
 
 
@@ -141,9 +141,6 @@ private:
 	GrManager* m_gr;
 	GrManager* m_gr;
 	StackAllocator<U8> m_tmpAlloc;
 	StackAllocator<U8> m_tmpAlloc;
 
 
-	class PassBatch;
-	class BakeContext;
-
 	/// Render targets of the same type+size+format.
 	/// Render targets of the same type+size+format.
 	class RenderTargetCacheEntry
 	class RenderTargetCacheEntry
 	{
 	{
@@ -155,33 +152,72 @@ private:
 	HashMap<TextureInitInfo, RenderTargetCacheEntry*> m_renderTargetCache; ///< Imported render targets.
 	HashMap<TextureInitInfo, RenderTargetCacheEntry*> m_renderTargetCache; ///< Imported render targets.
 	HashMap<FramebufferInitInfo, FramebufferPtr> m_framebufferCache;
 	HashMap<FramebufferInitInfo, FramebufferPtr> m_framebufferCache;
 
 
-	class RenderTarget
-	{
-	public:
-		TexturePtr m_tex;
-		Bool8 m_imported = false;
-		Array<char, MAX_GR_OBJECT_NAME_LENGTH + 1> m_name;
-	};
-
-	RenderTarget* m_renderTargets = nullptr;
-	U32 m_renderTargetsCount = 0;
-	U32 m_renderTargetsStorage = 0;
+	// Forward declarations
+	class PassBatch;
+	class RenderTarget;
+	class Pass;
+	class BakeContext;
 
 
-	/// Render pass or compute job.
-	class Pass
+	template<typename T>
+	class Storage
 	{
 	{
 	public:
 	public:
-		FramebufferPtr m_framebuffer;
-		DynamicArray<RenderGraphDependency> m_consumers;
-		DynamicArray<RenderGraphDependency> m_producers;
-		DynamicArray<Pass*> m_dependsOn;
-		U32 m_index;
-		Array<char, MAX_GR_OBJECT_NAME_LENGTH + 1> m_name;
-
-		void destroy(StackAllocator<U8>& alloc);
+		T* m_arr = nullptr;
+		U32 m_count = 0;
+		U32 m_storage = 0;
+
+		T& operator[](U i)
+		{
+			ANKI_ASSERT(i < m_count);
+			return m_arr[i];
+		}
+
+		const T& operator[](U i) const
+		{
+			ANKI_ASSERT(i < m_count);
+			return m_arr[i];
+		}
+
+		T* begin()
+		{
+			return &m_arr[0];
+		}
+
+		const T* begin() const
+		{
+			return &m_arr[0];
+		}
+
+		T* end()
+		{
+			return &m_arr[m_count];
+		}
+
+		const T* end() const
+		{
+			return &m_arr[m_count];
+		}
+
+		U32 getSize() const
+		{
+			return m_count;
+		}
+
+		void pushBack(StackAllocator<U8>& alloc, T&& x);
+
+		void reset()
+		{
+			m_arr = nullptr;
+			m_count = 0;
+			m_storage = 0;
+		}
 	};
 	};
 
 
-	DynamicArray<Pass*> m_passes;
+	/// @name Runtime stuff
+	/// @{
+	Storage<RenderTarget> m_renderTargets;
+	Storage<Pass> m_passes;
+	/// @}
 
 
 	RenderGraphHandle pushRenderTarget(CString name, TexturePtr tex, Bool imported);
 	RenderGraphHandle pushRenderTarget(CString name, TexturePtr tex, Bool imported);
 
 
@@ -191,9 +227,6 @@ private:
 
 
 	/// Dump the dependency graph into a file.
 	/// Dump the dependency graph into a file.
 	ANKI_USE_RESULT Error dumpDependencyDotFile(const BakeContext& ctx, CString path) const;
 	ANKI_USE_RESULT Error dumpDependencyDotFile(const BakeContext& ctx, CString path) const;
-
-	template<typename T>
-	void increaseStorage(T*& oldStorage, U32& count, U32& storage);
 };
 };
 /// @}
 /// @}
 
 

+ 2 - 0
src/anki/renderer/Renderer.cpp

@@ -231,6 +231,8 @@ void Renderer::initJitteredMats()
 
 
 Error Renderer::render(RenderingContext& ctx)
 Error Renderer::render(RenderingContext& ctx)
 {
 {
+	m_rgraph->reset();
+
 	CommandBufferPtr& cmdb = ctx.m_commandBuffer;
 	CommandBufferPtr& cmdb = ctx.m_commandBuffer;
 
 
 	ctx.m_jitterMat = m_jitteredMats8x[m_frameCount & (8 - 1)];
 	ctx.m_jitterMat = m_jitteredMats8x[m_frameCount & (8 - 1)];

+ 8 - 0
src/anki/renderer/Renderer.h

@@ -105,6 +105,9 @@ public:
 
 
 		StackAllocator<U8> m_alloc;
 		StackAllocator<U8> m_alloc;
 
 
+		RenderGraphHandle m_spotRt;
+		RenderGraphHandle m_omniRt;
+
 		ShadowMapping(const StackAllocator<U8>& alloc)
 		ShadowMapping(const StackAllocator<U8>& alloc)
 			: m_alloc(alloc)
 			: m_alloc(alloc)
 		{
 		{
@@ -403,6 +406,11 @@ anki_internal:
 		return m_linearSampler;
 		return m_linearSampler;
 	}
 	}
 
 
+	RenderGraphPtr getRenderGraph() const
+	{
+		return m_rgraph;
+	}
+
 private:
 private:
 	ThreadPool* m_threadpool = nullptr;
 	ThreadPool* m_threadpool = nullptr;
 	ResourceManager* m_resources = nullptr;
 	ResourceManager* m_resources = nullptr;

+ 7 - 4
src/anki/util/Assert.cpp

@@ -18,12 +18,15 @@ namespace anki
 
 
 void akassert(const char* exprTxt, const char* file, int line, const char* func)
 void akassert(const char* exprTxt, const char* file, int line, const char* func)
 {
 {
-#if ANKI_OS == ANKI_OS_LINUX
-	fprintf(stderr, "\033[1;31m(%s:%d %s) Assertion failed: %s\033[0m\n", file, line, func, exprTxt);
-#elif ANKI_OS == ANKI_OS_ANDROID
+#if ANKI_OS == ANKI_OS_ANDROID
 	__android_log_print(ANDROID_LOG_ERROR, "AnKi", "(%s:%d %s) Assertion failed: %s", file, line, func, exprTxt);
 	__android_log_print(ANDROID_LOG_ERROR, "AnKi", "(%s:%d %s) Assertion failed: %s", file, line, func, exprTxt);
 #else
 #else
-	fprintf(stderr, "(%s:%d %s) Assertion failed: %s\n", file, line, func, exprTxt);
+#if ANKI_OS == ANKI_OS_LINUX
+	if(runningFromATerminal())
+		fprintf(stderr, "\033[1;31m(%s:%d %s) Assertion failed: %s\033[0m\n", file, line, func, exprTxt);
+	else
+#endif
+		fprintf(stderr, "(%s:%d %s) Assertion failed: %s\n", file, line, func, exprTxt);
 #endif
 #endif
 
 
 	class BW : public BackTraceWalker
 	class BW : public BackTraceWalker

+ 12 - 4
src/anki/util/Logger.cpp

@@ -5,6 +5,7 @@
 
 
 #include <anki/util/Logger.h>
 #include <anki/util/Logger.h>
 #include <anki/util/File.h>
 #include <anki/util/File.h>
+#include <anki/util/System.h>
 #include <cstring>
 #include <cstring>
 #include <cstdarg>
 #include <cstdarg>
 #include <cstdio>
 #include <cstdio>
@@ -95,8 +96,7 @@ void Logger::defaultSystemMessageHandler(void*, const Info& info)
 	FILE* out = nullptr;
 	FILE* out = nullptr;
 	const char* terminalColor = nullptr;
 	const char* terminalColor = nullptr;
 	const char* terminalColorBg = nullptr;
 	const char* terminalColorBg = nullptr;
-
-#define ANKI_END_TERMINAL_FMT "\033[0m"
+	const char* endTerminalColor = "\033[0m";
 
 
 	switch(info.m_type)
 	switch(info.m_type)
 	{
 	{
@@ -124,18 +124,26 @@ void Logger::defaultSystemMessageHandler(void*, const Info& info)
 		ANKI_ASSERT(0);
 		ANKI_ASSERT(0);
 	}
 	}
 
 
-	const char* fmt = "%s[%s][%s]" ANKI_END_TERMINAL_FMT "%s %s (%s:%d %s)" ANKI_END_TERMINAL_FMT "\n";
+	const char* fmt = "%s[%s][%s]%s%s %s (%s:%d %s)%s\n";
+	if(runningFromATerminal())
+	{
+		terminalColorBg = "";
+		terminalColorBg = "";
+		endTerminalColor = "";
+	}
 
 
 	fprintf(out,
 	fprintf(out,
 		fmt,
 		fmt,
 		terminalColorBg,
 		terminalColorBg,
 		MSG_TEXT[static_cast<U>(info.m_type)],
 		MSG_TEXT[static_cast<U>(info.m_type)],
 		info.m_subsystem ? info.m_subsystem : "N/A ",
 		info.m_subsystem ? info.m_subsystem : "N/A ",
+		endTerminalColor,
 		terminalColor,
 		terminalColor,
 		info.m_msg,
 		info.m_msg,
 		info.m_file,
 		info.m_file,
 		info.m_line,
 		info.m_line,
-		info.m_func);
+		info.m_func,
+		endTerminalColor);
 #elif ANKI_OS == ANKI_OS_ANDROID
 #elif ANKI_OS == ANKI_OS_ANDROID
 	U32 andMsgType = ANDROID_LOG_INFO;
 	U32 andMsgType = ANDROID_LOG_INFO;
 
 

+ 9 - 0
src/anki/util/System.cpp

@@ -67,4 +67,13 @@ void BackTraceWalker::exec()
 #endif
 #endif
 }
 }
 
 
+Bool runningFromATerminal()
+{
+#if ANKI_POSIX
+	return isatty(fileno(stdin));
+#else
+	return false;
+#endif
+}
+
 } // end namespace anki
 } // end namespace anki

+ 3 - 0
src/anki/util/System.h

@@ -36,6 +36,9 @@ public:
 private:
 private:
 	U m_stackSize;
 	U m_stackSize;
 };
 };
+
+/// Return true if the engine is running from a terminal emulator.
+Bool runningFromATerminal();
 /// @}
 /// @}
 
 
 } // end namespace anki
 } // end namespace anki

+ 9 - 0
tests/gr/Gr.cpp

@@ -1531,4 +1531,13 @@ ANKI_TEST(Gr, 3DTextures)
 	COMMON_END()
 	COMMON_END()
 }
 }
 
 
+ANKI_TEST(Gr, RenderGraph)
+{
+	COMMON_BEGIN()
+
+	// TODO
+
+	COMMON_END()
+}
+
 } // end namespace anki
 } // end namespace anki