Browse Source

Fix bugs and enable some unit testing on Android

Panagiotis Christopoulos Charitos 4 years ago
parent
commit
97c029dae2

+ 22 - 3
AnKi/Core/NativeWindowAndroid.cpp

@@ -23,9 +23,9 @@ Error NativeWindow::init(NativeWindowInitInfo& init, HeapAllocator<U8>& alloc)
 		android_poll_source* source;
 		android_poll_source* source;
 
 
 		const int timeoutMs = 5;
 		const int timeoutMs = 5;
-		while((ident = ALooper_pollAll(timeoutMs, NULL, &events, reinterpret_cast<void**>(&source))) >= 0)
+		while((ident = ALooper_pollAll(timeoutMs, nullptr, &events, reinterpret_cast<void**>(&source))) >= 0)
 		{
 		{
-			if(source != NULL)
+			if(source != nullptr)
 			{
 			{
 				source->process(g_androidApp, source);
 				source->process(g_androidApp, source);
 			}
 			}
@@ -43,7 +43,26 @@ Error NativeWindow::init(NativeWindowInitInfo& init, HeapAllocator<U8>& alloc)
 
 
 void NativeWindow::destroy()
 void NativeWindow::destroy()
 {
 {
-	// Nothing
+	ANKI_CORE_LOGI("Destroying Android window");
+	ANativeActivity_finish(g_androidApp->activity);
+
+	// Loop until destroyRequested is set
+	while(!g_androidApp->destroyRequested)
+	{
+		int ident;
+		int events;
+		android_poll_source* source;
+
+		while((ident = ALooper_pollAll(0, nullptr, &events, reinterpret_cast<void**>(&source))) >= 0)
+		{
+			if(source != nullptr)
+			{
+				source->process(g_androidApp, source);
+			}
+		}
+	}
+
+	m_alloc.deleteInstance(m_impl);
 }
 }
 
 
 void NativeWindow::setWindowTitle(CString title)
 void NativeWindow::setWindowTitle(CString title)

+ 4 - 4
AnKi/Gr/Vulkan/BufferImpl.cpp

@@ -26,12 +26,12 @@ BufferImpl::~BufferImpl()
 #if ANKI_EXTRA_CHECKS
 #if ANKI_EXTRA_CHECKS
 	if(m_needsFlush && m_flushCount.load() == 0)
 	if(m_needsFlush && m_flushCount.load() == 0)
 	{
 	{
-		ANKI_VK_LOGW("Buffer needed flushing but you never flushed");
+		ANKI_VK_LOGW("Buffer needed flushing but you never flushed: %s", getName().cstr());
 	}
 	}
 
 
 	if(m_needsInvalidate && m_invalidateCount.load() == 0)
 	if(m_needsInvalidate && m_invalidateCount.load() == 0)
 	{
 	{
-		ANKI_VK_LOGW("Buffer needed invalidation but you never invalidated");
+		ANKI_VK_LOGW("Buffer needed invalidation but you never invalidated: %s", getName().cstr());
 	}
 	}
 #endif
 #endif
 }
 }
@@ -154,12 +154,12 @@ Error BufferImpl::init(const BufferInitInfo& inf)
 	const VkPhysicalDeviceMemoryProperties& props = getGrManagerImpl().getMemoryProperties();
 	const VkPhysicalDeviceMemoryProperties& props = getGrManagerImpl().getMemoryProperties();
 	m_memoryFlags = props.memoryTypes[memIdx].propertyFlags;
 	m_memoryFlags = props.memoryTypes[memIdx].propertyFlags;
 
 
-	if(!!(m_access & BufferMapAccessBit::READ) && !(m_memoryFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
+	if(!!(access & BufferMapAccessBit::READ) && !(m_memoryFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
 	{
 	{
 		m_needsInvalidate = true;
 		m_needsInvalidate = true;
 	}
 	}
 
 
-	if(!!(m_access & BufferMapAccessBit::WRITE) && !(m_memoryFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
+	if(!!(access & BufferMapAccessBit::WRITE) && !(m_memoryFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
 	{
 	{
 		m_needsFlush = true;
 		m_needsFlush = true;
 	}
 	}

+ 3 - 3
AnKi/Gr/Vulkan/PipelineCache.cpp

@@ -72,11 +72,13 @@ Error PipelineCache::init(VkDevice dev, VkPhysicalDevice pdev, CString cacheDir,
 
 
 void PipelineCache::destroy(VkDevice dev, VkPhysicalDevice pdev, GrAllocator<U8> alloc)
 void PipelineCache::destroy(VkDevice dev, VkPhysicalDevice pdev, GrAllocator<U8> alloc)
 {
 {
-	Error err = destroyInternal(dev, pdev, alloc);
+	const Error err = destroyInternal(dev, pdev, alloc);
 	if(err)
 	if(err)
 	{
 	{
 		ANKI_VK_LOGE("An error occurred while storing the pipeline cache to disk. Will ignore");
 		ANKI_VK_LOGE("An error occurred while storing the pipeline cache to disk. Will ignore");
 	}
 	}
+
+	m_dumpFilename.destroy(alloc);
 }
 }
 
 
 Error PipelineCache::destroyInternal(VkDevice dev, VkPhysicalDevice pdev, GrAllocator<U8> alloc)
 Error PipelineCache::destroyInternal(VkDevice dev, VkPhysicalDevice pdev, GrAllocator<U8> alloc)
@@ -115,8 +117,6 @@ Error PipelineCache::destroyInternal(VkDevice dev, VkPhysicalDevice pdev, GrAllo
 		m_cacheHandle = VK_NULL_HANDLE;
 		m_cacheHandle = VK_NULL_HANDLE;
 	}
 	}
 
 
-	m_dumpFilename.destroy(alloc);
-
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 

+ 3 - 2
AnKi/Gr/Vulkan/ShaderImpl.cpp

@@ -8,7 +8,7 @@
 #include <AnKi/Gr/Utils/Functions.h>
 #include <AnKi/Gr/Utils/Functions.h>
 #include <SprivCross/spirv_cross.hpp>
 #include <SprivCross/spirv_cross.hpp>
 
 
-#define ANKI_DUMP_SHADERS ANKI_EXTRA_CHECKS
+#define ANKI_DUMP_SHADERS 0
 
 
 #if ANKI_DUMP_SHADERS
 #if ANKI_DUMP_SHADERS
 #	include <AnKi/Util/File.h>
 #	include <AnKi/Util/File.h>
@@ -61,7 +61,8 @@ Error ShaderImpl::init(const ShaderInitInfo& inf)
 		fnameSpirv.sprintf("%s/%05u.spv", getManager().getCacheDirectory().cstr(), getUuid());
 		fnameSpirv.sprintf("%s/%05u.spv", getManager().getCacheDirectory().cstr(), getUuid());
 
 
 		File fileSpirv;
 		File fileSpirv;
-		ANKI_CHECK(fileSpirv.open(fnameSpirv.toCString(), FileOpenFlag::BINARY | FileOpenFlag::WRITE));
+		ANKI_CHECK(
+			fileSpirv.open(fnameSpirv.toCString(), FileOpenFlag::BINARY | FileOpenFlag::WRITE | FileOpenFlag::SPECIAL));
 		ANKI_CHECK(fileSpirv.write(&inf.m_binary[0], inf.m_binary.getSize()));
 		ANKI_CHECK(fileSpirv.write(&inf.m_binary[0], inf.m_binary.getSize()));
 	}
 	}
 #endif
 #endif

+ 2 - 0
AnKi/Input/CMakeLists.txt

@@ -2,6 +2,8 @@ set(SOURCES Input.cpp)
 
 
 if(SDL)
 if(SDL)
 	set(SOURCES ${SOURCES} InputSdl.cpp)
 	set(SOURCES ${SOURCES} InputSdl.cpp)
+elseif(ANDROID)
+set(SOURCES ${SOURCES} InputAndroid.cpp)
 else()
 else()
 	set(SOURCES ${SOURCES} InputDummy.cpp)
 	set(SOURCES ${SOURCES} InputDummy.cpp)
 endif()
 endif()

+ 21 - 18
AnKi/Input/InputAndroid.cpp

@@ -7,51 +7,54 @@
 #include <AnKi/Core/NativeWindowAndroid.h>
 #include <AnKi/Core/NativeWindowAndroid.h>
 #include <AnKi/Util/Logger.h>
 #include <AnKi/Util/Logger.h>
 #include <AnKi/Core/App.h>
 #include <AnKi/Core/App.h>
+#if ANKI_OS_ANDROID
+#	include <android_native_app_glue.h>
+#endif
 
 
 namespace anki
 namespace anki
 {
 {
 
 
 static void handleAndroidEvents(android_app* app, int32_t cmd)
 static void handleAndroidEvents(android_app* app, int32_t cmd)
 {
 {
-	Input* input = (Input*)app->userData;
+	Input* input = static_cast<Input*>(app->userData);
 	ANKI_ASSERT(input != nullptr);
 	ANKI_ASSERT(input != nullptr);
 
 
 	switch(cmd)
 	switch(cmd)
 	{
 	{
 	case APP_CMD_TERM_WINDOW:
 	case APP_CMD_TERM_WINDOW:
 	case APP_CMD_LOST_FOCUS:
 	case APP_CMD_LOST_FOCUS:
-		ANKI_LOGI("New event 0x%x", cmd);
-		input->addEvent(Input::WINDOW_CLOSED_EVENT);
+		input->addEvent(InputEvent::WINDOW_CLOSED);
 		break;
 		break;
 	}
 	}
 }
 }
 
 
-Input::~Input()
-{
-}
-
-void Input::handleEvents()
+Error Input::handleEvents()
 {
 {
 	int ident;
 	int ident;
-	int outEvents;
+	int events;
 	android_poll_source* source;
 	android_poll_source* source;
 
 
-	zeroMemory(events);
-
-	while((ident = ALooper_pollAll(0, NULL, &outEvents, (void**)&source)) >= 0)
+	while((ident = ALooper_pollAll(0, nullptr, &events, reinterpret_cast<void**>(&source))) >= 0)
 	{
 	{
-		if(source != NULL)
+		if(source != nullptr)
 		{
 		{
-			source->process(gAndroidApp, source);
+			source->process(g_androidApp, source);
 		}
 		}
 	}
 	}
+
+	return Error::NONE;
+}
+
+Error Input::initInternal(NativeWindow*)
+{
+	g_androidApp->userData = this;
+	g_androidApp->onAppCmd = handleAndroidEvents;
+
+	return Error::NONE;
 }
 }
 
 
-void Input::init(NativeWindow* /*nativeWindow*/)
+void Input::destroy()
 {
 {
-	ANKI_ASSERT(gAndroidApp);
-	gAndroidApp->userData = this;
-	gAndroidApp->onAppCmd = handleAndroidEvents;
 }
 }
 
 
 void Input::moveCursor(const Vec2& posNdc)
 void Input::moveCursor(const Vec2& posNdc)

+ 12 - 0
AnKi/Util/FilesystemPosix.cpp

@@ -19,6 +19,9 @@
 #include <ftw.h> // For walkDirectoryTree
 #include <ftw.h> // For walkDirectoryTree
 #include <cstdlib>
 #include <cstdlib>
 #include <time.h>
 #include <time.h>
+#if ANKI_OS_ANDROID
+#	include <android_native_app_glue.h>
+#endif
 
 
 #ifndef USE_FDS
 #ifndef USE_FDS
 #	define USE_FDS 15
 #	define USE_FDS 15
@@ -195,6 +198,7 @@ Error createDirectory(const CString& dir)
 
 
 Error getHomeDirectory(StringAuto& out)
 Error getHomeDirectory(StringAuto& out)
 {
 {
+#if ANKI_OS_LINUX
 	const char* home = getenv("HOME");
 	const char* home = getenv("HOME");
 	if(home == nullptr)
 	if(home == nullptr)
 	{
 	{
@@ -203,12 +207,20 @@ Error getHomeDirectory(StringAuto& out)
 	}
 	}
 
 
 	out.create(home);
 	out.create(home);
+#else
+	out.create(g_androidApp->activity->internalDataPath);
+#endif
+
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
 Error getTempDirectory(StringAuto& out)
 Error getTempDirectory(StringAuto& out)
 {
 {
+#if ANKI_OS_LINUX
 	out.create("/tmp/");
 	out.create("/tmp/");
+#else
+	out.create(g_androidApp->activity->internalDataPath);
+#endif
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 

+ 12 - 2
Tests/Framework/Framework.cpp

@@ -4,6 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <Tests/Framework/Framework.h>
 #include <Tests/Framework/Framework.h>
+#include <AnKi/Util/Filesystem.h>
 #include <iostream>
 #include <iostream>
 #include <cstring>
 #include <cstring>
 #include <malloc.h>
 #include <malloc.h>
@@ -233,7 +234,7 @@ void initConfig(ConfigSet& cfg)
 	cfg.set("rsrc_dataPaths", ".:..");
 	cfg.set("rsrc_dataPaths", ".:..");
 }
 }
 
 
-NativeWindow* createWindow(const ConfigSet& cfg)
+NativeWindow* createWindow(ConfigSet& cfg)
 {
 {
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
 
 
@@ -245,6 +246,9 @@ NativeWindow* createWindow(const ConfigSet& cfg)
 
 
 	ANKI_TEST_EXPECT_NO_ERR(win->init(inf, alloc));
 	ANKI_TEST_EXPECT_NO_ERR(win->init(inf, alloc));
 
 
+	cfg.set("width", win->getWidth());
+	cfg.set("height", win->getHeight());
+
 	return win;
 	return win;
 }
 }
 
 
@@ -252,7 +256,13 @@ GrManager* createGrManager(const ConfigSet& cfg, NativeWindow* win)
 {
 {
 	GrManagerInitInfo inf;
 	GrManagerInitInfo inf;
 	inf.m_allocCallback = allocAligned;
 	inf.m_allocCallback = allocAligned;
-	inf.m_cacheDirectory = "./";
+	StringAuto home(HeapAllocator<U8>(allocAligned, nullptr));
+	const Error err = getTempDirectory(home);
+	if(err)
+	{
+		return nullptr;
+	}
+	inf.m_cacheDirectory = home;
 	inf.m_config = &cfg;
 	inf.m_config = &cfg;
 	inf.m_window = win;
 	inf.m_window = win;
 	GrManager* gr;
 	GrManager* gr;

+ 1 - 1
Tests/Framework/Framework.h

@@ -223,7 +223,7 @@ extern void deleteTesterSingleton();
 
 
 void initConfig(ConfigSet& cfg);
 void initConfig(ConfigSet& cfg);
 
 
-NativeWindow* createWindow(const ConfigSet& cfg);
+NativeWindow* createWindow(ConfigSet& cfg);
 
 
 GrManager* createGrManager(const ConfigSet& cfg, NativeWindow* win);
 GrManager* createGrManager(const ConfigSet& cfg, NativeWindow* win);
 
 

+ 67 - 47
Tests/Gr/Gr.cpp

@@ -1,3 +1,4 @@
+
 // Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
 // Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
 // All rights reserved.
 // All rights reserved.
 // Code licensed under the BSD License.
 // Code licensed under the BSD License.
@@ -6,6 +7,7 @@
 #include <Tests/Framework/Framework.h>
 #include <Tests/Framework/Framework.h>
 #include <AnKi/Gr.h>
 #include <AnKi/Gr.h>
 #include <AnKi/Core/NativeWindow.h>
 #include <AnKi/Core/NativeWindow.h>
+#include <AnKi/Input/Input.h>
 #include <AnKi/Core/ConfigSet.h>
 #include <AnKi/Core/ConfigSet.h>
 #include <AnKi/Util/HighRezTimer.h>
 #include <AnKi/Util/HighRezTimer.h>
 #include <AnKi/Core/StagingGpuMemoryManager.h>
 #include <AnKi/Core/StagingGpuMemoryManager.h>
@@ -174,43 +176,6 @@ void main()
 	out_color = texture(u_tex0, in_uv);
 	out_color = texture(u_tex0, in_uv);
 })";
 })";
 
 
-static const char* FRAG_2TEX_SRC = R"(layout (location = 0) out vec4 out_color;
-
-layout(location = 0) in vec2 in_uv;
-
-layout(set = 0, binding = 0) uniform sampler2D u_tex0;
-layout(set = 0, binding = 1) uniform sampler2D u_tex1;
-
-void main()
-{
-	if(gl_FragCoord.x < 1024 / 2)
-	{
-		if(gl_FragCoord.y < 768 / 2)
-		{
-			vec2 uv = in_uv * 2.0;
-			out_color = textureLod(u_tex0, uv, 0.0);
-		}
-		else
-		{
-			vec2 uv = in_uv * 2.0 - vec2(0.0, 1.0);
-			out_color = textureLod(u_tex0, uv, 1.0);
-		}
-	}
-	else
-	{
-		if(gl_FragCoord.y < 768 / 2)
-		{
-			vec2 uv = in_uv * 2.0 - vec2(1.0, 0.0);
-			out_color = textureLod(u_tex1, uv, 0.0);
-		}
-		else
-		{
-			vec2 uv = in_uv * 2.0 - vec2(1.0, 1.0);
-			out_color = textureLod(u_tex1, uv, 1.0);
-		}
-	}
-})";
-
 static const char* FRAG_TEX3D_SRC = R"(layout (location = 0) out vec4 out_color;
 static const char* FRAG_TEX3D_SRC = R"(layout (location = 0) out vec4 out_color;
 
 
 layout(set = 0, binding = 0) uniform u0_
 layout(set = 0, binding = 0) uniform u0_
@@ -288,6 +253,7 @@ void main()
 static NativeWindow* win = nullptr;
 static NativeWindow* win = nullptr;
 static GrManager* gr = nullptr;
 static GrManager* gr = nullptr;
 static StagingGpuMemoryManager* stagingMem = nullptr;
 static StagingGpuMemoryManager* stagingMem = nullptr;
+static Input* input = nullptr;
 
 
 #define COMMON_BEGIN() \
 #define COMMON_BEGIN() \
 	stagingMem = new StagingGpuMemoryManager(); \
 	stagingMem = new StagingGpuMemoryManager(); \
@@ -299,6 +265,8 @@ static StagingGpuMemoryManager* stagingMem = nullptr;
 	cfg.set("gr_rayTracing", true); \
 	cfg.set("gr_rayTracing", true); \
 	cfg.set("gr_debugMarkers", true); \
 	cfg.set("gr_debugMarkers", true); \
 	win = createWindow(cfg); \
 	win = createWindow(cfg); \
+	input = new Input(); \
+	ANKI_TEST_EXPECT_NO_ERR(input->init(win)); \
 	gr = createGrManager(cfg, win); \
 	gr = createGrManager(cfg, win); \
 	ANKI_TEST_EXPECT_NO_ERR(stagingMem->init(gr, cfg)); \
 	ANKI_TEST_EXPECT_NO_ERR(stagingMem->init(gr, cfg)); \
 	TransferGpuAllocator* transfAlloc = new TransferGpuAllocator(); \
 	TransferGpuAllocator* transfAlloc = new TransferGpuAllocator(); \
@@ -313,6 +281,7 @@ static StagingGpuMemoryManager* stagingMem = nullptr;
 	delete transfAlloc; \
 	delete transfAlloc; \
 	delete stagingMem; \
 	delete stagingMem; \
 	GrManager::deleteInstance(gr); \
 	GrManager::deleteInstance(gr); \
+	delete input; \
 	delete win; \
 	delete win; \
 	win = nullptr; \
 	win = nullptr; \
 	gr = nullptr; \
 	gr = nullptr; \
@@ -510,7 +479,7 @@ ANKI_TEST(Gr, SimpleDrawcall)
 		cinit.m_flags = CommandBufferFlag::GENERAL_WORK;
 		cinit.m_flags = CommandBufferFlag::GENERAL_WORK;
 		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
 		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
 
 
-		cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+		cmdb->setViewport(0, 0, win->getWidth(), win->getHeight());
 		cmdb->bindShaderProgram(prog);
 		cmdb->bindShaderProgram(prog);
 		presentBarrierA(cmdb, presentTex);
 		presentBarrierA(cmdb, presentTex);
 		cmdb->beginRenderPass(fb, {{TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}}, {});
 		cmdb->beginRenderPass(fb, {{TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}}, {});
@@ -703,8 +672,8 @@ ANKI_TEST(Gr, ViewportAndScissorOffscreen)
 		cmdb->endRenderPass();
 		cmdb->endRenderPass();
 
 
 		// Draw onscreen
 		// Draw onscreen
-		cmdb->setViewport(0, 0, WIDTH, HEIGHT);
-		cmdb->setScissor(0, 0, WIDTH, HEIGHT);
+		cmdb->setViewport(0, 0, win->getWidth(), win->getHeight());
+		cmdb->setScissor(0, 0, win->getWidth(), win->getHeight());
 		cmdb->bindShaderProgram(blitProg);
 		cmdb->bindShaderProgram(blitProg);
 		cmdb->setTextureSurfaceBarrier(rt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
 		cmdb->setTextureSurfaceBarrier(rt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
 									   TextureUsageBit::SAMPLED_FRAGMENT, TextureSurfaceInfo(0, 0, 0, 0));
 									   TextureUsageBit::SAMPLED_FRAGMENT, TextureSurfaceInfo(0, 0, 0, 0));
@@ -734,10 +703,17 @@ ANKI_TEST(Gr, Buffer)
 {
 {
 	COMMON_BEGIN()
 	COMMON_BEGIN()
 
 
-	BufferPtr a = gr->newBuffer(BufferInitInfo(512, BufferUsageBit::ALL_UNIFORM, BufferMapAccessBit::NONE));
+	BufferInitInfo buffInit("a");
+	buffInit.m_size = 512;
+	buffInit.m_usage = BufferUsageBit::ALL_UNIFORM;
+	buffInit.m_mapAccess = BufferMapAccessBit::NONE;
+	BufferPtr a = gr->newBuffer(buffInit);
 
 
-	BufferPtr b = gr->newBuffer(
-		BufferInitInfo(64, BufferUsageBit::ALL_STORAGE, BufferMapAccessBit::WRITE | BufferMapAccessBit::READ));
+	buffInit.setName("b");
+	buffInit.m_size = 64;
+	buffInit.m_usage = BufferUsageBit::ALL_STORAGE;
+	buffInit.m_mapAccess = BufferMapAccessBit::WRITE | BufferMapAccessBit::READ;
+	BufferPtr b = gr->newBuffer(buffInit);
 
 
 	void* ptr = b->map(0, 64, BufferMapAccessBit::WRITE);
 	void* ptr = b->map(0, 64, BufferMapAccessBit::WRITE);
 	ANKI_TEST_EXPECT_NEQ(ptr, nullptr);
 	ANKI_TEST_EXPECT_NEQ(ptr, nullptr);
@@ -785,7 +761,7 @@ ANKI_TEST(Gr, DrawWithUniforms)
 		cinit.m_flags = CommandBufferFlag::GENERAL_WORK;
 		cinit.m_flags = CommandBufferFlag::GENERAL_WORK;
 		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
 		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
 
 
-		cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+		cmdb->setViewport(0, 0, win->getWidth(), win->getHeight());
 		cmdb->bindShaderProgram(prog);
 		cmdb->bindShaderProgram(prog);
 		presentBarrierA(cmdb, presentTex);
 		presentBarrierA(cmdb, presentTex);
 		cmdb->beginRenderPass(fb, {TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}, {});
 		cmdb->beginRenderPass(fb, {TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}, {});
@@ -875,7 +851,7 @@ ANKI_TEST(Gr, DrawWithVertex)
 		cmdb->setVertexAttribute(1, 0, Format::R8G8B8_UNORM, sizeof(Vec3));
 		cmdb->setVertexAttribute(1, 0, Format::R8G8B8_UNORM, sizeof(Vec3));
 		cmdb->setVertexAttribute(2, 1, Format::R32G32B32_SFLOAT, 0);
 		cmdb->setVertexAttribute(2, 1, Format::R32G32B32_SFLOAT, 0);
 
 
-		cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+		cmdb->setViewport(0, 0, win->getWidth(), win->getHeight());
 		cmdb->setPolygonOffset(0.0, 0.0);
 		cmdb->setPolygonOffset(0.0, 0.0);
 		cmdb->bindShaderProgram(prog);
 		cmdb->bindShaderProgram(prog);
 		presentBarrierA(cmdb, presentTex);
 		presentBarrierA(cmdb, presentTex);
@@ -1040,6 +1016,47 @@ ANKI_TEST(Gr, DrawWithTexture)
 	//
 	//
 	// Create prog
 	// Create prog
 	//
 	//
+	static const char* FRAG_2TEX_SRC = R"(layout (location = 0) out vec4 out_color;
+
+layout(location = 0) in vec2 in_uv;
+
+layout(set = 0, binding = 0) uniform sampler2D u_tex0;
+layout(set = 0, binding = 1) uniform sampler2D u_tex1;
+
+layout(push_constant) uniform b_pc
+{
+	Vec4 u_viewport;
+};
+
+void main()
+{
+	if(gl_FragCoord.x < u_viewport.x / 2.0)
+	{
+		if(gl_FragCoord.y < u_viewport.y / 2.0)
+		{
+			vec2 uv = in_uv * 2.0;
+			out_color = textureLod(u_tex0, uv, 0.0);
+		}
+		else
+		{
+			vec2 uv = in_uv * 2.0 - vec2(0.0, 1.0);
+			out_color = textureLod(u_tex0, uv, 1.0);
+		}
+	}
+	else
+	{
+		if(gl_FragCoord.y < u_viewport.y / 2.0)
+		{
+			vec2 uv = in_uv * 2.0 - vec2(1.0, 0.0);
+			out_color = textureLod(u_tex1, uv, 0.0);
+		}
+		else
+		{
+			vec2 uv = in_uv * 2.0 - vec2(1.0, 1.0);
+			out_color = textureLod(u_tex1, uv, 1.0);
+		}
+	}
+})";
 	ShaderProgramPtr prog = createProgram(VERT_QUAD_SRC, FRAG_2TEX_SRC, *gr);
 	ShaderProgramPtr prog = createProgram(VERT_QUAD_SRC, FRAG_2TEX_SRC, *gr);
 
 
 	//
 	//
@@ -1059,11 +1076,14 @@ ANKI_TEST(Gr, DrawWithTexture)
 		cinit.m_flags = CommandBufferFlag::GENERAL_WORK | CommandBufferFlag::SMALL_BATCH;
 		cinit.m_flags = CommandBufferFlag::GENERAL_WORK | CommandBufferFlag::SMALL_BATCH;
 		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
 		CommandBufferPtr cmdb = gr->newCommandBuffer(cinit);
 
 
-		cmdb->setViewport(0, 0, WIDTH, HEIGHT);
+		cmdb->setViewport(0, 0, win->getWidth(), win->getHeight());
 		cmdb->bindShaderProgram(prog);
 		cmdb->bindShaderProgram(prog);
 		presentBarrierA(cmdb, presentTex);
 		presentBarrierA(cmdb, presentTex);
 		cmdb->beginRenderPass(fb, {TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}, {});
 		cmdb->beginRenderPass(fb, {TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE}, {});
 
 
+		Vec4 pc(F32(win->getWidth()), F32(win->getHeight()), 0.0f, 0.0f);
+		cmdb->setPushConstants(&pc, sizeof(pc));
+
 		cmdb->bindTextureAndSampler(0, 0, aView, sampler);
 		cmdb->bindTextureAndSampler(0, 0, aView, sampler);
 		cmdb->bindTextureAndSampler(0, 1, bView, sampler);
 		cmdb->bindTextureAndSampler(0, 1, bView, sampler);
 		cmdb->drawArrays(PrimitiveTopology::TRIANGLES, 6);
 		cmdb->drawArrays(PrimitiveTopology::TRIANGLES, 6);
@@ -2537,7 +2557,7 @@ void main()
 }
 }
 		)";
 		)";
 
 
-		StringAuto fragSrc(HeapAllocator<U8>{allocAligned, nullptr});
+		StringAuto fragSrc(HeapAllocator<U8>(allocAligned, nullptr));
 		if(useRayTracing)
 		if(useRayTracing)
 		{
 		{
 			fragSrc.append("#define USE_RAY_TRACING 1\n");
 			fragSrc.append("#define USE_RAY_TRACING 1\n");