Переглянути джерело

Vulkan: Adding occlusion query support and an offscreen test

Panagiotis Christopoulos Charitos 9 роки тому
батько
коміт
07f92ad9cd

+ 0 - 2
include/anki/gr/Framebuffer.h

@@ -21,7 +21,6 @@ class FramebufferAttachmentInfo
 public:
 	TexturePtr m_texture;
 	TextureSurfaceInfo m_surface;
-	PixelFormat m_format;
 	AttachmentLoadOperation m_loadOperation = AttachmentLoadOperation::CLEAR;
 	AttachmentStoreOperation m_storeOperation = AttachmentStoreOperation::STORE;
 	ClearValue m_clearValue;
@@ -39,7 +38,6 @@ public:
 	{
 		m_texture = b.m_texture;
 		m_surface = b.m_surface;
-		m_format = b.m_format;
 		m_loadOperation = b.m_loadOperation;
 		m_storeOperation = b.m_storeOperation;
 		memcpy(&m_clearValue, &b.m_clearValue, sizeof(m_clearValue));

+ 11 - 3
include/anki/gr/vulkan/CommandBufferImpl.h

@@ -7,9 +7,6 @@
 
 #include <anki/gr/vulkan/VulkanObject.h>
 #include <anki/gr/CommandBuffer.h>
-
-#include <anki/gr/vulkan/TextureImpl.h>
-
 #include <anki/util/List.h>
 
 namespace anki
@@ -72,6 +69,16 @@ public:
 		vkCmdDraw(m_handle, count, instanceCount, first, baseInstance);
 	}
 
+	void drawElements(U32 count,
+		U32 instanceCount,
+		U32 firstIndex,
+		U32 baseVertex,
+		U32 baseInstance);
+
+	void beginOcclusionQuery(OcclusionQueryPtr query);
+
+	void endOcclusionQuery(OcclusionQueryPtr query);
+
 	void uploadTextureSurface(TexturePtr tex,
 		const TextureSurfaceInfo& surf,
 		const TransientMemoryToken& token);
@@ -122,6 +129,7 @@ private:
 	List<FramebufferPtr> m_fbList;
 	List<ResourceGroupPtr> m_rcList;
 	List<TexturePtr> m_texList;
+	List<OcclusionQueryPtr> m_queryList;
 /// @}
 
 #if ANKI_ASSERTIONS

+ 40 - 0
include/anki/gr/vulkan/CommandBufferImpl.inl.h

@@ -5,6 +5,9 @@
 
 #include <anki/gr/vulkan/CommandBufferImpl.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
+#include <anki/gr/vulkan/TextureImpl.h>
+#include <anki/gr/OcclusionQuery.h>
+#include <anki/gr/vulkan/OcclusionQueryImpl.h>
 
 namespace anki
 {
@@ -171,4 +174,41 @@ inline void CommandBufferImpl::uploadTextureSurface(TexturePtr tex,
 	m_texList.pushBack(m_alloc, tex);
 }
 
+//==============================================================================
+inline void CommandBufferImpl::drawElements(U32 count,
+	U32 instanceCount,
+	U32 firstIndex,
+	U32 baseVertex,
+	U32 baseInstance)
+{
+	drawcallCommon();
+	vkCmdDrawIndexed(
+		m_handle, count, instanceCount, firstIndex, baseVertex, baseInstance);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::beginOcclusionQuery(OcclusionQueryPtr query)
+{
+	commandCommon();
+	VkQueryPool handle = query->getImplementation().m_handle;
+	ANKI_ASSERT(handle);
+
+	vkCmdResetQueryPool(m_handle, handle, 0, 0);
+	vkCmdBeginQuery(m_handle, handle, 0, 0);
+
+	m_queryList.pushBack(m_alloc, query);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::endOcclusionQuery(OcclusionQueryPtr query)
+{
+	commandCommon();
+	VkQueryPool handle = query->getImplementation().m_handle;
+	ANKI_ASSERT(handle);
+
+	vkCmdEndQuery(m_handle, handle, 0);
+
+	m_queryList.pushBack(m_alloc, query);
+}
+
 } // end namespace anki

+ 4 - 4
include/anki/gr/vulkan/Common.h

@@ -25,8 +25,8 @@ class GrManagerImpl;
 #define ANKI_VK_CHECKF(x)                                                      \
 	do                                                                         \
 	{                                                                          \
-		VkResult rez = x;                                                      \
-		if(VK_SUCCESS != rez)                                                  \
+		VkResult rez;                                                          \
+		if((rez = (x)) < 0)                                                    \
 		{                                                                      \
 			ANKI_LOGF("Vulkan function failed (%d): %s", rez, #x);             \
 		}                                                                      \
@@ -36,8 +36,8 @@ class GrManagerImpl;
 #define ANKI_VK_CHECK(x)                                                       \
 	do                                                                         \
 	{                                                                          \
-		VkResult rez = x;                                                      \
-		if(VK_SUCCESS != rez)                                                  \
+		VkResult rez;                                                          \
+		if((rez = (x)) < 0)                                                    \
 		{                                                                      \
 			ANKI_LOGE("Vulkan function failed (%d): %s", rez, #x);             \
 			return ErrorCode::FUNCTION_FAILED;                                 \

+ 21 - 1
include/anki/gr/vulkan/OcclusionQueryImpl.h

@@ -17,14 +17,34 @@ namespace anki
 class OcclusionQueryImpl : public VulkanObject
 {
 public:
+	VkQueryPool m_handle = VK_NULL_HANDLE;
+
 	OcclusionQueryImpl(GrManager* manager)
 		: VulkanObject(manager)
 	{
 	}
 
-	~OcclusionQueryImpl()
+	~OcclusionQueryImpl();
+
+	/// Create the query.
+	/// @param condRenderingBit If the query is used in conditional rendering
+	///        the result will be checked against this mask. If the result
+	///        contains any of the bits then the dracall will not be skipped.
+	ANKI_USE_RESULT Error init(OcclusionQueryResultBit condRenderingBit);
+
+	/// Get query result.
+	OcclusionQueryResult getResult() const;
+
+	/// Return true if the drawcall should be skipped.
+	Bool skipDrawcall() const
 	{
+		U resultBit = 1 << U(getResult());
+		U condBit = U(m_condRenderingBit);
+		return !(resultBit & condBit);
 	}
+
+private:
+	OcclusionQueryResultBit m_condRenderingBit;
 };
 /// @}
 

+ 4 - 0
src/gr/vulkan/CommandBuffer.cpp

@@ -101,6 +101,8 @@ void CommandBuffer::drawElements(U32 count,
 	U32 baseVertex,
 	U32 baseInstance)
 {
+	m_impl->drawElements(
+		count, instanceCount, firstIndex, baseVertex, baseInstance);
 }
 
 //==============================================================================
@@ -194,11 +196,13 @@ void CommandBuffer::setBufferMemoryBarrier(
 //==============================================================================
 void CommandBuffer::beginOcclusionQuery(OcclusionQueryPtr query)
 {
+	m_impl->beginOcclusionQuery(query);
 }
 
 //==============================================================================
 void CommandBuffer::endOcclusionQuery(OcclusionQueryPtr query)
 {
+	m_impl->endOcclusionQuery(query);
 }
 
 //==============================================================================

+ 1 - 0
src/gr/vulkan/CommandBufferImpl.cpp

@@ -46,6 +46,7 @@ CommandBufferImpl::~CommandBufferImpl()
 	m_fbList.destroy(m_alloc);
 	m_rcList.destroy(m_alloc);
 	m_texList.destroy(m_alloc);
+	m_queryList.destroy(m_alloc);
 }
 
 //==============================================================================

+ 4 - 3
src/gr/vulkan/Fence.cpp

@@ -31,8 +31,9 @@ Fence* FenceFactory::newFence()
 		U count = m_fenceCount;
 		while(count--)
 		{
-			VkResult status =
-				vkGetFenceStatus(m_dev, m_fences[count]->getHandle());
+			VkResult status;
+			ANKI_VK_CHECKF(
+				status = vkGetFenceStatus(m_dev, m_fences[count]->getHandle()));
 			if(status == VK_SUCCESS)
 			{
 				out = m_fences[count];
@@ -51,7 +52,7 @@ Fence* FenceFactory::newFence()
 			}
 			else if(status != VK_NOT_READY)
 			{
-				ANKI_LOGF("vkGetFenceStatus() failed");
+				ANKI_ASSERT(0);
 			}
 		}
 	}

+ 4 - 5
src/gr/vulkan/FramebufferImpl.cpp

@@ -6,6 +6,7 @@
 #include <anki/gr/vulkan/FramebufferImpl.h>
 #include <anki/gr/Framebuffer.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
+#include <anki/gr/vulkan/TextureImpl.h>
 
 namespace anki
 {
@@ -90,7 +91,7 @@ void FramebufferImpl::setupAttachmentDescriptor(
 	desc = {};
 	desc.format = (m_defaultFramebuffer)
 		? getGrManagerImpl().getDefaultSurfaceFormat()
-		: convertFormat(att.m_format);
+		: convertFormat(att.m_texture->getImplementation().m_format);
 	desc.samples = VK_SAMPLE_COUNT_1_BIT;
 	desc.loadOp = convertLoadOp(att.m_loadOperation);
 	desc.storeOp = convertStoreOp(att.m_storeOperation);
@@ -111,8 +112,7 @@ Error FramebufferImpl::initRenderPass(const FramebufferInitInfo& init)
 	Array<VkAttachmentDescription, MAX_COLOR_ATTACHMENTS + 1>
 		attachmentDescriptions;
 	Array<VkAttachmentReference, MAX_COLOR_ATTACHMENTS> references;
-	Bool hasDepthStencil = init.m_depthStencilAttachment.m_format.m_components
-		!= ComponentFormat::NONE;
+	Bool hasDepthStencil = init.m_depthStencilAttachment.m_texture == true;
 
 	for(U i = 0; i < init.m_colorAttachmentCount; ++i)
 	{
@@ -161,8 +161,7 @@ Error FramebufferImpl::initRenderPass(const FramebufferInitInfo& init)
 //==============================================================================
 Error FramebufferImpl::initFramebuffer(const FramebufferInitInfo& init)
 {
-	Bool hasDepthStencil = init.m_depthStencilAttachment.m_format.m_components
-		!= ComponentFormat::NONE;
+	Bool hasDepthStencil = init.m_depthStencilAttachment.m_texture == true;
 
 	VkFramebufferCreateInfo ci = {};
 	ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;

+ 7 - 1
src/gr/vulkan/OcclusionQuery.cpp

@@ -23,6 +23,12 @@ OcclusionQuery::~OcclusionQuery()
 //==============================================================================
 void OcclusionQuery::init(OcclusionQueryResultBit condRenderingBit)
 {
+	m_impl.reset(getAllocator().newInstance<OcclusionQueryImpl>(&getManager()));
+
+	if(m_impl->init(condRenderingBit))
+	{
+		ANKI_LOGF("Cannot recover");
+	}
 }
 
-} // end namespace anki
+} // end namespace anki

+ 65 - 0
src/gr/vulkan/OcclusionQueryImpl.cpp

@@ -4,3 +4,68 @@
 // http://www.anki3d.org/LICENSE
 
 #include <anki/gr/vulkan/OcclusionQueryImpl.h>
+
+namespace anki
+{
+
+//==============================================================================
+OcclusionQueryImpl::~OcclusionQueryImpl()
+{
+	if(m_handle)
+	{
+		vkDestroyQueryPool(getDevice(), m_handle, nullptr);
+	}
+}
+
+//==============================================================================
+Error OcclusionQueryImpl::init(OcclusionQueryResultBit condRenderingBit)
+{
+	m_condRenderingBit = condRenderingBit;
+
+	VkQueryPoolCreateInfo ci = {};
+	ci.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+	ci.queryType = VK_QUERY_TYPE_OCCLUSION;
+	ci.queryCount = 1;
+
+	ANKI_VK_CHECK(vkCreateQueryPool(getDevice(), &ci, nullptr, &m_handle));
+
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
+OcclusionQueryResult OcclusionQueryImpl::getResult() const
+{
+	ANKI_ASSERT(m_handle);
+	U64 out = 0;
+
+	VkResult res;
+	ANKI_VK_CHECKF(
+		res = vkGetQueryPoolResults(getDevice(),
+			m_handle,
+			0,
+			1,
+			sizeof(out),
+			&out,
+			sizeof(out),
+			VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT
+				| VK_QUERY_RESULT_PARTIAL_BIT));
+
+	OcclusionQueryResult qout = OcclusionQueryResult::NOT_AVAILABLE;
+	if(res == VK_SUCCESS)
+	{
+		qout = (out) ? OcclusionQueryResult::VISIBLE
+					 : OcclusionQueryResult::NOT_VISIBLE;
+	}
+	else if(res == VK_NOT_READY)
+	{
+		qout = OcclusionQueryResult::NOT_AVAILABLE;
+	}
+	else
+	{
+		ANKI_ASSERT(0);
+	}
+
+	return qout;
+}
+
+} // end namespace anki

+ 0 - 1
src/renderer/Ir.cpp

@@ -297,7 +297,6 @@ Error Ir::renderReflection(RenderingContext& ctx,
 		fbinit.m_colorAttachments[0].m_texture = m_irradianceCubemapArr;
 		fbinit.m_colorAttachments[0].m_surface.m_layer = cubemapIdx;
 		fbinit.m_colorAttachments[0].m_surface.m_face = i;
-		fbinit.m_colorAttachments[0].m_format = Is::RT_PIXEL_FORMAT;
 		fbinit.m_colorAttachments[0].m_loadOperation =
 			AttachmentLoadOperation::DONT_CARE;
 		FramebufferPtr fb = getGrManager().newInstance<Framebuffer>(fbinit);

+ 8 - 8
src/renderer/LightBin.cpp

@@ -348,7 +348,7 @@ Error LightBin::bin(FrustumComponent& frc,
 	TransientMemoryToken& clustersToken,
 	TransientMemoryToken& lightIndicesToken)
 {
-	ANKI_TRACE_START_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_START_EVENT(RENDERER_LIGHT_BINNING);
 
 	// Prepare the clusterer
 	m_clusterer.prepare(*m_threadPool, frc);
@@ -467,7 +467,7 @@ Error LightBin::bin(FrustumComponent& frc,
 	// Sync
 	ANKI_CHECK(m_threadPool->waitForAllThreadsToFinish());
 
-	ANKI_TRACE_STOP_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_STOP_EVENT(RENDERER_LIGHT_BINNING);
 	return ErrorCode::NONE;
 }
 
@@ -475,7 +475,7 @@ Error LightBin::bin(FrustumComponent& frc,
 void LightBin::binLights(
 	U32 threadId, PtrSize threadsCount, LightBinContext& ctx)
 {
-	ANKI_TRACE_START_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_START_EVENT(RENDERER_LIGHT_BINNING);
 	const FrustumComponent& camfrc = *ctx.m_frc;
 	const MoveComponent& cammove =
 		camfrc.getSceneNode().getComponent<MoveComponent>();
@@ -493,9 +493,9 @@ void LightBin::binLights(
 		ctx.m_tempClusters[i].reset();
 	}
 
-	ANKI_TRACE_STOP_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_STOP_EVENT(RENDERER_LIGHT_BINNING);
 	m_barrier.wait();
-	ANKI_TRACE_START_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_START_EVENT(RENDERER_LIGHT_BINNING);
 
 	//
 	// Iterate lights and probes and bin them
@@ -550,9 +550,9 @@ void LightBin::binLights(
 	//
 	// Last thing, update the real clusters
 	//
-	ANKI_TRACE_STOP_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_STOP_EVENT(RENDERER_LIGHT_BINNING);
 	m_barrier.wait();
-	ANKI_TRACE_START_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_START_EVENT(RENDERER_LIGHT_BINNING);
 
 	// Run per cluster
 	const U CLUSTER_GROUP = 16;
@@ -644,7 +644,7 @@ void LightBin::binLights(
 		} // end for
 	} // end while
 
-	ANKI_TRACE_STOP_EVENT(RENDER_LIGHT_BINNING);
+	ANKI_TRACE_STOP_EVENT(RENDERER_LIGHT_BINNING);
 }
 
 //==============================================================================

+ 369 - 3
tests/gr/Gr.cpp

@@ -8,6 +8,7 @@
 #include <anki/core/NativeWindow.h>
 #include <anki/core/Config.h>
 #include <anki/util/HighRezTimer.h>
+#include <anki/collision/Frustum.h>
 
 namespace anki
 {
@@ -112,6 +113,27 @@ void main()
 	out_uv = POSITIONS[gl_VertexID] / 2.0 + 0.5;
 })";
 
+static const char* VERT_MRT_SRC = R"(
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+layout(location = 0) in vec3 in_pos;
+
+layout(ANKI_UBO_BINDING(0, 0)) uniform u0_
+{
+	mat4 u_mvp;
+};
+
+void main()
+{
+	gl_Position = u_mvp * vec4(in_pos, 1.0);
+#if defined(ANKI_VK)
+	gl_Position.y = -gl_Position.y;
+#endif	
+})";
+
 static const char* FRAG_SRC = R"(layout (location = 0) out vec4 out_color;
 
 void main()
@@ -159,6 +181,37 @@ void main()
 	out_color = vec4((col1 + col1) * 0.5, 1.0);
 })";
 
+static const char* FRAG_MRT_SRC = R"(layout (location = 0) out vec4 out_color0;
+layout (location = 1) out vec4 out_color1;
+
+layout(ANKI_UBO_BINDING(0, 1)) uniform u1_
+{
+	vec4 u_color0;
+	vec4 u_color1;
+};
+
+void main()
+{
+	out_color0 = u_color0;
+	out_color1 = u_color1;
+})";
+
+static const char* FRAG_MRT2_SRC = R"(layout (location = 0) out vec4 out_color;
+
+layout(location = 0) in vec2 in_uv;
+
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_tex0;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_tex1;
+
+void main()
+{
+	float factor = in_uv.x;
+	vec3 col0 = texture(u_tex0, in_uv).rgb;
+	vec3 col1 = texture(u_tex1, in_uv).rgb;
+	
+	out_color = vec4(col1 + col1, 1.0);
+})";
+
 #define COMMON_BEGIN()                                                         \
 	NativeWindow* win = nullptr;                                               \
 	GrManager* gr = nullptr;                                                   \
@@ -226,6 +279,86 @@ static FramebufferPtr createDefaultFb(GrManager& gr)
 	return gr.newInstance<Framebuffer>(fbinit);
 }
 
+//==============================================================================
+static void createCube(GrManager& gr, BufferPtr& verts, BufferPtr& indices)
+{
+	static const Array<F32, 8 * 3> pos = {{1,
+		1,
+		1,
+		-1,
+		1,
+		1,
+		-1,
+		-1,
+		1,
+		1,
+		-2,
+		1,
+		1,
+		1,
+		-1,
+		-1,
+		1,
+		-1,
+		-1,
+		-1,
+		-1,
+		1,
+		-2,
+		-1}};
+
+	static const Array<U16, 6 * 2 * 3> idx = {{0,
+		1,
+		3,
+		3,
+		1,
+		2,
+		1,
+		5,
+		6,
+		1,
+		6,
+		2,
+		7,
+		4,
+		0,
+		7,
+		0,
+		3,
+		6,
+		5,
+		7,
+		7,
+		5,
+		4,
+		0,
+		4,
+		5,
+		0,
+		5,
+		1,
+		3,
+		2,
+		6,
+		3,
+		6,
+		7}};
+
+	verts = gr.newInstance<Buffer>(
+		sizeof(pos), BufferUsageBit::VERTEX, BufferAccessBit::CLIENT_MAP_WRITE);
+
+	void* mapped =
+		verts->map(0, sizeof(pos), BufferAccessBit::CLIENT_MAP_WRITE);
+	memcpy(mapped, &pos[0], sizeof(pos));
+	verts->unmap();
+
+	indices = gr.newInstance<Buffer>(
+		sizeof(idx), BufferUsageBit::INDEX, BufferAccessBit::CLIENT_MAP_WRITE);
+	mapped = indices->map(0, sizeof(idx), BufferAccessBit::CLIENT_MAP_WRITE);
+	memcpy(mapped, &idx[0], sizeof(idx));
+	indices->unmap();
+}
+
 //==============================================================================
 ANKI_TEST(Gr, GrManager)
 {
@@ -388,6 +521,7 @@ ANKI_TEST(Gr, DrawWithUniforms)
 		{
 			HighRezTimer timer;
 			timer.start();
+			gr->beginFrame();
 
 			// Uploaded buffer
 			TransientMemoryInfo transientInfo;
@@ -404,9 +538,6 @@ ANKI_TEST(Gr, DrawWithUniforms)
 			(*rotMat)[2] = sin(angle);
 			(*rotMat)[3] = cos(angle);
 
-			// Start drawing
-			gr->beginFrame();
-
 			CommandBufferInitInfo cinit;
 			cinit.m_flags =
 				CommandBufferFlag::FRAME_FIRST | CommandBufferFlag::FRAME_LAST;
@@ -842,4 +973,239 @@ ANKI_TEST(Gr, DrawWithTexture)
 	COMMON_END();
 }
 
+//==============================================================================
+ANKI_TEST(Gr, DrawOffscreen)
+{
+	COMMON_BEGIN();
+	{
+		//
+		// Create textures
+		//
+		const PixelFormat COL_FORMAT =
+			PixelFormat(ComponentFormat::R8G8B8A8, TransformFormat::UNORM);
+		const PixelFormat DS_FORMAT =
+			PixelFormat(ComponentFormat::D24, TransformFormat::FLOAT);
+		const U TEX_SIZE = 256;
+
+		TextureInitInfo init;
+		init.m_depth = 1;
+		init.m_format = COL_FORMAT;
+		init.m_usage = TextureUsageBit::FRAGMENT_SHADER_SAMPLED
+			| TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE;
+		init.m_height = TEX_SIZE;
+		init.m_width = TEX_SIZE;
+		init.m_mipmapsCount = 1;
+		init.m_depth = 1;
+		init.m_layerCount = 1;
+		init.m_samples = 1;
+		init.m_sampling.m_minMagFilter = SamplingFilter::LINEAR;
+		init.m_sampling.m_mipmapFilter = SamplingFilter::LINEAR;
+		init.m_type = TextureType::_2D;
+
+		TexturePtr col0 = gr->newInstance<Texture>(init);
+		TexturePtr col1 = gr->newInstance<Texture>(init);
+
+		init.m_format = DS_FORMAT;
+		TexturePtr dp = gr->newInstance<Texture>(init);
+
+		//
+		// Create FB
+		//
+		FramebufferInitInfo fbinit;
+		fbinit.m_colorAttachmentCount = 2;
+		fbinit.m_colorAttachments[0].m_texture = col0;
+		fbinit.m_colorAttachments[0].m_clearValue.m_colorf = {
+			0.1, 0.0, 0.0, 0.0};
+		fbinit.m_colorAttachments[1].m_texture = col1;
+		fbinit.m_colorAttachments[1].m_clearValue.m_colorf = {
+			0.0, 0.1, 0.0, 0.0};
+		fbinit.m_depthStencilAttachment.m_texture = dp;
+		fbinit.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth =
+			1.0;
+
+		FramebufferPtr fb = gr->newInstance<Framebuffer>(fbinit);
+
+		//
+		// Create default FB
+		//
+		FramebufferPtr dfb = createDefaultFb(*gr);
+
+		//
+		// Create buffs and rc groups
+		//
+		BufferPtr verts, indices;
+		createCube(*gr, verts, indices);
+
+		ResourceGroupInitInfo rcinit;
+		rcinit.m_uniformBuffers[0].m_uploadedMemory = true;
+		rcinit.m_uniformBuffers[1].m_uploadedMemory = true;
+		rcinit.m_vertexBuffers[0].m_buffer = verts;
+		rcinit.m_indexBuffer.m_buffer = indices;
+		rcinit.m_indexSize = 2;
+
+		ResourceGroupPtr rc0 = gr->newInstance<ResourceGroup>(rcinit);
+
+		rcinit = {};
+		rcinit.m_textures[0].m_texture = col0;
+		rcinit.m_textures[1].m_texture = col1;
+		ResourceGroupPtr rc1 = gr->newInstance<ResourceGroup>(rcinit);
+
+		//
+		// Create pplines
+		//
+		ShaderPtr vert =
+			gr->newInstance<Shader>(ShaderType::VERTEX, VERT_MRT_SRC);
+		ShaderPtr frag =
+			gr->newInstance<Shader>(ShaderType::FRAGMENT, FRAG_MRT_SRC);
+
+		PipelineInitInfo pinit;
+		pinit.m_shaders[ShaderType::VERTEX] = vert;
+		pinit.m_shaders[ShaderType::FRAGMENT] = frag;
+		pinit.m_color.m_drawsToDefaultFramebuffer = false;
+		pinit.m_color.m_attachmentCount = 2;
+		pinit.m_color.m_attachments[0].m_format = COL_FORMAT;
+		pinit.m_color.m_attachments[1].m_format = COL_FORMAT;
+		pinit.m_depthStencil.m_depthWriteEnabled = true;
+		pinit.m_depthStencil.m_format = DS_FORMAT;
+
+		pinit.m_vertex.m_attributeCount = 1;
+		pinit.m_vertex.m_attributes[0].m_format =
+			PixelFormat(ComponentFormat::R32G32B32, TransformFormat::FLOAT);
+
+		pinit.m_vertex.m_bindingCount = 1;
+		pinit.m_vertex.m_bindings[0].m_stride = sizeof(Vec3);
+
+		PipelinePtr ppline = gr->newInstance<Pipeline>(pinit);
+
+		PipelinePtr pplineResolve =
+			createSimplePpline(VERT_QUAD_SRC, FRAG_MRT2_SRC, *gr);
+
+		//
+		// Draw
+		//
+		Mat4 viewMat(Vec4(0.0, 0.0, 5.0, 1.0), Mat3::getIdentity(), 1.0f);
+		viewMat.invert();
+
+		Mat4 projMat;
+		PerspectiveFrustum::calculateProjectionMatrix(
+			toRad(70.0), toRad(70.0), 0.1f, 100.0f, projMat);
+
+		const U ITERATION_COUNT = 200;
+		U iterations = ITERATION_COUNT;
+		F32 ang = 0.0;
+		while(iterations--)
+		{
+			HighRezTimer timer;
+			timer.start();
+			gr->beginFrame();
+
+			TransientMemoryInfo transientInfo;
+
+			CommandBufferInitInfo cinit;
+			cinit.m_flags =
+				CommandBufferFlag::FRAME_FIRST | CommandBufferFlag::FRAME_LAST;
+			CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cinit);
+
+			cmdb->setTextureBarrier(col0,
+				TextureUsageBit::NONE,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureSurfaceInfo(0, 0, 0, 0));
+			cmdb->setTextureBarrier(col1,
+				TextureUsageBit::NONE,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureSurfaceInfo(0, 0, 0, 0));
+			cmdb->setTextureBarrier(dp,
+				TextureUsageBit::NONE,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE,
+				TextureSurfaceInfo(0, 0, 0, 0));
+			cmdb->beginRenderPass(fb);
+
+			// First draw
+			Mat4 modelMat(Vec4(0.0, 0.0, 0.0, 1.0),
+				Mat3(Euler(ang, ang / 2.0f, ang / 3.0f)),
+				1.0f);
+
+			Mat4* mvp = static_cast<Mat4*>(
+				gr->allocateFrameTransientMemory(sizeof(*mvp),
+					BufferUsage::UNIFORM,
+					transientInfo.m_uniformBuffers[0],
+					nullptr));
+			*mvp = projMat * viewMat * modelMat;
+
+			Vec4* color = static_cast<Vec4*>(
+				gr->allocateFrameTransientMemory(sizeof(*color) * 2,
+					BufferUsage::UNIFORM,
+					transientInfo.m_uniformBuffers[1],
+					nullptr));
+			*color++ = Vec4(1.0, 0.0, 0.0, 0.0);
+			*color = Vec4(0.0, 1.0, 0.0, 0.0);
+
+			cmdb->bindPipeline(ppline);
+			cmdb->setViewport(0, 0, TEX_SIZE, TEX_SIZE);
+			cmdb->setPolygonOffset(0.0, 0.0);
+			cmdb->bindResourceGroup(rc0, 0, &transientInfo);
+			cmdb->drawElements(6 * 2 * 3);
+
+			// 2nd draw
+			modelMat = Mat4(Vec4(0.0, 0.0, 0.0, 1.0),
+				Mat3(Euler(ang * 2.0, ang, ang / 3.0f * 2.0)),
+				1.0f);
+
+			static_cast<Mat4*>(gr->allocateFrameTransientMemory(sizeof(*mvp),
+				BufferUsage::UNIFORM,
+				transientInfo.m_uniformBuffers[0],
+				nullptr));
+			*mvp = projMat * viewMat * modelMat;
+
+			color = static_cast<Vec4*>(
+				gr->allocateFrameTransientMemory(sizeof(*color) * 2,
+					BufferUsage::UNIFORM,
+					transientInfo.m_uniformBuffers[1],
+					nullptr));
+			*color++ = Vec4(1.0, 0.0, 1.0, 0.0);
+			*color = Vec4(0.0, 1.0, 1.0, 0.0);
+
+			cmdb->bindResourceGroup(rc0, 0, &transientInfo);
+			cmdb->drawElements(6 * 2 * 3);
+
+			cmdb->endRenderPass();
+
+			cmdb->setTextureBarrier(col0,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+				TextureSurfaceInfo(0, 0, 0, 0));
+			cmdb->setTextureBarrier(col1,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+				TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+				TextureSurfaceInfo(0, 0, 0, 0));
+			cmdb->setTextureBarrier(dp,
+				TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE,
+				TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+				TextureSurfaceInfo(0, 0, 0, 0));
+
+			// Draw quad
+			cmdb->beginRenderPass(dfb);
+			cmdb->bindPipeline(pplineResolve);
+			cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+			cmdb->bindResourceGroup(rc1, 0, nullptr);
+			cmdb->drawArrays(6);
+			cmdb->endRenderPass();
+
+			cmdb->flush();
+
+			// End
+			ang += toRad(10.0f);
+			gr->swapBuffers();
+
+			timer.stop();
+			const F32 TICK = 1.0 / 30.0;
+			if(timer.getElapsedTime() < TICK)
+			{
+				HighRezTimer::sleep(TICK - timer.getElapsedTime());
+			}
+		}
+	}
+	COMMON_END();
+}
+
 } // end namespace anki