Add basic WSI rendering test

Bug: 27536551
Bug: 27641433
Change-Id: Icc74ea9c6d669ced21e536ee96f0dd04d5e47343
diff --git a/android/cts/master/com.drawelements.deqp.vk.xml b/android/cts/master/com.drawelements.deqp.vk.xml
index 9c4e1d4..ea34bef 100644
--- a/android/cts/master/com.drawelements.deqp.vk.xml
+++ b/android/cts/master/com.drawelements.deqp.vk.xml
@@ -261362,6 +261362,11 @@
 							<TestInstance/>
 						</Test>
 					</TestCase>
+					<TestCase name="render">
+						<Test name="basic">
+							<TestInstance/>
+						</Test>
+					</TestCase>
 				</TestSuite>
 			</TestSuite>
 		</TestSuite>
diff --git a/android/cts/master/vk-master.txt b/android/cts/master/vk-master.txt
index 94fd6a8..8e09788 100644
--- a/android/cts/master/vk-master.txt
+++ b/android/cts/master/vk-master.txt
@@ -81128,6 +81128,7 @@
 dEQP-VK.wsi.android.swapchain.simulate_oom.composite_alpha
 dEQP-VK.wsi.android.swapchain.simulate_oom.present_mode
 dEQP-VK.wsi.android.swapchain.simulate_oom.clipped
+dEQP-VK.wsi.android.swapchain.render.basic
 dEQP-VK.synchronization.fences
 dEQP-VK.synchronization.semaphores
 dEQP-VK.synchronization.events
diff --git a/external/vulkancts/framework/vulkan/vkBasicTypes.inl b/external/vulkancts/framework/vulkan/vkBasicTypes.inl
index 5e5db01..a00b8e3 100644
--- a/external/vulkancts/framework/vulkan/vkBasicTypes.inl
+++ b/external/vulkancts/framework/vulkan/vkBasicTypes.inl
@@ -9,6 +9,7 @@
 enum { VK_MAX_MEMORY_HEAPS				= 16						};
 enum { VK_MAX_DESCRIPTION_SIZE			= 256						};
 enum { VK_ATTACHMENT_UNUSED				= (~0U)						};
+enum { VK_SUBPASS_EXTERNAL				= (~0U)						};
 
 VK_DEFINE_HANDLE					(VkInstance,				HANDLE_TYPE_INSTANCE);
 VK_DEFINE_HANDLE					(VkPhysicalDevice,			HANDLE_TYPE_PHYSICAL_DEVICE);
diff --git a/external/vulkancts/framework/vulkan/vkWsiUtil.cpp b/external/vulkancts/framework/vulkan/vkWsiUtil.cpp
index 6f0968d..d94610b 100644
--- a/external/vulkancts/framework/vulkan/vkWsiUtil.cpp
+++ b/external/vulkancts/framework/vulkan/vkWsiUtil.cpp
@@ -310,5 +310,25 @@
 		return std::vector<VkPresentModeKHR>();
 }
 
+std::vector<VkImage> getSwapchainImages (const DeviceInterface&			vkd,
+					 					 VkDevice						device,
+					 					 VkSwapchainKHR					swapchain)
+{
+	deUint32	numImages	= 0;
+
+	VK_CHECK(vkd.getSwapchainImagesKHR(device, swapchain, &numImages, DE_NULL));
+
+	if (numImages > 0)
+	{
+		std::vector<VkImage>	images	(numImages);
+
+		VK_CHECK(vkd.getSwapchainImagesKHR(device, swapchain, &numImages, &images[0]));
+
+		return images;
+	}
+	else
+		return std::vector<VkImage>();
+}
+
 } // wsi
 } // vk
diff --git a/external/vulkancts/framework/vulkan/vkWsiUtil.hpp b/external/vulkancts/framework/vulkan/vkWsiUtil.hpp
index e7da7d7..bee82de 100644
--- a/external/vulkancts/framework/vulkan/vkWsiUtil.hpp
+++ b/external/vulkancts/framework/vulkan/vkWsiUtil.hpp
@@ -94,6 +94,10 @@
 																		 VkPhysicalDevice				physicalDevice,
 																		 VkSurfaceKHR					surface);
 
+std::vector<VkImage>			getSwapchainImages						(const DeviceInterface&			vkd,
+																		 VkDevice						device,
+																		 VkSwapchainKHR					swapchain);
+
 } // wsi
 } // vk
 
diff --git a/external/vulkancts/gen_framework.py b/external/vulkancts/gen_framework.py
index 4cf7263..e6d9e55 100644
--- a/external/vulkancts/gen_framework.py
+++ b/external/vulkancts/gen_framework.py
@@ -113,6 +113,7 @@
 	"VK_MAX_MEMORY_HEAPS",
 	"VK_MAX_DESCRIPTION_SIZE",
 	"VK_ATTACHMENT_UNUSED",
+	"VK_SUBPASS_EXTERNAL"
 ]
 
 PLATFORM_TYPES		= [
diff --git a/external/vulkancts/modules/vulkan/wsi/vktWsiSwapchainTests.cpp b/external/vulkancts/modules/vulkan/wsi/vktWsiSwapchainTests.cpp
index 5ddfa64..6935899 100644
--- a/external/vulkancts/modules/vulkan/wsi/vktWsiSwapchainTests.cpp
+++ b/external/vulkancts/modules/vulkan/wsi/vktWsiSwapchainTests.cpp
@@ -48,6 +48,9 @@
 #include "deUniquePtr.hpp"
 #include "deStringUtil.hpp"
 #include "deArrayUtil.hpp"
+#include "deSharedPtr.hpp"
+
+#include <limits>
 
 namespace vkt
 {
@@ -671,12 +674,894 @@
 	}
 }
 
+VkSwapchainCreateInfoKHR getBasicSwapchainParameters (Type						wsiType,
+													  const InstanceInterface&	vki,
+													  VkPhysicalDevice			physicalDevice,
+													  VkSurfaceKHR				surface,
+													  const tcu::UVec2&			desiredSize,
+													  deUint32					desiredImageCount)
+{
+	const VkSurfaceCapabilitiesKHR		capabilities		= getPhysicalDeviceSurfaceCapabilities(vki,
+																								   physicalDevice,
+																								   surface);
+	const vector<VkSurfaceFormatKHR>	formats				= getPhysicalDeviceSurfaceFormats(vki,
+																							  physicalDevice,
+																							  surface);
+	const PlatformProperties&			platformProperties	= getPlatformProperties(wsiType);
+	const VkSwapchainCreateInfoKHR		parameters			=
+	{
+		VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+		DE_NULL,
+		(VkSwapchainCreateFlagsKHR)0,
+		surface,
+		de::clamp(desiredImageCount, capabilities.minImageCount, capabilities.maxImageCount),
+		formats[0].format,
+		formats[0].colorSpace,
+		(platformProperties.swapchainExtent == PlatformProperties::SWAPCHAIN_EXTENT_MUST_MATCH_WINDOW_SIZE
+			? capabilities.currentExtent : vk::makeExtent2D(desiredSize.x(), desiredSize.y())),
+		1u,									// imageArrayLayers
+		VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+		VK_SHARING_MODE_EXCLUSIVE,
+		0u,
+		(const deUint32*)DE_NULL,
+		VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+		VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+		VK_PRESENT_MODE_FIFO_KHR,
+		VK_FALSE,							// clipped
+		(VkSwapchainKHR)0					// oldSwapchain
+	};
+
+	return parameters;
+}
+
+typedef de::SharedPtr<Unique<VkImageView> >		ImageViewSp;
+typedef de::SharedPtr<Unique<VkFramebuffer> >	FramebufferSp;
+
+class TriangleRenderer
+{
+public:
+									TriangleRenderer	(const DeviceInterface&		vkd,
+														 const VkDevice				device,
+														 Allocator&					allocator,
+														 const BinaryCollection&	binaryRegistry,
+														 const vector<VkImage>		swapchainImages,
+														 const VkFormat				framebufferFormat,
+														 const UVec2&				renderSize);
+									~TriangleRenderer	(void);
+
+	void							recordFrame			(VkCommandBuffer			cmdBuffer,
+														 deUint32					imageNdx,
+														 deUint32					frameNdx) const;
+
+	static void						getPrograms			(SourceCollections& dst);
+
+private:
+	static Move<VkRenderPass>		createRenderPass	(const DeviceInterface&		vkd,
+														 const VkDevice				device,
+														 const VkFormat				colorAttachmentFormat);
+	static Move<VkPipelineLayout>	createPipelineLayout(const DeviceInterface&		vkd,
+														 VkDevice					device);
+	static Move<VkPipeline>			createPipeline		(const DeviceInterface&		vkd,
+														 const VkDevice				device,
+														 const VkRenderPass			renderPass,
+														 const VkPipelineLayout		pipelineLayout,
+														 const BinaryCollection&	binaryCollection,
+														 const UVec2&				renderSize);
+
+	static Move<VkImageView>		createAttachmentView(const DeviceInterface&		vkd,
+														 const VkDevice				device,
+														 const VkImage				image,
+														 const VkFormat				format);
+	static Move<VkFramebuffer>		createFramebuffer	(const DeviceInterface&		vkd,
+														 const VkDevice				device,
+														 const VkRenderPass			renderPass,
+														 const VkImageView			colorAttachment,
+														 const UVec2&				renderSize);
+
+	static Move<VkBuffer>			createBuffer		(const DeviceInterface&		vkd,
+														 VkDevice					device,
+														 VkDeviceSize				size,
+														 VkBufferUsageFlags			usage);
+
+	const DeviceInterface&			m_vkd;
+
+	const vector<VkImage>			m_swapchainImages;
+	const tcu::UVec2				m_renderSize;
+
+	const Unique<VkRenderPass>		m_renderPass;
+	const Unique<VkPipelineLayout>	m_pipelineLayout;
+	const Unique<VkPipeline>		m_pipeline;
+
+	const Unique<VkBuffer>			m_vertexBuffer;
+	const UniquePtr<Allocation>		m_vertexBufferMemory;
+
+	vector<ImageViewSp>				m_attachmentViews;
+	vector<FramebufferSp>			m_framebuffers;
+};
+
+Move<VkRenderPass> TriangleRenderer::createRenderPass (const DeviceInterface&	vkd,
+													   const VkDevice			device,
+													   const VkFormat			colorAttachmentFormat)
+{
+	const VkAttachmentDescription	colorAttDesc		=
+	{
+		(VkAttachmentDescriptionFlags)0,
+		colorAttachmentFormat,
+		VK_SAMPLE_COUNT_1_BIT,
+		VK_ATTACHMENT_LOAD_OP_CLEAR,
+		VK_ATTACHMENT_STORE_OP_STORE,
+		VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+		VK_ATTACHMENT_STORE_OP_DONT_CARE,
+		VK_IMAGE_LAYOUT_UNDEFINED,
+		VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+	};
+	const VkAttachmentReference		colorAttRef			=
+	{
+		0u,
+		VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+	};
+	const VkSubpassDescription		subpassDesc			=
+	{
+		(VkSubpassDescriptionFlags)0u,
+		VK_PIPELINE_BIND_POINT_GRAPHICS,
+		0u,							// inputAttachmentCount
+		DE_NULL,					// pInputAttachments
+		1u,							// colorAttachmentCount
+		&colorAttRef,				// pColorAttachments
+		DE_NULL,					// pResolveAttachments
+		DE_NULL,					// depthStencilAttachment
+		0u,							// preserveAttachmentCount
+		DE_NULL,					// pPreserveAttachments
+	};
+	const VkSubpassDependency		dependencies[]		=
+	{
+		{
+			VK_SUBPASS_EXTERNAL,	// srcSubpass
+			0u,						// dstSubpass
+			VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+			VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+			VK_ACCESS_MEMORY_READ_BIT,
+			(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|
+			 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
+			VK_DEPENDENCY_BY_REGION_BIT
+		},
+		{
+			0u,						// srcSubpass
+			VK_SUBPASS_EXTERNAL,	// dstSubpass
+			VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+			VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+			(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|
+			 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
+			VK_ACCESS_MEMORY_READ_BIT,
+			VK_DEPENDENCY_BY_REGION_BIT
+		},
+	};
+	const VkRenderPassCreateInfo	renderPassParams	=
+	{
+		VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+		DE_NULL,
+		(VkRenderPassCreateFlags)0,
+		1u,
+		&colorAttDesc,
+		1u,
+		&subpassDesc,
+		DE_LENGTH_OF_ARRAY(dependencies),
+		dependencies,
+	};
+
+	return vk::createRenderPass(vkd, device, &renderPassParams);
+}
+
+Move<VkPipelineLayout> TriangleRenderer::createPipelineLayout (const DeviceInterface&	vkd,
+															   const VkDevice			device)
+{
+	const VkPushConstantRange						pushConstantRange		=
+	{
+		VK_SHADER_STAGE_VERTEX_BIT,
+		0u,											// offset
+		(deUint32)sizeof(deUint32),					// size
+	};
+	const VkPipelineLayoutCreateInfo				pipelineLayoutParams	=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+		DE_NULL,
+		(vk::VkPipelineLayoutCreateFlags)0,
+		0u,											// setLayoutCount
+		DE_NULL,									// pSetLayouts
+		1u,
+		&pushConstantRange,
+	};
+
+	return vk::createPipelineLayout(vkd, device, &pipelineLayoutParams);
+}
+
+Move<VkPipeline> TriangleRenderer::createPipeline (const DeviceInterface&	vkd,
+												   const VkDevice			device,
+												   const VkRenderPass		renderPass,
+												   const VkPipelineLayout	pipelineLayout,
+												   const BinaryCollection&	binaryCollection,
+												   const UVec2&				renderSize)
+{
+	// \note VkShaderModules are fully consumed by vkCreateGraphicsPipelines()
+	//		 and can be deleted immediately following that call.
+	const Unique<VkShaderModule>					vertShaderModule		(createShaderModule(vkd, device, binaryCollection.get("tri-vert"), 0));
+	const Unique<VkShaderModule>					fragShaderModule		(createShaderModule(vkd, device, binaryCollection.get("tri-frag"), 0));
+
+	const VkSpecializationInfo						emptyShaderSpecParams	=
+	{
+		0u,											// mapEntryCount
+		DE_NULL,									// pMap
+		0,											// dataSize
+		DE_NULL,									// pData
+	};
+	const VkPipelineShaderStageCreateInfo			shaderStageParams[]		=
+	{
+		{
+			VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+			DE_NULL,
+			(VkPipelineShaderStageCreateFlags)0,
+			VK_SHADER_STAGE_VERTEX_BIT,
+			*vertShaderModule,
+			"main",
+			&emptyShaderSpecParams,
+		},
+		{
+			VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+			DE_NULL,
+			(VkPipelineShaderStageCreateFlags)0,
+			VK_SHADER_STAGE_FRAGMENT_BIT,
+			*fragShaderModule,
+			"main",
+			&emptyShaderSpecParams,
+		}
+	};
+	const VkPipelineDepthStencilStateCreateInfo		depthStencilParams		=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineDepthStencilStateCreateFlags)0,
+		DE_FALSE,									// depthTestEnable
+		DE_FALSE,									// depthWriteEnable
+		VK_COMPARE_OP_ALWAYS,						// depthCompareOp
+		DE_FALSE,									// depthBoundsTestEnable
+		DE_FALSE,									// stencilTestEnable
+		{
+			VK_STENCIL_OP_KEEP,							// failOp
+			VK_STENCIL_OP_KEEP,							// passOp
+			VK_STENCIL_OP_KEEP,							// depthFailOp
+			VK_COMPARE_OP_ALWAYS,						// compareOp
+			0u,											// compareMask
+			0u,											// writeMask
+			0u,											// reference
+		},											// front
+		{
+			VK_STENCIL_OP_KEEP,							// failOp
+			VK_STENCIL_OP_KEEP,							// passOp
+			VK_STENCIL_OP_KEEP,							// depthFailOp
+			VK_COMPARE_OP_ALWAYS,						// compareOp
+			0u,											// compareMask
+			0u,											// writeMask
+			0u,											// reference
+		},											// back
+		-1.0f,										// minDepthBounds
+		+1.0f,										// maxDepthBounds
+	};
+	const VkViewport								viewport0				=
+	{
+		0.0f,										// x
+		0.0f,										// y
+		(float)renderSize.x(),						// width
+		(float)renderSize.y(),						// height
+		0.0f,										// minDepth
+		1.0f,										// maxDepth
+	};
+	const VkRect2D									scissor0				=
+	{
+		{ 0u, 0u, },								// offset
+		{ renderSize.x(), renderSize.y() },			// extent
+	};
+	const VkPipelineViewportStateCreateInfo			viewportParams			=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineViewportStateCreateFlags)0,
+		1u,
+		&viewport0,
+		1u,
+		&scissor0
+	};
+	const VkPipelineMultisampleStateCreateInfo		multisampleParams		=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineMultisampleStateCreateFlags)0,
+		VK_SAMPLE_COUNT_1_BIT,						// rasterizationSamples
+		VK_FALSE,									// sampleShadingEnable
+		0.0f,										// minSampleShading
+		(const VkSampleMask*)DE_NULL,				// sampleMask
+		VK_FALSE,									// alphaToCoverageEnable
+		VK_FALSE,									// alphaToOneEnable
+	};
+	const VkPipelineRasterizationStateCreateInfo	rasterParams			=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineRasterizationStateCreateFlags)0,
+		VK_TRUE,									// depthClampEnable
+		VK_FALSE,									// rasterizerDiscardEnable
+		VK_POLYGON_MODE_FILL,						// polygonMode
+		VK_CULL_MODE_NONE,							// cullMode
+		VK_FRONT_FACE_COUNTER_CLOCKWISE,			// frontFace
+		VK_FALSE,									// depthBiasEnable
+		0.0f,										// depthBiasConstantFactor
+		0.0f,										// depthBiasClamp
+		0.0f,										// depthBiasSlopeFactor
+		1.0f,										// lineWidth
+	};
+	const VkPipelineInputAssemblyStateCreateInfo	inputAssemblyParams		=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineInputAssemblyStateCreateFlags)0,
+		VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+		DE_FALSE,									// primitiveRestartEnable
+	};
+	const VkVertexInputBindingDescription			vertexBinding0			=
+	{
+		0u,											// binding
+		(deUint32)sizeof(tcu::Vec4),				// stride
+		VK_VERTEX_INPUT_RATE_VERTEX,				// inputRate
+	};
+	const VkVertexInputAttributeDescription			vertexAttrib0			=
+	{
+		0u,											// location
+		0u,											// binding
+		VK_FORMAT_R32G32B32A32_SFLOAT,				// format
+		0u,											// offset
+	};
+	const VkPipelineVertexInputStateCreateInfo		vertexInputStateParams	=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineVertexInputStateCreateFlags)0,
+		1u,
+		&vertexBinding0,
+		1u,
+		&vertexAttrib0,
+	};
+	const VkPipelineColorBlendAttachmentState		attBlendParams0			=
+	{
+		VK_FALSE,									// blendEnable
+		VK_BLEND_FACTOR_ONE,						// srcColorBlendFactor
+		VK_BLEND_FACTOR_ZERO,						// dstColorBlendFactor
+		VK_BLEND_OP_ADD,							// colorBlendOp
+		VK_BLEND_FACTOR_ONE,						// srcAlphaBlendFactor
+		VK_BLEND_FACTOR_ZERO,						// dstAlphaBlendFactor
+		VK_BLEND_OP_ADD,							// alphaBlendOp
+		(VK_COLOR_COMPONENT_R_BIT|
+		 VK_COLOR_COMPONENT_G_BIT|
+		 VK_COLOR_COMPONENT_B_BIT|
+		 VK_COLOR_COMPONENT_A_BIT),					// colorWriteMask
+	};
+	const VkPipelineColorBlendStateCreateInfo		blendParams				=
+	{
+		VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineColorBlendStateCreateFlags)0,
+		VK_FALSE,									// logicOpEnable
+		VK_LOGIC_OP_COPY,
+		1u,
+		&attBlendParams0,
+		{ 0.0f, 0.0f, 0.0f, 0.0f },					// blendConstants[4]
+	};
+	const VkGraphicsPipelineCreateInfo				pipelineParams			=
+	{
+		VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+		DE_NULL,
+		(VkPipelineCreateFlags)0,
+		(deUint32)DE_LENGTH_OF_ARRAY(shaderStageParams),
+		shaderStageParams,
+		&vertexInputStateParams,
+		&inputAssemblyParams,
+		(const VkPipelineTessellationStateCreateInfo*)DE_NULL,
+		&viewportParams,
+		&rasterParams,
+		&multisampleParams,
+		&depthStencilParams,
+		&blendParams,
+		(const VkPipelineDynamicStateCreateInfo*)DE_NULL,
+		pipelineLayout,
+		renderPass,
+		0u,											// subpass
+		DE_NULL,									// basePipelineHandle
+		0u,											// basePipelineIndex
+	};
+
+	return vk::createGraphicsPipeline(vkd, device, (VkPipelineCache)0, &pipelineParams);
+}
+
+Move<VkImageView> TriangleRenderer::createAttachmentView (const DeviceInterface&	vkd,
+														  const VkDevice			device,
+														  const VkImage				image,
+														  const VkFormat			format)
+{
+	const VkImageViewCreateInfo		viewParams	=
+	{
+		VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+		DE_NULL,
+		(VkImageViewCreateFlags)0,
+		image,
+		VK_IMAGE_VIEW_TYPE_2D,
+		format,
+		vk::makeComponentMappingRGBA(),
+		{
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			0u,						// baseMipLevel
+			1u,						// levelCount
+			0u,						// baseArrayLayer
+			1u,						// layerCount
+		},
+	};
+
+	return vk::createImageView(vkd, device, &viewParams);
+}
+
+Move<VkFramebuffer> TriangleRenderer::createFramebuffer	(const DeviceInterface&		vkd,
+														 const VkDevice				device,
+														 const VkRenderPass			renderPass,
+														 const VkImageView			colorAttachment,
+														 const UVec2&				renderSize)
+{
+	const VkFramebufferCreateInfo	framebufferParams	=
+	{
+		VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+		DE_NULL,
+		(VkFramebufferCreateFlags)0,
+		renderPass,
+		1u,
+		&colorAttachment,
+		renderSize.x(),
+		renderSize.y(),
+		1u,							// layers
+	};
+
+	return vk::createFramebuffer(vkd, device, &framebufferParams);
+}
+
+Move<VkBuffer> TriangleRenderer::createBuffer (const DeviceInterface&	vkd,
+											   VkDevice					device,
+											   VkDeviceSize				size,
+											   VkBufferUsageFlags		usage)
+{
+	const VkBufferCreateInfo	bufferParams	=
+	{
+		VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+		DE_NULL,
+		(VkBufferCreateFlags)0,
+		size,
+		usage,
+		VK_SHARING_MODE_EXCLUSIVE,
+		0,
+		DE_NULL
+	};
+
+	return vk::createBuffer(vkd, device, &bufferParams);
+}
+
+TriangleRenderer::TriangleRenderer (const DeviceInterface&	vkd,
+									const VkDevice			device,
+									Allocator&				allocator,
+									const BinaryCollection&	binaryRegistry,
+									const vector<VkImage>	swapchainImages,
+									const VkFormat			framebufferFormat,
+									const UVec2&			renderSize)
+	: m_vkd					(vkd)
+	, m_swapchainImages		(swapchainImages)
+	, m_renderSize			(renderSize)
+	, m_renderPass			(createRenderPass(vkd, device, framebufferFormat))
+	, m_pipelineLayout		(createPipelineLayout(vkd, device))
+	, m_pipeline			(createPipeline(vkd, device, *m_renderPass, *m_pipelineLayout, binaryRegistry, renderSize))
+	, m_vertexBuffer		(createBuffer(vkd, device, (VkDeviceSize)(sizeof(float)*4*3), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT))
+	, m_vertexBufferMemory	(allocator.allocate(getBufferMemoryRequirements(vkd, device, *m_vertexBuffer),
+							 MemoryRequirement::HostVisible))
+{
+	m_attachmentViews.resize(swapchainImages.size());
+	m_framebuffers.resize(swapchainImages.size());
+
+	for (size_t imageNdx = 0; imageNdx < swapchainImages.size(); ++imageNdx)
+	{
+		m_attachmentViews[imageNdx]	= ImageViewSp(new Unique<VkImageView>(createAttachmentView(vkd, device, swapchainImages[imageNdx], framebufferFormat)));
+		m_framebuffers[imageNdx]	= FramebufferSp(new Unique<VkFramebuffer>(createFramebuffer(vkd, device, *m_renderPass, **m_attachmentViews[imageNdx], renderSize)));
+	}
+
+	VK_CHECK(vkd.bindBufferMemory(device, *m_vertexBuffer, m_vertexBufferMemory->getMemory(), m_vertexBufferMemory->getOffset()));
+
+	{
+		const VkMappedMemoryRange	memRange	=
+		{
+			VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+			DE_NULL,
+			m_vertexBufferMemory->getMemory(),
+			m_vertexBufferMemory->getOffset(),
+			VK_WHOLE_SIZE
+		};
+		const tcu::Vec4				vertices[]	=
+		{
+			tcu::Vec4(-0.5f, -0.5f, 0.0f, 1.0f),
+			tcu::Vec4(+0.5f, -0.5f, 0.0f, 1.0f),
+			tcu::Vec4( 0.0f, +0.5f, 0.0f, 1.0f)
+		};
+		DE_STATIC_ASSERT(sizeof(vertices) == sizeof(float)*4*3);
+
+		deMemcpy(m_vertexBufferMemory->getHostPtr(), &vertices[0], sizeof(vertices));
+		VK_CHECK(vkd.flushMappedMemoryRanges(device, 1u, &memRange));
+	}
+}
+
+TriangleRenderer::~TriangleRenderer (void)
+{
+}
+
+void TriangleRenderer::recordFrame (VkCommandBuffer	cmdBuffer,
+									deUint32		imageNdx,
+									deUint32		frameNdx) const
+{
+	const VkImage		curImage		= m_swapchainImages[imageNdx];
+	const VkFramebuffer	curFramebuffer	= **m_framebuffers[imageNdx];
+
+	{
+		const VkCommandBufferBeginInfo	cmdBufBeginParams	=
+		{
+			VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+			DE_NULL,
+			(VkCommandBufferUsageFlags)0,
+			(const VkCommandBufferInheritanceInfo*)DE_NULL,
+		};
+		VK_CHECK(m_vkd.beginCommandBuffer(cmdBuffer, &cmdBufBeginParams));
+	}
+
+	{
+		const VkImageMemoryBarrier	fromPresentationBarrier	=
+		{
+			VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+			DE_NULL,
+			VK_ACCESS_MEMORY_READ_BIT,
+			(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|
+			 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+			VK_QUEUE_FAMILY_IGNORED,
+			VK_QUEUE_FAMILY_IGNORED,
+			curImage,
+			{
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				0u,					// baseMipLevel
+				1u,					// levelCount
+				0u,					// baseArrayLayer
+				1u,					// layerCount
+			}
+		};
+		m_vkd.cmdPipelineBarrier(cmdBuffer,
+								 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+								 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+								 (VkDependencyFlags)0,
+								 0, (const VkMemoryBarrier*)DE_NULL,
+								 0, (const VkBufferMemoryBarrier*)DE_NULL,
+								 1, &fromPresentationBarrier);
+	}
+
+	{
+		const VkClearValue			clearValue		= makeClearValueColorF32(0.125f, 0.25f, 0.75f, 1.0f);
+		const VkRenderPassBeginInfo	passBeginParams	=
+		{
+			VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+			DE_NULL,
+			*m_renderPass,
+			curFramebuffer,
+			{
+				{ 0, 0 },
+				{ (deUint32)m_renderSize.x(), (deUint32)m_renderSize.y() }
+			},													// renderArea
+			1u,													// clearValueCount
+			&clearValue,										// pClearValues
+		};
+		m_vkd.cmdBeginRenderPass(cmdBuffer, &passBeginParams, VK_SUBPASS_CONTENTS_INLINE);
+	}
+
+	m_vkd.cmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipeline);
+
+	{
+		const VkDeviceSize bindingOffset = 0;
+		m_vkd.cmdBindVertexBuffers(cmdBuffer, 0u, 1u, &m_vertexBuffer.get(), &bindingOffset);
+	}
+
+	m_vkd.cmdPushConstants(cmdBuffer, *m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0u, (deUint32)sizeof(deUint32), &frameNdx);
+	m_vkd.cmdDraw(cmdBuffer, 3u, 1u, 0u, 0u);
+	m_vkd.cmdEndRenderPass(cmdBuffer);
+
+	{
+		const VkImageMemoryBarrier	toPresentationBarrier	=
+		{
+			VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+			DE_NULL,
+			(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|
+			 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
+			VK_ACCESS_MEMORY_READ_BIT,
+			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+			VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+			VK_QUEUE_FAMILY_IGNORED,
+			VK_QUEUE_FAMILY_IGNORED,
+			curImage,
+			{
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				0u,					// baseMipLevel
+				1u,					// levelCount
+				0u,					// baseArrayLayer
+				1u,					// layerCount
+			}
+		};
+		m_vkd.cmdPipelineBarrier(cmdBuffer,
+								 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+								 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+								 (VkDependencyFlags)0,
+								 0, (const VkMemoryBarrier*)DE_NULL,
+								 0, (const VkBufferMemoryBarrier*)DE_NULL,
+								 1, &toPresentationBarrier);
+	}
+
+	VK_CHECK(m_vkd.endCommandBuffer(cmdBuffer));
+}
+
+void TriangleRenderer::getPrograms (SourceCollections& dst)
+{
+	dst.glslSources.add("tri-vert") << glu::VertexSource(
+		"#version 310 es\n"
+		"layout(location = 0) in highp vec4 a_position;\n"
+		"layout(push_constant) uniform FrameData\n"
+		"{\n"
+		"    highp uint frameNdx;\n"
+		"} frameData;\n"
+		"void main (void)\n"
+		"{\n"
+		"    highp float angle = float(frameData.frameNdx) / 100.0;\n"
+		"    highp float c     = cos(angle);\n"
+		"    highp float s     = sin(angle);\n"
+		"    highp mat4  t     = mat4( c, -s,  0,  0,\n"
+		"                              s,  c,  0,  0,\n"
+		"                              0,  0,  1,  0,\n"
+		"                              0,  0,  0,  1);\n"
+		"    gl_Position = t * a_position;\n"
+		"}\n");
+	dst.glslSources.add("tri-frag") << glu::FragmentSource(
+		"#version 310 es\n"
+		"layout(location = 0) out lowp vec4 o_color;\n"
+		"void main (void) { o_color = vec4(1.0, 0.0, 1.0, 1.0); }\n");
+}
+
+typedef de::SharedPtr<Unique<VkCommandBuffer> >	CommandBufferSp;
+typedef de::SharedPtr<Unique<VkFence> >			FenceSp;
+typedef de::SharedPtr<Unique<VkSemaphore> >		SemaphoreSp;
+
+Move<VkFence> createFence (const DeviceInterface&	vkd,
+						   const VkDevice			device)
+{
+	const VkFenceCreateInfo	fenceParams	=
+	{
+		VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+		DE_NULL,
+		(VkFenceCreateFlags)0,
+	};
+	return vk::createFence(vkd, device, &fenceParams);
+}
+
+vector<FenceSp> createFences (const DeviceInterface&	vkd,
+							  const VkDevice			device,
+							  size_t					numFences)
+{
+	vector<FenceSp> fences(numFences);
+
+	for (size_t ndx = 0; ndx < numFences; ++ndx)
+		fences[ndx] = FenceSp(new Unique<VkFence>(createFence(vkd, device)));
+
+	return fences;
+}
+
+Move<VkSemaphore> createSemaphore (const DeviceInterface&	vkd,
+								   const VkDevice			device)
+{
+	const VkSemaphoreCreateInfo	semaphoreParams	=
+	{
+		VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+		DE_NULL,
+		(VkSemaphoreCreateFlags)0,
+	};
+	return vk::createSemaphore(vkd, device, &semaphoreParams);
+}
+
+vector<SemaphoreSp> createSemaphores (const DeviceInterface&	vkd,
+									  const VkDevice			device,
+									  size_t					numSemaphores)
+{
+	vector<SemaphoreSp> semaphores(numSemaphores);
+
+	for (size_t ndx = 0; ndx < numSemaphores; ++ndx)
+		semaphores[ndx] = SemaphoreSp(new Unique<VkSemaphore>(createSemaphore(vkd, device)));
+
+	return semaphores;
+}
+
+Move<VkCommandPool> createCommandPool (const DeviceInterface&	vkd,
+									   const VkDevice			device,
+									   VkCommandPoolCreateFlags	flags,
+									   deUint32					queueFamilyIndex)
+{
+	const VkCommandPoolCreateInfo	commandPoolParams	=
+	{
+		VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+		DE_NULL,
+		flags,
+		queueFamilyIndex
+	};
+
+	return createCommandPool(vkd, device, &commandPoolParams);
+}
+
+vector<CommandBufferSp> allocateCommandBuffers (const DeviceInterface&		vkd,
+												const VkDevice				device,
+												const VkCommandPool			commandPool,
+												const VkCommandBufferLevel	level,
+												const size_t				numCommandBuffers)
+{
+	const VkCommandBufferAllocateInfo	allocInfo	=
+	{
+		VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+		DE_NULL,
+		commandPool,
+		level,
+		1u,
+	};
+
+	vector<CommandBufferSp>				buffers		(numCommandBuffers);
+
+	for (size_t ndx = 0; ndx < numCommandBuffers; ++ndx)
+		buffers[ndx] = CommandBufferSp(new Unique<VkCommandBuffer>(allocateCommandBuffer(vkd, device, &allocInfo)));
+
+	return buffers;
+}
+
+tcu::TestStatus basicRenderTest (Context& context, Type wsiType)
+{
+	const tcu::UVec2				desiredSize					(256, 256);
+	const InstanceHelper			instHelper					(context, wsiType);
+	const NativeObjects				native						(context, instHelper.supportedExtensions, wsiType, tcu::just(desiredSize));
+	const Unique<VkSurfaceKHR>		surface						(createSurface(instHelper.vki, *instHelper.instance, wsiType, *native.display, *native.window));
+	const DeviceHelper				devHelper					(context, instHelper.vki, *instHelper.instance, *surface);
+	const DeviceInterface&			vkd							= devHelper.vkd;
+	const VkDevice					device						= *devHelper.device;
+	const VkSwapchainCreateInfoKHR	swapchainInfo				= getBasicSwapchainParameters(wsiType, instHelper.vki, devHelper.physicalDevice, *surface, desiredSize, 2);
+	const Unique<VkSwapchainKHR>	swapchain					(createSwapchainKHR(vkd, device, &swapchainInfo));
+	const vector<VkImage>			swapchainImages				= getSwapchainImages(vkd, device, *swapchain);
+
+	const TriangleRenderer			renderer					(vkd,
+																	 device,
+																	 context.getDefaultAllocator(),
+																	 context.getBinaryCollection(),
+																	 swapchainImages,
+																	 swapchainInfo.imageFormat,
+																	 tcu::UVec2(swapchainInfo.imageExtent.width, swapchainInfo.imageExtent.height));
+
+	const Unique<VkCommandPool>		commandPool					(createCommandPool(vkd, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, devHelper.queueFamilyIndex));
+
+	const size_t					maxQueuedFrames				= swapchainImages.size()*2;
+
+	// We need to keep hold of fences from vkAcquireNextImageKHR to actually
+	// limit number of frames we allow to be queued.
+	const vector<FenceSp>			imageReadyFences			(createFences(vkd, device, maxQueuedFrames));
+
+	// We need maxQueuedFrames+1 for imageReadySemaphores pool as we need to pass
+	// the semaphore in same time as the fence we use to meter rendering.
+	const vector<SemaphoreSp>		imageReadySemaphores		(createSemaphores(vkd, device, maxQueuedFrames+1));
+
+	// For rest we simply need maxQueuedFrames as we will wait for image
+	// from frameNdx-maxQueuedFrames to become available to us, guaranteeing that
+	// previous uses must have completed.
+	const vector<SemaphoreSp>		renderingCompleteSemaphores	(createSemaphores(vkd, device, maxQueuedFrames));
+	const vector<CommandBufferSp>	commandBuffers				(allocateCommandBuffers(vkd, device, *commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, maxQueuedFrames));
+
+	try
+	{
+		const deUint32	numFramesToRender	= 60*10;
+
+		for (deUint32 frameNdx = 0; frameNdx < numFramesToRender; ++frameNdx)
+		{
+			const VkFence		imageReadyFence		= **imageReadyFences[frameNdx%imageReadyFences.size()];
+			const VkSemaphore	imageReadySemaphore	= **imageReadySemaphores[frameNdx%imageReadySemaphores.size()];
+			deUint32			imageNdx			= ~0u;
+
+			if (frameNdx >= maxQueuedFrames)
+				VK_CHECK(vkd.waitForFences(device, 1u, &imageReadyFence, VK_TRUE, std::numeric_limits<deUint64>::max()));
+
+			VK_CHECK(vkd.resetFences(device, 1, &imageReadyFence));
+
+			{
+				const VkResult	acquireResult	= vkd.acquireNextImageKHR(device,
+																		  *swapchain,
+																		  std::numeric_limits<deUint64>::max(),
+																		  imageReadySemaphore,
+																		  imageReadyFence,
+																		  &imageNdx);
+
+				if (acquireResult == VK_SUBOPTIMAL_KHR)
+					context.getTestContext().getLog() << TestLog::Message << "Got " << acquireResult << " at frame " << frameNdx << TestLog::EndMessage;
+				else
+					VK_CHECK(acquireResult);
+			}
+
+			TCU_CHECK((size_t)imageNdx < swapchainImages.size());
+
+			{
+				const VkSemaphore			renderingCompleteSemaphore	= **renderingCompleteSemaphores[frameNdx%renderingCompleteSemaphores.size()];
+				const VkCommandBuffer		commandBuffer				= **commandBuffers[frameNdx%commandBuffers.size()];
+				const VkPipelineStageFlags	waitDstStage				= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+				const VkSubmitInfo			submitInfo					=
+				{
+					VK_STRUCTURE_TYPE_SUBMIT_INFO,
+					DE_NULL,
+					1u,
+					&imageReadySemaphore,
+					&waitDstStage,
+					1u,
+					&commandBuffer,
+					1u,
+					&renderingCompleteSemaphore
+				};
+				const VkPresentInfoKHR		presentInfo					=
+				{
+					VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+					DE_NULL,
+					1u,
+					&renderingCompleteSemaphore,
+					1u,
+					&*swapchain,
+					&imageNdx,
+					(VkResult*)DE_NULL
+				};
+
+				renderer.recordFrame(commandBuffer, imageNdx, frameNdx);
+				VK_CHECK(vkd.queueSubmit(devHelper.queue, 1u, &submitInfo, (VkFence)0));
+				VK_CHECK(vkd.queuePresentKHR(devHelper.queue, &presentInfo));
+			}
+		}
+
+		VK_CHECK(vkd.deviceWaitIdle(device));
+	}
+	catch (...)
+	{
+		// Make sure device is idle before destroying resources
+		vkd.deviceWaitIdle(device);
+		throw;
+	}
+
+	return tcu::TestStatus::pass("Rendering tests suceeded");
+}
+
+void getBasicRenderPrograms (SourceCollections& dst, Type)
+{
+	TriangleRenderer::getPrograms(dst);
+}
+
+void populateRenderGroup (tcu::TestCaseGroup* testGroup, Type wsiType)
+{
+	addFunctionCaseWithPrograms(testGroup, "basic", "Basic Rendering Test", getBasicRenderPrograms, basicRenderTest, wsiType);
+}
+
 } // anonymous
 
 void createSwapchainTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
 {
 	addTestGroup(testGroup, "create",			"Create VkSwapchain with various parameters",					populateSwapchainGroup, GroupParameters(wsiType, createSwapchainTest));
 	addTestGroup(testGroup, "simulate_oom",		"Simulate OOM using callbacks during swapchain construction",	populateSwapchainGroup, GroupParameters(wsiType, createSwapchainSimulateOOMTest));
+	addTestGroup(testGroup, "render",			"Rendering Tests",												populateRenderGroup,	wsiType);
 }
 
 } // wsi