/* $Id$ */

/**
 * this example is quite minimal, you may need to modify it if your graphics card is more esoteric
 *
 * ioixd maintains this file. nishi doesn't know vulkan at all
 */

#include "Mw/Error.h"
#include <Mw/Milsko.h>
#include <Mw/Widget/Vulkan.h>

#include <vulkan/vulkan.h>
#include <vulkan/vulkan_core.h>
#ifdef HAS_VK_ENUM_STRING_HELPER
#include <vulkan/vk_enum_string_helper.h>
#else
char buffer[512];

char* string_VkResult(VkResult res) {
	sprintf(buffer, "%d", res);

	return &buffer[0];
}
#endif

MwWidget window, vulkan;
int	 ow = 300;
int	 oh = 250;

PFN_vkGetInstanceProcAddr _vkGetInstanceProcAddr;
VkInstance		  instance;
VkDevice		  device;
VkPhysicalDevice	  physicalDevice;
VkSurfaceKHR		  surface;
VkQueue			  graphicsQueue;
VkQueue			  presentQueue;
uint32_t*		  graphicsQueueIndex;
uint32_t*		  presentQueueIndex;

VkSwapchainKHR swapchain;
VkImage*       swapchainImages;
VkImageView*   swapchainImageView;
uint32_t       swapchainImageViewCount;
uint32_t       currentImageIndex;
uint32_t       frameNumber;

VkFence*     fences;
VkSemaphore* renderFinishedSemaphores;
VkSemaphore* imageAvaliableSemaphores;

VkFramebuffer* framebuffers;

VkPipeline	 pipeline;
VkCommandBuffer* cmdBuffers;
VkRenderPass	 renderPass;

VkResult res;

VkSwapchainCreateInfoKHR swapchainCreateInfo = {};

#define MAX_FRAMES_IN_FLIGHT swapchainImageViewCount

// convienence macro for loading a vulkan function pointer into memory
#define LOAD_VK_FUNCTION(name) \
	PFN_##name _##name = (PFN_##name)_vkGetInstanceProcAddr(instance, #name); \
	assert(_##name);

void tick(MwWidget handle, void* user_data, void* call_data) {
	(void)handle;
	(void)user_data;
	(void)call_data;

	VkCommandBufferBeginInfo beginInfo	= {};
	VkRenderPassBeginInfo	 renderPassInfo = {};
	VkSubmitInfo		 submitInfo	= {};
	VkPresentInfoKHR	 presentInfo	= {};

	VkClearValue clearColor	   = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
	uint32_t     vertexCount   = 3;
	uint32_t     instanceCount = 1;

	VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT};

	LOAD_VK_FUNCTION(vkCreateFence);
	LOAD_VK_FUNCTION(vkWaitForFences);
	LOAD_VK_FUNCTION(vkResetFences);
	LOAD_VK_FUNCTION(vkResetCommandBuffer);
	LOAD_VK_FUNCTION(vkBeginCommandBuffer);
	LOAD_VK_FUNCTION(vkEndCommandBuffer);
	LOAD_VK_FUNCTION(vkQueueSubmit);
	LOAD_VK_FUNCTION(vkAcquireNextImageKHR);
	LOAD_VK_FUNCTION(vkQueuePresentKHR);
	LOAD_VK_FUNCTION(vkCreateSwapchainKHR);

	LOAD_VK_FUNCTION(vkCmdBeginRenderPass);
	LOAD_VK_FUNCTION(vkCmdEndRenderPass);
	LOAD_VK_FUNCTION(vkCmdDraw);
	LOAD_VK_FUNCTION(vkCmdBindPipeline);

	if((res = _vkWaitForFences(device, 1, &fences[frameNumber], VK_TRUE, UINT64_MAX)) != VK_SUCCESS) {
		printf("error waiting on fence: %s\n", string_VkResult(res));
		exit(0);
	}

	if((res = _vkResetFences(device, 1, &fences[frameNumber])) != VK_SUCCESS) {
		printf("error resetting fence: %s\n", string_VkResult(res));
		exit(0);
	}

swapchainRetry:
	if((res = _vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, imageAvaliableSemaphores[frameNumber], NULL, &currentImageIndex)) != VK_SUCCESS) {
		if(res == VK_ERROR_OUT_OF_DATE_KHR) {
			if(_vkCreateSwapchainKHR(device, &swapchainCreateInfo, NULL, &swapchain) != VK_SUCCESS) {
				printf("failed to create swapchain: %s\n", string_VkResult(res));
				exit(0);
			};
			goto swapchainRetry;
		}
	};

	beginInfo.sType		   = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	beginInfo.pNext		   = NULL;
	beginInfo.flags		   = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
	beginInfo.pInheritanceInfo = NULL;

	if((res = _vkResetCommandBuffer(cmdBuffers[frameNumber], 0)) != VK_SUCCESS) {
		printf("error beginning command buffer record: %s\n", string_VkResult(res));
		exit(0);
	}

	if((res = _vkBeginCommandBuffer(cmdBuffers[frameNumber], &beginInfo)) != VK_SUCCESS) {
		printf("error beginning command buffer record: %s\n", string_VkResult(res));
		exit(0);
	}

	renderPassInfo.sType		 = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	renderPassInfo.pNext		 = NULL;
	renderPassInfo.renderPass	 = renderPass;
	renderPassInfo.framebuffer	 = framebuffers[frameNumber];
	renderPassInfo.renderArea.offset = (VkOffset2D){0, 0};
	renderPassInfo.renderArea.extent = (VkExtent2D){(uint32_t)ow, (uint32_t)oh};
	renderPassInfo.clearValueCount	 = 1;
	renderPassInfo.pClearValues	 = &clearColor;

	_vkCmdBeginRenderPass(cmdBuffers[frameNumber], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

	_vkCmdBindPipeline(cmdBuffers[frameNumber], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);

	_vkCmdDraw(cmdBuffers[frameNumber], vertexCount, instanceCount, 0, 0);

	_vkCmdEndRenderPass(cmdBuffers[frameNumber]);

	if((res = _vkEndCommandBuffer(cmdBuffers[frameNumber])) != VK_SUCCESS) {
		printf("error recording command buffer: %s\n", string_VkResult(res));
		exit(0);
	}

	submitInfo.sType		= VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submitInfo.pNext		= NULL;
	submitInfo.waitSemaphoreCount	= 1;
	submitInfo.pWaitSemaphores	= &imageAvaliableSemaphores[frameNumber];
	submitInfo.pWaitDstStageMask	= waitStages;
	submitInfo.commandBufferCount	= 1;
	submitInfo.pCommandBuffers	= &cmdBuffers[frameNumber];
	submitInfo.signalSemaphoreCount = 1;
	submitInfo.pSignalSemaphores	= &renderFinishedSemaphores[frameNumber];

	if((res = _vkQueueSubmit(graphicsQueue, 1, &submitInfo, fences[frameNumber])) != VK_SUCCESS) {
		printf("error submitting command buffer: %s\n", string_VkResult(res));
		exit(0);
	}

	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;

	presentInfo.waitSemaphoreCount = 1;
	presentInfo.pWaitSemaphores    = &renderFinishedSemaphores[frameNumber];
	presentInfo.swapchainCount     = 1;
	presentInfo.pSwapchains	       = &swapchain;
	presentInfo.pImageIndices      = &currentImageIndex;

	presentInfo.pResults = NULL; // Optional

	_vkQueuePresentKHR(presentQueue, &presentInfo);

	// frameNumber = currentImageIndex;
	frameNumber = (frameNumber + 1) % (MAX_FRAMES_IN_FLIGHT);
}

void vulkan_setup(MwWidget handle) {
	FILE*  vertFile;
	FILE*  fragFile;
	void*  vertBuf;
	void*  fragBuf;
	size_t vertFileSize;
	size_t fragFileSize;
	size_t amountRead;

	uint32_t		i;
	VkViewport		viewport = {};
	VkRect2D		scissor	 = {};
	VkCommandPool		cmdPool	 = VK_NULL_HANDLE;
	VkResult		res	 = 0;
	VkShaderModule		fragShaderModule;
	VkShaderModule		vertShaderModule;
	VkPipelineLayout	pipelineLayout	   = VK_NULL_HANDLE;
	VkAttachmentDescription colorAttachment	   = {};
	VkAttachmentReference	colorAttachmentRef = {};
	VkSubpassDescription	subpass		   = {};

	VkRenderPassCreateInfo		       renderPassInfo	    = {};
	VkShaderModuleCreateInfo	       vertInfo		    = {};
	VkShaderModuleCreateInfo	       fragInfo		    = {};
	VkPipelineLayoutCreateInfo	       pipelineLayoutInfo   = {};
	VkPipelineShaderStageCreateInfo	       vertShaderStageInfo  = {};
	VkPipelineShaderStageCreateInfo	       fragShaderStageInfo  = {};
	VkPipelineShaderStageCreateInfo	       shaderStages[2]	    = {};
	VkPipelineVertexInputStateCreateInfo   vertexInputInfo	    = {};
	VkPipelineInputAssemblyStateCreateInfo inputAssembly	    = {};
	VkPipelineViewportStateCreateInfo      viewportState	    = {};
	VkPipelineRasterizationStateCreateInfo rasterizer	    = {};
	VkPipelineMultisampleStateCreateInfo   multisampling	    = {};
	VkPipelineColorBlendAttachmentState    colorBlendAttachment = {};
	VkPipelineColorBlendStateCreateInfo    colorBlending	    = {};
	VkGraphicsPipelineCreateInfo	       pipelineInfo	    = {};
	VkFramebufferCreateInfo		       framebufferInfo	    = {};
	VkCommandBufferAllocateInfo	       allocInfo	    = {};
	VkCommandPoolCreateInfo		       poolInfo		    = {};
	VkImageViewCreateInfo		       imageViewCreateInfo  = {};
	VkSemaphoreCreateInfo		       semaphoreInfo	    = {};
	VkFenceCreateInfo		       fenceInfo	    = {};

	MwErrorEnum err	       = MwEsuccess;
	_vkGetInstanceProcAddr = MwVulkanGetField(handle, MwVulkanField_GetInstanceProcAddr, &err);
	if(err != MwEsuccess) {
		printf("Error getting vkGetInstanceProcAddr!\n%s\n", MwGetLastError());
	}
	instance = MwVulkanGetField(handle, MwVulkanField_Instance, &err);
	if(err != MwEsuccess) {
		printf("Error getting vulkan instance!\n%s\n", MwGetLastError());
	}
	device = MwVulkanGetField(handle, MwVulkanField_LogicalDevice, &err);
	if(err != MwEsuccess) {
		printf("Error getting VkDevice!\n%s\n", MwGetLastError());
	}
	physicalDevice = MwVulkanGetField(handle, MwVulkanField_PhysicalDevice, &err);
	if(err != MwEsuccess) {
		printf("Error getting physical device!\n%s\n", MwGetLastError());
	}
	graphicsQueue = MwVulkanGetField(handle, MwVulkanField_GraphicsQueue, &err);
	if(err != MwEsuccess) {
		printf("Error getting graphics queue!\n%s\n", MwGetLastError());
	}
	presentQueue = MwVulkanGetField(handle, MwVulkanField_PresentQueue, &err);
	if(err != MwEsuccess) {
		printf("Error getting present queue!\n%s\n", MwGetLastError());
	}
	surface = MwVulkanGetField(handle, MwVulkanField_Surface, &err);
	if(err != MwEsuccess) {
		printf("Error getting surface!\n%s\n", MwGetLastError());
	}
	presentQueueIndex = MwVulkanGetField(handle, MwVulkanField_PresentQueueIndex, &err);
	if(err != MwEsuccess) {
		printf("Error getting present queue index!\n%s\n", MwGetLastError());
	}
	graphicsQueueIndex = MwVulkanGetField(handle, MwVulkanField_GraphicsQueueIndex, &err);
	if(err != MwEsuccess) {
		printf("Error getting graphics queue index!\n%s\n", MwGetLastError());
	}

	LOAD_VK_FUNCTION(vkCreateShaderModule);
	LOAD_VK_FUNCTION(vkCreatePipelineLayout);
	LOAD_VK_FUNCTION(vkCreateGraphicsPipelines);
	LOAD_VK_FUNCTION(vkCreateCommandPool);
	LOAD_VK_FUNCTION(vkCreateFramebuffer);
	LOAD_VK_FUNCTION(vkAllocateCommandBuffers);
	LOAD_VK_FUNCTION(vkCreateFence);
	LOAD_VK_FUNCTION(vkCreateSemaphore);
	LOAD_VK_FUNCTION(vkCreateSwapchainKHR);
	LOAD_VK_FUNCTION(vkGetSwapchainImagesKHR);
	LOAD_VK_FUNCTION(vkCreateImageView);
	LOAD_VK_FUNCTION(vkCreateRenderPass);

	// create a swapchain
	swapchainCreateInfo.sType	     = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
	swapchainCreateInfo.surface	     = surface;
	swapchainCreateInfo.minImageCount    = 3;
	swapchainCreateInfo.imageFormat	     = VK_FORMAT_B8G8R8A8_SRGB;
	swapchainCreateInfo.imageColorSpace  = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
	swapchainCreateInfo.imageExtent	     = (VkExtent2D){.width = ow, .height = oh},
	swapchainCreateInfo.imageArrayLayers = 1,
	swapchainCreateInfo.imageUsage	     = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
					 VK_IMAGE_USAGE_TRANSFER_DST_BIT |
					 VK_IMAGE_USAGE_SAMPLED_BIT;
	// th is how we specify no transformation.
	swapchainCreateInfo.preTransform   = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
	swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
	swapchainCreateInfo.presentMode	   = VK_PRESENT_MODE_IMMEDIATE_KHR;
	// we don't care about the color of pixels that are obscured.
	swapchainCreateInfo.clipped	 = VK_TRUE;
	swapchainCreateInfo.oldSwapchain = NULL;

	if(*graphicsQueueIndex != *presentQueueIndex) {
		swapchainCreateInfo.imageSharingMode	  = VK_SHARING_MODE_CONCURRENT;
		swapchainCreateInfo.queueFamilyIndexCount = 2;

		uint32_t indices[] = {
		    *graphicsQueueIndex,
		    *presentQueueIndex,
		};
		swapchainCreateInfo.pQueueFamilyIndices = indices;
	} else {
		swapchainCreateInfo.imageSharingMode	  = VK_SHARING_MODE_EXCLUSIVE;
		swapchainCreateInfo.queueFamilyIndexCount = 0;
		swapchainCreateInfo.pQueueFamilyIndices	  = NULL;
	}

	if(_vkCreateSwapchainKHR(device, &swapchainCreateInfo, NULL, &swapchain) != VK_SUCCESS) {
		printf("failed to create swapchain: %s\n", string_VkResult(res));
		exit(0);
	};

	imageViewCreateInfo.sType			    = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	imageViewCreateInfo.viewType			    = VK_IMAGE_VIEW_TYPE_2D;
	imageViewCreateInfo.format			    = swapchainCreateInfo.imageFormat;
	imageViewCreateInfo.components.r		    = VK_COMPONENT_SWIZZLE_R;
	imageViewCreateInfo.components.g		    = VK_COMPONENT_SWIZZLE_G;
	imageViewCreateInfo.components.b		    = VK_COMPONENT_SWIZZLE_B;
	imageViewCreateInfo.components.a		    = VK_COMPONENT_SWIZZLE_A;
	imageViewCreateInfo.subresourceRange.aspectMask	    = VK_IMAGE_ASPECT_COLOR_BIT;
	imageViewCreateInfo.subresourceRange.baseMipLevel   = 0;
	imageViewCreateInfo.subresourceRange.levelCount	    = VK_REMAINING_MIP_LEVELS;
	imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
	imageViewCreateInfo.subresourceRange.layerCount	    = VK_REMAINING_ARRAY_LAYERS;

	if(_vkGetSwapchainImagesKHR(device, swapchain, &swapchainImageViewCount, NULL) != VK_SUCCESS) {
		printf("failed to get swapchain images: %s\n", string_VkResult(res));
		exit(0);
	}
	swapchainImages	   = malloc(sizeof(VkImage) * MAX_FRAMES_IN_FLIGHT);
	swapchainImageView = malloc(sizeof(VkImageView) * MAX_FRAMES_IN_FLIGHT);
	_vkGetSwapchainImagesKHR(device, swapchain, &swapchainImageViewCount, swapchainImages);
	for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
		imageViewCreateInfo.image = swapchainImages[i];
		if(_vkCreateImageView(device, &imageViewCreateInfo, NULL, &swapchainImageView[i]) != VK_SUCCESS) {
			printf("failed to get swapchain images: %s\n", string_VkResult(res));
			exit(0);
		}
	}

	//  Create a Render Pass.
	colorAttachment.flags	       = 0;
	colorAttachment.format	       = swapchainCreateInfo.imageFormat;
	colorAttachment.samples	       = VK_SAMPLE_COUNT_1_BIT;
	colorAttachment.loadOp	       = VK_ATTACHMENT_LOAD_OP_CLEAR;
	colorAttachment.storeOp	       = VK_ATTACHMENT_STORE_OP_STORE;
	colorAttachment.stencilLoadOp  = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
	colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
	colorAttachment.initialLayout  = VK_IMAGE_LAYOUT_UNDEFINED;
	colorAttachment.finalLayout    = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
	colorAttachmentRef.attachment  = 0;
	colorAttachmentRef.layout      = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

	subpass.flags			= 0;
	subpass.pipelineBindPoint	= VK_PIPELINE_BIND_POINT_GRAPHICS;
	subpass.inputAttachmentCount	= 0;
	subpass.pInputAttachments	= NULL;
	subpass.colorAttachmentCount	= 1;
	subpass.pColorAttachments	= &colorAttachmentRef;
	subpass.pResolveAttachments	= NULL;
	subpass.pDepthStencilAttachment = NULL;
	subpass.preserveAttachmentCount = 0;
	subpass.pPreserveAttachments	= NULL;

	renderPassInfo.sType	       = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
	renderPassInfo.pNext	       = NULL;
	renderPassInfo.flags	       = 0;
	renderPassInfo.attachmentCount = 1;
	renderPassInfo.pAttachments    = &colorAttachment;
	renderPassInfo.subpassCount    = 1;
	renderPassInfo.pSubpasses      = &subpass;
	renderPassInfo.dependencyCount = 0;
	renderPassInfo.pDependencies   = NULL;

	if((res = _vkCreateRenderPass(device, &renderPassInfo, NULL, &renderPass)) != VK_SUCCESS) {
		printf("error creating the render pass: %s\n", string_VkResult(res));
		exit(0);
	}

	// Create the Vertex Shader Module.
	vertFile = fopen("triangle.vert.spv", "rb");
	if(vertFile == NULL) vertFile = fopen("examples/vkdemos/triangle.vert.spv", "rb");
	fragFile = fopen("triangle.frag.spv", "rb");
	if(fragFile == NULL) fragFile = fopen("examples/vkdemos/triangle.frag.spv", "rb");

	fseek(vertFile, 0L, SEEK_END);
	vertFileSize = ftell(vertFile);
	rewind(vertFile);
	vertBuf = malloc(vertFileSize);
	memset(vertBuf, 0, vertFileSize);
	amountRead = fread(vertBuf, 1, vertFileSize, vertFile);
	printf("triangle.vert.spv: read %zu bytes\n", amountRead);

	vertInfo.sType	  = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
	vertInfo.pNext	  = NULL;
	vertInfo.flags	  = 0;
	vertInfo.codeSize = amountRead;
	vertInfo.pCode	  = vertBuf;

	if(_vkCreateShaderModule(device, &vertInfo, NULL, &vertShaderModule) != VK_SUCCESS) {
		printf("failed to create the shader module: %s\n", string_VkResult(res));
		exit(0);
	}

	// Create the Fragment Shader Module.
	fseek(fragFile, 0L, SEEK_END);
	fragFileSize = ftell(fragFile);
	rewind(fragFile);
	fragBuf = malloc(fragFileSize);
	memset(fragBuf, 0, fragFileSize);
	amountRead = fread(fragBuf, 1, fragFileSize, fragFile);
	printf("triangle.frag.spv: read %zu bytes\n", amountRead);

	fragInfo.sType	  = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
	fragInfo.pNext	  = NULL;
	fragInfo.flags	  = 0;
	fragInfo.codeSize = amountRead;
	fragInfo.pCode	  = fragBuf;

	if(_vkCreateShaderModule(device, &fragInfo, NULL, &fragShaderModule) != VK_SUCCESS) {
		printf("error creating the shader module: %s\n", string_VkResult(res));
		exit(0);
	}

	// Create Pipeline Layout.
	pipelineLayoutInfo.sType		  = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
	pipelineLayoutInfo.pNext		  = NULL;
	pipelineLayoutInfo.flags		  = 0;
	pipelineLayoutInfo.setLayoutCount	  = 0;
	pipelineLayoutInfo.pSetLayouts		  = NULL;
	pipelineLayoutInfo.pushConstantRangeCount = 0;

	if((res = _vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout)) != VK_SUCCESS) {
		printf("error creating the pipeline layout: %s\n", string_VkResult(res));
		exit(0);
	}

	vertShaderStageInfo.sType		= VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	vertShaderStageInfo.pNext		= NULL;
	vertShaderStageInfo.flags		= 0;
	vertShaderStageInfo.stage		= VK_SHADER_STAGE_VERTEX_BIT;
	vertShaderStageInfo.module		= vertShaderModule;
	vertShaderStageInfo.pName		= "main";
	vertShaderStageInfo.pSpecializationInfo = NULL;

	fragShaderStageInfo.sType		= VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
	fragShaderStageInfo.pNext		= NULL;
	fragShaderStageInfo.flags		= 0;
	fragShaderStageInfo.stage		= VK_SHADER_STAGE_FRAGMENT_BIT;
	fragShaderStageInfo.module		= fragShaderModule;
	fragShaderStageInfo.pName		= "main";
	fragShaderStageInfo.pSpecializationInfo = NULL;

	shaderStages[0] = vertShaderStageInfo;
	shaderStages[1] = fragShaderStageInfo;

	vertexInputInfo.sType				= VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
	vertexInputInfo.pNext				= NULL;
	vertexInputInfo.flags				= 0;
	vertexInputInfo.vertexBindingDescriptionCount	= 0;
	vertexInputInfo.pVertexBindingDescriptions	= NULL;
	vertexInputInfo.vertexAttributeDescriptionCount = 0;
	vertexInputInfo.pVertexAttributeDescriptions	= NULL;

	inputAssembly.sType		     = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
	inputAssembly.pNext		     = NULL;
	inputAssembly.flags		     = 0;
	inputAssembly.topology		     = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
	inputAssembly.primitiveRestartEnable = VK_FALSE;

	viewport.x	  = 0.0f;
	viewport.y	  = 0.0f;
	viewport.width	  = (float)ow;
	viewport.height	  = (float)oh;
	viewport.minDepth = 0.0f;
	viewport.maxDepth = 1.0f;

	scissor.offset = (VkOffset2D){0, 0};
	scissor.extent = (VkExtent2D){(uint32_t)viewport.width, (uint32_t)viewport.height};

	viewportState		    = (VkPipelineViewportStateCreateInfo){};
	viewportState.sType	    = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
	viewportState.viewportCount = 1;
	viewportState.pViewports    = &viewport;
	viewportState.scissorCount  = 1;
	viewportState.pScissors	    = &scissor;

	rasterizer.sType		   = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
	rasterizer.pNext		   = NULL;
	rasterizer.flags		   = 0;
	rasterizer.depthClampEnable	   = VK_FALSE;
	rasterizer.rasterizerDiscardEnable = VK_FALSE;
	rasterizer.polygonMode		   = VK_POLYGON_MODE_FILL;
	rasterizer.cullMode		   = VK_CULL_MODE_BACK_BIT;
	rasterizer.frontFace		   = VK_FRONT_FACE_CLOCKWISE;
	rasterizer.depthBiasEnable	   = VK_FALSE;
	rasterizer.depthBiasConstantFactor = 0.0;
	rasterizer.depthBiasClamp	   = 0.0;
	rasterizer.depthBiasSlopeFactor	   = 0.0;
	rasterizer.lineWidth		   = 1.0f;

	multisampling.sType		    = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
	multisampling.pNext		    = NULL;
	multisampling.flags		    = 0;
	multisampling.rasterizationSamples  = VK_SAMPLE_COUNT_1_BIT;
	multisampling.sampleShadingEnable   = VK_FALSE;
	multisampling.minSampleShading	    = 0.0;
	multisampling.pSampleMask	    = NULL;
	multisampling.alphaToCoverageEnable = VK_FALSE;
	multisampling.alphaToOneEnable	    = VK_FALSE;

	colorBlendAttachment.blendEnable	 = VK_FALSE;
	colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
	colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
	colorBlendAttachment.colorBlendOp	 = VK_BLEND_OP_ADD;
	colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
	colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
	colorBlendAttachment.alphaBlendOp	 = VK_BLEND_OP_ADD;
	colorBlendAttachment.colorWriteMask	 = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

	colorBlending.sType		= VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
	colorBlending.pNext		= NULL;
	colorBlending.flags		= 0;
	colorBlending.logicOpEnable	= VK_FALSE;
	colorBlending.logicOp		= VK_LOGIC_OP_COPY;
	colorBlending.attachmentCount	= 1;
	colorBlending.pAttachments	= &colorBlendAttachment;
	colorBlending.blendConstants[0] = 0.0f;
	colorBlending.blendConstants[1] = 0.0f;
	colorBlending.blendConstants[2] = 0.0f;
	colorBlending.blendConstants[3] = 0.0f;

	pipelineInfo.sType		 = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
	pipelineInfo.pNext		 = NULL;
	pipelineInfo.flags		 = 0;
	pipelineInfo.stageCount		 = 2;
	pipelineInfo.pStages		 = shaderStages;
	pipelineInfo.pVertexInputState	 = &vertexInputInfo;
	pipelineInfo.pInputAssemblyState = &inputAssembly;
	pipelineInfo.pTessellationState	 = NULL;
	pipelineInfo.pViewportState	 = &viewportState;
	pipelineInfo.pRasterizationState = &rasterizer;
	pipelineInfo.pMultisampleState	 = &multisampling;
	pipelineInfo.pDepthStencilState	 = NULL;
	pipelineInfo.pColorBlendState	 = &colorBlending;
	pipelineInfo.pDynamicState	 = NULL;
	pipelineInfo.layout		 = pipelineLayout;
	pipelineInfo.renderPass		 = renderPass;
	pipelineInfo.subpass		 = 0;
	pipelineInfo.basePipelineHandle	 = VK_NULL_HANDLE;
	pipelineInfo.basePipelineIndex	 = 0;

	if((res = _vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &pipeline)) != VK_SUCCESS) {
		printf("failed to create graphics pipeline: %s\n", string_VkResult(res));
		exit(0);
	}

	framebuffers = malloc(sizeof(VkFramebuffer) * MAX_FRAMES_IN_FLIGHT);

	framebufferInfo.sType		= VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
	framebufferInfo.pNext		= NULL;
	framebufferInfo.flags		= 0;
	framebufferInfo.renderPass	= renderPass;
	framebufferInfo.attachmentCount = 1;
	framebufferInfo.width		= ow;
	framebufferInfo.height		= oh;
	framebufferInfo.layers		= 1;
	for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
		framebufferInfo.pAttachments = &swapchainImageView[i];
		if((res = _vkCreateFramebuffer(device, &framebufferInfo, NULL, &framebuffers[i])) != VK_SUCCESS) {
			printf("error creating the frame buffer: %s\n", string_VkResult(res));
			exit(0);
		}
	}

	// Create Command Pool.
	poolInfo.sType		  = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
	poolInfo.pNext		  = NULL;
	poolInfo.flags		  = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
	poolInfo.queueFamilyIndex = *graphicsQueueIndex;

	if((res = _vkCreateCommandPool(device, &poolInfo, NULL, &cmdPool)) != VK_SUCCESS) {
		printf("error creating the command pool: %s\n", string_VkResult(res));
		exit(0);
	}

	// Create Command Buffer to record draw commands.
	allocInfo.sType		     = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
	allocInfo.pNext		     = NULL;
	allocInfo.commandPool	     = cmdPool;
	allocInfo.level		     = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
	allocInfo.commandBufferCount = 1;

	cmdBuffers = malloc(sizeof(VkCommandBuffer) * MAX_FRAMES_IN_FLIGHT);

	for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
		if((res = _vkAllocateCommandBuffers(device, &allocInfo, &cmdBuffers[i])) != VK_SUCCESS) {
			printf("error allocating the command buffers: %s\n", string_VkResult(res));
			exit(0);
		}
	}

	fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
	fenceInfo.pNext = 0;
	fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

	semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
	semaphoreInfo.pNext = 0;
	semaphoreInfo.flags = VK_SEMAPHORE_TYPE_BINARY;

	imageAvaliableSemaphores = malloc(sizeof(VkSemaphore) * MAX_FRAMES_IN_FLIGHT);
	renderFinishedSemaphores = malloc(sizeof(VkSemaphore) * MAX_FRAMES_IN_FLIGHT);
	fences			 = malloc(sizeof(VkFence) * MAX_FRAMES_IN_FLIGHT);

	for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
		if(_vkCreateSemaphore(device, &semaphoreInfo, NULL, &imageAvaliableSemaphores[i]) != VK_SUCCESS) {
			printf("error creating fence: %s\n", string_VkResult(res));
			exit(0);
		}
		if(_vkCreateSemaphore(device, &semaphoreInfo, NULL, &renderFinishedSemaphores[i]) != VK_SUCCESS) {
			printf("error creating fence: %s\n", string_VkResult(res));
			exit(0);
		}
		if(_vkCreateFence(device, &fenceInfo, NULL, &fences[i]) != VK_SUCCESS) {
			printf("error creating fence: %s\n", string_VkResult(res));
			exit(0);
		}
	}
}

int main() {
	if(!MwVulkanSupported()) {
		printf("Vulkan not found or unsupported on target platform.\n");
		return 1;
	}

	window = MwVaCreateWidget(MwWindowClass, "main", NULL, MwDEFAULT, MwDEFAULT, 400, 450,
				  MwNtitle, "hello world",
				  NULL);

	MwVulkanEnableExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);

	vulkan = MwCreateWidget(MwVulkanClass, "vulkan", window, 50, 50, ow, oh);

	MwAddUserHandler(window, MwNtickHandler, tick, NULL);

	vulkan_setup(vulkan);

	MwLoop(window);
}
