Browse Source

Vulkan: Some work for the texture

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
f1a1e6a232

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

@@ -278,19 +278,6 @@ enum class AttachmentStoreOperation : U8
 	DONT_CARE
 };
 
-/// Buffer usage modes.
-enum class BufferUsageBit : U8
-{
-	NONE = 0,
-	UNIFORM = 1 << 0,
-	STORAGE = 1 << 1,
-	INDEX = 1 << 2,
-	VERTEX = 1 << 3,
-	INDIRECT = 1 << 4,
-	ATOMIC = 1 << 5
-};
-ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(BufferUsageBit, inline)
-
 /// Buffer usage modes.
 enum class BufferUsage : U8
 {
@@ -299,7 +286,6 @@ enum class BufferUsage : U8
 	INDEX,
 	VERTEX,
 	INDIRECT,
-	ATOMIC,
 	TRANSFER, ///< For texture upload and buffer write.
 
 	COUNT,
@@ -307,6 +293,19 @@ enum class BufferUsage : U8
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(BufferUsage, inline)
 
+/// Buffer usage modes.
+enum class BufferUsageBit : U8
+{
+	NONE = 0,
+	UNIFORM = 1 << 0,
+	STORAGE = 1 << 1,
+	INDEX = 1 << 2,
+	VERTEX = 1 << 3,
+	INDIRECT = 1 << 4,
+	TRANSFER = 1 << 5
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(BufferUsageBit, inline)
+
 /// Buffer access from client modes.
 enum class BufferAccessBit : U8
 {

+ 1 - 1
include/anki/gr/GrManager.h

@@ -114,10 +114,10 @@ anki_internal:
 
 private:
 	GrAllocator<U8> m_alloc; ///< Keep it first to get deleted last
+	Array<GrObjectCache, U(GrObjectType::COUNT)> m_caches;
 	UniquePtr<GrManagerImpl> m_impl;
 	String m_cacheDir;
 	U64 m_uuidIndex = 1;
-	Array<GrObjectCache, U(GrObjectType::COUNT)> m_caches;
 };
 
 //==============================================================================

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

@@ -89,6 +89,10 @@ public:
 		vkCmdDraw(m_handle, count, instanceCount, first, baseInstance);
 	}
 
+	void uploadTextureSurface(TexturePtr tex,
+		const TextureSurfaceInfo& surf,
+		const TransientMemoryToken& token);
+
 	void endRecording();
 
 	void setImageBarrier(VkPipelineStageFlags srcStage,
@@ -101,6 +105,8 @@ public:
 		const VkImageSubresourceRange& range)
 	{
 		ANKI_ASSERT(img);
+		commandCommon();
+
 		VkImageMemoryBarrier inf = {};
 		inf.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
 		inf.srcAccessMask = srcAccess;
@@ -131,7 +137,7 @@ public:
 			dstStage,
 			dstAccess,
 			newLayout,
-			tex->getImplementation().getImageHandle(),
+			tex->getImplementation().m_imageHandle,
 			range);
 
 		m_texList.pushBack(m_alloc, tex);

+ 10 - 1
include/anki/gr/vulkan/GrManagerImpl.h

@@ -10,6 +10,7 @@
 #include <anki/gr/vulkan/CommandBufferInternal.h>
 #include <anki/gr/vulkan/Semaphore.h>
 #include <anki/gr/vulkan/Fence.h>
+#include <anki/gr/vulkan/TransientMemoryManager.h>
 #include <anki/util/HashMap.h>
 
 namespace anki
@@ -24,6 +25,7 @@ class GrManagerImpl
 public:
 	GrManagerImpl(GrManager* manager)
 		: m_manager(manager)
+		, m_transientMem(manager)
 	{
 		ANKI_ASSERT(manager);
 	}
@@ -180,6 +182,11 @@ public:
 	U findMemoryType(U resourceMemTypeBits,
 		VkMemoryPropertyFlags preferFlags,
 		VkMemoryPropertyFlags avoidFlags) const;
+
+	TransientMemoryManager& getTransientMemoryManager()
+	{
+		return m_transientMem;
+	}
 	/// @}
 
 private:
@@ -237,6 +244,8 @@ private:
 
 	/// One for each mem type.
 	DynamicArray<GpuMemoryAllocator> m_gpuMemAllocs;
+
+	TransientMemoryManager m_transientMem;
 	/// @}
 
 	/// @name Per_thread_cache
@@ -289,7 +298,7 @@ private:
 	ANKI_USE_RESULT Error initGlobalDsetLayout();
 	ANKI_USE_RESULT Error initGlobalDsetPool();
 	ANKI_USE_RESULT Error initGlobalPplineLayout();
-	void initMemory();
+	ANKI_USE_RESULT Error initMemory(const ConfigSet& cfg);
 
 	static void* allocateCallback(void* userData,
 		size_t size,

+ 2 - 2
include/anki/gr/vulkan/SamplerImpl.h

@@ -17,7 +17,7 @@ namespace anki
 class SamplerImpl : public VulkanObject
 {
 public:
-	VkSampler m_sampler = VK_NULL_HANDLE;
+	VkSampler m_handle = VK_NULL_HANDLE;
 
 	SamplerImpl(GrManager* manager)
 		: VulkanObject(manager)
@@ -26,7 +26,7 @@ public:
 
 	~SamplerImpl();
 
-	void init(const SamplerInitInfo& init);
+	ANKI_USE_RESULT Error init(const SamplerInitInfo& init);
 };
 /// @}
 

+ 12 - 22
include/anki/gr/vulkan/TextureImpl.h

@@ -19,38 +19,28 @@ namespace anki
 class TextureImpl : public VulkanObject
 {
 public:
-	TextureImpl(GrManager* manager);
-
-	~TextureImpl();
-
-	ANKI_USE_RESULT Error init(const TextureInitInfo& init);
-
-	const SamplerPtr& getSampler() const
-	{
-		return m_sampler;
-	}
-
-	VkImage getImageHandle() const
-	{
-		ANKI_ASSERT(m_imageHandle);
-		return m_imageHandle;
-	}
-
-private:
-	class CreateContext;
-
 	VkImage m_imageHandle = VK_NULL_HANDLE;
 	VkImageView m_viewHandle = VK_NULL_HANDLE;
 	SamplerPtr m_sampler;
 	U32 m_memIdx = MAX_U32;
 	GpuMemoryAllocationHandle m_memHandle;
 
-	SemaphorePtr m_initLayoutSem;
-
+	U32 m_width = 0;
+	U32 m_height = 0;
 	TextureType m_type = TextureType::CUBE;
 	U8 m_mipCount = 0;
 	U32 m_layerCount = 0;
 	VkImageAspectFlags m_aspect = 0;
+	VkImageLayout m_optimalLayout = VK_IMAGE_LAYOUT_MAX_ENUM;
+
+	TextureImpl(GrManager* manager);
+
+	~TextureImpl();
+
+	ANKI_USE_RESULT Error init(const TextureInitInfo& init, Texture* tex);
+
+private:
+	class CreateContext;
 
 	ANKI_USE_RESULT static VkFormatFeatureFlags calcFeatures(
 		const TextureInitInfo& init);

+ 86 - 0
include/anki/gr/vulkan/TransientMemoryManager.h

@@ -0,0 +1,86 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/gr/vulkan/GpuMemoryAllocator.h>
+#include <anki/gr/common/GpuFrameRingAllocator.h>
+
+namespace anki
+{
+
+// Forward
+class ConfigSet;
+
+/// @addtogroup vulkan
+/// @{
+
+/// Manages the transient memory.
+class TransientMemoryManager
+{
+public:
+	TransientMemoryManager(GrManager* gr)
+		: m_manager(gr)
+	{
+		ANKI_ASSERT(gr);
+	}
+
+	~TransientMemoryManager()
+	{
+	}
+
+	ANKI_USE_RESULT Error init(const ConfigSet& cfg);
+
+	void destroy();
+
+	/// @note Not thread-safe.
+	void endFrame();
+
+	void allocate(PtrSize size,
+		BufferUsage usage,
+		TransientMemoryToken& token,
+		void*& ptr,
+		Error* outErr);
+
+	void* getBaseAddress(const TransientMemoryToken& token) const
+	{
+		ANKI_ASSERT(
+			token.m_lifetime == TransientMemoryTokenLifetime::PER_FRAME);
+		const PerFrameBuffer& frame =
+			m_perFrameBuffers[TransientMemoryTokenLifetime::PER_FRAME];
+		void* addr = frame.m_mappedMem;
+		ANKI_ASSERT(addr);
+		return addr;
+	}
+
+	VkBuffer getBufferHandle(const TransientMemoryToken& token) const
+	{
+		ANKI_ASSERT(
+			token.m_lifetime == TransientMemoryTokenLifetime::PER_FRAME);
+		const PerFrameBuffer& frame =
+			m_perFrameBuffers[TransientMemoryTokenLifetime::PER_FRAME];
+		ANKI_ASSERT(frame.m_bufferHandle);
+		return frame.m_bufferHandle;
+	}
+
+private:
+	class PerFrameBuffer
+	{
+	public:
+		PtrSize m_size = 0;
+		GpuMemoryAllocationHandle m_handle;
+		BufferImpl* m_buff = nullptr;
+		U8* m_mappedMem = nullptr;
+		VkBuffer m_bufferHandle = VK_NULL_HANDLE; ///< Cache it.
+		GpuFrameRingAllocator m_alloc;
+	};
+
+	GrManager* m_manager = nullptr;
+
+	Array<PerFrameBuffer, U(BufferUsage::COUNT)> m_perFrameBuffers;
+};
+/// @}
+
+} // end namespace anki

+ 2 - 2
src/gr/gl/DynamicMemoryManager.cpp

@@ -201,7 +201,7 @@ void DynamicMemoryManager::allocate(PtrSize size,
 	}
 	else
 	{
-		ANKI_LOGF("Out of dynamic GPU memory");
+		ANKI_LOGF("Out of transient GPU memory");
 	}
 }
 
@@ -235,4 +235,4 @@ void DynamicMemoryManager::endFrame()
 	}
 }
 
-} // end namespace anki
+} // end namespace anki

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

@@ -160,6 +160,7 @@ void CommandBuffer::uploadTextureSurface(TexturePtr tex,
 	const TextureSurfaceInfo& surf,
 	const TransientMemoryToken& token)
 {
+	m_impl->uploadTextureSurface(tex, surf, token);
 }
 
 //==============================================================================

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

@@ -112,6 +112,7 @@ void CommandBufferImpl::commandCommon()
 
 	ANKI_ASSERT(!m_finalized);
 	ANKI_ASSERT(m_handle);
+	m_empty = false;
 }
 
 //==============================================================================
@@ -130,7 +131,6 @@ void CommandBufferImpl::beginRenderPass(FramebufferPtr fb)
 {
 	commandCommon();
 	ANKI_ASSERT(!insideRenderPass());
-	m_empty = false;
 
 	m_firstRpassDrawcall = true;
 	m_activeFb = fb;
@@ -269,4 +269,81 @@ 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 = surf.m_depth;
+		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,
+		impl.m_optimalLayout,
+		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 = 0; // XXX
+	region.bufferOffset = token.m_offset;
+	region.bufferImageHeight = 0; // XXX
+	region.bufferRowLength = 0; // XXX
+
+	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
+	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,
+		impl.m_optimalLayout,
+		impl.m_imageHandle,
+		range);
+}
+
 } // end namespace anki

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

@@ -516,6 +516,12 @@ VkBufferUsageFlags convertBufferUsageBit(BufferUsageBit usageMask)
 		out |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
 	}
 
+	if((usageMask & BufferUsageBit::TRANSFER) != BufferUsageBit::NONE)
+	{
+		out |=
+			VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+	}
+
 	ANKI_ASSERT(out);
 
 	return out;

+ 9 - 2
src/gr/vulkan/GrManager.cpp

@@ -25,6 +25,11 @@ Error GrManager::init(GrManagerInitInfo& init)
 	m_alloc =
 		HeapAllocator<U8>(init.m_allocCallback, init.m_allocCallbackUserData);
 
+	for(auto& c : m_caches)
+	{
+		c.init(m_alloc);
+	}
+
 	m_impl.reset(m_alloc.newInstance<GrManagerImpl>(this));
 	ANKI_CHECK(m_impl->init(init));
 
@@ -52,7 +57,9 @@ void GrManager::finish()
 void* GrManager::allocateFrameTransientMemory(
 	PtrSize size, BufferUsage usage, TransientMemoryToken& token, Error* err)
 {
-	return nullptr;
+	void* ptr = nullptr;
+	m_impl->getTransientMemoryManager().allocate(size, usage, token, ptr, err);
+	return ptr;
 }
 
-} // end namespace anki
+} // end namespace anki

+ 9 - 2
src/gr/vulkan/GrManagerImpl.cpp

@@ -136,6 +136,7 @@ GrManagerImpl::~GrManagerImpl()
 
 	m_perThread.destroy(getAllocator());
 
+	m_transientMem.destroy();
 	m_gpuMemAllocs.destroy(getAllocator());
 
 	m_semaphores.destroy(); // Destroy before fences
@@ -191,7 +192,7 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	vkGetDeviceQueue(m_device, m_queueIdx, 0, &m_queue);
 	ANKI_CHECK(initSwapchain(init));
 
-	initMemory();
+	ANKI_CHECK(initMemory(*init.m_config));
 	ANKI_CHECK(initGlobalDsetLayout());
 	ANKI_CHECK(initGlobalDsetPool());
 	ANKI_CHECK(initGlobalPplineLayout());
@@ -668,16 +669,22 @@ VkRenderPass GrManagerImpl::getOrCreateCompatibleRenderPass(
 }
 
 //==============================================================================
-void GrManagerImpl::initMemory()
+Error GrManagerImpl::initMemory(const ConfigSet& cfg)
 {
 	vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &m_memoryProperties);
 
+	// Create the high level allocators
 	m_gpuMemAllocs.create(getAllocator(), m_memoryProperties.memoryTypeCount);
 	U idx = 0;
 	for(GpuMemoryAllocator& alloc : m_gpuMemAllocs)
 	{
 		alloc.init(getAllocator(), m_device, idx++);
 	}
+
+	// Transient mem
+	ANKI_CHECK(m_transientMem.init(cfg));
+
+	return ErrorCode::NONE;
 }
 
 //==============================================================================

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

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

+ 13 - 2
src/gr/vulkan/SamplerImpl.cpp

@@ -11,7 +11,16 @@ namespace anki
 {
 
 //==============================================================================
-void SamplerImpl::init(const SamplerInitInfo& ii)
+SamplerImpl::~SamplerImpl()
+{
+	if(m_handle)
+	{
+		vkDestroySampler(getDevice(), m_handle, nullptr);
+	}
+}
+
+//==============================================================================
+Error SamplerImpl::init(const SamplerInitInfo& ii)
 {
 	// Fill the create cio
 	VkSamplerCreateInfo ci;
@@ -70,7 +79,9 @@ void SamplerImpl::init(const SamplerInitInfo& ii)
 	ci.unnormalizedCoordinates = VK_FALSE;
 
 	// Create
-	ANKI_VK_CHECKF(vkCreateSampler(getDevice(), &ci, nullptr, &m_sampler));
+	ANKI_VK_CHECK(vkCreateSampler(getDevice(), &ci, nullptr, &m_handle));
+
+	return ErrorCode::NONE;
 }
 
 } // end namespace anki

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

@@ -23,6 +23,12 @@ Texture::~Texture()
 //==============================================================================
 void Texture::init(const TextureInitInfo& init)
 {
+	m_impl.reset(getAllocator().newInstance<TextureImpl>(&getManager()));
+
+	if(m_impl->init(init, this))
+	{
+		ANKI_LOGF("Cannot recover");
+	}
 }
 
-} // end namespace anki
+} // end namespace anki

+ 15 - 6
src/gr/vulkan/TextureImpl.cpp

@@ -149,18 +149,21 @@ Bool TextureImpl::imageSupported(const TextureInitInfo& init)
 }
 
 //==============================================================================
-Error TextureImpl::init(const TextureInitInfo& init)
+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;
 
 	CreateContext ctx;
 	ctx.m_init = init;
 	ANKI_CHECK(initImage(ctx));
 	ANKI_CHECK(initView(ctx));
 
-	// Change the image layout
+	// Transition the image layout from undefined to something relevant
 	VkImageLayout newLayout;
 	CommandBufferInitInfo cmdbinit;
+	cmdbinit.m_flags = CommandBufferFlag::GRAPHICS_WORK;
 	CommandBufferPtr cmdb = getGrManager().newInstance<CommandBuffer>(cmdbinit);
 	if(init.m_framebufferAttachment)
 	{
@@ -177,6 +180,7 @@ Error TextureImpl::init(const TextureInitInfo& init)
 	{
 		newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 	}
+	m_optimalLayout = newLayout;
 
 	VkImageSubresourceRange range;
 	range.aspectMask = m_aspect;
@@ -186,12 +190,17 @@ Error TextureImpl::init(const TextureInitInfo& init)
 	range.levelCount = m_mipCount;
 
 	cmdb->getImplementation().setImageBarrier(
-		0, 0, VK_IMAGE_LAYOUT_UNDEFINED, 0, 0, newLayout, m_imageHandle, range);
-
-	m_initLayoutSem = getGrManagerImpl().newSemaphore();
+		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,
+		newLayout,
+		TexturePtr(tex),
+		range);
 
 	cmdb->getImplementation().endRecording();
-	getGrManagerImpl().flushCommandBuffer(cmdb, m_initLayoutSem);
+	getGrManagerImpl().flushCommandBuffer(cmdb);
 
 	return ErrorCode::NONE;
 }

+ 111 - 0
src/gr/vulkan/TransientMemoryManager.cpp

@@ -0,0 +1,111 @@
+// 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/TransientMemoryManager.h>
+#include <anki/gr/GrManager.h>
+#include <anki/gr/vulkan/GrManagerImpl.h>
+#include <anki/gr/vulkan/BufferImpl.h>
+#include <anki/core/Config.h>
+
+namespace anki
+{
+
+//==============================================================================
+Error TransientMemoryManager::init(const ConfigSet& cfg)
+{
+	Array<const char*, U(BufferUsage::COUNT)> configVars = {
+		{"gr.uniformPerFrameMemorySize",
+			"gr.storagePerFrameMemorySize",
+			nullptr,
+			"gr.vertexPerFrameMemorySize",
+			nullptr,
+			"gr.transferPerFrameMemorySize"}};
+
+	const VkPhysicalDeviceLimits& limits =
+		m_manager->getImplementation().getPhysicalDeviceProperties().limits;
+	Array<U32, U(BufferUsage::COUNT)> alignments = {
+		{U32(limits.minUniformBufferOffsetAlignment),
+			U32(limits.minStorageBufferOffsetAlignment),
+			0,
+			sizeof(F32) * 4,
+			0,
+			sizeof(F32) * 4}};
+
+	auto alloc = m_manager->getAllocator();
+	for(U i = 0; i < U(BufferUsage::COUNT); ++i)
+	{
+		if(configVars[i] == nullptr)
+		{
+			continue;
+		}
+
+		PerFrameBuffer& frame = m_perFrameBuffers[i];
+		frame.m_size = cfg.getNumber(configVars[i]);
+		ANKI_ASSERT(frame.m_size);
+
+		// Create buffer
+		BufferUsageBit usage = BufferUsageBit(1 << i);
+		frame.m_buff = alloc.newInstance<BufferImpl>(m_manager);
+		ANKI_CHECK(frame.m_buff->init(
+			frame.m_size, usage, BufferAccessBit::CLIENT_MAP_WRITE));
+
+		frame.m_bufferHandle = frame.m_buff->getHandle();
+
+		// Map once
+		frame.m_mappedMem = static_cast<U8*>(frame.m_buff->map(
+			0, frame.m_size, BufferAccessBit::CLIENT_MAP_WRITE));
+		ANKI_ASSERT(frame.m_mappedMem);
+
+		// Init the allocator
+		frame.m_alloc.init(frame.m_size, alignments[i]);
+	}
+
+	return ErrorCode::NONE;
+}
+
+//==============================================================================
+void TransientMemoryManager::destroy()
+{
+	for(PerFrameBuffer& frame : m_perFrameBuffers)
+	{
+		if(frame.m_buff)
+		{
+			frame.m_buff->unmap();
+			m_manager->getAllocator().deleteInstance(frame.m_buff);
+		}
+	}
+}
+
+//==============================================================================
+void TransientMemoryManager::allocate(PtrSize size,
+	BufferUsage usage,
+	TransientMemoryToken& token,
+	void*& ptr,
+	Error* outErr)
+{
+	Error err = ErrorCode::NONE;
+	ptr = nullptr;
+
+	PerFrameBuffer& buff = m_perFrameBuffers[usage];
+	err = buff.m_alloc.allocate(size, token.m_offset);
+
+	if(!err)
+	{
+		token.m_usage = usage;
+		token.m_range = size;
+		token.m_lifetime = TransientMemoryTokenLifetime::PER_FRAME;
+		ptr = buff.m_mappedMem + token.m_offset;
+	}
+	else if(outErr)
+	{
+		*outErr = err;
+	}
+	else
+	{
+		ANKI_LOGF("Out of transient GPU memory");
+	}
+}
+
+} // end namespace anki

+ 97 - 0
tests/gr/Gr.cpp

@@ -505,4 +505,101 @@ ANKI_TEST(Gr, DrawWithVertex)
 	COMMON_END();
 }
 
+//==============================================================================
+ANKI_TEST(Gr, Sampler)
+{
+	COMMON_BEGIN();
+
+	{
+		SamplerInitInfo init;
+
+		SamplerPtr b = gr->newInstance<Sampler>(init);
+	}
+
+	COMMON_END();
+}
+
+//==============================================================================
+ANKI_TEST(Gr, Texture)
+{
+	COMMON_BEGIN();
+
+	{
+		TextureInitInfo init;
+		init.m_depth = 1;
+		init.m_format =
+			PixelFormat(ComponentFormat::R8G8B8, TransformFormat::UNORM);
+		init.m_framebufferAttachment = false;
+		init.m_height = 4;
+		init.m_width = 4;
+		init.m_mipmapsCount = 2;
+		init.m_samples = 1;
+		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);
+	}
+
+	COMMON_END();
+}
+
+//==============================================================================
+ANKI_TEST(Gr, DrawWithTexture)
+{
+	COMMON_BEGIN();
+
+	{
+		TextureInitInfo init;
+		init.m_depth = 1;
+		init.m_format =
+			PixelFormat(ComponentFormat::R8G8B8, TransformFormat::UNORM);
+		init.m_framebufferAttachment = false;
+		init.m_height = 2;
+		init.m_width = 2;
+		init.m_mipmapsCount = 2;
+		init.m_samples = 1;
+		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, 3> mip1 = {{128, 128, 128}};
+
+		CommandBufferInitInfo cmdbinit;
+		cmdbinit.m_flags = CommandBufferFlag::TRANSFER_WORK;
+		CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cmdbinit);
+
+		Error err = ErrorCode::NONE;
+		TransientMemoryToken token;
+		void* ptr = gr->allocateFrameTransientMemory(
+			sizeof(mip0), BufferUsage::TRANSFER, token, &err);
+		ANKI_TEST_EXPECT_NEQ(ptr, nullptr);
+		ANKI_TEST_EXPECT_NO_ERR(err);
+		memcpy(ptr, &mip0[0], sizeof(mip0));
+
+		cmdb->uploadTextureSurface(b, TextureSurfaceInfo(0, 0, 0), token);
+
+		ptr = gr->allocateFrameTransientMemory(
+			sizeof(mip1), BufferUsage::TRANSFER, token, &err);
+		ANKI_TEST_EXPECT_NEQ(ptr, nullptr);
+		ANKI_TEST_EXPECT_NO_ERR(err);
+		memcpy(ptr, &mip1[0], sizeof(mip1));
+
+		cmdb->uploadTextureSurface(b, TextureSurfaceInfo(1, 0, 0), token);
+
+		cmdb->flush();
+
+		// Draw
+		// TODO
+	}
+
+	COMMON_END();
+}
+
 } // end namespace anki