#include "svk.hpp"

#include <SDL.h>
#include <SDL_vulkan.h>

#include "svk_key_converter.hpp"
#include "svk_initializers.hpp"
#include "svk_types.hpp"
#include "svk_images.hpp"
#include "svk_pipelines.hpp"

#include <vk_mem_alloc.h>

#include <VkBootstrap.h>

#include <thread>
#include <sys/time.h>

constexpr bool bUseValidationLayers = true;

SVK* loadedEngine = nullptr;

void SVK::initWindowSize(int w, int h)
{
  _windowExtent.width = w;
  _windowExtent.height = h;
}

void SVK::allocateVertexBuffer(AllocatedBuffer& buffer,
                               size_t const& allocationSize)
{
    _allocateGPUBuffer(buffer, allocationSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);

    _mainDeletionQueue.push_function([&]() {
		_destroyBuffer(buffer);
									 });
}

void SVK::allocateIndexBuffer(AllocatedBuffer& buffer, size_t const& allocationSize)
{
    _allocateGPUBuffer(buffer, allocationSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);

    _mainDeletionQueue.push_function([&]() {
		_destroyBuffer(buffer);
									 });
}

void SVK::allocateUniformBuffer(AllocatedBuffer& buffer, size_t const& allocationSize)
{
    _allocateCPUBuffer(buffer, allocationSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);

    _mainDeletionQueue.push_function([&]() {
		_destroyBuffer(buffer);
									 });
}

void SVK::uploadToGPUBuffer(AllocatedBuffer& buffer, void const* bufferData, size_t const& bufferSize)
{

    AllocatedBuffer staging;
    _allocateStagingBuffer(staging, bufferSize);

    memcpy(staging.info.pMappedData, bufferData, bufferSize);

    _immediateSubmit([&](VkCommandBuffer cmd) {
        VkBufferCopy bufferCopy{};
        bufferCopy.dstOffset = 0;
        bufferCopy.srcOffset = 0;
        bufferCopy.size = bufferSize;

        vkCmdCopyBuffer(cmd, staging.buffer, buffer.buffer, 1, &bufferCopy);
                     });

    _destroyBuffer(staging);
}

void SVK::uploadToUniformBuffer(AllocatedBuffer& buffer, void const* bufferData, size_t const& bufferSize)
{
    memcpy(buffer.info.pMappedData, bufferData, bufferSize);
}

void SVK::uploadToUniformBuffer(AllocatedBuffer& buffer, void const* bufferData)
{
    memcpy(buffer.info.pMappedData, bufferData, buffer.info.size);
}

// The following two should be replaced by beginRendering /Ingemar
// Also note that Z-buffer here is unnecessary, just use svk.setDepthTestEnabled and svk.setCullMode(VK_CULL_MODE_NONE);

VkRenderingInfo SVK::getRenderingInfo()
{
    _colorAttachment = _generateVkRenderingAttachmentInfo(_drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    _depthAttachment = _generateVkRenderingDepthAttachmentInfo(_depthImage.imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);

    return _generateVkRenderingInfo(_windowExtent, &_colorAttachment, &_depthAttachment);
}

VkRenderingInfo SVK::getRenderingInfoNoDepthCulling()
{
    _colorAttachment = _generateVkRenderingAttachmentInfo(_drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);

    return _generateVkRenderingInfo(_windowExtent, &_colorAttachment, nullptr);
}

// Combine both plus begin rendering
// Should probably add stencil!
// VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
// VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
// etc
// https://docs.vulkan.org/refpages/latest/refpages/source/VkImageLayout.html

void SVK::beginRendering(VkCommandBuffer cmd, bool useDepthBuffer)
{
    _colorAttachment = _generateVkRenderingAttachmentInfo(_drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);

    VkRenderingInfo info;
    if (useDepthBuffer)
    {
      _depthAttachment = _generateVkRenderingDepthAttachmentInfo(_depthImage.imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);

      info = _generateVkRenderingInfo(_windowExtent, &_colorAttachment, &_depthAttachment);
    }
    else
    {
       info = _generateVkRenderingInfo(_windowExtent, &_colorAttachment, NULL);
    }

    vkCmdBeginRendering(cmd, &info);
}

void SVK::beginRendering(VkCommandBuffer cmd, PipelineData pld)
{
    VkRenderingInfo info;

    _colorAttachment = _generateVkRenderingAttachmentInfo(_drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);

// VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
// VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL
// VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL

    if (pld.stencil)
    {
      if (pld.depth) // stencil & depth
        _depthAttachment = _generateVkRenderingDepthAttachmentInfo(_depthImage.imageView, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
      else // stencil, not depth
        _depthAttachment = _generateVkRenderingDepthAttachmentInfo(_depthImage.imageView, VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL);
      
      info = _generateVkRenderingInfo(_windowExtent, &_colorAttachment, &_depthAttachment);
    }
    else // no stencil
    {
      if (pld.depth)
      {
        _depthAttachment = _generateVkRenderingDepthAttachmentInfo(_depthImage.imageView,     VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
        info = _generateVkRenderingInfo(_windowExtent, &_colorAttachment, &_depthAttachment);
      }
      else
      {
        info = _generateVkRenderingInfo(_windowExtent, &_colorAttachment, NULL);
      }
    }

    vkCmdBeginRendering(cmd, &info);
}


// Copied from milli.cpp but just gives the global time
// Added by Ingemar as a super simple way to get the time.
double GetSeconds()
{
	struct timeval tv;
	
	gettimeofday(&tv, NULL);
	return (double)(tv.tv_sec + tv.tv_usec/1000000.0) ;
}

void SVK::init()
{
    // only one engine initialization is allowed with the application.
    assert(loadedEngine == nullptr);
    loadedEngine = this;

    // We initialize SDL and create a window with it.
    SDL_Init(SDL_INIT_VIDEO);

    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);

    _window = SDL_CreateWindow(
        _windowName.c_str(),
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        _windowExtent.width,
        _windowExtent.height,
        window_flags);

    _initVulkan();
    _initSwapchain();
    _initCommands();
    _initSyncStructures();
    _initDescriptors();
    _initDefaultData();

    lastTime = GetSeconds();
    //frameTime = 0.01; // 100 FPS Set with svk.repeatingTimer

    // everything went fine
    _isInitialized = true;
}

void SVK::repeatingTimer(double time)
{
  frameTime = time;
}

void SVK::initDescriptor(VkDescriptorSetLayout& descriptorSetLayout,
                         VkShaderStageFlags const& shaderStages,
                         std::vector<std::pair<uint32_t, VkDescriptorType>> const& bindings)
{
    DescriptorLayoutBuilder descriptorLayoutBuilder;
    for (auto& binding : bindings)
        descriptorLayoutBuilder.add_binding(binding.first, binding.second);
    descriptorSetLayout = descriptorLayoutBuilder.build(_device, shaderStages);
    _mainDeletionQueue.push_function([&]() {
        vkDestroyDescriptorSetLayout(_device, descriptorSetLayout, nullptr);
                                     });
}

void SVK::initEmptyDescriptor(VkDescriptorSetLayout& descriptorSetLayout)
{

	VkDescriptorSetLayoutCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	info.pNext = nullptr;

	info.pBindings = nullptr;
	info.bindingCount = 0;
	info.flags = 0;

	VK_CHECK(vkCreateDescriptorSetLayout(_device, &info, nullptr, &descriptorSetLayout));

    _mainDeletionQueue.push_function([&]() {
        vkDestroyDescriptorSetLayout(_device, descriptorSetLayout, nullptr);
                                     });
}

void SVK::initPipelineLayout(VkPipelineLayout& pipelineLayout,
                             VkDescriptorSetLayout const& descriptorSetLayout,
                             uint64_t const& pushConstantSizeVertex,
                             uint64_t const& pushConstantSizeFragment)
{
    if (descriptorSetLayout == VK_NULL_HANDLE)
		throw std::logic_error("initPipelineLayout was called with an uninitialized descriptor set layout");

	VkPhysicalDeviceProperties deviceProperties;
	vkGetPhysicalDeviceProperties(_chosenGPU, &deviceProperties);
	if (pushConstantSizeVertex + pushConstantSizeFragment > deviceProperties.limits.maxPushConstantsSize)
		throw std::length_error("pushConstantSizeVertex (" + std::to_string(pushConstantSizeVertex) + ") + pushConstantSizeFragment (" + std::to_string(pushConstantSizeFragment) + ") are greater than maxPushConstantsSize (" + std::to_string(deviceProperties.limits.maxPushConstantsSize) + ")");

	std::vector<VkPushConstantRange> pushConstantRanges{};
	VkPushConstantRange vertexPushConstantRange{};
	if (pushConstantSizeVertex > 0)
	{
		vertexPushConstantRange.offset = 0;
		vertexPushConstantRange.size = pushConstantSizeVertex;
		vertexPushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
		pushConstantRanges.push_back(vertexPushConstantRange);
	}

	VkPushConstantRange fragmentPushConstantRange{};
	if (pushConstantSizeFragment > 0)
	{
		fragmentPushConstantRange.offset = pushConstantSizeVertex;
		fragmentPushConstantRange.size = pushConstantSizeFragment;
		fragmentPushConstantRange.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
		pushConstantRanges.push_back(fragmentPushConstantRange);
	}

	VkPipelineLayoutCreateInfo pipeline_layout_info = _generateVkPipelineLayoutCreateInfo();
	pipeline_layout_info.pPushConstantRanges = pushConstantRanges.data();
	pipeline_layout_info.pushConstantRangeCount = pushConstantRanges.size();
	pipeline_layout_info.pSetLayouts = &descriptorSetLayout;
	pipeline_layout_info.setLayoutCount = 1;

	VK_CHECK(vkCreatePipelineLayout(_device, &pipeline_layout_info, nullptr, &pipelineLayout));

    _mainDeletionQueue.push_function([&]() {
		vkDestroyPipelineLayout(_device, pipelineLayout, nullptr);
									 });
}

void SVK::initPipelineLayoutCombinedPushConstants(VkPipelineLayout& pipelineLayout,
												  VkDescriptorSetLayout const& descriptorSetLayout,
												  uint64_t const& pushConstantSize)
{
    if (descriptorSetLayout == VK_NULL_HANDLE)
		throw std::logic_error("initPipelineLayoutCombinedPushConstants was called with an uninitialized descriptor set layout");

	VkPhysicalDeviceProperties deviceProperties;
	vkGetPhysicalDeviceProperties(_chosenGPU, &deviceProperties);
	if (pushConstantSize > deviceProperties.limits.maxPushConstantsSize)
		throw std::length_error("pushConstantSize (" + std::to_string(pushConstantSize) + ") is greater than maxPushConstantsSize (" + std::to_string(deviceProperties.limits.maxPushConstantsSize) + ")");

	VkPushConstantRange pushConstantRange{};
	pushConstantRange.offset = 0;
	pushConstantRange.size = pushConstantSize;
	pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;


	VkPipelineLayoutCreateInfo pipeline_layout_info = _generateVkPipelineLayoutCreateInfo();
	pipeline_layout_info.pPushConstantRanges = &pushConstantRange;
	pipeline_layout_info.pushConstantRangeCount = 1;
	pipeline_layout_info.pSetLayouts = &descriptorSetLayout;
	pipeline_layout_info.setLayoutCount = 1;

	VK_CHECK(vkCreatePipelineLayout(_device, &pipeline_layout_info, nullptr, &pipelineLayout));

    _mainDeletionQueue.push_function([&]() {
		vkDestroyPipelineLayout(_device, pipelineLayout, nullptr);
									 });
}

void SVK::initPipeline(VkPipeline& pipeline,
                       VkPipelineLayout const& pipelineLayout,
                       VertexInputDescriptionHandler const& handler,
                       std::string const& vertPath,
                       std::string const& fragPath)
{
    VkShaderModule triangleFragShader;
	VkShaderModule triangleVertexShader;

	_loadShaders(_device, vertPath, fragPath, triangleVertexShader, triangleFragShader, true);

    PipelineBuilder pipelineBuilder;

	pipelineBuilder._pipelineLayout = pipelineLayout;
	pipelineBuilder.setShaders(triangleVertexShader, triangleFragShader);
	pipelineBuilder.setInputTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
	pipelineBuilder.setPolygonMode(_polygonMode);
//	pipelineBuilder.setCullMode(_cullMode, VK_FRONT_FACE_COUNTER_CLOCKWISE);
	pipelineBuilder.setCullMode(_cullMode, VK_FRONT_FACE_CLOCKWISE); // Swapped for flipped Y 
	
	// multisamping not done yet!
	if (_multisampling)
	  pipelineBuilder.setMultisamplingNone(); // TO DO: setMultisampling in svk_pipelines!
	else
	  pipelineBuilder.setMultisamplingNone();
	if (_depthTestEnabled)
		pipelineBuilder.enableDepthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
	else
		pipelineBuilder.disableDepthtest();
	// Added by Ingemar: Support for blending
	if (!_blendingEnabled)
	        pipelineBuilder.disableBlending(); // This is where colorWriteMask is initialized!
	else
	        if (!_blendingAdditive) // Normal
	          pipelineBuilder.enableBlendingAlphablend();
	        else // additive
	          pipelineBuilder.enableBlendingAdditive();
	// NOTE: Stencil support is partially implemented but not yet working!
        pipelineBuilder.setStencil(_stencil_enable, _stencil_compareOp, _stencil_failOp, _stencil_depthFailOp, _stencil_passOp, _stencil_ref);
	if (!_colorsEnabled)
	    pipelineBuilder.disableAllColorChannels();
        pipelineBuilder.setColorAttachmentFormat(_drawImage.imageFormat);
	pipelineBuilder.setDepthFormat(_depthImage.imageFormat);
	if (_depthClipControlEnabled)
		pipelineBuilder.enableDepthClipControl();
	pipelineBuilder.buildPipeline(_device, pipeline, handler);

	vkDestroyShaderModule(_device, triangleFragShader, nullptr);
	vkDestroyShaderModule(_device, triangleVertexShader, nullptr);


    _mainDeletionQueue.push_function([&]()
    {
	vkDestroyPipeline(_device, pipeline, nullptr);
    });   
}

// Added by Ingemar to avoid incompatibilty crashes in loadTGATexture
void convertTo4(TextureData &textureData)
{
	if (textureData.bpp == 32)
		return;
	// Other possibilities: 24 or 8
	printf("Convert to 4 bytes per pixel (needs 32, has %d)\n", textureData.bpp);
	unsigned char *newImageData = (unsigned char *)calloc(textureData.width*textureData.height, 4);
	if (textureData.bpp == 24)
	{
		for (long i = 0; i < textureData.width*textureData.height; i++)
		{
			newImageData[i * 4] = textureData.imageData[i * 3];
			newImageData[i * 4+1] = textureData.imageData[i * 3+1];
			newImageData[i * 4+2] = textureData.imageData[i * 3+2];
			newImageData[i * 4+3] = 255;
		}
	}
	else
	if (textureData.bpp == 8)
	{
		for (long i = 0; i < textureData.width*textureData.height; i++)
		{
			newImageData[i * 4] = textureData.imageData[i];
			newImageData[i * 4+1] = textureData.imageData[i];
			newImageData[i * 4+2] = textureData.imageData[i];
			newImageData[i * 4+3] = 255;
		}
	}
	else
		printf("UNSUPPORTED BPP!\n");
	free(textureData.imageData);
	textureData.imageData = newImageData;
	textureData.bpp = 32;
}


void SVK::loadTGATexture(AllocatedImage& texture, const char* path)
{
    TextureData textureData;
    loadTGATextureData(path, &textureData);
    printf("bpp %d\n", textureData.bpp);
    convertTo4(textureData); // Added by Ingemar
    _createImage(texture, (void*)textureData.imageData, VkExtent3D{static_cast<unsigned int>(textureData.width), static_cast<unsigned int>(textureData.height), 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
    freeTextureData(&textureData); // Disposes textureData.imageData
    _mainDeletionQueue.push_function([&]()
    {
        _destroyImage(texture);
    });
}

void SVK::createSingleColorTexture(AllocatedImage& texture, vec4 const& v)
{
    createSingleColorTexture(texture, v.r, v.g, v.b, v.a);
}

void SVK::createSingleColorTexture(AllocatedImage& texture,
					uint8_t const& red,
					uint8_t const& green,
					uint8_t const& blue,
					uint8_t const& alpha)
{
    uint32_t r = red;
    uint32_t g = green;
    uint32_t b = blue;
    uint32_t a = alpha;

    a <<= 6 * 4;
    b <<= 4 * 4;
    g <<= 2 * 4;
    uint32_t convertedColor = r | g | b | a;
    _createImage(texture, (void*)&convertedColor, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
	_mainDeletionQueue.push_function([&]() {
        _destroyImage(texture);
                                     });
}

// loadInlineTexture with and without format, added by Ingemar
// Must use 4 bytes per pixel!
void SVK::loadInlineTexture(AllocatedImage& texture, unsigned char *data, int width, int height)
{
  loadInlineTexture(texture, data, width, height, VK_FORMAT_R8G8B8A8_UNORM);  

//   svk._createImage(texture, (void*)data, VkExtent3D{static_cast<unsigned int>(width), static_cast<unsigned int>(height), 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);
//    _mainDeletionQueue.push_function([&]()
//    {
//        _destroyImage(texture);
//    });
}

// This must exist somewhere but I have not found it. /Ingemar
int getSizeFromFormat(VkFormat format)
{
      int theSize = 4;
        if (format == VK_FORMAT_R8G8B8A8_UNORM)
          {theSize = 4;}
        if (format == VK_FORMAT_R16G16B16A16_SFLOAT)
          {theSize = 8;}
        if (format == VK_FORMAT_R32G32B32A32_SFLOAT)
          {theSize = 16;}
          // MANY formats not accounted for. Add if needed! https://registry.khronos.org/VulkanSC/specs/1.0-extensions/man/html/VkFormat.html
        return theSize;
}

// Must use 4 bytes per pixel!
void SVK::loadInlineTexture(AllocatedImage& texture, unsigned char *data, int width, int height, VkFormat format)
{
    // Just in case since there is no zeroing option. Or is it?
    bool freemalloc = false;
    if (data == NULL)
    {
      int theSize = getSizeFromFormat(format);

      data = (unsigned char *)calloc(width * theSize, height);
      freemalloc = true;
    }
    /*svk.*/ _createImage(texture, (void*)data, VkExtent3D{static_cast<unsigned int>(width), static_cast<unsigned int>(height), 1}, format, VK_IMAGE_USAGE_SAMPLED_BIT);
 	if (freemalloc) free(data);
    _mainDeletionQueue.push_function([&]()
    {
        _destroyImage(texture);
    });
}

void SVK::createFloatTexture(AllocatedImage& texture, unsigned int width, unsigned int height)
{
  loadInlineTexture(texture, NULL, width, height, VK_FORMAT_R16G16B16A16_SFLOAT);
}


/* TO DO?
void SVK::createTexture(AllocatedImage& texture, unsigned int width, umsigned int height, int format) // like  VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R16G16B16A16_SFLOAT
{
    svk._createImage(texture, NULL, VkExtent3D{static_cast<unsigned int>(width), static_cast<unsigned int>(height), 1}, format, VK_IMAGE_USAGE_SAMPLED_BIT);
    _mainDeletionQueue.push_function([&]()
    {
        _destroyImage(texture);
    });
}
*/

void SVK::setBackgroundColor(vec3 const& color)
{
    _backgroundColor = vec4(color.r, color.g, color.b, 1.0);
}

void SVK::setBackgroundColor(float const& red,
				float const& green,
				float const& blue)
{
    _backgroundColor = vec4(red, green, blue, 1.0);
}

void SVK::setCullMode(VkCullModeFlagBits const& value)
{
    _cullMode = value;
}

// Three cullings hiding VkCullModeFlagBits
void SVK::cullBack()
{
    _cullMode = VK_CULL_MODE_BACK_BIT;
}
void SVK::cullFront()
{
    _cullMode = VK_CULL_MODE_FRONT_BIT;
}

void SVK::cullNone()
{
    _cullMode = VK_CULL_MODE_NONE;
}

void SVK::setDepthTestEnabled(bool const& value)
{
    _depthTestEnabled = value;
}

void SVK::setBlendingEnabled(bool const& value)
{
    _blendingEnabled = value;
}
void SVK::setBlendingAdditive(bool const& value)
{
    _blendingAdditive = value;
}

void SVK::disableStencil()
{
  _stencil_enable = false;
}

void SVK::setColorsEnable(bool const& value)
{
  _colorsEnabled = value;
}

// Parameters passed straight to PipelineBuilder::setStencil to fill a VkStencilOpState
void SVK::setStencil(VkCompareOp compareOp, VkStencilOp failOp, VkStencilOp depthFailOp, VkStencilOp passOp, uint32_t ref)
{
  _stencil_enable = true;
  _stencil_compareOp = compareOp;
 _stencil_failOp = failOp;
 _stencil_depthFailOp = depthFailOp;
 _stencil_passOp = passOp;
 _stencil_ref = ref;
}

// Idé för tillägg: setStencilToFillMask + setStencilToUseMask
// Två användbara standardvärden?


// Not supported yet
void SVK::setMultisamplingEnabled(bool const& value)
{
    _multisampling = value;
}

void SVK::setPolygonMode(VkPolygonMode const& value)
{
    _polygonMode = value;
}

void SVK::bindDrawingFunction(std::function<void(VkCommandBuffer cmd)>&& function)
{
    _userDrawFunction = function;
}

void SVK::bindMouseMotionFunction(std::function<void(uint32_t xRel, uint32_t yRel)>&& function)
{
    _userMouseMotionFunction = function;
}

void SVK::bindMousedownFunction(std::function<void(uint32_t button, bool state, uint32_t xPos, uint32_t yPos)>&& function)
{
    _userMousedownFunction = function;
}

void SVK::bindKeydownFunction(std::function<void(const char* name)>&& function)
//void SVK::bindKeydownFunction(std::function<void(std::string name)>&& function)
{
    _userKeydownFunction = function;
}

void SVK::bindResizeFunction(std::function<void(int width, int height)>&& function)
{
    _userResizeFunction = function;
}



void SVK::printDeviceLimits(std::ostream& os) const
{
    VkPhysicalDeviceProperties deviceProperties;
	vkGetPhysicalDeviceProperties(_chosenGPU, &deviceProperties);
    os << "maxPushConstantSize: " << deviceProperties.limits.maxPushConstantsSize << std::endl
       << "maxVertexInputAttributes: " << deviceProperties.limits.maxVertexInputAttributes << std::endl
       << "maxVertexInputBindings: " << deviceProperties.limits.maxVertexInputBindings << std::endl
       << "maxVertexInputAttributeOffset: " << deviceProperties.limits.maxVertexInputAttributeOffset << std::endl
       << "maxVertexInputBindingStride: " << deviceProperties.limits.maxVertexInputBindingStride << std::endl
       << "maxFragmentInputComponents: " << deviceProperties.limits.maxFragmentInputComponents << std::endl
       << "maxFragmentOutputAttachments: " << deviceProperties.limits.maxFragmentOutputAttachments << std::endl
       << "maxUniformBufferRange: " << deviceProperties.limits.maxUniformBufferRange << std::endl
       << "maxPerStageDescriptorSampledImages: " <<  deviceProperties.limits.maxPerStageDescriptorSampledImages << std::endl;
}

bool SVK::keyIsDown(std::string const &key) const // Changed to "key is down" from "is key down" since "if key is down" is more logical than "if is key down"
{
    std::string copy{key};
    for (char &c : copy)
        c = std::tolower(c);
    return SDL_GetKeyboardState(nullptr)[_SimpleVulkanToSDLKeyTable[_handleSpecialKeys(copy)]];
}

bool SVK::mouseIsDown(int button) const
{
    int x, y;
    Uint8 state = SDL_GetMouseState(&x, &y);
    if((state & (1 << button)) != 0) return true;
    return false;
}

void SVK::getMouse(int *x, int *y) const
{
    SDL_GetMouseState(x, y);  
}

float SVK::getAspectRatio() const
{
    return static_cast<float>(_drawExtent.width) / static_cast<float>(_drawExtent.height);
}

//void SVK::enableDepthClipControl()
//{
//    if (_isInitialized)
//        throw std::logic_error("You can only call enableDepthClipControl() before init()");
//    _depthClipControlEnabled = true;
//}

void SVK::disableDepthClipControl()
{
    if (_isInitialized)
        throw std::logic_error("You can only call disableDepthClipControl() before init()");
    _depthClipControlEnabled = false;
}

void SVK::uncapFrameRate()
{
    if (_isInitialized)
        throw std::logic_error("You can only call uncapFrameRate() before init()");
    _uncappedFrameRate = true;
}

void SVK::initWindowName(std::string const& name)
{
    if (_isInitialized)
        throw std::logic_error("You can only call initWindowName() before init()");
    _windowName = name;
}

void SVK::initStencil()
{
    if (_isInitialized)
        throw std::logic_error("You can only call initStencil() before init()");
    _allocateStencil = true;
}

void SVK::setDebugMessengerSeverity(VkDebugUtilsMessageSeverityFlagBitsEXT const& severity)
{
    if (_isInitialized)
        throw std::logic_error("You can only call setDebugMessengerSeverity() before init()");
    _debugMessengerSeverity = severity;
}

double SVK::getDelta() const
{
    return _deltaHandler.getDelta();
}

double SVK::getTimeSinceStart() const
{
    return _deltaHandler.getTimeSinceRestart();
}

void SVK::run()
{
    SDL_Event e;
    bool bQuit = false;
    bool pauseRendering = false;
    bool resizing = false;

    // main loop
    _deltaHandler.restart();
    SDL_SetRelativeMouseMode(SDL_FALSE);
    while (!bQuit && !gQuit)
    {
        // Handle events on queue
        while (SDL_PollEvent(&e) != 0)
        {
            // close the window when user alt-f4s or clicks the X button

            if (e.type == SDL_QUIT)
                bQuit = true;

            if (e.type == SDL_WINDOWEVENT)
            {
                if (e.window.event == SDL_WINDOWEVENT_MINIMIZED)
                {
                    pauseRendering = true;
                }
                if (e.window.event == SDL_WINDOWEVENT_RESTORED)
                {
                    pauseRendering = false;
                }
                if (e.window.event == SDL_WINDOWEVENT_RESIZED)
                {
                    _resizeRequested = true;
                    resizing = true;
                    
                    if (_userResizeFunction)
                    {
                      int w, h;
                      SDL_GetWindowSize(_window, &w, &h);
                      _userResizeFunction(w, h);
                    }
                } 
            }

            else if (e.type ==  SDL_MOUSEBUTTONDOWN || e.type ==  SDL_MOUSEBUTTONUP)// if the event is mouse click
            {
                //SDL_MouseButtonEvent button = e.button;
                if(_userMousedownFunction)
                 {
                    _userMousedownFunction(e.button.which, e.type ==  SDL_MOUSEBUTTONDOWN, e.button.x, e.button.y);
                    //void mouseUpDown(int button, int state, int x, int y)
                }
            }
            else if (e.type == SDL_KEYDOWN)
            {
                if (_userKeydownFunction)
                {
                  const char* key = SDL_GetKeyName(e.key.keysym.sym);
//                  _userKeydownFunction(std::string(key)); I want this but it did not work.
                  _userKeydownFunction(key);
                }
                else
                {
                  SDL_Scancode scanCode = e.key.keysym.scancode;
                  if (scanCode == SDL_SCANCODE_ESCAPE)
  //                  _switchMouseMode();
                      bQuit = true; // Changed by Ingemar
                }
            }

            else if (e.type == SDL_MOUSEMOTION) // and _isMouseHidden)
            {
                if (_userMouseMotionFunction)
                    _userMouseMotionFunction(e.motion.xrel, e.motion.yrel);
            }
        }

        if (bQuit)
            break;

        // This helps prevent validation errors from Vulkan trying 
        // to create a new swapchain while the user is still resizing.
        if (resizing) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
            resizing = false;
            continue;
        }
            

        // do not draw if we are minimized
        if (pauseRendering) {
            // throttle the speed to avoid the endless spinning
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            continue;
        }

        if (_resizeRequested)
            _resizeSwapchain();

        _deltaHandler.updateDelta();
        
        // Added by Ingemar: Make sure the frame rate is reasonably steady and let the CPU rest a bit when it can.
        double currentTime = GetSeconds();
        //printf("%f %f \n", currentTime, lastTime);
        if ((currentTime - lastTime) > frameTime)
        {
           _draw();
          lastTime = GetSeconds();
        }
        else
          // Always sleep a tiny bit
          std::this_thread::sleep_for(std::chrono::milliseconds(1));
        
    }
}

void SVK::cleanup()
{
    if (_isInitialized) {
        vkDeviceWaitIdle(_device);

        

        for (unsigned int i = 0; i < FRAME_OVERLAP; ++i){
            _frames[i]._deletionQueue.flush();
            vkDestroyCommandPool(_device, _frames[i]._commandPool, nullptr);

            vkDestroyFence(_device, _frames[i]._renderFence, nullptr);
            vkDestroySemaphore(_device, _frames[i]._swapchainSemaphore, nullptr);
            vkDestroySemaphore(_device, _frames[i]._renderSemaphore, nullptr);
        }
        
        _destroySwapchain();
        _destroyAllocatedImages();
        _destroyDescriptors();

        _mainDeletionQueue.flush();

        vkDestroySurfaceKHR(_instance, _surface, nullptr);
        vkDestroyDevice(_device, nullptr);

        vkb::destroy_debug_utils_messenger(_instance, _debug_messenger);
        vkDestroyInstance(_instance, nullptr);
        SDL_DestroyWindow(_window);
    }

    // clear engine pointer
    loadedEngine = nullptr;
}

SVK& SVK::get()
{
    return *loadedEngine;
}

void SVK::_initVulkan()
{
    vkb::InstanceBuilder builder;

    auto inst_ret = builder.set_app_name("Simpler Vulkan")
        .request_validation_layers(bUseValidationLayers)
        .use_default_debug_messenger()
        .set_debug_messenger_severity(_debugMessengerSeverity)
        .require_api_version(1, 3, 0)
        .build();

    vkb::Instance vkb_inst = inst_ret.value();

    _instance = vkb_inst.instance;
    _debug_messenger = vkb_inst.debug_messenger;

    SDL_Vulkan_CreateSurface(_window, _instance, &_surface);

    VkPhysicalDeviceFeatures features{};
    features.fillModeNonSolid = true;

    VkPhysicalDeviceVulkan12Features features12{};
    features12.bufferDeviceAddress = true;
    features12.descriptorIndexing = true;

    VkPhysicalDeviceVulkan13Features features13{};
    features13.dynamicRendering = true;
    features13.synchronization2 = true;
    

    vkb::PhysicalDeviceSelector selector{vkb_inst};
    selector
        .set_minimum_version(1, 3)
        .set_required_features(features)
        .set_required_features_12(features12)
        .set_required_features_13(features13)
        .set_surface(_surface);

    if (_depthClipControlEnabled)
    {
        VkPhysicalDeviceDepthClipControlFeaturesEXT depthClipControlFeatures = {};
        depthClipControlFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT;
        depthClipControlFeatures.depthClipControl = VK_TRUE;
        
        selector
        .add_required_extension(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME)
        .add_required_extension_features(depthClipControlFeatures);
    }

    auto selectedDevice = selector.select();

    if (!selectedDevice and _depthClipControlEnabled)
        throw std::runtime_error("Device does not support VK_EXT_depth_clip_control.");

    vkb::PhysicalDevice physicalDevice = selectedDevice.value();

    vkb::DeviceBuilder deviceBuilder{physicalDevice};
    vkb::Device vkbDevice = deviceBuilder
        .build()
        .value();

    _device = vkbDevice.device;
    _chosenGPU = physicalDevice.physical_device;

    _graphicsQueue = vkbDevice.get_queue(vkb::QueueType::graphics).value();
    _graphicsQueueFamily = vkbDevice.get_queue_index(vkb::QueueType::graphics).value();

    VmaAllocatorCreateInfo allocatorInfo = {};
    allocatorInfo.physicalDevice = _chosenGPU;
    allocatorInfo.device = _device;
    allocatorInfo.instance = _instance;
    allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
    vmaCreateAllocator(&allocatorInfo, &_allocator);

    _mainDeletionQueue.push_function([&]() { vmaDestroyAllocator(_allocator); });
}

void SVK::_initSwapchain()
{
    _createSwapchain(_windowExtent.width, _windowExtent.height);

    _createAllocatedImages();
}

void SVK::_initCommands()
{
    VkCommandPoolCreateInfo commandPoolInfo = _generateVkCommandPoolCreateInfo(_graphicsQueueFamily, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);

    for (unsigned int i = 0; i < FRAME_OVERLAP; ++i) {
        VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, &_frames[i]._commandPool));

        VkCommandBufferAllocateInfo cmdAllocInfo = _generateVkCommandBufferAllocateInfo(_frames[i]._commandPool, 1);

        VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &_frames[i]._mainCommandBuffer));
    }

    VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, &_immCommandPool));

    VkCommandBufferAllocateInfo cmdAllocInfo = _generateVkCommandBufferAllocateInfo(_immCommandPool, 1);

    VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &_immCommandBuffer));

    _mainDeletionQueue.push_function([this]() { vkDestroyCommandPool(_device, _immCommandPool, nullptr); });
}

void SVK::_initSyncStructures()
{
    VkFenceCreateInfo fenceCreateInfo = _generateVkFenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
    VkSemaphoreCreateInfo semaphoreCreateInfo = _generateVkSemaphoreCreateInfo();

    for (unsigned int i = 0; i < FRAME_OVERLAP; ++i) {
        VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &_frames[i]._renderFence));

        VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_frames[i]._swapchainSemaphore));
        VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_frames[i]._renderSemaphore));
    }

    VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &_immFence));
    _mainDeletionQueue.push_function([this]() { vkDestroyFence(_device, _immFence, nullptr); });
}

void SVK::_initDescriptors()
{
    std::vector<DescriptorAllocator::PoolSizeRatio> sizes =
    {
        {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1}
    };

    _globalDescriptorAllocator.init_pool(_device, 10, sizes);

    DescriptorLayoutBuilder builder;
    builder.add_binding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE);
    _drawImageDescriptorLayout = builder.build(_device, VK_SHADER_STAGE_COMPUTE_BIT);

    _drawImageDescriptors = _globalDescriptorAllocator.allocate(_device, _drawImageDescriptorLayout);

    DescriptorWriter writer;
    writer.write_image(0, _drawImage.imageView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE);
    writer.update_set(_device, _drawImageDescriptors);


    for (unsigned int i = 0; i < FRAME_OVERLAP; ++i)
    {
        std::vector<DescriptorAllocatorGrowable::PoolSizeRatio> frame_sizes =
        {
            { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 3 },
            { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 },
            { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3 },
            { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4 },
        };

        _frames[i]._frameDescriptors = DescriptorAllocatorGrowable{};
        _frames[i]._frameDescriptors.init(_device, 1000, frame_sizes);
    }

    DescriptorLayoutBuilder singleImageBuilder;
    singleImageBuilder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
    _singleImageDescriptorLayout = singleImageBuilder.build(_device, VK_SHADER_STAGE_FRAGMENT_BIT);
}

void SVK::_initDefaultData()
{
    uint32_t white = 0xFFFFFFFF;
    _createImage(_whiteImage, (void*)&white, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);

    uint32_t grey = 0xFFAAAAAA;
    _createImage(_greyImage, (void*)&grey, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);

    uint32_t black = 0xFF000000;
    _createImage(_blackImage, (void*)&black, VkExtent3D{1, 1, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);

    uint32_t magenta = 0xFFFF00FF;
    std::array<uint32_t, 16 * 16 > pixels; //for 16x16 checkerboard texture
    for (int x = 0; x < 16; x++){
        for (int y = 0; y < 16; y++){
            pixels[y * 16 + x] = ((x % 2) ^ (y % 2)) ? magenta : black;
        }
    }
    _createImage(_errorCheckerboardImage, pixels.data(), VkExtent3D{16, 16, 1}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT);


    VkSamplerCreateInfo sampl = {};
    sampl.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;

    sampl.magFilter = VK_FILTER_NEAREST;
    sampl.minFilter = VK_FILTER_NEAREST;
//        sampl.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; // VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE


    vkCreateSampler(_device, &sampl, nullptr, &_defaultSamplerNearest);

    sampl.magFilter = VK_FILTER_LINEAR;
    sampl.minFilter = VK_FILTER_LINEAR;
    sampl.addressModeU = VkSamplerAddressMode(0);
//    sampl.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
    vkCreateSampler(_device, &sampl, nullptr, &_defaultSamplerLinear);

    _mainDeletionQueue.push_function([&]() {
        _destroyImage(_whiteImage);
        _destroyImage(_greyImage);
        _destroyImage(_blackImage);
        _destroyImage(_errorCheckerboardImage);
        vkDestroySampler(_device, _defaultSamplerNearest, nullptr);
        vkDestroySampler(_device, _defaultSamplerLinear, nullptr);
                                     });
}

void SVK::_draw()
{
    VK_CHECK(vkWaitForFences(_device, 1, &_getCurrentFrame()._renderFence, true, UINT64_MAX));

    _getCurrentFrame()._deletionQueue.flush();
    _getCurrentFrame()._frameDescriptors.clear_pools(_device);

    uint32_t swapchainImageIndex;
    VkResult e = vkAcquireNextImageKHR(_device, _swapchain, UINT64_MAX, _getCurrentFrame()._swapchainSemaphore, nullptr, &swapchainImageIndex);
    if (e == VK_ERROR_OUT_OF_DATE_KHR) {
        _resizeRequested = true;
        return;
    }

    _drawExtent.width = std::min(_swapchainExtent.width, _drawImage.imageExtent.width) * _renderScale;
    _drawExtent.height = std::min(_swapchainExtent.height, _drawImage.imageExtent.height) * _renderScale;

    VK_CHECK(vkResetFences(_device, 1, &_getCurrentFrame()._renderFence));

    VkCommandBuffer cmd = _getCurrentFrame()._mainCommandBuffer;
    VK_CHECK(vkResetCommandBuffer(cmd, 0));
    VkCommandBufferBeginInfo cmdBeginInfo =
            _generateVkCommandBufferBeginInfo(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
            

    VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));

    transitionImage(cmd, _drawImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);

    _drawBackground(cmd);

    transitionImage(cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    transitionImage(cmd, _depthImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);

    _setViewportAndScissor(cmd);

// Added here by Ingemar but removed since we need multiple passes.  Must be in your draw function.
//    	VkRenderingInfo renderInfo = getRenderingInfo();
//	vkCmdBeginRendering(cmd, &renderInfo);

    _userDrawFunction(cmd);

// Added here by Ingemar but removed since we need multiple passes. Must be in your draw function.
//    vkCmdEndRendering(cmd);

    transitionImage(cmd, _drawImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
    transitionImage(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);

    copyImageToImage(cmd, _drawImage.image, _swapchainImages[swapchainImageIndex], _drawExtent, _swapchainExtent);

    transitionImage(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);

    VK_CHECK(vkEndCommandBuffer(cmd));

    VkCommandBufferSubmitInfo cmdinfo = _generateVkCommandBufferSubmitInfo(cmd);
    VkSemaphoreSubmitInfo waitInfo = _generateVkSemaphoreSubmitInfo(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, _getCurrentFrame()._swapchainSemaphore);
    VkSemaphoreSubmitInfo signalInfo = _generateVkSemaphoreSubmitInfo(VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, _getCurrentFrame()._renderSemaphore);
    VkSubmitInfo2 submit = _generateVkSubmitInfo2(&cmdinfo, &signalInfo, &waitInfo);
    VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, _getCurrentFrame()._renderFence));

    VkPresentInfoKHR presentInfo = {};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    presentInfo.pNext = nullptr;
    presentInfo.pSwapchains = &_swapchain;
    presentInfo.swapchainCount = 1;
    presentInfo.pWaitSemaphores = &_getCurrentFrame()._renderSemaphore;
    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pImageIndices = &swapchainImageIndex;

    

    VkResult presentResult = vkQueuePresentKHR(_graphicsQueue, &presentInfo);
    if (presentResult == VK_ERROR_OUT_OF_DATE_KHR) {
        _resizeRequested = true;
        return;
    }
    ++_frameNumber;
}

void SVK::_allocateStagingBuffer(AllocatedBuffer& buffer, size_t const& allocationSize)
{
    _allocateCPUBuffer(buffer, allocationSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
}

void SVK::_allocateGPUBuffer(AllocatedBuffer& buffer,size_t const& allocSize, VkBufferUsageFlags const& usage)
{
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.pNext = nullptr;
    bufferInfo.size = allocSize;
    bufferInfo.usage = usage;

    VmaAllocationCreateInfo vmaallocInfo = {};
    vmaallocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
    vmaallocInfo.flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;

    VK_CHECK(vmaCreateBuffer(_allocator, &bufferInfo, &vmaallocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
}

void SVK::_allocateCPUBuffer(AllocatedBuffer& buffer, size_t const& allocSize, VkBufferUsageFlags const& usage)
{
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.pNext = nullptr;
    bufferInfo.size = allocSize;
    bufferInfo.usage = usage;

    VmaAllocationCreateInfo vmaallocInfo = {};
    vmaallocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
    vmaallocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;

    VK_CHECK(vmaCreateBuffer(_allocator, &bufferInfo, &vmaallocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
}

void SVK::_destroyBuffer(AllocatedBuffer& buffer)
{
    vmaDestroyBuffer(_allocator, buffer.buffer, buffer.allocation);
}

void SVK::_createSwapchain(uint32_t width, uint32_t height)
{
    vkb::SwapchainBuilder swapchainBuilder{_chosenGPU, _device, _surface};

    _swapchainImageFormat = VK_FORMAT_B8G8R8A8_UNORM;

    swapchainBuilder
        .set_desired_format(VkSurfaceFormatKHR{.format = _swapchainImageFormat, .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
        .set_desired_extent(width, height)
        .add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT);
    
    if (_uncappedFrameRate)
        swapchainBuilder.set_desired_present_mode(VK_PRESENT_MODE_IMMEDIATE_KHR);
    else
        swapchainBuilder.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR);
    

    
    vkb::Swapchain vkbSwapChain = swapchainBuilder.build().value();

    _swapchainExtent = vkbSwapChain.extent;
    _swapchain = vkbSwapChain.swapchain;
    _swapchainImages = vkbSwapChain.get_images().value();
    _swapchainImageViews = vkbSwapChain.get_image_views().value();
}

void SVK::_createAllocatedImages()
{
    VkExtent3D drawImageExtent = {
        _windowExtent.width,
        _windowExtent.height,
        1
    };

    _drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
    _drawImage.imageExtent = drawImageExtent;

    VkImageUsageFlags drawImageUsages{};
    drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
    drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    drawImageUsages |= VK_IMAGE_USAGE_STORAGE_BIT;
    drawImageUsages |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

    VkImageCreateInfo rimg_info = _generateVkImageCreateInfo(_drawImage.imageFormat, drawImageUsages, drawImageExtent);

    VmaAllocationCreateInfo rimg_allocinfo = {};
    rimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
    rimg_allocinfo.requiredFlags = VkMemoryPropertyFlags(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    vmaCreateImage(_allocator, &rimg_info, &rimg_allocinfo, &_drawImage.image, &_drawImage.allocation, nullptr);// EVIL

    VkImageViewCreateInfo rview_info = _generateVkImageViewCreateInfo(_drawImage.imageFormat, _drawImage.image, VK_IMAGE_ASPECT_COLOR_BIT);

    VK_CHECK(vkCreateImageView(_device, &rview_info, nullptr, &_drawImage.imageView));

    if (_allocateStencil)
    _depthImage.imageFormat = VK_FORMAT_D24_UNORM_S8_UINT; // If stencil, VK_FORMAT_D32_SFLOAT_S8_UINT or VK_FORMAT_D24_UNORM_S8_UINT
    else
    _depthImage.imageFormat = VK_FORMAT_D32_SFLOAT; // No stencil
    _depthImage.imageExtent = drawImageExtent;

    VkImageUsageFlags depthImageUsages{};
    depthImageUsages |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; // Test by Ingemar to allow reading depth

    VkImageCreateInfo dimg_info = _generateVkImageCreateInfo(_depthImage.imageFormat, depthImageUsages, drawImageExtent);

    vmaCreateImage(_allocator, &dimg_info, &rimg_allocinfo, &_depthImage.image, &_depthImage.allocation, nullptr);

    VkImageViewCreateInfo dview_info;
    if (_allocateStencil)
      dview_info = _generateVkImageViewCreateInfo(_depthImage.imageFormat, _depthImage.image, VK_IMAGE_ASPECT_DEPTH_BIT + VK_IMAGE_ASPECT_STENCIL_BIT);
    else
      dview_info = _generateVkImageViewCreateInfo(_depthImage.imageFormat, _depthImage.image, VK_IMAGE_ASPECT_DEPTH_BIT); // No stencil

    VK_CHECK(vkCreateImageView(_device, &dview_info, nullptr, &_depthImage.imageView));
}

void SVK::_createImage(AllocatedImage& image,
                       VkExtent3D size,
                       VkFormat format,
                       VkImageUsageFlags usage,
                       bool mipmapped)
{
    image.imageFormat = format;
    image.imageExtent = size;

    VkImageCreateInfo img_info = _generateVkImageCreateInfo(format, usage, size);
    if (mipmapped)
        img_info.mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(size.width, size.height)))) + 1;

    VmaAllocationCreateInfo allocinfo = {};
    allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
    allocinfo.requiredFlags = VkMemoryPropertyFlags(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    VK_CHECK(vmaCreateImage(_allocator, &img_info, &allocinfo, &image.image, &image.allocation, nullptr));

    VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
    if (format == VK_FORMAT_D32_SFLOAT)
        aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;

    VkImageViewCreateInfo view_info = _generateVkImageViewCreateInfo(format, image.image, aspectFlag);
    view_info.subresourceRange.levelCount = img_info.mipLevels;

    VK_CHECK(vkCreateImageView(_device, &view_info, nullptr, &image.imageView));
}

void SVK::_createImage(AllocatedImage& image,
                       void* data,
                       VkExtent3D size,
                       VkFormat format,
                       VkImageUsageFlags usage,
                       bool mipmapped)
{

// Added format to size.
      int theSize = getSizeFromFormat(format);

    size_t data_size = size.depth * size.width * size.height * theSize; // Was 4 - why?
    AllocatedBuffer stagingBuffer;
    _allocateStagingBuffer(stagingBuffer, data_size);

    if (data != NULL) // Allow NULL data - unnecesssary?
      memcpy(stagingBuffer.info.pMappedData, data, data_size);

    _createImage(image, size, format, usage | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, mipmapped);
    image.textureSampler = NULL;

    _immediateSubmit([&](VkCommandBuffer cmd)
    {
        transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);

        VkBufferImageCopy copyRegion = {};
        copyRegion.bufferOffset = 0;
        copyRegion.bufferRowLength = 0;
        copyRegion.bufferImageHeight = 0;

        copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        copyRegion.imageSubresource.mipLevel = 0;
        copyRegion.imageSubresource.baseArrayLayer = 0;
        copyRegion.imageSubresource.layerCount = 1;
        copyRegion.imageExtent = size;

        vkCmdCopyBufferToImage(cmd, stagingBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);

        transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
    });

    _destroyBuffer(stagingBuffer);
}

void SVK::_resizeSwapchain()
{
    vkDeviceWaitIdle(_device);

    _destroySwapchain();
    _destroyAllocatedImages();
    _destroyDescriptors();

    int w, h;
    SDL_GetWindowSize(_window, &w, &h);
    _windowExtent.width = w;
    _windowExtent.height = h;

    _createSwapchain(_windowExtent.width, _windowExtent.height);
    _createAllocatedImages();
    _initDescriptors();

    _resizeRequested = false;
}

void SVK::_destroySwapchain()
{
    vkDestroySwapchainKHR(_device, _swapchain, nullptr);

    for (size_t i = 0; i < _swapchainImageViews.size(); ++i)
        vkDestroyImageView(_device, _swapchainImageViews[i], nullptr);
}

void SVK::_destroyAllocatedImages()
{
    vmaDestroyImage(_allocator, _drawImage.image, _drawImage.allocation);
    vkDestroyImageView(_device, _drawImage.imageView, nullptr);
    vmaDestroyImage(_allocator, _depthImage.image, _depthImage.allocation);
    vkDestroyImageView(_device, _depthImage.imageView, nullptr);
}

void SVK::_destroyImage(AllocatedImage& img)
{
    vkDestroyImageView(_device, img.imageView, nullptr);
    vmaDestroyImage(_allocator, img.image, img.allocation);
}

void SVK::_destroyDescriptors()
{
    _globalDescriptorAllocator.destroy_pool(_device);
    vkDestroyDescriptorSetLayout(_device, _drawImageDescriptorLayout, nullptr);
    vkDestroyDescriptorSetLayout(_device, _singleImageDescriptorLayout, nullptr);

    for (unsigned int i = 0; i < FRAME_OVERLAP; ++i)
        _frames[i]._frameDescriptors.destroy_pools(_device);
}

void SVK::_drawBackground(VkCommandBuffer cmd)
{
    VkClearColorValue clearValue{_backgroundColor.r, _backgroundColor.g, _backgroundColor.b, _backgroundColor.a};
    VkImageSubresourceRange clearRange = _generateVkImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
    vkCmdClearColorImage(cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange);
}

void SVK::_setViewportAndScissor(VkCommandBuffer cmd)
{
    VkViewport viewport = {};
    viewport.x = 0;
    viewport.y = 0;
    viewport.width = _drawExtent.width;
    viewport.height = _drawExtent.height; // Try negative! Should be negative! Requires Vulkan 1.3 or an extension.
    viewport.minDepth = 1.0f;
    viewport.maxDepth = 0.0f;
    vkCmdSetViewport(cmd, 0, 1, &viewport);

    VkRect2D scissor = {};
    scissor.offset.x = 0;
    scissor.offset.y = 0;
    scissor.extent.width = _drawExtent.width;
    scissor.extent.height = _drawExtent.height;
    vkCmdSetScissor(cmd, 0, 1, &scissor);
}

void SVK::_immediateSubmit(std::function<void(VkCommandBuffer cmd)>&& function)
{
    VK_CHECK(vkResetFences(_device, 1, &_immFence));
    VK_CHECK(vkResetCommandBuffer(_immCommandBuffer, 0));

    VkCommandBuffer cmd = _immCommandBuffer;

    VkCommandBufferBeginInfo cmdBeginInfo = _generateVkCommandBufferBeginInfo(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);

    VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));

    function(cmd);

    VK_CHECK(vkEndCommandBuffer(cmd));

    VkCommandBufferSubmitInfo cmdInfo = _generateVkCommandBufferSubmitInfo(cmd);
    VkSubmitInfo2 submit = _generateVkSubmitInfo2(&cmdInfo, nullptr, nullptr);

    VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, _immFence));
    VK_CHECK(vkWaitForFences(_device, 1, &_immFence, true, UINT64_MAX));
}

void SVK::_switchMouseMode()
{
    if (_isMouseHidden){
        SDL_SetRelativeMouseMode(SDL_FALSE);
        SDL_WarpMouseInWindow(_window, _windowExtent.width / 2, _windowExtent.height / 2);
    }
    else
        SDL_SetRelativeMouseMode(SDL_TRUE);
    _isMouseHidden = !_isMouseHidden;
}

void SVK::hideMouse()
{
    if (!_isMouseHidden)
        SDL_SetRelativeMouseMode(SDL_TRUE);
    _isMouseHidden = true;
}

void SVK::showMouse()
{
    if (_isMouseHidden)
    {
        SDL_SetRelativeMouseMode(SDL_FALSE);
        SDL_WarpMouseInWindow(_window, _windowExtent.width / 2, _windowExtent.height / 2);
    }
    _isMouseHidden = false;
}

// Added by Ingemar for external access
//VkDevice SVK::getDevice()
//{
//  return _device;
//}

void SVK::quit()
{
  gQuit = true;
}



// Added by Ingemar for texture without "Object"
// I think most of this could be done as initialization, but vkCmdBindDescriptorSets can not.

VkDescriptorSet objectSet;

void SVK::beginBindings(VkDescriptorSetLayout descriptorSetLayout)
{
  objectSet = _getCurrentFrame()._frameDescriptors.allocate(_device, descriptorSetLayout);
}

void SVK::endBindings(VkCommandBuffer cmd, VkPipelineLayout& pipelineLayout)
{
  vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &objectSet, 0, nullptr);
}

void SVK::bindTexture(AllocatedImage *texture, const int binding)
{
    DescriptorWriter descriptorWriter;

    if (texture->textureSampler == NULL)
        descriptorWriter.write_image(binding, texture->imageView, _defaultSamplerNearest, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
    else
    {
        descriptorWriter.write_image(binding, texture->imageView, *(texture->textureSampler) /*_defaultSamplerNearest*/, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
    }
    
    descriptorWriter.update_set(_device, objectSet);
}

void SVK::bindUniform(AllocatedBuffer buffer, int buffersize, int binding)
{
	DescriptorWriter descriptorWriter;
	descriptorWriter.write_buffer(binding, buffer.buffer, buffersize, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
	descriptorWriter.update_set(_device, objectSet);
}

// This creates a subset. Add more as needed. See https://docs.vulkan.org/refpages/latest/refpages/source/VkSamplerCreateInfo.html for more options.
void SVK::createTextureSampler(VkSampler &theSampler, VkFilter magFilter, VkFilter minFilter, VkSamplerAddressMode addressMode)
{
    VkSamplerCreateInfo sampl = {};
    sampl.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;

    sampl.magFilter = magFilter; // VK_FILTER_NEAREST (0), VK_FILTER_LINEAR (1)
    sampl.minFilter = minFilter; // VK_FILTER_NEAREST, VK_FILTER_LINEAR
    sampl.addressModeU = addressMode; // VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE
    sampl.addressModeV = addressMode; // or 0, 1, 2
    sampl.addressModeW = addressMode;
	
    vkCreateSampler(svk._device, &sampl, nullptr, &theSampler);

    _mainDeletionQueue.push_function([&]()
    {
       vkDestroySampler(_device, theSampler, nullptr);
    });
}

/* From above:
	vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, *object.pipeline);

	VkDescriptorSet objectSet = _getCurrentFrame()._frameDescriptors.allocate(_device, *object.descriptorSetLayout);
	DescriptorWriter descriptorWriter;
	descriptorWriter.write_buffer(uniformData.binding, uniformData.buffer->buffer, uniformData.size, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
	descriptorWriter.update_set(_device, objectSet);

	vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, *object.pipelineLayout, 0, 1, &objectSet, 0, nullptr);
*/

/*
void SVK::copyFramebufferToTexture(VkCommandBuffer cmd, AllocatedImage texture)
{
   VkExtent2D src, dest;
    src.height = _drawImage.imageExtent.height;
    src.width = _drawImage.imageExtent.width;
    dest.height = texture.imageExtent.height;
    dest.width = texture.imageExtent.width;
   copyImageToImage(cmd, _drawImage.image, texture.image, src, dest);
}
*/

// New version with expected transitions.
void SVK::copyFramebufferToTexture(VkCommandBuffer cmd, AllocatedImage texture)
{
   VkExtent2D src, dest;
    src.height = _drawImage.imageExtent.height;
    src.width = _drawImage.imageExtent.width;
    dest.height = texture.imageExtent.height;
    dest.width = texture.imageExtent.width;

            transitionImage(cmd, texture.image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
            transitionImage(cmd, svk._drawImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
            copyImageToImage(cmd, _drawImage.image, texture.image, src, dest);
            transitionImage(cmd, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
            transitionImage(cmd, svk._drawImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}


void SVK::clearDepth(VkCommandBuffer cmd)
{
  VkClearDepthStencilValue clearValue = {};
  clearValue.depth = 1.0f;   // Clear depth to 1.0 (far plane)
  clearValue.stencil = 0;   // Clear stencil to 0

  // Define the subresource range (aspect, mip level, array layer)
  VkImageSubresourceRange subresourceRange = {};
  subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
  subresourceRange.baseMipLevel = 0;
  subresourceRange.levelCount = 1;
  subresourceRange.baseArrayLayer = 0;
  subresourceRange.layerCount = 1;


  vkCmdClearDepthStencilImage(
    cmd,
    _depthImage.image,
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
    &clearValue,
    1,
    &subresourceRange
    );
}

// Important here:
//void SVK::uploadToUniformBuffer(AllocatedBuffer& buffer, void const* bufferData, size_t const& bufferSize)
//void SVK::allocateUniformBuffer(AllocatedBuffer& buffer, size_t const& allocationSize)

// Global svk since there is currently no reason to have several.
SVK svk;
