Browse Source

Vulkan: The vulkan backend is feature complete. Now need to start testing the renderer

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
0783c03676

+ 26 - 1
include/anki/gr/vulkan/CommandBufferImpl.h

@@ -77,6 +77,12 @@ public:
 		U32 baseVertex,
 		U32 baseInstance);
 
+	void dispatchCompute(U32 groupCountX, U32 groupCountY, U32 groupCountZ)
+	{
+		commandCommon();
+		vkCmdDispatch(m_handle, groupCountX, groupCountY, groupCountZ);
+	}
+
 	void beginOcclusionQuery(OcclusionQueryPtr query);
 
 	void endOcclusionQuery(OcclusionQueryPtr query);
@@ -87,6 +93,15 @@ public:
 
 	void generateMipmaps(TexturePtr tex, U depth, U face, U layer);
 
+	void clearTexture(TexturePtr tex,
+		const TextureSurfaceInfo& surf,
+		const ClearValue& clearValue);
+
+	void uploadBuffer(
+		BufferPtr buff, PtrSize offset, const TransientMemoryToken& token);
+
+	void pushSecondLevelCommandBuffer(CommandBufferPtr cmdb);
+
 	void endRecording();
 
 	void setImageBarrier(VkPipelineStageFlags srcStage,
@@ -122,7 +137,7 @@ private:
 	Bool8 m_empty = true;
 	Thread::Id m_tid = 0;
 
-	U m_rpDrawcallCount = 0; ///< Number of drawcalls in renderpass.
+	U m_rpCommandCount = 0; ///< Number of drawcalls or pushed cmdbs in rp.
 	FramebufferPtr m_activeFb;
 
 	/// @name cleanup_references
@@ -132,6 +147,8 @@ private:
 	List<ResourceGroupPtr> m_rcList;
 	List<TexturePtr> m_texList;
 	List<OcclusionQueryPtr> m_queryList;
+	List<BufferPtr> m_bufferList;
+	List<CommandBufferPtr> m_cmdbList;
 /// @}
 
 #if ANKI_ASSERTIONS
@@ -151,6 +168,14 @@ private:
 	}
 
 	void beginRenderPassInternal();
+
+	void endRecordingInternal();
+
+	Bool secondLevel() const
+	{
+		return (m_flags & CommandBufferFlag::SECOND_LEVEL)
+			!= CommandBufferFlag::NONE;
+	}
 };
 /// @}
 

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

@@ -6,6 +6,8 @@
 #include <anki/gr/vulkan/CommandBufferImpl.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
 #include <anki/gr/vulkan/TextureImpl.h>
+#include <anki/gr/Buffer.h>
+#include <anki/gr/vulkan/BufferImpl.h>
 #include <anki/gr/OcclusionQuery.h>
 #include <anki/gr/vulkan/OcclusionQueryImpl.h>
 
@@ -220,4 +222,81 @@ inline void CommandBufferImpl::endOcclusionQuery(OcclusionQueryPtr query)
 	m_queryList.pushBack(m_alloc, query);
 }
 
+//==============================================================================
+inline void CommandBufferImpl::clearTexture(TexturePtr tex,
+	const TextureSurfaceInfo& surf,
+	const ClearValue& clearValue)
+{
+	commandCommon();
+	const TextureImpl& impl = tex->getImplementation();
+
+	VkClearColorValue vclear;
+	static_assert(sizeof(vclear) == sizeof(clearValue), "See file");
+	memcpy(&vclear, &clearValue, sizeof(clearValue));
+
+	VkImageSubresourceRange range;
+	impl.computeSubResourceRange(surf, range);
+
+	if(impl.m_aspect == VK_IMAGE_ASPECT_COLOR_BIT)
+	{
+		vkCmdClearColorImage(m_handle,
+			impl.m_imageHandle,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			&vclear,
+			1,
+			&range);
+	}
+	else
+	{
+		ANKI_ASSERT(0 && "TODO");
+	}
+}
+
+//==============================================================================
+inline void CommandBufferImpl::uploadBuffer(
+	BufferPtr buff, PtrSize offset, const TransientMemoryToken& token)
+{
+	commandCommon();
+	BufferImpl& impl = buff->getImplementation();
+
+	VkBufferCopy region;
+	region.srcOffset = token.m_offset;
+	region.dstOffset = offset;
+	region.size = token.m_range;
+
+	vkCmdCopyBuffer(m_handle,
+		impl.getHandle(),
+		getGrManagerImpl().getTransientMemoryManager().getBufferHandle(
+			token.m_usage),
+		1,
+		&region);
+
+	m_bufferList.pushBack(m_alloc, buff);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::pushSecondLevelCommandBuffer(
+	CommandBufferPtr cmdb)
+{
+	commandCommon();
+	ANKI_ASSERT(insideRenderPass());
+	ANKI_ASSERT(m_subpassContents == VK_SUBPASS_CONTENTS_MAX_ENUM
+		|| m_subpassContents == VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
+#if ANKI_ASSERTIONS
+	m_subpassContents = VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS;
+#endif
+
+	if(ANKI_UNLIKELY(m_rpCommandCount == 0))
+	{
+		beginRenderPassInternal();
+	}
+
+	cmdb->getImplementation().endRecordingInternal();
+
+	vkCmdExecuteCommands(m_handle, 1, &cmdb->getImplementation().m_handle);
+
+	++m_rpCommandCount;
+	m_cmdbList.pushBack(m_alloc, cmdb);
+}
+
 } // end namespace anki

+ 3 - 3
shaders/Bloom.frag.glsl

@@ -11,14 +11,14 @@
 #endif
 
 // Vars
-layout(TEX_BINDING(0, 0)) uniform lowp sampler2D u_tex; ///< Its the IS RT
+layout(ANKI_TEX_BINDING(0, 0)) uniform lowp sampler2D u_tex; ///< Its the IS RT
 
-layout(UBO_BINDING(0, 0), std140) uniform u0_
+layout(ANKI_UBO_BINDING(0, 0), std140) uniform u0_
 {
 	vec4 u_thresholdScalePad2;
 };
 
-layout(SS_BINDING(0, 0), std140) readonly buffer ss0_
+layout(ANKI_SS_BINDING(0, 0), std140) readonly buffer ss0_
 {
 	vec4 u_averageLuminancePad3;
 };

+ 0 - 13
shaders/Common.glsl

@@ -31,19 +31,6 @@ const uint UBO_MAX_SIZE = 16384;
 //#define textureRt(tex_, texc_) texture(tex_, texc_)
 #define textureRt(tex_, texc_) textureLod(tex_, texc_, 0.0)
 
-// Binding macros
-#if defined(ANKI_GL)
-#define UBO_BINDING(set_, binding_) binding = set_ * 4 + binding_
-#define SS_BINDING(set_, binding_) binding = set_ * 4 + binding_
-#define TEX_BINDING(set_, binding_) binding = set_ * 10 + binding_
-#elif defined(ANKI_VK)
-#define UBO_BINDING(set_, binding_) set = set_, binding = 10 + binding_
-#define SS_BINDING(set_, binding_) set = set_, binding = 14 + binding_
-#define TEX_BINDING(set_, binding_) set = set_, binding = binding_
-#else
-#error Missing define
-#endif
-
 // Common locations
 #define POSITION_LOCATION 0
 #define TEXTURE_COORDINATE_LOCATION 1

+ 5 - 5
shaders/Downsample.frag.glsl

@@ -5,11 +5,11 @@
 
 #include "shaders/Common.glsl"
 
-layout(TEX_BINDING(0, 0)) uniform sampler2D u_rt0;
-layout(TEX_BINDING(0, 1)) uniform sampler2D u_rt1;
-layout(TEX_BINDING(0, 2)) uniform sampler2D u_rt2;
-layout(TEX_BINDING(0, 3)) uniform sampler2D u_depth;
-layout(TEX_BINDING(0, 4)) uniform sampler2D u_isRt;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_rt0;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_rt1;
+layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_rt2;
+layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2D u_depth;
+layout(ANKI_TEX_BINDING(0, 4)) uniform sampler2D u_isRt;
 
 layout(location = 0) in vec2 in_uv;
 

+ 1 - 1
shaders/DownscaleBlur.frag.glsl

@@ -5,7 +5,7 @@
 
 #include "shaders/Common.glsl"
 
-layout(TEX_BINDING(0, 0)) uniform sampler2D u_tex;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_tex;
 
 layout(location = 0) in vec2 in_uv;
 layout(location = 0) out vec3 out_color;

+ 1 - 1
shaders/FsCommonFrag.glsl

@@ -9,7 +9,7 @@
 #include "shaders/Clusterer.glsl"
 
 // Global resources
-layout(TEX_BINDING(1, 0)) uniform sampler2D anki_msDepthRt;
+layout(ANKI_TEX_BINDING(1, 0)) uniform sampler2D anki_msDepthRt;
 #define LIGHT_SET 1
 #define LIGHT_UBO_BINDING 0
 #define LIGHT_SS_BINDING 0

+ 1 - 1
shaders/GaussianBlurGeneric.frag.glsl

@@ -23,7 +23,7 @@
 #error See file
 #endif
 
-layout(TEX_BINDING(0, 0)) uniform sampler2D u_tex; ///< Input FAI
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_tex; ///< Input FAI
 
 layout(location = 0) in vec2 in_uv;
 

+ 2 - 2
shaders/Irradiance.frag.glsl

@@ -10,9 +10,9 @@
 layout(location = 0) in vec2 in_uv;
 layout(location = 0) out vec3 out_color;
 
-layout(TEX_BINDING(0, 0)) uniform samplerCubeArray u_envTex;
+layout(ANKI_TEX_BINDING(0, 0)) uniform samplerCubeArray u_envTex;
 
-layout(UBO_BINDING(0, 0)) uniform u0_
+layout(ANKI_UBO_BINDING(0, 0)) uniform u0_
 {
 	// x: The face index to render to
 	// y: The array index to read from the u_envTex

+ 4 - 4
shaders/Is.frag.glsl

@@ -17,10 +17,10 @@
 #undef LIGHT_TEX_BINDING
 #undef LIGHT_UBO_BINDING
 
-layout(TEX_BINDING(0, 0)) uniform sampler2D u_msRt0;
-layout(TEX_BINDING(0, 1)) uniform sampler2D u_msRt1;
-layout(TEX_BINDING(0, 2)) uniform sampler2D u_msRt2;
-layout(TEX_BINDING(0, 3)) uniform sampler2D u_msDepthRt;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_msRt0;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_msRt1;
+layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_msRt2;
+layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2D u_msDepthRt;
 
 layout(location = 0) in vec2 in_texCoord;
 layout(location = 1) flat in int in_instanceId;

+ 15 - 11
shaders/IsFsCommon.glsl

@@ -48,19 +48,21 @@ struct ReflectionProbe
 	vec4 cubemapIndexPad3;
 };
 
-layout(std140, row_major, UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING)) uniform u0_
+layout(ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING),
+	std140,
+	row_major) uniform u0_
 {
 	LightingUniforms u_lightingUniforms;
 };
 
 #ifdef FRAGMENT_SHADER
 
-layout(std140, UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 1)) uniform u1_
+layout(ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 1), std140) uniform u1_
 {
 	PointLight u_pointLights[UBO_MAX_SIZE / (3 * 4 * 4)];
 };
 
-layout(UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 2),
+layout(ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 2),
 	std140,
 	row_major) uniform u2_
 {
@@ -69,33 +71,35 @@ layout(UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 2),
 
 layout(std140,
 	row_major,
-	UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 3)) uniform u3_
+	ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 3)) uniform u3_
 {
 	ReflectionProbe u_reflectionProbes[UBO_MAX_SIZE / (2 * 4 * 4)];
 };
 
-layout(SS_BINDING(LIGHT_SET, LIGHT_SS_BINDING + 0), std430) readonly buffer s0_
+layout(ANKI_SS_BINDING(LIGHT_SET, LIGHT_SS_BINDING + 0),
+	std430) readonly buffer s0_
 {
 	uint u_clusters[];
 };
 
-layout(std430, SS_BINDING(LIGHT_SET, LIGHT_SS_BINDING + 1)) readonly buffer s1_
+layout(std430,
+	ANKI_SS_BINDING(LIGHT_SET, LIGHT_SS_BINDING + 1)) readonly buffer s1_
 {
 	uint u_lightIndices[];
 };
 
-layout(TEX_BINDING(LIGHT_SET,
+layout(ANKI_TEX_BINDING(LIGHT_SET,
 	LIGHT_TEX_BINDING)) uniform highp sampler2DArrayShadow u_spotMapArr;
-layout(TEX_BINDING(LIGHT_SET,
+layout(ANKI_TEX_BINDING(LIGHT_SET,
 	LIGHT_TEX_BINDING + 1)) uniform highp samplerCubeArrayShadow u_omniMapArr;
 
-layout(TEX_BINDING(LIGHT_SET,
+layout(ANKI_TEX_BINDING(LIGHT_SET,
 	LIGHT_TEX_BINDING + 2)) uniform samplerCubeArray u_reflectionsTex;
 
-layout(TEX_BINDING(
+layout(ANKI_TEX_BINDING(
 	LIGHT_SET, LIGHT_TEX_BINDING + 3)) uniform samplerCubeArray u_irradianceTex;
 
-layout(TEX_BINDING(
+layout(ANKI_TEX_BINDING(
 	LIGHT_SET, LIGHT_TEX_BINDING + 4)) uniform sampler2D u_integrationLut;
 
 //==============================================================================

+ 1 - 1
shaders/LfOcclusion.vert.glsl

@@ -7,7 +7,7 @@
 
 #include "shaders/Common.glsl"
 
-layout(UBO_BINDING(0, 0), std140, row_major) uniform _block
+layout(ANKI_UBO_BINDING(0, 0), std140, row_major) uniform _block
 {
 	mat4 u_mvp;
 };

+ 1 - 1
shaders/LfSpritePass.frag.glsl

@@ -7,7 +7,7 @@
 
 #include "shaders/Common.glsl"
 
-layout(TEX_BINDING(0, 0)) uniform sampler2DArray u_tex;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2DArray u_tex;
 
 layout(location = 0) in vec3 in_uv;
 layout(location = 1) flat in vec4 in_color;

+ 6 - 6
shaders/NearDepthUpscale.frag.glsl

@@ -10,15 +10,15 @@ layout(location = 0) in vec2 in_uv;
 
 layout(location = 0) out vec4 out_color;
 
-layout(TEX_BINDING(0, 0)) uniform sampler2D u_depthFullTex;
-layout(TEX_BINDING(0, 1)) uniform sampler2D u_depthHalfTex;
-layout(TEX_BINDING(0, 2)) uniform sampler2D u_colorTexNearest;
-layout(TEX_BINDING(0, 3)) uniform sampler2D u_colorTexLinear;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_depthFullTex;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_depthHalfTex;
+layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_colorTexNearest;
+layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2D u_colorTexLinear;
 #if SSAO_ENABLED
-layout(TEX_BINDING(0, 4)) uniform sampler2D u_ssaoTex;
+layout(ANKI_TEX_BINDING(0, 4)) uniform sampler2D u_ssaoTex;
 #endif
 
-layout(UBO_BINDING(0, 0)) uniform _u0
+layout(ANKI_UBO_BINDING(0, 0)) uniform _u0
 {
 	vec4 u_linearizePad2;
 };

+ 4 - 4
shaders/Pps.frag.glsl

@@ -7,16 +7,16 @@
 #include "shaders/Tonemapping.glsl"
 #include "shaders/Functions.glsl"
 
-layout(binding = 0) uniform sampler2D u_isRt;
-layout(binding = 1) uniform sampler2D u_ppsBloomLfRt;
-layout(binding = 2) uniform sampler3D u_lut;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_isRt;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_ppsBloomLfRt;
+layout(ANKI_TEX_BINDING(0, 2)) uniform sampler3D u_lut;
 
 struct Luminance
 {
 	vec4 averageLuminancePad3;
 };
 
-layout(std140, SS_BINDING(0, 0)) readonly buffer s0_
+layout(std140, ANKI_SS_BINDING(0, 0)) readonly buffer s0_
 {
 	Luminance u_luminance;
 };

+ 4 - 4
shaders/Ssao.frag.glsl

@@ -24,14 +24,14 @@ layout(location = 0) in vec2 in_texCoords;
 
 layout(location = 0) out float out_color;
 
-layout(UBO_BINDING(0, 0), std140, row_major) uniform _blk
+layout(ANKI_UBO_BINDING(0, 0), std140, row_major) uniform _blk
 {
 	RendererCommonUniforms u_uniforms;
 };
 
-layout(TEX_BINDING(0, 0)) uniform sampler2D u_mMsDepthRt;
-layout(TEX_BINDING(0, 1)) uniform sampler2D u_msRt;
-layout(TEX_BINDING(0, 2)) uniform sampler2D u_noiseMap;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_mMsDepthRt;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_msRt;
+layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_noiseMap;
 
 // Get normal
 vec3 readNormal(in vec2 uv)

+ 1 - 1
shaders/VariableSamplingBlurGeneric.frag.glsl

@@ -29,7 +29,7 @@
 #endif
 
 // Input RT with different binding for less GL API calls
-layout(TEX_BINDING(0, 0)) uniform sampler2D uTex;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D uTex;
 
 layout(location = 0) in vec2 in_texCoord;
 

+ 1 - 1
shaders/Volumetric.frag.glsl

@@ -10,7 +10,7 @@ layout(location = 0) in vec2 in_uv;
 
 layout(binding = 0) uniform sampler2D u_msDepthRt;
 
-layout(std140, UBO_BINDING(0, 0)) uniform ubo0_
+layout(std140, ANKI_UBO_BINDING(0, 0)) uniform ubo0_
 {
 	vec4 u_linearizePad2;
 	vec4 u_fogColorFogFactor;

+ 4 - 0
src/gr/gl/ShaderImpl.cpp

@@ -42,6 +42,10 @@ static const char* SHADER_HEADER = R"(#version %u %s
 #define ANKI_USING_FRAG_COORD(height_) vec4 anki_fragCoord = gl_FragCoord;
 #endif
 
+#if defined(VERTEX_SHADER)
+#define ANKI_WRITE_POSITION(x_) gl_Position = x_
+#endif
+
 %s)";
 
 //==============================================================================

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

@@ -121,6 +121,7 @@ void CommandBuffer::drawElementsConditional(OcclusionQueryPtr query,
 	U32 baseVertex,
 	U32 baseInstance)
 {
+	ANKI_ASSERT(0);
 }
 
 //==============================================================================
@@ -130,12 +131,14 @@ void CommandBuffer::drawArraysConditional(OcclusionQueryPtr query,
 	U32 first,
 	U32 baseInstance)
 {
+	ANKI_ASSERT(0);
 }
 
 //==============================================================================
 void CommandBuffer::dispatchCompute(
 	U32 groupCountX, U32 groupCountY, U32 groupCountZ)
 {
+	m_impl->dispatchCompute(groupCountX, groupCountY, groupCountZ);
 }
 
 //==============================================================================
@@ -150,6 +153,7 @@ void CommandBuffer::copyTextureToTexture(TexturePtr src,
 	TexturePtr dest,
 	const TextureSurfaceInfo& destSurf)
 {
+	ANKI_ASSERT(0);
 }
 
 //==============================================================================
@@ -157,6 +161,7 @@ void CommandBuffer::clearTexture(TexturePtr tex,
 	const TextureSurfaceInfo& surf,
 	const ClearValue& clearValue)
 {
+	m_impl->clearTexture(tex, surf, clearValue);
 }
 
 //==============================================================================
@@ -171,6 +176,7 @@ void CommandBuffer::uploadTextureSurface(TexturePtr tex,
 void CommandBuffer::uploadBuffer(
 	BufferPtr buff, PtrSize offset, const TransientMemoryToken& token)
 {
+	m_impl->uploadBuffer(buff, offset, token);
 }
 
 //==============================================================================
@@ -203,6 +209,7 @@ void CommandBuffer::endOcclusionQuery(OcclusionQueryPtr query)
 //==============================================================================
 void CommandBuffer::pushSecondLevelCommandBuffer(CommandBufferPtr cmdb)
 {
+	m_impl->pushSecondLevelCommandBuffer(cmdb);
 }
 
 //==============================================================================

+ 37 - 14
src/gr/vulkan/CommandBufferImpl.cpp

@@ -47,6 +47,8 @@ CommandBufferImpl::~CommandBufferImpl()
 	m_rcList.destroy(m_alloc);
 	m_texList.destroy(m_alloc);
 	m_queryList.destroy(m_alloc);
+	m_bufferList.destroy(m_alloc);
+	m_cmdbList.destroy(m_alloc);
 }
 
 //==============================================================================
@@ -72,16 +74,31 @@ Error CommandBufferImpl::init(const CommandBufferInitInfo& init)
 	VkCommandBufferInheritanceInfo inheritance = {};
 	inheritance.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
 
-	if(secondLevel)
-	{
-		ANKI_ASSERT(0 && "TODO");
-	}
-
 	VkCommandBufferBeginInfo begin = {};
 	begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
 	begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
 	begin.pInheritanceInfo = &inheritance;
 
+	if(secondLevel)
+	{
+		const FramebufferImpl& impl = init.m_framebuffer->getImplementation();
+
+		inheritance.renderPass = impl.getRenderPassHandle();
+		inheritance.subpass = 0;
+
+		if(!impl.isDefaultFramebuffer())
+		{
+			inheritance.framebuffer = impl.getFramebufferHandle(0);
+		}
+		else
+		{
+			inheritance.framebuffer = impl.getFramebufferHandle(
+				getGrManagerImpl().getFrame() % MAX_FRAMES_IN_FLIGHT);
+		}
+
+		begin.flags |= VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
+	}
+
 	vkBeginCommandBuffer(m_handle, &begin);
 
 	// If it's the frame's first command buffer then do the default fb image
@@ -133,7 +150,7 @@ void CommandBufferImpl::beginRenderPass(FramebufferPtr fb)
 	commandCommon();
 	ANKI_ASSERT(!insideRenderPass());
 
-	m_rpDrawcallCount = 0;
+	m_rpCommandCount = 0;
 	m_activeFb = fb;
 
 	m_fbList.pushBack(m_alloc, fb);
@@ -148,7 +165,7 @@ void CommandBufferImpl::endRenderPass()
 {
 	commandCommon();
 	ANKI_ASSERT(insideRenderPass());
-	ANKI_ASSERT(m_rpDrawcallCount > 0);
+	ANKI_ASSERT(m_rpCommandCount > 0);
 
 	vkCmdEndRenderPass(m_handle);
 	m_activeFb.reset(nullptr);
@@ -187,7 +204,7 @@ void CommandBufferImpl::beginRenderPassInternal()
 			getGrManagerImpl().getDefaultSurfaceHeight();
 	}
 
-	vkCmdBeginRenderPass(m_handle, &bi, VK_SUBPASS_CONTENTS_INLINE);
+	vkCmdBeginRenderPass(m_handle, &bi, m_subpassContents);
 }
 
 //==============================================================================
@@ -195,25 +212,24 @@ void CommandBufferImpl::drawcallCommon()
 {
 	// Preconditions
 	commandCommon();
-	ANKI_ASSERT(insideRenderPass());
+	ANKI_ASSERT(insideRenderPass() || secondLevel());
 	ANKI_ASSERT(m_subpassContents == VK_SUBPASS_CONTENTS_MAX_ENUM
 		|| m_subpassContents == VK_SUBPASS_CONTENTS_INLINE);
 #if ANKI_ASSERTIONS
 	m_subpassContents = VK_SUBPASS_CONTENTS_INLINE;
 #endif
 
-	if(ANKI_UNLIKELY(m_rpDrawcallCount == 0))
+	if(ANKI_UNLIKELY(m_rpCommandCount == 0) && !secondLevel())
 	{
 		beginRenderPassInternal();
 	}
 
-	++m_rpDrawcallCount;
+	++m_rpCommandCount;
 }
 
 //==============================================================================
-void CommandBufferImpl::endRecording()
+void CommandBufferImpl::endRecordingInternal()
 {
-	commandCommon();
 	ANKI_ASSERT(!m_finalized);
 	ANKI_ASSERT(!m_empty);
 
@@ -236,6 +252,13 @@ void CommandBufferImpl::endRecording()
 	m_finalized = true;
 }
 
+//==============================================================================
+void CommandBufferImpl::endRecording()
+{
+	commandCommon();
+	endRecordingInternal();
+}
+
 //==============================================================================
 void CommandBufferImpl::bindResourceGroup(
 	ResourceGroupPtr rc, U slot, const TransientMemoryInfo* dynInfo)
@@ -296,7 +319,7 @@ void CommandBufferImpl::generateMipmaps(
 	const TextureImpl& impl = tex->getImplementation();
 	ANKI_ASSERT(impl.m_type != TextureType::_3D && "Not design for that ATM");
 
-	U mipCount = computeMaxMipmapCount(impl.m_width, impl.m_height);
+	U mipCount = computeMaxMipmapCount2d(impl.m_width, impl.m_height);
 
 	for(U i = 0; i < mipCount - 1; ++i)
 	{

+ 20 - 0
src/gr/vulkan/Common.cpp

@@ -94,6 +94,12 @@ void computeBarrierInfo(TextureUsageBit before,
 		srcAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
 	}
 
+	if((before & TextureUsageBit::CLEAR) != TextureUsageBit::NONE)
+	{
+		srcStages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+		srcAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
+	}
+
 	if(srcStages == 0)
 	{
 		srcStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
@@ -168,6 +174,12 @@ void computeBarrierInfo(TextureUsageBit before,
 		dstAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
 	}
 
+	if((after & TextureUsageBit::CLEAR) != TextureUsageBit::NONE)
+	{
+		dstStages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+		dstAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
+	}
+
 	ANKI_ASSERT(dstStages);
 }
 
@@ -212,6 +224,10 @@ VkImageLayout computeLayout(
 				out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
 			}
 		}
+		else if(usage == TextureUsageBit::CLEAR)
+		{
+			out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+		}
 	}
 	else
 	{
@@ -240,6 +256,10 @@ VkImageLayout computeLayout(
 		{
 			out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
 		}
+		else if(usage == TextureUsageBit::CLEAR)
+		{
+			out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+		}
 	}
 
 	ANKI_ASSERT(out != VK_IMAGE_LAYOUT_MAX_ENUM);

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

@@ -162,6 +162,10 @@ static const char* SHADER_HEADER = R"(#version 450 core
 	vec4(gl_FragCoord.x, h_ - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
 #endif
 
+#if defined(VERTEX_SHADER)
+#define ANKI_WRITE_POSITION(x_) gl_Position = x_; gl_Position.y = -gl_Position.y
+#endif
+
 %s)";
 
 //==============================================================================

+ 2 - 2
src/resource/MaterialLoader.cpp

@@ -444,7 +444,7 @@ Error MaterialLoader::parseProgramTag(const XmlElement& programEl)
 	if((m_uniformBlockReferencedMask & glshaderbit) != ShaderTypeBit::NONE)
 	{
 		lines.pushBackSprintf(m_alloc,
-			"\nlayout(UBO_BINDING(0, 0), std140, row_major) "
+			"\nlayout(ANKI_UBO_BINDING(0, 0), std140, row_major) "
 			"uniform u00_\n{");
 
 		for(Input& in : m_inputs)
@@ -707,7 +707,7 @@ void MaterialLoader::processInputs()
 					in.m_binding = m_texBinding++;
 
 					in.m_line.sprintf(m_alloc,
-						"layout(TEX_BINDING(0, %u)) uniform %s tex%u;",
+						"layout(ANKI_TEX_BINDING(0, %u)) uniform %s tex%u;",
 						in.m_binding,
 						&in.typeStr()[0],
 						in.m_binding);

+ 243 - 215
tests/gr/Gr.cpp

@@ -27,10 +27,7 @@ void main()
 	const vec2 POSITIONS[3] =
 		vec2[](vec2(-1.0, 1.0), vec2(0.0, -1.0), vec2(1.0, 1.0));
 
-	gl_Position = vec4(POSITIONS[gl_VertexID % 3], 0.0, 1.0);
-#if defined(ANKI_VK)
-	gl_Position.y = -gl_Position.y;
-#endif
+	ANKI_WRITE_POSITION(vec4(POSITIONS[gl_VertexID % 3], 0.0, 1.0));
 })";
 
 static const char* VERT_UBO_SRC = R"(
@@ -128,10 +125,7 @@ layout(ANKI_UBO_BINDING(0, 0), std140, row_major) uniform u0_
 
 void main()
 {
-	gl_Position = u_mvp * vec4(in_pos, 1.0);
-#if defined(ANKI_VK)
-	gl_Position.y = -gl_Position.y;
-#endif	
+	ANKI_WRITE_POSITION(u_mvp * vec4(in_pos, 1.0));
 })";
 
 static const char* FRAG_SRC = R"(layout (location = 0) out vec4 out_color;
@@ -244,6 +238,9 @@ void main()
 	delete gr;                                                                 \
 	delete win
 
+const PixelFormat DS_FORMAT =
+	PixelFormat(ComponentFormat::D24, TransformFormat::UNORM);
+
 //==============================================================================
 static NativeWindow* createWindow()
 {
@@ -996,238 +993,269 @@ ANKI_TEST(Gr, DrawWithTexture)
 }
 
 //==============================================================================
-ANKI_TEST(Gr, DrawOffscreen)
+static void drawOffscreenDrawcalls(GrManager& gr,
+	PipelinePtr ppline,
+	ResourceGroupPtr rc0,
+	CommandBufferPtr cmdb,
+	U viewPortSize)
 {
-	COMMON_BEGIN();
-	{
-		//
-		// Create textures
-		//
-		const PixelFormat COL_FORMAT =
-			PixelFormat(ComponentFormat::R8G8B8A8, TransformFormat::UNORM);
-		const PixelFormat DS_FORMAT =
-			PixelFormat(ComponentFormat::D24, TransformFormat::UNORM);
-		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;
+	static F32 ang = -2.5f;
+	ang += toRad(2.5f);
 
-		TexturePtr col0 = gr->newInstance<Texture>(init);
-		TexturePtr col1 = gr->newInstance<Texture>(init);
+	Mat4 viewMat(Vec4(0.0, 0.0, 5.0, 1.0), Mat3::getIdentity(), 1.0f);
+	viewMat.invert();
 
-		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);
+	Mat4 projMat;
+	PerspectiveFrustum::calculateProjectionMatrix(
+		toRad(60.0), toRad(60.0), 0.1f, 100.0f, projMat);
 
-		//
-		// Create default FB
-		//
-		FramebufferPtr dfb = createDefaultFb(*gr);
+	TransientMemoryInfo transientInfo;
 
-		//
-		// Create buffs and rc groups
-		//
-		BufferPtr verts, indices;
-		createCube(*gr, verts, indices);
+	Mat4 modelMat(Vec4(0.0, 0.0, 0.0, 1.0),
+		Mat3(Euler(ang, ang / 2.0f, ang / 3.0f)),
+		1.0f);
 
-		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;
+	Mat4* mvp = static_cast<Mat4*>(gr.allocateFrameTransientMemory(sizeof(*mvp),
+		BufferUsageBit::UNIFORM_ANY_SHADER,
+		transientInfo.m_uniformBuffers[0],
+		nullptr));
+	*mvp = projMat * viewMat * modelMat;
 
-		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);
+	Vec4* color =
+		static_cast<Vec4*>(gr.allocateFrameTransientMemory(sizeof(*color) * 2,
+			BufferUsageBit::UNIFORM_ANY_SHADER,
+			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, viewPortSize, viewPortSize);
+	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);
+
+	mvp = static_cast<Mat4*>(gr.allocateFrameTransientMemory(sizeof(*mvp),
+		BufferUsageBit::UNIFORM_ANY_SHADER,
+		transientInfo.m_uniformBuffers[0],
+		nullptr));
+	*mvp = projMat * viewMat * modelMat;
+
+	color =
+		static_cast<Vec4*>(gr.allocateFrameTransientMemory(sizeof(*color) * 2,
+			BufferUsageBit::UNIFORM_ANY_SHADER,
+			transientInfo.m_uniformBuffers[1],
+			nullptr));
+	*color++ = Vec4(0.0, 0.0, 1.0, 0.0);
+	*color = Vec4(0.0, 1.0, 1.0, 0.0);
 
-		//
-		// 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);
+	cmdb->bindResourceGroup(rc0, 0, &transientInfo);
+	cmdb->drawElements(6 * 2 * 3);
+}
 
-		pinit.m_vertex.m_bindingCount = 1;
-		pinit.m_vertex.m_bindings[0].m_stride = sizeof(Vec3);
+//==============================================================================
+static void drawOffscreen(GrManager& gr, Bool useSecondLevel)
+{
+	//
+	// Create textures
+	//
+	const PixelFormat COL_FORMAT =
+		PixelFormat(ComponentFormat::R8G8B8A8, TransformFormat::UNORM);
+	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
+	//
+	const U ITERATION_COUNT = 200;
+	U iterations = ITERATION_COUNT;
+	while(iterations--)
+	{
+		HighRezTimer timer;
+		timer.start();
+		gr.beginFrame();
 
-		PipelinePtr ppline = gr->newInstance<Pipeline>(pinit);
+		CommandBufferInitInfo cinit;
+		cinit.m_flags =
+			CommandBufferFlag::FRAME_FIRST | CommandBufferFlag::FRAME_LAST;
+		CommandBufferPtr cmdb = gr.newInstance<CommandBuffer>(cinit);
 
-		PipelinePtr pplineResolve =
-			createSimplePpline(VERT_QUAD_SRC, FRAG_MRT2_SRC, *gr);
+		cmdb->setPolygonOffset(0.0, 0.0);
 
-		//
-		// Draw
-		//
-		Mat4 viewMat(Vec4(0.0, 0.0, 5.0, 1.0), Mat3::getIdentity(), 1.0f);
-		viewMat.invert();
-
-		Mat4 projMat;
-		PerspectiveFrustum::calculateProjectionMatrix(
-			toRad(60.0), toRad(60.0), 0.1f, 100.0f, projMat);
+		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);
 
-		const U ITERATION_COUNT = 200;
-		U iterations = ITERATION_COUNT;
-		F32 ang = 0.0;
-		while(iterations--)
+		if(!useSecondLevel)
+		{
+			drawOffscreenDrawcalls(gr, ppline, rc0, cmdb, TEX_SIZE);
+		}
+		else
 		{
-			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->setPolygonOffset(0.0, 0.0);
-
-			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);
+			cinit.m_flags = CommandBufferFlag::SECOND_LEVEL;
+			cinit.m_framebuffer = fb;
+			CommandBufferPtr cmdb2 = gr.newInstance<CommandBuffer>(cinit);
 
-			Mat4* mvp = static_cast<Mat4*>(
-				gr->allocateFrameTransientMemory(sizeof(*mvp),
-					BufferUsageBit::UNIFORM_ANY_SHADER,
-					transientInfo.m_uniformBuffers[0],
-					nullptr));
-			*mvp = projMat * viewMat * modelMat;
-
-			Vec4* color = static_cast<Vec4*>(
-				gr->allocateFrameTransientMemory(sizeof(*color) * 2,
-					BufferUsageBit::UNIFORM_ANY_SHADER,
-					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);
+			drawOffscreenDrawcalls(gr, ppline, rc0, cmdb2, TEX_SIZE);
 
-			cmdb->bindPipeline(ppline);
-			cmdb->setViewport(0, 0, TEX_SIZE, TEX_SIZE);
-			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);
-
-			mvp = static_cast<Mat4*>(
-				gr->allocateFrameTransientMemory(sizeof(*mvp),
-					BufferUsageBit::UNIFORM_ANY_SHADER,
-					transientInfo.m_uniformBuffers[0],
-					nullptr));
-			*mvp = projMat * viewMat * modelMat;
+			cmdb->pushSecondLevelCommandBuffer(cmdb2);
+		}
 
-			color = static_cast<Vec4*>(
-				gr->allocateFrameTransientMemory(sizeof(*color) * 2,
-					BufferUsageBit::UNIFORM_ANY_SHADER,
-					transientInfo.m_uniformBuffers[1],
-					nullptr));
-			*color++ = Vec4(0.0, 0.0, 1.0, 0.0);
-			*color = Vec4(0.0, 1.0, 1.0, 0.0);
+		cmdb->endRenderPass();
 
-			cmdb->bindResourceGroup(rc0, 0, &transientInfo);
-			cmdb->drawElements(6 * 2 * 3);
+		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));
 
-			cmdb->endRenderPass();
+		// 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->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));
+		cmdb->flush();
 
-			// Draw quad
-			cmdb->beginRenderPass(dfb);
-			cmdb->bindPipeline(pplineResolve);
-			cmdb->setViewport(0, 0, WIDTH, HEIGHT);
-			cmdb->bindResourceGroup(rc1, 0, nullptr);
-			cmdb->drawArrays(6);
-			cmdb->endRenderPass();
+		// End
+		gr.swapBuffers();
 
-			cmdb->flush();
+		timer.stop();
+		const F32 TICK = 1.0 / 30.0;
+		if(timer.getElapsedTime() < TICK)
+		{
+			HighRezTimer::sleep(TICK - timer.getElapsedTime());
+		}
+	}
+}
 
-			// End
-			ang += toRad(2.5f);
-			gr->swapBuffers();
+//==============================================================================
+ANKI_TEST(Gr, DrawOffscreen)
+{
+	COMMON_BEGIN();
+	{
+		drawOffscreen(*gr, false);
+	}
+	COMMON_END();
+}
 
-			timer.stop();
-			const F32 TICK = 1.0 / 30.0;
-			if(timer.getElapsedTime() < TICK)
-			{
-				HighRezTimer::sleep(TICK - timer.getElapsedTime());
-			}
-		}
+//==============================================================================
+ANKI_TEST(Gr, DrawWithSecondLevel)
+{
+	COMMON_BEGIN();
+	{
+		drawOffscreen(*gr, true);
 	}
 	COMMON_END();
 }