Browse Source

GL: Add image load/store support. Vulkan: Add some preliminary code for texture workarounds

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
05eda87311

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

@@ -112,7 +112,6 @@ public:
 };
 };
 
 
 // Some constants
 // Some constants
-// WARNING: If you change those you may need to update the shaders.
 const U MAX_VERTEX_ATTRIBUTES = 8;
 const U MAX_VERTEX_ATTRIBUTES = 8;
 const U MAX_COLOR_ATTACHMENTS = 4;
 const U MAX_COLOR_ATTACHMENTS = 4;
 const U MAX_MIPMAPS = 16;
 const U MAX_MIPMAPS = 16;
@@ -121,6 +120,7 @@ const U MAX_TEXTURE_BINDINGS = 10;
 const U MAX_UNIFORM_BUFFER_BINDINGS = 4;
 const U MAX_UNIFORM_BUFFER_BINDINGS = 4;
 const U MAX_STORAGE_BUFFER_BINDINGS = 4;
 const U MAX_STORAGE_BUFFER_BINDINGS = 4;
 const U MAX_ATOMIC_BUFFER_BINDINGS = 1;
 const U MAX_ATOMIC_BUFFER_BINDINGS = 1;
+const U MAX_IMAGE_BINDINGS = 4;
 const U MAX_FRAMES_IN_FLIGHT = 3; ///< Triple buffering.
 const U MAX_FRAMES_IN_FLIGHT = 3; ///< Triple buffering.
 /// Groups that can be bound at the same time.
 /// Groups that can be bound at the same time.
 const U MAX_BOUND_RESOURCE_GROUPS = 2;
 const U MAX_BOUND_RESOURCE_GROUPS = 2;

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

@@ -182,6 +182,8 @@ enum class TextureUsageBit : U16
 	/// @{
 	/// @{
 	COMPUTE_SHADER_IMAGE_READ = 1 << 6,
 	COMPUTE_SHADER_IMAGE_READ = 1 << 6,
 	COMPUTE_SHADER_IMAGE_WRITE = 1 << 7,
 	COMPUTE_SHADER_IMAGE_WRITE = 1 << 7,
+	COMPUTE_SHADER_IMAGE_READ_WRITE =
+		COMPUTE_SHADER_IMAGE_READ | COMPUTE_SHADER_IMAGE_WRITE,
 	/// @}
 	/// @}
 
 
 	/// @name Attachment
 	/// @name Attachment

+ 9 - 1
include/anki/gr/ResourceGroup.h

@@ -34,6 +34,14 @@ public:
 	Bool m_uploadedMemory = false;
 	Bool m_uploadedMemory = false;
 };
 };
 
 
+/// Image binding info.
+class ImageBinding
+{
+public:
+	TexturePtr m_texture;
+	U8 m_level = 0;
+};
+
 /// Resource group initializer.
 /// Resource group initializer.
 class ResourceGroupInitInfo
 class ResourceGroupInitInfo
 {
 {
@@ -41,7 +49,7 @@ public:
 	Array<TextureBinding, MAX_TEXTURE_BINDINGS> m_textures;
 	Array<TextureBinding, MAX_TEXTURE_BINDINGS> m_textures;
 	Array<BufferBinding, MAX_UNIFORM_BUFFER_BINDINGS> m_uniformBuffers;
 	Array<BufferBinding, MAX_UNIFORM_BUFFER_BINDINGS> m_uniformBuffers;
 	Array<BufferBinding, MAX_STORAGE_BUFFER_BINDINGS> m_storageBuffers;
 	Array<BufferBinding, MAX_STORAGE_BUFFER_BINDINGS> m_storageBuffers;
-	Array<BufferBinding, MAX_ATOMIC_BUFFER_BINDINGS> m_atomicBuffers;
+	Array<ImageBinding, MAX_IMAGE_BINDINGS> m_images;
 	Array<BufferBinding, MAX_VERTEX_ATTRIBUTES> m_vertexBuffers;
 	Array<BufferBinding, MAX_VERTEX_ATTRIBUTES> m_vertexBuffers;
 	BufferBinding m_indexBuffer;
 	BufferBinding m_indexBuffer;
 	I8 m_indexSize = -1; ///< Index size in bytes. 2 or 4
 	I8 m_indexSize = -1; ///< Index size in bytes. 2 or 4

+ 3 - 3
include/anki/gr/Texture.h

@@ -44,9 +44,9 @@ public:
 	TextureUsageBit m_initialUsage = TextureUsageBit::NONE;
 	TextureUsageBit m_initialUsage = TextureUsageBit::NONE;
 	U32 m_width = 0;
 	U32 m_width = 0;
 	U32 m_height = 0;
 	U32 m_height = 0;
-	U32 m_depth = 0; //< Relevant only for 3D textures.
-	U32 m_layerCount = 0; ///< Relevant only for texture arrays.
-	U8 m_mipmapsCount = 0;
+	U32 m_depth = 1; //< Relevant only for 3D textures.
+	U32 m_layerCount = 1; ///< Relevant only for texture arrays.
+	U8 m_mipmapsCount = 1;
 
 
 	PixelFormat m_format;
 	PixelFormat m_format;
 	U8 m_samples = 1;
 	U8 m_samples = 1;

+ 10 - 2
include/anki/gr/gl/ResourceGroupImpl.h

@@ -43,6 +43,14 @@ private:
 		U32 m_range = 0;
 		U32 m_range = 0;
 	};
 	};
 
 
+	class ImageBinding
+	{
+	public:
+		GLuint m_name = 0;
+		U16 m_level = 0;
+		GLenum m_format = GL_NONE;
+	};
+
 	Array<GLuint, MAX_TEXTURE_BINDINGS> m_textureNames;
 	Array<GLuint, MAX_TEXTURE_BINDINGS> m_textureNames;
 	Array<GLuint, MAX_TEXTURE_BINDINGS> m_samplerNames;
 	Array<GLuint, MAX_TEXTURE_BINDINGS> m_samplerNames;
 	U8 m_textureNamesCount = 0;
 	U8 m_textureNamesCount = 0;
@@ -54,8 +62,8 @@ private:
 	Array<InternalBufferBinding, MAX_STORAGE_BUFFER_BINDINGS> m_ssbos;
 	Array<InternalBufferBinding, MAX_STORAGE_BUFFER_BINDINGS> m_ssbos;
 	U8 m_ssbosCount = 0;
 	U8 m_ssbosCount = 0;
 
 
-	Array<InternalBufferBinding, MAX_ATOMIC_BUFFER_BINDINGS> m_atomics;
-	U8 m_atomicsCount = 0;
+	Array<ImageBinding, MAX_IMAGE_BINDINGS> m_images;
+	U8 m_imageCount = 0;
 
 
 	Array<GLuint, MAX_VERTEX_ATTRIBUTES> m_vertBuffNames;
 	Array<GLuint, MAX_VERTEX_ATTRIBUTES> m_vertBuffNames;
 	Array<GLintptr, MAX_VERTEX_ATTRIBUTES> m_vertBuffOffsets;
 	Array<GLintptr, MAX_VERTEX_ATTRIBUTES> m_vertBuffOffsets;

+ 11 - 0
include/anki/gr/vulkan/GrManagerImpl.h

@@ -16,6 +16,9 @@
 namespace anki
 namespace anki
 {
 {
 
 
+// Forward
+class TextureFallbackUploader;
+
 /// @addtogroup vulkan
 /// @addtogroup vulkan
 /// @{
 /// @{
 
 
@@ -194,6 +197,12 @@ public:
 	}
 	}
 	/// @}
 	/// @}
 
 
+	TextureFallbackUploader& getTextureFallbackUploader()
+	{
+		ANKI_ASSERT(m_texUploader);
+		return *m_texUploader;
+	}
+
 private:
 private:
 	GrManager* m_manager = nullptr;
 	GrManager* m_manager = nullptr;
 
 
@@ -289,6 +298,8 @@ private:
 	SemaphoreFactory m_semaphores;
 	SemaphoreFactory m_semaphores;
 	/// @}
 	/// @}
 
 
+	TextureFallbackUploader* m_texUploader = nullptr;
+
 	ANKI_USE_RESULT Error initInternal(const GrManagerInitInfo& init);
 	ANKI_USE_RESULT Error initInternal(const GrManagerInitInfo& init);
 	ANKI_USE_RESULT Error initInstance(const GrManagerInitInfo& init);
 	ANKI_USE_RESULT Error initInstance(const GrManagerInitInfo& init);
 	ANKI_USE_RESULT Error initSurface(const GrManagerInitInfo& init);
 	ANKI_USE_RESULT Error initSurface(const GrManagerInitInfo& init);

+ 31 - 0
include/anki/gr/vulkan/TextureMisc.h

@@ -0,0 +1,31 @@
+// 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/Common.h>
+#include <anki/gr/Pipeline.h>
+
+namespace anki
+{
+
+/// @addtogroup vulkan
+/// @{
+
+/// TODO
+class TextureFallbackUploader
+{
+public:
+	GrManagerImpl* m_manager;
+	PipelinePtr m_uploadR8g8b8Ppline;
+
+	TextureFallbackUploader(GrManagerImpl* manager)
+		: m_manager(manager)
+	{
+	}
+
+	ANKI_USE_RESULT Error init();
+};
+/// @}
+
+} // end namespace anki

+ 1 - 1
sandbox/Main.cpp

@@ -43,7 +43,6 @@ Error MyApp::init(int argc, char* argv[])
 	ANKI_CHECK(App::init(config, allocAligned, nullptr));
 	ANKI_CHECK(App::init(config, allocAligned, nullptr));
 
 
 	// Other init
 	// Other init
-	SceneGraph& scene = getSceneGraph();
 	MainRenderer& renderer = getMainRenderer();
 	MainRenderer& renderer = getMainRenderer();
 	ResourceManager& resources = getResourceManager();
 	ResourceManager& resources = getResourceManager();
 
 
@@ -72,6 +71,7 @@ Error MyApp::init(int argc, char* argv[])
 		"textures/adis/dungeon.ankitex"));
 		"textures/adis/dungeon.ankitex"));
 
 
 #if PLAYER
 #if PLAYER
+	SceneGraph& scene = getSceneGraph();
 	SceneNode& cam = scene.getActiveCamera();
 	SceneNode& cam = scene.getActiveCamera();
 
 
 	PlayerNode* pnode;
 	PlayerNode* pnode;

+ 2 - 0
src/gr/gl/RenderingThread.cpp

@@ -133,6 +133,8 @@ void RenderingThread::start()
 		m_manager->newInstance<CommandBuffer>(CommandBufferInitInfo());
 		m_manager->newInstance<CommandBuffer>(CommandBufferInitInfo());
 	m_swapBuffersCommands->getImplementation()
 	m_swapBuffersCommands->getImplementation()
 		.pushBackNewCommand<SwapBuffersCommand>(this);
 		.pushBackNewCommand<SwapBuffersCommand>(this);
+	// Just in case noone swaps
+	m_swapBuffersCommands->getImplementation().makeExecuted();
 
 
 	m_manager->getImplementation().pinContextToCurrentThread(false);
 	m_manager->getImplementation().pinContextToCurrentThread(false);
 
 

+ 34 - 23
src/gr/gl/ResourceGroupImpl.cpp

@@ -108,11 +108,26 @@ void ResourceGroupImpl::init(const ResourceGroupInitInfo& init)
 		m_ssbosCount,
 		m_ssbosCount,
 		resourcesCount,
 		resourcesCount,
 		transCount);
 		transCount);
-	initBuffers(init.m_atomicBuffers,
-		m_atomics,
-		m_atomicsCount,
-		resourcesCount,
-		transCount);
+
+	// Init images
+	for(U i = 0; i < MAX_IMAGE_BINDINGS; ++i)
+	{
+		const auto& in = init.m_images[i];
+		if(in.m_texture)
+		{
+			TextureImpl& impl = in.m_texture->getImplementation();
+			impl.checkSurface(TextureSurfaceInfo(in.m_level, 0, 0, 0));
+
+			ImageBinding& out = m_images[i];
+
+			out.m_name = in.m_texture->getImplementation().getGlName();
+			out.m_level = in.m_level;
+			out.m_format = impl.m_internalFormat;
+
+			++m_imageCount;
+			++resourcesCount;
+		}
+	}
 
 
 	// Init vert buffers
 	// Init vert buffers
 	m_vertBindingsCount = 0;
 	m_vertBindingsCount = 0;
@@ -206,12 +221,12 @@ void ResourceGroupImpl::initResourceReferences(
 		}
 		}
 	}
 	}
 
 
-	for(U i = 0; i < init.m_atomicBuffers.getSize(); ++i)
+	for(U i = 0; i < MAX_IMAGE_BINDINGS; ++i)
 	{
 	{
-		const BufferBinding& binding = init.m_atomicBuffers[i];
-		if(binding.m_buffer.isCreated())
+		const auto& binding = init.m_images[i];
+		if(binding.m_texture)
 		{
 		{
-			m_refs[count++] = binding.m_buffer;
+			m_refs[count++] = binding.m_texture;
 		}
 		}
 	}
 	}
 
 
@@ -332,23 +347,19 @@ void ResourceGroupImpl::bind(
 		}
 		}
 	}
 	}
 
 
-	// Atomic
-	for(U i = 0; i < m_atomicsCount; ++i)
+	// Images
+	for(U i = 0; i < m_imageCount; ++i)
 	{
 	{
-		const auto& binding = m_atomics[i];
-		if(binding.m_name == MAX_U32)
-		{
-			// Transient
-			ANKI_ASSERT(0);
-		}
-		else if(binding.m_name != 0)
+		const ImageBinding& binding = m_images[i];
+		if(binding.m_name)
 		{
 		{
-			// Static
-			glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER,
-				MAX_ATOMIC_BUFFER_BINDINGS * slot + i,
+			glBindImageTexture(MAX_IMAGE_BINDINGS * slot + i,
 				binding.m_name,
 				binding.m_name,
-				binding.m_offset,
-				binding.m_range);
+				binding.m_level,
+				GL_TRUE,
+				0,
+				GL_READ_WRITE,
+				binding.m_format);
 		}
 		}
 	}
 	}
 
 

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

@@ -37,6 +37,7 @@ static const char* SHADER_HEADER = R"(#version %u %s
 #define ANKI_UBO_BINDING(set_, binding_) binding = set_ * %u + binding_
 #define ANKI_UBO_BINDING(set_, binding_) binding = set_ * %u + binding_
 #define ANKI_SS_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_
 #define ANKI_TEX_BINDING(set_, binding_) binding = set_ * %u + binding_
+#define ANKI_IMAGE_BINDING(set_, binding_) binding = set_ * %u + binding_
 
 
 #if defined(FRAGMENT_SHADER)
 #if defined(FRAGMENT_SHADER)
 #define ANKI_USING_FRAG_COORD(height_) vec4 anki_fragCoord = gl_FragCoord;
 #define ANKI_USING_FRAG_COORD(height_) vec4 anki_fragCoord = gl_FragCoord;
@@ -106,6 +107,7 @@ Error ShaderImpl::init(ShaderType type, const CString& source)
 		MAX_UNIFORM_BUFFER_BINDINGS,
 		MAX_UNIFORM_BUFFER_BINDINGS,
 		MAX_STORAGE_BUFFER_BINDINGS,
 		MAX_STORAGE_BUFFER_BINDINGS,
 		MAX_TEXTURE_BINDINGS,
 		MAX_TEXTURE_BINDINGS,
+		MAX_IMAGE_BINDINGS,
 		&source[0]);
 		&source[0]);
 
 
 	// 2) Gen name, create, compile and link
 	// 2) Gen name, create, compile and link

+ 1 - 1
src/gr/gl/TextureImpl.cpp

@@ -635,7 +635,7 @@ void TextureImpl::clear(
 		height,
 		height,
 		1,
 		1,
 		m_format,
 		m_format,
-		m_type,
+		GL_FLOAT,
 		&clearValue.m_colorf[0]);
 		&clearValue.m_colorf[0]);
 }
 }
 
 

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

@@ -9,6 +9,7 @@
 #include <anki/gr/Pipeline.h>
 #include <anki/gr/Pipeline.h>
 #include <anki/gr/vulkan/CommandBufferImpl.h>
 #include <anki/gr/vulkan/CommandBufferImpl.h>
 #include <anki/gr/CommandBuffer.h>
 #include <anki/gr/CommandBuffer.h>
+#include <anki/gr/vulkan/TextureMisc.h>
 
 
 #include <anki/util/HashMap.h>
 #include <anki/util/HashMap.h>
 #include <anki/util/Hash.h>
 #include <anki/util/Hash.h>
@@ -107,6 +108,11 @@ GrManagerImpl::~GrManagerImpl()
 	m_perThread.destroy(getAllocator());
 	m_perThread.destroy(getAllocator());
 
 
 	// THIRD THING: Continue with the rest
 	// THIRD THING: Continue with the rest
+	if(m_texUploader)
+	{
+		getAllocator().deleteInstance(m_texUploader);
+	}
+
 	if(m_renderPasses)
 	if(m_renderPasses)
 	{
 	{
 		auto it = m_renderPasses->m_hashmap.getBegin();
 		auto it = m_renderPasses->m_hashmap.getBegin();
@@ -210,6 +216,9 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	m_fences.init(getAllocator(), m_device);
 	m_fences.init(getAllocator(), m_device);
 	m_semaphores.init(getAllocator(), m_device);
 	m_semaphores.init(getAllocator(), m_device);
 
 
+	m_texUploader = getAllocator().newInstance<TextureFallbackUploader>(this);
+	ANKI_CHECK(m_texUploader->init());
+
 	return ErrorCode::NONE;
 	return ErrorCode::NONE;
 }
 }
 
 

+ 44 - 0
src/gr/vulkan/TextureMisc.cpp

@@ -0,0 +1,44 @@
+// 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/TextureMisc.h>
+
+namespace anki
+{
+
+static_assert(MAX_RESOURCE_GROUPS == 2, "Code bellow assumes that");
+
+static const CString UPLOAD_R8G8B8_COMPUTE_SHADER = R"(
+const uint WORKGROUP_SIZE_X = 4u;
+
+layout(local_size_x = WORKGROUP_SIZE_X,
+	local_size_y = 1,
+	local_size_z = 1) in;
+	
+layout(push_constant) uniform pc0_ 
+{
+	uvec4 texSize;
+} u_regs;
+
+layout(std430, set = 2, binding = 0) buffer blk0_
+{
+	uint u_pixels[];
+};
+
+void main()
+{
+	// XXX
+}
+
+)";
+
+//==============================================================================
+Error TextureFallbackUploader::init()
+{
+
+	return ErrorCode::NONE;
+}
+
+} // end namespace anki

+ 145 - 0
tests/gr/Gr.cpp

@@ -229,6 +229,29 @@ void main()
 	out_color = vec4(col1 + col0, 1.0);
 	out_color = vec4(col1 + col0, 1.0);
 })";
 })";
 
 
+static const char* FRAG_SIMPLE_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_tex0;
+
+void main()
+{
+	out_color = textureLod(u_tex0, in_uv, 1.0);
+})";
+
+static const char* COMP_WRITE_IMAGE_SRC = R"(
+layout(ANKI_IMAGE_BINDING(0, 0), rgba8) writeonly uniform image2D u_img;
+
+layout(local_size_x = 1,
+	local_size_y = 1,
+	local_size_z = 1) in;
+	
+void main()
+{
+	vec4 col = vec4(1.0, 0.0, 0.0, 0.0);
+	imageStore(u_img, ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y), col);
+})";
+
 #define COMMON_BEGIN()                                                         \
 #define COMMON_BEGIN()                                                         \
 	NativeWindow* win = nullptr;                                               \
 	NativeWindow* win = nullptr;                                               \
 	GrManager* gr = nullptr;                                                   \
 	GrManager* gr = nullptr;                                                   \
@@ -1252,4 +1275,126 @@ ANKI_TEST(Gr, DrawWithSecondLevel)
 	COMMON_END();
 	COMMON_END();
 }
 }
 
 
+//==============================================================================
+ANKI_TEST(Gr, ImageLoadStore)
+{
+	COMMON_BEGIN();
+	{
+		TextureInitInfo init;
+		init.m_width = init.m_height = 4;
+		init.m_mipmapsCount = 2;
+		init.m_usage = TextureUsageBit::CLEAR
+			| TextureUsageBit::ANY_SHADER_SAMPLED
+			| TextureUsageBit::COMPUTE_SHADER_IMAGE_WRITE;
+		init.m_type = TextureType::_2D;
+		init.m_format =
+			PixelFormat(ComponentFormat::R8G8B8A8, TransformFormat::UNORM);
+		init.m_sampling.m_mipmapFilter = SamplingFilter::LINEAR;
+
+		TexturePtr tex = gr->newInstance<Texture>(init);
+
+		// Ppline
+		PipelinePtr ppline =
+			createSimplePpline(VERT_QUAD_SRC, FRAG_SIMPLE_TEX_SRC, *gr);
+
+		// Create shader & compute ppline
+		ShaderPtr shader =
+			gr->newInstance<Shader>(ShaderType::COMPUTE, COMP_WRITE_IMAGE_SRC);
+
+		PipelineInitInfo ppinit;
+		ppinit.m_shaders[ShaderType::COMPUTE] = shader;
+		PipelinePtr compPpline = gr->newInstance<Pipeline>(ppinit);
+
+		// RC group
+		ResourceGroupInitInfo rcinit;
+		rcinit.m_textures[0].m_texture = tex;
+		ResourceGroupPtr rc0 = gr->newInstance<ResourceGroup>(rcinit);
+
+		rcinit = ResourceGroupInitInfo();
+		rcinit.m_images[0].m_texture = tex;
+		rcinit.m_images[0].m_level = 1;
+		ResourceGroupPtr rc1 = gr->newInstance<ResourceGroup>(rcinit);
+
+		// FB
+		FramebufferPtr dfb = createDefaultFb(*gr);
+
+		// Write texture data
+		CommandBufferInitInfo cmdbinit;
+		CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cmdbinit);
+
+		cmdb->setTextureBarrier(tex,
+			TextureUsageBit::NONE,
+			TextureUsageBit::CLEAR,
+			TextureSurfaceInfo(0, 0, 0, 0));
+
+		ClearValue clear;
+		clear.m_colorf = {{0.0, 1.0, 0.0, 1.0}};
+		cmdb->clearTexture(tex, TextureSurfaceInfo(0, 0, 0, 0), clear);
+
+		cmdb->setTextureBarrier(tex,
+			TextureUsageBit::CLEAR,
+			TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+			TextureSurfaceInfo(0, 0, 0, 0));
+
+		cmdb->setTextureBarrier(tex,
+			TextureUsageBit::NONE,
+			TextureUsageBit::CLEAR,
+			TextureSurfaceInfo(1, 0, 0, 0));
+
+		clear.m_colorf = {{0.0, 0.0, 1.0, 1.0}};
+		cmdb->clearTexture(tex, TextureSurfaceInfo(1, 0, 0, 0), clear);
+
+		cmdb->setTextureBarrier(tex,
+			TextureUsageBit::CLEAR,
+			TextureUsageBit::COMPUTE_SHADER_IMAGE_WRITE,
+			TextureSurfaceInfo(1, 0, 0, 0));
+
+		cmdb->flush();
+
+		const U ITERATION_COUNT = 100;
+		U iterations = ITERATION_COUNT;
+		while(iterations--)
+		{
+			HighRezTimer timer;
+			timer.start();
+			gr->beginFrame();
+
+			CommandBufferInitInfo cinit;
+			CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cinit);
+
+			// Write iamge
+			cmdb->bindPipeline(compPpline);
+			cmdb->bindResourceGroup(rc1, 0, nullptr);
+			cmdb->dispatchCompute(WIDTH / 2, HEIGHT / 2, 1);
+			cmdb->setTextureBarrier(tex,
+				TextureUsageBit::COMPUTE_SHADER_IMAGE_WRITE,
+				TextureUsageBit::FRAGMENT_SHADER_SAMPLED,
+				TextureSurfaceInfo(1, 0, 0, 0));
+
+			// Present image
+			cmdb->setPolygonOffset(0.0, 0.0);
+			cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+
+			cmdb->bindPipeline(ppline);
+			cmdb->beginRenderPass(dfb);
+			cmdb->bindResourceGroup(rc0, 0, nullptr);
+			cmdb->drawArrays(6);
+			cmdb->endRenderPass();
+
+			cmdb->flush();
+
+			// End
+			gr->swapBuffers();
+
+			timer.stop();
+			const F32 TICK = 1.0 / 30.0;
+			if(timer.getElapsedTime() < TICK)
+			{
+				HighRezTimer::sleep(TICK - timer.getElapsedTime());
+			}
+		}
+	}
+	COMMON_END();
+}
+
 } // end namespace anki
 } // end namespace anki