Browse Source

Vulkan: Texture upload support

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
0388d77ab3

+ 4 - 0
include/anki/gr/CommandBuffer.h

@@ -221,6 +221,10 @@ public:
 
 	/// @name Sync
 	/// @{
+	void setTextureBarrier(TexturePtr tex,
+		TextureUsageBit prevUsage,
+		TextureUsageBit nextUsage,
+		const TextureSurfaceInfo& surf);
 
 	void setPipelineBarrier(PipelineStageBit src, PipelineStageBit dst);
 

+ 18 - 14
include/anki/gr/Enums.h

@@ -164,34 +164,38 @@ enum class TextureUsageBit : U16
 
 	/// @name Sampled
 	/// @{
-	FRAGMENT_SHADER_SAMPLED = 1 << 0,
-	VERTEX_OR_TESSELLATION_SHADER_SAMPLED = 1 << 1,
-	COMPUTE_SHADER_SAMPLED = 1 << 2,
-	ANY_SHADER_SAMPLED = FRAGMENT_SHADER_SAMPLED
-		| VERTEX_OR_TESSELLATION_SHADER_SAMPLED
+	VERTEX_SHADER_SAMPLED = 1 << 0,
+	TESSELLATION_CONTROL_SHADER_SAMPLED = 1 << 1,
+	TESSELLATION_EVALUATION_SHADER_SAMPLED = 1 << 2,
+	GEOMETRY_SHADER_SAMPLED = 1 << 3,
+	FRAGMENT_SHADER_SAMPLED = 1 << 4,
+	COMPUTE_SHADER_SAMPLED = 1 << 5,
+	ANY_SHADER_SAMPLED = VERTEX_SHADER_SAMPLED
+		| TESSELLATION_CONTROL_SHADER_SAMPLED
+		| TESSELLATION_EVALUATION_SHADER_SAMPLED
+		| GEOMETRY_SHADER_SAMPLED
+		| FRAGMENT_SHADER_SAMPLED
 		| COMPUTE_SHADER_SAMPLED,
 	/// @}
 
 	/// @name Image_load_store
 	/// @{
-	COMPUTE_SHADER_IMAGE_READ = 1 << 3,
-	COMPUTE_SHADER_IMAGE_WRITE = 1 << 4,
-	COMPUTE_SHADER_IMAGE_READ_WRITE =
-		COMPUTE_SHADER_IMAGE_READ | COMPUTE_SHADER_IMAGE_WRITE,
+	COMPUTE_SHADER_IMAGE_READ = 1 << 6,
+	COMPUTE_SHADER_IMAGE_WRITE = 1 << 7,
 	/// @}
 
 	/// @name Attachment
 	/// @{
-	FRAMEBUFFER_ATTACHMENT_READ = 1 << 5,
-	FRAMEBUFFER_ATTACHMENT_WRITE = 1 << 6,
+	FRAMEBUFFER_ATTACHMENT_READ = 1 << 8,
+	FRAMEBUFFER_ATTACHMENT_WRITE = 1 << 9,
 	FRAMEBUFFER_ATTACHMENT_READ_WRITE =
 		FRAMEBUFFER_ATTACHMENT_READ | FRAMEBUFFER_ATTACHMENT_WRITE,
 	/// @}
 
-	/// @name Transfer
+	/// @name Misc
 	/// @{
-	TRANSFER_SOURCE = 1 << 7,
-	TRANSFER_DESTINATION = 1 << 8
+	GENERATE_MIPMAPS = 1 << 10,
+	UPLOAD = 1 << 11,
 	/// @}
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(TextureUsageBit, inline)

+ 1 - 0
include/anki/gr/Texture.h

@@ -41,6 +41,7 @@ class TextureInitInfo
 public:
 	TextureType m_type = TextureType::_2D;
 	TextureUsageBit m_usage = TextureUsageBit::NONE;
+	TextureUsageBit m_initialUsage = TextureUsageBit::NONE;
 	U32 m_width = 0;
 	U32 m_height = 0;
 	U32 m_depth = 0; //< Relevant only for 3D textures.

+ 10 - 50
include/anki/gr/vulkan/CommandBufferImpl.h

@@ -55,24 +55,7 @@ public:
 			== CommandBufferFlag::FRAME_LAST;
 	}
 
-	void setViewport(U16 minx, U16 miny, U16 maxx, U16 maxy)
-	{
-		commandCommon();
-		ANKI_ASSERT(minx < maxx && miny < maxy);
-		VkViewport s;
-		s.x = minx;
-		s.y = miny;
-		s.width = maxx - minx;
-		s.height = maxy - miny;
-		vkCmdSetViewport(m_handle, 0, 1, &s);
-
-		VkRect2D scissor = {};
-		scissor.extent.width = maxx - minx;
-		scissor.extent.height = maxy - miny;
-		scissor.offset.x = minx;
-		scissor.offset.y = miny;
-		vkCmdSetScissor(m_handle, 0, 1, &scissor);
-	}
+	void setViewport(U16 minx, U16 miny, U16 maxx, U16 maxy);
 
 	void bindPipeline(PipelinePtr ppline);
 
@@ -102,25 +85,7 @@ public:
 		VkAccessFlags dstAccess,
 		VkImageLayout newLayout,
 		VkImage img,
-		const VkImageSubresourceRange& range)
-	{
-		ANKI_ASSERT(img);
-		commandCommon();
-
-		VkImageMemoryBarrier inf = {};
-		inf.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-		inf.srcAccessMask = srcAccess;
-		inf.dstAccessMask = dstAccess;
-		inf.oldLayout = prevLayout;
-		inf.newLayout = newLayout;
-		inf.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-		inf.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-		inf.image = img;
-		inf.subresourceRange = range;
-
-		vkCmdPipelineBarrier(
-			m_handle, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &inf);
-	}
+		const VkImageSubresourceRange& range);
 
 	void setImageBarrier(VkPipelineStageFlags srcStage,
 		VkAccessFlags srcAccess,
@@ -129,19 +94,12 @@ public:
 		VkAccessFlags dstAccess,
 		VkImageLayout newLayout,
 		TexturePtr tex,
-		const VkImageSubresourceRange& range)
-	{
-		setImageBarrier(srcStage,
-			srcAccess,
-			prevLayout,
-			dstStage,
-			dstAccess,
-			newLayout,
-			tex->getImplementation().m_imageHandle,
-			range);
-
-		m_texList.pushBack(m_alloc, tex);
-	}
+		const VkImageSubresourceRange& range);
+
+	void setImageBarrier(TexturePtr tex,
+		TextureUsageBit prevUsage,
+		TextureUsageBit nextUsage,
+		const TextureSurfaceInfo& surf);
 
 private:
 	StackAllocator<U8> m_alloc;
@@ -183,3 +141,5 @@ private:
 /// @}
 
 } // end namespace anki
+
+#include <anki/gr/vulkan/CommandBufferImpl.inl.h>

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

@@ -0,0 +1,162 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/gr/vulkan/CommandBufferImpl.h>
+#include <anki/gr/vulkan/GrManagerImpl.h>
+
+namespace anki
+{
+
+//==============================================================================
+inline void CommandBufferImpl::setViewport(
+	U16 minx, U16 miny, U16 maxx, U16 maxy)
+{
+	commandCommon();
+	ANKI_ASSERT(minx < maxx && miny < maxy);
+	VkViewport s;
+	s.x = minx;
+	s.y = miny;
+	s.width = maxx - minx;
+	s.height = maxy - miny;
+	vkCmdSetViewport(m_handle, 0, 1, &s);
+
+	VkRect2D scissor = {};
+	scissor.extent.width = maxx - minx;
+	scissor.extent.height = maxy - miny;
+	scissor.offset.x = minx;
+	scissor.offset.y = miny;
+	vkCmdSetScissor(m_handle, 0, 1, &scissor);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::setImageBarrier(VkPipelineStageFlags srcStage,
+	VkAccessFlags srcAccess,
+	VkImageLayout prevLayout,
+	VkPipelineStageFlags dstStage,
+	VkAccessFlags dstAccess,
+	VkImageLayout newLayout,
+	VkImage img,
+	const VkImageSubresourceRange& range)
+{
+	ANKI_ASSERT(img);
+	commandCommon();
+
+	VkImageMemoryBarrier inf = {};
+	inf.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+	inf.srcAccessMask = srcAccess;
+	inf.dstAccessMask = dstAccess;
+	inf.oldLayout = prevLayout;
+	inf.newLayout = newLayout;
+	inf.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	inf.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	inf.image = img;
+	inf.subresourceRange = range;
+
+	vkCmdPipelineBarrier(
+		m_handle, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &inf);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::setImageBarrier(VkPipelineStageFlags srcStage,
+	VkAccessFlags srcAccess,
+	VkImageLayout prevLayout,
+	VkPipelineStageFlags dstStage,
+	VkAccessFlags dstAccess,
+	VkImageLayout newLayout,
+	TexturePtr tex,
+	const VkImageSubresourceRange& range)
+{
+	setImageBarrier(srcStage,
+		srcAccess,
+		prevLayout,
+		dstStage,
+		dstAccess,
+		newLayout,
+		tex->getImplementation().m_imageHandle,
+		range);
+
+	m_texList.pushBack(m_alloc, tex);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::setImageBarrier(TexturePtr tex,
+	TextureUsageBit prevUsage,
+	TextureUsageBit nextUsage,
+	const TextureSurfaceInfo& surf)
+{
+	const TextureImpl& impl = tex->getImplementation();
+	tex->getImplementation().checkSurface(surf);
+	Bool isDepthStencil = formatIsDepthStencil(impl.m_format);
+
+	VkPipelineStageFlags srcStage;
+	VkAccessFlags srcAccess;
+	VkImageLayout oldLayout;
+	VkPipelineStageFlags dstStage;
+	VkAccessFlags dstAccess;
+	VkImageLayout newLayout;
+	computeBarrierInfo(prevUsage,
+		nextUsage,
+		isDepthStencil,
+		surf.m_level,
+		srcStage,
+		srcAccess,
+		dstStage,
+		dstAccess);
+	oldLayout = computeLayout(prevUsage, isDepthStencil, surf.m_level);
+	newLayout = computeLayout(nextUsage, isDepthStencil, surf.m_level);
+
+	VkImageSubresourceRange range;
+	impl.computeSubResourceRange(surf, range);
+
+	setImageBarrier(srcStage,
+		srcAccess,
+		oldLayout,
+		dstStage,
+		dstAccess,
+		newLayout,
+		tex,
+		range);
+}
+
+//==============================================================================
+inline void CommandBufferImpl::uploadTextureSurface(TexturePtr tex,
+	const TextureSurfaceInfo& surf,
+	const TransientMemoryToken& token)
+{
+	commandCommon();
+	TextureImpl& impl = tex->getImplementation();
+	impl.checkSurface(surf);
+
+	VkImageSubresourceRange range;
+	impl.computeSubResourceRange(surf, range);
+
+	U width = impl.m_width >> surf.m_level;
+	U height = impl.m_height >> surf.m_level;
+
+	// Copy
+	VkBufferImageCopy region;
+	region.imageSubresource.aspectMask = impl.m_aspect;
+	region.imageSubresource.baseArrayLayer = range.baseArrayLayer;
+	region.imageSubresource.layerCount = 1;
+	region.imageSubresource.mipLevel = surf.m_level;
+	region.imageOffset = {0, 0, 0};
+	region.imageExtent.width = width;
+	region.imageExtent.height = height;
+	region.imageExtent.depth = 1;
+	region.bufferOffset = token.m_offset;
+	region.bufferImageHeight = 0;
+	region.bufferRowLength = 0;
+
+	vkCmdCopyBufferToImage(m_handle,
+		getGrManagerImpl().getTransientMemoryManager().getBufferHandle(token),
+		impl.m_imageHandle,
+		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+		1,
+		&region);
+
+	m_texList.pushBack(m_alloc, tex);
+}
+
+} // end namespace anki

+ 17 - 1
include/anki/gr/vulkan/Common.h

@@ -58,6 +58,21 @@ ANKI_USE_RESULT inline Bool formatIsDepthStencil(PixelFormat fmt)
 	}
 }
 
+/// By knowing the previous and new texture usage calculate the relavant info
+/// for a ppline barrier.
+void computeBarrierInfo(TextureUsageBit before,
+	TextureUsageBit after,
+	Bool isDepthStencil,
+	U level,
+	VkPipelineStageFlags& srcStages,
+	VkAccessFlags& srcAccesses,
+	VkPipelineStageFlags& dstStages,
+	VkAccessFlags& dstAccesses);
+
+/// Predict the image layout.
+ANKI_USE_RESULT VkImageLayout computeLayout(
+	TextureUsageBit usage, Bool isDepthStencil, U level);
+
 /// Convert compare op.
 ANKI_USE_RESULT VkCompareOp convertCompareOp(CompareOperation ak);
 
@@ -99,7 +114,8 @@ ANKI_USE_RESULT VkImageType convertTextureType(TextureType ak);
 
 ANKI_USE_RESULT VkImageViewType convertTextureViewType(TextureType ak);
 
-ANKI_USE_RESULT VkImageUsageFlags convertTextureUsage(TextureUsageBit ak);
+ANKI_USE_RESULT VkImageUsageFlags convertTextureUsage(
+	TextureUsageBit ak, const PixelFormat& format);
 /// @}
 
 } // end namespace anki

+ 36 - 1
include/anki/gr/vulkan/TextureImpl.h

@@ -33,6 +33,7 @@ public:
 	U32 m_layerCount = 0;
 	VkImageAspectFlags m_aspect = 0;
 	TextureUsageBit m_usage = TextureUsageBit::NONE;
+	PixelFormat m_format;
 
 	TextureImpl(GrManager* manager);
 
@@ -40,11 +41,14 @@ public:
 
 	ANKI_USE_RESULT Error init(const TextureInitInfo& init, Texture* tex);
 
-	void checkSurface(const TextureSurfaceInfo& surf)
+	void checkSurface(const TextureSurfaceInfo& surf) const
 	{
 		checkTextureSurface(m_type, m_depth, m_mipCount, m_layerCount, surf);
 	}
 
+	void computeSubResourceRange(
+		const TextureSurfaceInfo& surf, VkImageSubresourceRange& range) const;
+
 private:
 	class CreateContext;
 
@@ -59,6 +63,37 @@ private:
 	ANKI_USE_RESULT Error initImage(CreateContext& ctx);
 	ANKI_USE_RESULT Error initView(CreateContext& ctx);
 };
+
+//==============================================================================
+inline void TextureImpl::computeSubResourceRange(
+	const TextureSurfaceInfo& surf, VkImageSubresourceRange& range) const
+{
+	checkSurface(surf);
+	range.aspectMask = m_aspect;
+	range.baseMipLevel = surf.m_level;
+	range.levelCount = 1;
+	switch(m_type)
+	{
+	case TextureType::_2D:
+		range.baseArrayLayer = 0;
+		break;
+	case TextureType::_3D:
+		range.baseArrayLayer = surf.m_depth;
+		break;
+	case TextureType::CUBE:
+		range.baseArrayLayer = surf.m_face;
+		break;
+	case TextureType::_2D_ARRAY:
+		range.baseArrayLayer = surf.m_layer;
+		break;
+	case TextureType::CUBE_ARRAY:
+		range.baseArrayLayer = surf.m_layer * 6 + surf.m_face;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+	range.layerCount = 1;
+}
 /// @}
 
 } // end namespace anki

+ 0 - 2
include/anki/util/ThreadHive.h

@@ -113,8 +113,6 @@ private:
 
 	/// Complete a task.
 	void completeTask(U taskId);
-
-	/// Inject dummy depedency tasks if there are
 };
 /// @}
 

+ 9 - 0
src/gr/gl/CommandBuffer.cpp

@@ -595,6 +595,15 @@ void CommandBuffer::setBufferMemoryBarrier(
 	m_impl->pushBackNewCommand<SetBufferMemBarrierCommand>(d);
 }
 
+//==============================================================================
+void CommandBuffer::setTextureBarrier(TexturePtr tex,
+	TextureUsageBit prevUsage,
+	TextureUsageBit nextUsage,
+	const TextureSurfaceInfo& surf)
+{
+	// Do nothing
+}
+
 //==============================================================================
 class ClearTextCommand final : public GlCommand
 {

+ 13 - 1
src/gr/gl/ShaderImpl.cpp

@@ -30,6 +30,15 @@ static void deleteShaders(GLsizei n, const GLuint* names)
 	glDeleteShader(*names);
 }
 
+static const char* SHADER_HEADER = R"(#version %u %s
+#define ANKI_GL 1
+#define %s
+#define ANKI_UBO_BINDING(set_, binding_) binding = set_ * %u + binding_
+#define ANKI_SS_BINDING(set_, binding_) binding = set_ * %u + binding_
+#define ANKI_TEX_BINDING(set_, binding_) binding = set_ * %u + binding_
+
+%s)";
+
 //==============================================================================
 // ShaderImpl                                                                  =
 //==============================================================================
@@ -81,10 +90,13 @@ Error ShaderImpl::init(ShaderType type, const CString& source)
 	static const char* versionType = "es";
 #endif
 
-	fullSrc.sprintf("#version %d %s\n#define ANKI_GL\n#define %s\n%s\n",
+	fullSrc.sprintf(SHADER_HEADER,
 		version,
 		versionType,
 		shaderName[U(type)],
+		MAX_UNIFORM_BUFFER_BINDINGS,
+		MAX_STORAGE_BUFFER_BINDINGS,
+		MAX_TEXTURE_BINDINGS,
 		&source[0]);
 
 	// 2) Gen name, create, compile and link

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

@@ -169,6 +169,15 @@ void CommandBuffer::uploadBuffer(
 {
 }
 
+//==============================================================================
+void CommandBuffer::setTextureBarrier(TexturePtr tex,
+	TextureUsageBit prevUsage,
+	TextureUsageBit nextUsage,
+	const TextureSurfaceInfo& surf)
+{
+	m_impl->setImageBarrier(tex, prevUsage, nextUsage, surf);
+}
+
 //==============================================================================
 void CommandBuffer::setPipelineBarrier(
 	PipelineStageBit src, PipelineStageBit dst)

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

@@ -181,7 +181,7 @@ void CommandBufferImpl::drawcallCommon()
 			bi.framebuffer = impl.getFramebufferHandle(0);
 
 			ANKI_ASSERT(0);
-			// TODO Get the render arrea from one of the attachments
+			// TODO Get the render area from one of the attachments
 		}
 		else
 		{
@@ -269,82 +269,4 @@ void CommandBufferImpl::bindResourceGroup(
 	m_rcList.pushBack(m_alloc, rc);
 }
 
-//==============================================================================
-void CommandBufferImpl::uploadTextureSurface(TexturePtr tex,
-	const TextureSurfaceInfo& surf,
-	const TransientMemoryToken& token)
-{
-	commandCommon();
-	TextureImpl& impl = tex->getImplementation();
-
-	// First transition to transfer layout
-	VkImageSubresourceRange range;
-	range.aspectMask = impl.m_aspect;
-	switch(impl.m_type)
-	{
-	case TextureType::CUBE:
-		range.baseArrayLayer = surf.m_face;
-		break;
-	case TextureType::CUBE_ARRAY:
-		range.baseArrayLayer = surf.m_depth * impl.m_layerCount + surf.m_face;
-		break;
-	case TextureType::_2D:
-		range.baseArrayLayer = 0;
-		break;
-	case TextureType::_2D_ARRAY:
-		range.baseArrayLayer = surf.m_depth;
-		break;
-	case TextureType::_3D:
-		range.baseArrayLayer = 0;
-		break;
-	default:
-		ANKI_ASSERT(0);
-	}
-	range.baseMipLevel = surf.m_level;
-	range.layerCount = 1;
-	range.levelCount = 1;
-
-	setImageBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-		VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
-		VK_IMAGE_LAYOUT_UNDEFINED,
-		VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-		VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
-		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-		tex,
-		range);
-
-	// Copy
-	VkBufferImageCopy region;
-	region.imageSubresource.aspectMask = impl.m_aspect;
-	region.imageSubresource.baseArrayLayer = range.baseArrayLayer;
-	region.imageSubresource.layerCount = 1;
-	region.imageSubresource.mipLevel = range.baseMipLevel;
-	region.imageOffset = {0, 0, 0};
-	region.imageExtent.width = impl.m_width;
-	region.imageExtent.height = impl.m_height;
-	region.imageExtent.depth = 1;
-	region.bufferOffset = token.m_offset;
-	region.bufferImageHeight = 0;
-	region.bufferRowLength = 0;
-
-	vkCmdCopyBufferToImage(m_handle,
-		getGrManagerImpl().getTransientMemoryManager().getBufferHandle(token),
-		impl.m_imageHandle,
-		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-		1,
-		&region);
-
-	// Transition back. Use the "impl.m_imageHandle" since we already hold a
-	// reference
-	// TOOD: Use the correct layout
-	setImageBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-		VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
-		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-		VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-		VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
-		VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
-		impl.m_imageHandle,
-		range);
-}
-
 } // end namespace anki

+ 241 - 17
src/gr/vulkan/Common.cpp

@@ -8,6 +8,230 @@
 namespace anki
 {
 
+//==============================================================================
+void computeBarrierInfo(TextureUsageBit before,
+	TextureUsageBit after,
+	Bool isDepthStencil,
+	U level,
+	VkPipelineStageFlags& srcStages,
+	VkAccessFlags& srcAccesses,
+	VkPipelineStageFlags& dstStages,
+	VkAccessFlags& dstAccesses)
+{
+	srcStages = 0;
+	srcAccesses = 0;
+	dstStages = 0;
+	dstAccesses = 0;
+
+	//
+	// Before
+	//
+	if((before & TextureUsageBit::FRAGMENT_SHADER_SAMPLED)
+		!= TextureUsageBit::NONE)
+	{
+		srcStages |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		srcAccesses |= VK_ACCESS_SHADER_READ_BIT;
+	}
+
+	if((before & TextureUsageBit::COMPUTE_SHADER_SAMPLED)
+		!= TextureUsageBit::NONE)
+	{
+		srcStages |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+		srcAccesses |= VK_ACCESS_SHADER_READ_BIT;
+	}
+
+	if((before & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ)
+		!= TextureUsageBit::NONE)
+	{
+		if(isDepthStencil)
+		{
+			srcStages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
+				| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+			srcAccesses |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
+		}
+		else
+		{
+			srcStages |= VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
+			srcAccesses |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
+		}
+	}
+
+	if((before & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE)
+		!= TextureUsageBit::NONE)
+	{
+		srcStages |= VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
+
+		if(isDepthStencil)
+		{
+			srcAccesses |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		}
+		else
+		{
+			srcAccesses |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		}
+	}
+
+	if((before & TextureUsageBit::GENERATE_MIPMAPS) != TextureUsageBit::NONE)
+	{
+		srcStages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+
+		if(level == 0)
+		{
+			srcAccesses |= VK_ACCESS_TRANSFER_READ_BIT;
+		}
+		else
+		{
+			srcAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
+		}
+	}
+
+	if((before & TextureUsageBit::UPLOAD) != TextureUsageBit::NONE)
+	{
+		srcStages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+		srcAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
+	}
+
+	//
+	// After
+	//
+	if((after & TextureUsageBit::FRAGMENT_SHADER_SAMPLED)
+		!= TextureUsageBit::NONE)
+	{
+		dstStages |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dstAccesses |= VK_ACCESS_SHADER_READ_BIT;
+	}
+
+	if((after & TextureUsageBit::COMPUTE_SHADER_SAMPLED)
+		!= TextureUsageBit::NONE)
+	{
+		dstStages |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+		dstAccesses |= VK_ACCESS_SHADER_READ_BIT;
+	}
+
+	if((after & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ)
+		!= TextureUsageBit::NONE)
+	{
+		if(isDepthStencil)
+		{
+			dstStages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
+				| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+			dstAccesses |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
+		}
+		else
+		{
+			dstStages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dstAccesses |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
+		}
+	}
+
+	if((after & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE)
+		!= TextureUsageBit::NONE)
+	{
+		if(isDepthStencil)
+		{
+			dstStages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
+				| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+			dstAccesses |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		}
+		else
+		{
+			dstStages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dstAccesses |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		}
+	}
+
+	if((after & TextureUsageBit::GENERATE_MIPMAPS) != TextureUsageBit::NONE)
+	{
+		dstStages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+
+		if(level == 0)
+		{
+			dstAccesses |= VK_ACCESS_TRANSFER_READ_BIT;
+		}
+		else
+		{
+			dstAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
+		}
+	}
+
+	if((after & TextureUsageBit::UPLOAD) != TextureUsageBit::NONE)
+	{
+		dstStages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+		dstAccesses |= VK_ACCESS_TRANSFER_WRITE_BIT;
+	}
+}
+
+//==============================================================================
+VkImageLayout computeLayout(TextureUsageBit usage, Bool isDepthStencil, U level)
+{
+	VkImageLayout out = VK_IMAGE_LAYOUT_MAX_ENUM;
+
+	if(usage == TextureUsageBit::NONE)
+	{
+		out = VK_IMAGE_LAYOUT_UNDEFINED;
+	}
+	else if(isDepthStencil)
+	{
+		if(usage == TextureUsageBit::FRAGMENT_SHADER_SAMPLED
+			|| usage == TextureUsageBit::COMPUTE_SHADER_SAMPLED)
+		{
+			out = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		}
+		else if(usage == TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE
+			|| usage == TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE)
+		{
+			out = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		}
+		else if(usage == (TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ
+							 | TextureUsageBit::FRAGMENT_SHADER_SAMPLED))
+		{
+			out = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
+		}
+		else if(usage == TextureUsageBit::GENERATE_MIPMAPS)
+		{
+			if(level == 0)
+			{
+				out = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+			}
+			else
+			{
+				out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+			}
+		}
+	}
+	else
+	{
+		if(usage == TextureUsageBit::FRAGMENT_SHADER_SAMPLED
+			|| usage == TextureUsageBit::COMPUTE_SHADER_SAMPLED)
+		{
+			out = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		}
+		else if(usage == TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE
+			|| usage == TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE)
+		{
+			out = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		}
+		else if(usage == TextureUsageBit::GENERATE_MIPMAPS)
+		{
+			if(level == 0)
+			{
+				out = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+			}
+			else
+			{
+				out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+			}
+		}
+		else if(usage == TextureUsageBit::UPLOAD)
+		{
+			out = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+		}
+	}
+
+	ANKI_ASSERT(out != VK_IMAGE_LAYOUT_MAX_ENUM);
+	return out;
+}
+
 //==============================================================================
 VkCompareOp convertCompareOp(CompareOperation ak)
 {
@@ -584,40 +808,40 @@ VkImageViewType convertTextureViewType(TextureType ak)
 }
 
 //==============================================================================
-VkImageUsageFlags convertTextureUsage(TextureUsageBit ak)
+VkImageUsageFlags convertTextureUsage(
+	TextureUsageBit ak, const PixelFormat& format)
 {
 	VkImageUsageFlags out = 0;
 
-	if((ak & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE)
-		!= TextureUsageBit::NONE)
+	if((ak & TextureUsageBit::ANY_SHADER_SAMPLED) != TextureUsageBit::NONE)
 	{
-		out |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
-			| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+		out |= VK_IMAGE_USAGE_SAMPLED_BIT;
 	}
 
-	if((ak & TextureUsageBit::COMPUTE_SHADER_IMAGE_READ_WRITE)
+	if((ak & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE)
 		!= TextureUsageBit::NONE)
 	{
-		out |= VK_IMAGE_USAGE_STORAGE_BIT;
+		if(formatIsDepthStencil(format))
+		{
+			out |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+		}
+		else
+		{
+			out |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+		}
 	}
 
-	if((ak & TextureUsageBit::ANY_SHADER_SAMPLED) != TextureUsageBit::NONE)
+	if((ak & TextureUsageBit::GENERATE_MIPMAPS) != TextureUsageBit::NONE)
 	{
-		out |= VK_IMAGE_USAGE_SAMPLED_BIT;
+		out |=
+			VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 	}
 
-	if((ak & TextureUsageBit::TRANSFER_DESTINATION)
-		== TextureUsageBit::TRANSFER_DESTINATION)
+	if((ak & TextureUsageBit::UPLOAD) != TextureUsageBit::NONE)
 	{
 		out |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 	}
 
-	if((ak & TextureUsageBit::TRANSFER_SOURCE)
-		== TextureUsageBit::TRANSFER_SOURCE)
-	{
-		out |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
-	}
-
 	ANKI_ASSERT(out);
 	return out;
 }

+ 19 - 17
src/gr/vulkan/GrManagerImpl.cpp

@@ -87,7 +87,26 @@ GrManagerImpl::~GrManagerImpl()
 		vkQueueWaitIdle(m_queue);
 		m_queue = VK_NULL_HANDLE;
 	}
+	
+	// SECOND THING: The destroy everything that has a reference to GrObjects.
+	for(auto& x : m_perFrame)
+	{
+		if(x.m_imageView)
+		{
+			vkDestroyImageView(m_device, x.m_imageView, nullptr);
+			x.m_imageView = VK_NULL_HANDLE;
+		}
 
+		x.m_presentFence.reset(nullptr);
+		x.m_acquireSemaphore.reset(nullptr);
+		x.m_renderSemaphore.reset(nullptr);
+
+		x.m_cmdbsSubmitted.destroy(getAllocator());
+	}
+
+	m_perThread.destroy(getAllocator());
+
+	// THIRD THING: Continue with the rest
 	if(m_renderPasses)
 	{
 		auto it = m_renderPasses->m_hashmap.getBegin();
@@ -119,23 +138,6 @@ GrManagerImpl::~GrManagerImpl()
 			m_device, m_globalDescriptorSetLayout, nullptr);
 	}
 
-	for(auto& x : m_perFrame)
-	{
-		if(x.m_imageView)
-		{
-			vkDestroyImageView(m_device, x.m_imageView, nullptr);
-			x.m_imageView = VK_NULL_HANDLE;
-		}
-
-		x.m_presentFence.reset(nullptr);
-		x.m_acquireSemaphore.reset(nullptr);
-		x.m_renderSemaphore.reset(nullptr);
-
-		x.m_cmdbsSubmitted.destroy(getAllocator());
-	}
-
-	m_perThread.destroy(getAllocator());
-
 	m_transientMem.destroy();
 	m_gpuMemAllocs.destroy(getAllocator());
 

+ 65 - 2
src/gr/vulkan/ResourceGroupImpl.cpp

@@ -6,6 +6,8 @@
 #include <anki/gr/vulkan/ResourceGroupImpl.h>
 #include <anki/gr/ResourceGroup.h>
 #include <anki/gr/vulkan/GrManagerImpl.h>
+#include <anki/gr/vulkan/TextureImpl.h>
+#include <anki/gr/vulkan/SamplerImpl.h>
 #include <anki/gr/vulkan/BufferImpl.h>
 
 namespace anki
@@ -29,6 +31,19 @@ U ResourceGroupImpl::calcRefCount(
 	U count = 0;
 	hasUploaded = false;
 
+	for(U i = 0; i < MAX_TEXTURE_BINDINGS; ++i)
+	{
+		if(init.m_textures[i].m_texture)
+		{
+			++count;
+		}
+
+		if(init.m_textures[i].m_sampler)
+		{
+			++count;
+		}
+	}
+
 	for(U i = 0; i < MAX_UNIFORM_BUFFER_BINDINGS; ++i)
 	{
 		if(init.m_uniformBuffers[i].m_buffer)
@@ -73,14 +88,62 @@ Error ResourceGroupImpl::init(const ResourceGroupInitInfo& init)
 
 	// Update
 	//
+	Array<VkDescriptorImageInfo, MAX_TEXTURE_BINDINGS> texes = {{}};
+	U texAndSamplerCount = 0;
 	Array<VkDescriptorBufferInfo, MAX_UNIFORM_BUFFER_BINDINGS> unis = {{}};
 	U uniCount = 0;
-	Array<VkWriteDescriptorSet, 1> write = {{}};
+	Array<VkWriteDescriptorSet, 2> write = {{}};
 	U writeCount = 0;
 	refCount = 0;
 
 	// 1st the textures
-	// TODO
+	for(U i = 0; i < MAX_TEXTURE_BINDINGS; ++i)
+	{
+		if(init.m_textures[i].m_texture)
+		{
+			TextureImpl& teximpl =
+				init.m_textures[i].m_texture->getImplementation();
+
+			VkDescriptorImageInfo& inf = texes[i];
+			inf.imageView = teximpl.m_viewHandle;
+
+			m_refs[refCount++] = init.m_textures[i].m_texture;
+
+			if(init.m_textures[i].m_sampler)
+			{
+				inf.sampler =
+					init.m_textures[i].m_sampler->getImplementation().m_handle;
+
+				m_refs[refCount++] = init.m_textures[i].m_sampler;
+			}
+			else
+			{
+				inf.sampler = teximpl.m_sampler->getImplementation().m_handle;
+				// No need for ref
+			}
+
+			// TODO need another layout
+			inf.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			++texAndSamplerCount;
+		}
+	}
+
+	if(texAndSamplerCount)
+	{
+		if(m_handle == VK_NULL_HANDLE)
+		{
+			ANKI_CHECK(getGrManagerImpl().allocateDescriptorSet(m_handle));
+		}
+
+		VkWriteDescriptorSet& w = write[writeCount++];
+		w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		w.descriptorCount = texAndSamplerCount;
+		w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		w.dstBinding = 0;
+		w.pImageInfo = &texes[0];
+		w.dstSet = m_handle;
+	}
 
 	// 2nd the uniform buffers
 	for(U i = 0; i < MAX_UNIFORM_BUFFER_BINDINGS; ++i)

+ 31 - 45
src/gr/vulkan/TextureImpl.cpp

@@ -108,7 +108,7 @@ Bool TextureImpl::imageSupported(const TextureInitInfo& init)
 		convertFormat(init.m_format),
 		convertTextureType(init.m_type),
 		VK_IMAGE_TILING_OPTIMAL,
-		convertTextureUsage(init.m_usage),
+		convertTextureUsage(init.m_usage, init.m_format),
 		calcCreateFlags(init),
 		&props);
 
@@ -130,6 +130,7 @@ Error TextureImpl::init(const TextureInitInfo& init, Texture* tex)
 	m_sampler = getGrManager().newInstanceCached<Sampler>(init.m_sampling);
 	m_width = init.m_width;
 	m_height = init.m_height;
+	m_format = init.m_format;
 
 	CreateContext ctx;
 	ctx.m_init = init;
@@ -137,53 +138,38 @@ Error TextureImpl::init(const TextureInitInfo& init, Texture* tex)
 	ANKI_CHECK(initView(ctx));
 
 	// Transition the image layout from undefined to something relevant
-	VkImageLayout initialLayout;
-	CommandBufferInitInfo cmdbinit;
-	cmdbinit.m_flags = CommandBufferFlag::GRAPHICS_WORK;
-	CommandBufferPtr cmdb = getGrManager().newInstance<CommandBuffer>(cmdbinit);
 	m_usage = init.m_usage;
-	if((m_usage & TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE)
-		!= TextureUsageBit::NONE)
-	{
-		if(formatIsDepthStencil(init.m_format))
-		{
-			initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-		}
-		else
-		{
-			initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-		}
-	}
-	else if((m_usage & TextureUsageBit::ANY_SHADER_SAMPLED)
-		!= TextureUsageBit::NONE)
-	{
-		initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-	}
-	else
+	VkImageLayout initialLayout = computeLayout(
+		init.m_initialUsage, formatIsDepthStencil(init.m_format), 0);
+
+	if(initialLayout != VK_IMAGE_LAYOUT_UNDEFINED)
 	{
-		ANKI_ASSERT(0 && "TODO");
+		CommandBufferInitInfo cmdbinit;
+		cmdbinit.m_flags = CommandBufferFlag::GRAPHICS_WORK;
+		CommandBufferPtr cmdb =
+			getGrManager().newInstance<CommandBuffer>(cmdbinit);
+
+		VkImageSubresourceRange range;
+		range.aspectMask = m_aspect;
+		range.baseArrayLayer = 0;
+		range.baseMipLevel = 0;
+		range.layerCount = m_layerCount;
+		range.levelCount = m_mipCount;
+
+		cmdb->getImplementation().setImageBarrier(
+			VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+			VK_ACCESS_MEMORY_WRITE_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+			VK_ACCESS_MEMORY_READ_BIT,
+			initialLayout,
+			TexturePtr(tex),
+			range);
+
+		cmdb->getImplementation().endRecording();
+		getGrManagerImpl().flushCommandBuffer(cmdb);
 	}
 
-	VkImageSubresourceRange range;
-	range.aspectMask = m_aspect;
-	range.baseArrayLayer = 0;
-	range.baseMipLevel = 0;
-	range.layerCount = m_layerCount;
-	range.levelCount = m_mipCount;
-
-	cmdb->getImplementation().setImageBarrier(
-		VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-		VK_ACCESS_MEMORY_WRITE_BIT,
-		VK_IMAGE_LAYOUT_UNDEFINED,
-		VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-		VK_ACCESS_MEMORY_READ_BIT,
-		initialLayout,
-		TexturePtr(tex),
-		range);
-
-	cmdb->getImplementation().endRecording();
-	getGrManagerImpl().flushCommandBuffer(cmdb);
-
 	return ErrorCode::NONE;
 }
 
@@ -269,7 +255,7 @@ Error TextureImpl::initImage(CreateContext& ctx)
 
 	ci.samples = VK_SAMPLE_COUNT_1_BIT;
 	ci.tiling = VK_IMAGE_TILING_OPTIMAL;
-	ci.usage = convertTextureUsage(init.m_usage);
+	ci.usage = convertTextureUsage(init.m_usage, init.m_format);
 	ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
 	ci.queueFamilyIndexCount = 1;
 	U32 queueIdx = getGrManagerImpl().getGraphicsQueueFamily();

+ 1 - 1
src/renderer/Is.cpp

@@ -208,7 +208,7 @@ Error Is::binLights(RenderingContext& ctx)
 			: nullptr,
 		ctx.m_is.m_dynBufferInfo.m_storageBuffers[CLUSTERS_LOCATION],
 		ctx.m_is.m_dynBufferInfo.m_storageBuffers[LIGHT_IDS_LOCATION]));
-		
+
 	return ErrorCode::NONE;
 }
 

+ 2 - 2
src/renderer/LightBin.cpp

@@ -424,9 +424,9 @@ Error LightBin::bin(FrustumComponent& frc,
 					sizeof(ShaderProbe) * visibleProbeCount,
 					BufferUsage::UNIFORM,
 					*probesToken));
-	
+
 			ctx.m_probes = WeakArray<ShaderProbe>(data, visibleProbeCount);
-	
+
 			ctx.m_vProbes = WeakArray<VisibleNode>(
 				vi.getBegin(VisibilityGroupType::REFLECTION_PROBES),
 				visibleProbeCount);

+ 154 - 63
tests/gr/Gr.cpp

@@ -82,6 +82,27 @@ void main()
 	out_color1 = in_color1;
 })";
 
+static const char* VERT_QUAD_SRC = R"(
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+layout(location = 0) out vec2 out_uv;
+
+void main()
+{
+	const vec2 POSITIONS[6] =
+		vec2[](vec2(-1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, -1.0),
+		vec2(1.0, -1.0), vec2(1.0, 1.0), vec2(-1.0, 1.0));
+
+	gl_Position = vec4(POSITIONS[gl_VertexID], 0.0, 1.0);
+#if defined(ANKI_VK)
+	gl_Position.y = -gl_Position.y;
+#endif
+	out_uv = POSITIONS[gl_VertexID] / 2.0 + 0.5;
+})";
+
 static const char* FRAG_SRC = R"(layout (location = 0) out vec4 out_color;
 
 void main()
@@ -108,6 +129,18 @@ void main()
 	out_color = vec4(in_color0 + in_color1, 1.0);
 })";
 
+static const char* FRAG_TEX_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_tex;
+
+void main()
+{
+	float factor = in_uv.x;
+	out_color = vec4(textureLod(u_tex, in_uv, factor).rgb, 1.0);
+})";
+
 #define COMMON_BEGIN()                                                         \
 	NativeWindow* win = nullptr;                                               \
 	GrManager* gr = nullptr;                                                   \
@@ -148,6 +181,33 @@ static void createGrManager(NativeWindow*& win, GrManager*& gr)
 	ANKI_TEST_EXPECT_NO_ERR(gr->init(inf));
 }
 
+//==============================================================================
+static PipelinePtr createSimplePpline(
+	CString vertSrc, CString fragSrc, GrManager& gr)
+{
+	ShaderPtr vert = gr.newInstance<Shader>(ShaderType::VERTEX, vertSrc);
+	ShaderPtr frag = gr.newInstance<Shader>(ShaderType::FRAGMENT, fragSrc);
+
+	PipelineInitInfo init;
+	init.m_shaders[ShaderType::VERTEX] = vert;
+	init.m_shaders[ShaderType::FRAGMENT] = frag;
+	init.m_color.m_drawsToDefaultFramebuffer = true;
+	init.m_color.m_attachmentCount = 1;
+	init.m_depthStencil.m_depthWriteEnabled = false;
+
+	return gr.newInstance<Pipeline>(init);
+}
+
+//==============================================================================
+static FramebufferPtr createDefaultFb(GrManager& gr)
+{
+	FramebufferInitInfo fbinit;
+	fbinit.m_colorAttachmentCount = 1;
+	fbinit.m_colorAttachments[0].m_clearValue.m_colorf = {1.0, 0.0, 1.0, 1.0};
+
+	return gr.newInstance<Framebuffer>(fbinit);
+}
+
 //==============================================================================
 ANKI_TEST(Gr, GrManager)
 {
@@ -174,19 +234,7 @@ ANKI_TEST(Gr, Pipeline)
 	COMMON_BEGIN();
 
 	{
-		ShaderPtr vert = gr->newInstance<Shader>(ShaderType::VERTEX, VERT_SRC);
-		ShaderPtr frag =
-			gr->newInstance<Shader>(ShaderType::FRAGMENT, FRAG_SRC);
-
-		PipelineInitInfo init;
-		init.m_shaders[ShaderType::VERTEX] = vert;
-		init.m_shaders[ShaderType::FRAGMENT] = frag;
-		init.m_color.m_attachments[0].m_format =
-			PixelFormat(ComponentFormat::R8G8B8A8, TransformFormat::UNORM);
-		init.m_color.m_attachmentCount = 1;
-		init.m_depthStencil.m_depthWriteEnabled = false;
-
-		PipelinePtr ppline = gr->newInstance<Pipeline>(init);
+		PipelinePtr ppline = createSimplePpline(VERT_SRC, FRAG_SRC, *gr);
 	}
 
 	COMMON_END();
@@ -198,24 +246,8 @@ ANKI_TEST(Gr, SimpleDrawcall)
 	COMMON_BEGIN();
 
 	{
-		ShaderPtr vert = gr->newInstance<Shader>(ShaderType::VERTEX, VERT_SRC);
-		ShaderPtr frag =
-			gr->newInstance<Shader>(ShaderType::FRAGMENT, FRAG_SRC);
-
-		PipelineInitInfo init;
-		init.m_shaders[ShaderType::VERTEX] = vert;
-		init.m_shaders[ShaderType::FRAGMENT] = frag;
-		init.m_color.m_drawsToDefaultFramebuffer = true;
-		init.m_color.m_attachmentCount = 1;
-		init.m_depthStencil.m_depthWriteEnabled = false;
-
-		PipelinePtr ppline = gr->newInstance<Pipeline>(init);
-
-		FramebufferInitInfo fbinit;
-		fbinit.m_colorAttachmentCount = 1;
-		fbinit.m_colorAttachments[0].m_clearValue.m_colorf = {
-			1.0, 0.0, 1.0, 1.0};
-		FramebufferPtr fb = gr->newInstance<Framebuffer>(fbinit);
+		PipelinePtr ppline = createSimplePpline(VERT_SRC, FRAG_SRC, *gr);
+		FramebufferPtr fb = createDefaultFb(*gr);
 
 		U iterations = 100;
 		while(--iterations)
@@ -324,28 +356,12 @@ ANKI_TEST(Gr, DrawWithUniforms)
 		rcinit.m_uniformBuffers[0].m_buffer = b;
 		ResourceGroupPtr rc = gr->newInstance<ResourceGroup>(rcinit);
 
-		// Shaders
-		ShaderPtr vert =
-			gr->newInstance<Shader>(ShaderType::VERTEX, VERT_UBO_SRC);
-		ShaderPtr frag =
-			gr->newInstance<Shader>(ShaderType::FRAGMENT, FRAG_UBO_SRC);
-
 		// Ppline
-		PipelineInitInfo init;
-		init.m_shaders[ShaderType::VERTEX] = vert;
-		init.m_shaders[ShaderType::FRAGMENT] = frag;
-		init.m_color.m_drawsToDefaultFramebuffer = true;
-		init.m_color.m_attachmentCount = 1;
-		init.m_depthStencil.m_depthWriteEnabled = false;
-
-		PipelinePtr ppline = gr->newInstance<Pipeline>(init);
+		PipelinePtr ppline =
+			createSimplePpline(VERT_UBO_SRC, FRAG_UBO_SRC, *gr);
 
 		// FB
-		FramebufferInitInfo fbinit;
-		fbinit.m_colorAttachmentCount = 1;
-		fbinit.m_colorAttachments[0].m_clearValue.m_colorf = {
-			1.0, 0.0, 1.0, 1.0};
-		FramebufferPtr fb = gr->newInstance<Framebuffer>(fbinit);
+		FramebufferPtr fb = createDefaultFb(*gr);
 
 		U iterations = 100;
 		while(--iterations)
@@ -463,11 +479,7 @@ ANKI_TEST(Gr, DrawWithVertex)
 		PipelinePtr ppline = gr->newInstance<Pipeline>(init);
 
 		// FB
-		FramebufferInitInfo fbinit;
-		fbinit.m_colorAttachmentCount = 1;
-		fbinit.m_colorAttachments[0].m_clearValue.m_colorf = {
-			1.0, 0.0, 1.0, 1.0};
-		FramebufferPtr fb = gr->newInstance<Framebuffer>(fbinit);
+		FramebufferPtr fb = createDefaultFb(*gr);
 
 		U iterations = 100;
 		while(--iterations)
@@ -529,7 +541,7 @@ ANKI_TEST(Gr, Texture)
 		init.m_depth = 1;
 		init.m_format =
 			PixelFormat(ComponentFormat::R8G8B8, TransformFormat::UNORM);
-		init.m_usage = TextureUsageBit::ANY_SHADER_SAMPLED;
+		init.m_usage = TextureUsageBit::FRAGMENT_SHADER_SAMPLED;
 		init.m_height = 4;
 		init.m_width = 4;
 		init.m_mipmapsCount = 2;
@@ -552,34 +564,52 @@ ANKI_TEST(Gr, DrawWithTexture)
 	COMMON_BEGIN();
 
 	{
+		//
+		// Create the texture
+		//
 		TextureInitInfo init;
 		init.m_depth = 1;
 		init.m_format =
-			PixelFormat(ComponentFormat::R8G8B8, TransformFormat::UNORM);
-		init.m_usage = TextureUsageBit::ANY_SHADER_SAMPLED
-			| TextureUsageBit::TRANSFER_DESTINATION;
+			PixelFormat(ComponentFormat::R8G8B8A8, TransformFormat::UNORM);
+		init.m_usage =
+			TextureUsageBit::FRAGMENT_SHADER_SAMPLED | TextureUsageBit::UPLOAD;
+		init.m_initialUsage = TextureUsageBit::FRAGMENT_SHADER_SAMPLED;
 		init.m_height = 2;
 		init.m_width = 2;
 		init.m_mipmapsCount = 2;
 		init.m_samples = 1;
 		init.m_depth = 1;
 		init.m_layerCount = 1;
+		init.m_sampling.m_repeat = false;
 		init.m_sampling.m_minMagFilter = SamplingFilter::LINEAR;
 		init.m_sampling.m_mipmapFilter = SamplingFilter::LINEAR;
 		init.m_type = TextureType::_2D;
 
 		TexturePtr b = gr->newInstance<Texture>(init);
 
+		//
 		// Upload
-		Array2d<U8, 2 * 2, 3> mip0 = {
-			{255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255}};
+		//
+		Array<U8, 2 * 2 * 4> mip0 = {
+			{255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0}};
 
-		Array<U8, 3> mip1 = {{128, 128, 128}};
+		Array<U8, 4> mip1 = {{128, 128, 128, 0}};
 
 		CommandBufferInitInfo cmdbinit;
 		cmdbinit.m_flags = CommandBufferFlag::TRANSFER_WORK;
 		CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cmdbinit);
 
+		// Set barriers
+		cmdb->setTextureBarrier(b,
+			TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+			TextureUsageBit::UPLOAD,
+			TextureSurfaceInfo(0, 0, 0, 0));
+
+		cmdb->setTextureBarrier(b,
+			TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+			TextureUsageBit::UPLOAD,
+			TextureSurfaceInfo(1, 0, 0, 0));
+
 		Error err = ErrorCode::NONE;
 		TransientMemoryToken token;
 		void* ptr = gr->allocateFrameTransientMemory(
@@ -598,10 +628,71 @@ ANKI_TEST(Gr, DrawWithTexture)
 
 		cmdb->uploadTextureSurface(b, TextureSurfaceInfo(1, 0, 0, 0), token);
 
+		// Set barriers
+		cmdb->setTextureBarrier(b,
+			TextureUsageBit::UPLOAD,
+			TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+			TextureSurfaceInfo(0, 0, 0, 0));
+
+		cmdb->setTextureBarrier(b,
+			TextureUsageBit::UPLOAD,
+			TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+			TextureSurfaceInfo(1, 0, 0, 0));
+
 		cmdb->flush();
 
+		//
+		// Create resource group
+		//
+		ResourceGroupInitInfo rcinit;
+		rcinit.m_textures[0].m_texture = b;
+		ResourceGroupPtr rc = gr->newInstance<ResourceGroup>(rcinit);
+
+		//
+		// Create ppline
+		//
+		PipelinePtr ppline =
+			createSimplePpline(VERT_QUAD_SRC, FRAG_TEX_SRC, *gr);
+
+		//
+		// Create FB
+		//
+		FramebufferPtr fb = createDefaultFb(*gr);
+
+		//
 		// Draw
-		// TODO
+		//
+		U iterations = 100;
+		while(--iterations)
+		{
+			HighRezTimer timer;
+			timer.start();
+
+			gr->beginFrame();
+
+			CommandBufferInitInfo cinit;
+			cinit.m_flags =
+				CommandBufferFlag::FRAME_FIRST | CommandBufferFlag::FRAME_LAST;
+			CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cinit);
+
+			cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+			cmdb->setPolygonOffset(0.0, 0.0);
+			cmdb->bindPipeline(ppline);
+			cmdb->beginRenderPass(fb);
+			cmdb->bindResourceGroup(rc, 0, nullptr);
+			cmdb->drawArrays(6);
+			cmdb->endRenderPass();
+			cmdb->flush();
+
+			gr->swapBuffers();
+
+			timer.stop();
+			const F32 TICK = 1.0 / 30.0;
+			if(timer.getElapsedTime() < TICK)
+			{
+				HighRezTimer::sleep(TICK - timer.getElapsedTime());
+			}
+		}
 	}
 
 	COMMON_END();