/*
 * This source file is part of RmlUi, the HTML/CSS Interface Middleware
 *
 * For the latest information, see http://github.com/mikke89/RmlUi
 *
 * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
 * Copyright (c) 2019-2023 The RmlUi Team, and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#ifndef RMLUI_BACKENDS_RENDERER_VK_H
#define RMLUI_BACKENDS_RENDERER_VK_H

#include <RmlUi/Core/RenderInterface.h>

#ifdef RMLUI_PLATFORM_WIN32
	#include "RmlUi_Include_Windows.h"
	#define VK_USE_PLATFORM_WIN32_KHR
#endif

#include "RmlUi_Include_Vulkan.h"

#ifdef RMLUI_DEBUG
	#define RMLUI_VK_ASSERTMSG(statement, msg) RMLUI_ASSERTMSG(statement, msg)

	// Uncomment the following line to enable additional Vulkan debugging.
	// #define RMLUI_VK_DEBUG
#else
	#define RMLUI_VK_ASSERTMSG(statement, msg) static_cast<void>(statement)
#endif

// your specified api version, but in future it will be dynamic ^_^
#define RMLUI_VK_API_VERSION VK_API_VERSION_1_0

/**
 * Vulkan render interface for RmlUi
 *
 * My aim is to create compact, but easy to use class
 * I understand that it isn't good architectural choice to keep all things in one class
 * But I follow to RMLUI design and for implementing one GAPI backend it just needs one class
 * For user looks cool, but for programmer...
 *
 * It's better to try operate with very clean 'one-class' architecture rather than create own library for Vulkan
 * With many different classes, with not trivial signatures and etc
 * And as a result we should document that library so it's just a headache for all of us
 *
 * Reminder to users: If you want to implement your Vulkan renderer check previous commits of this work, because current system works only with new
 * and delete operations every frame (CPU side), on GPU we implemented the pre-allocated buffer with virtual allocs (Vma) so there's no problems and
 * all fine. I wrote all ideas and implementation for that.
 *
 * @author wh1t3lord (https://github.com/wh1t3lord)
 */

class RenderInterface_VK : public Rml::RenderInterface {
public:
	static constexpr uint32_t kSwapchainBackBufferCount = 3;
	static constexpr VkDeviceSize kVideoMemoryForAllocation = 4 * 1024 * 1024; // [bytes]

	RenderInterface_VK();
	~RenderInterface_VK();

	using CreateSurfaceCallback = bool (*)(VkInstance instance, VkSurfaceKHR* out_surface);

	bool Initialize(Rml::Vector<const char*> required_extensions, CreateSurfaceCallback create_surface_callback);
	void Shutdown();

	void BeginFrame();
	void EndFrame();

	void SetViewport(int width, int height);
	bool IsSwapchainValid();
	void RecreateSwapchain();

	// -- Inherited from Rml::RenderInterface --

	/// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future.
	Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
	/// Called by RmlUi when it wants to render application-compiled geometry.
	void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
	/// Called by RmlUi when it wants to release application-compiled geometry.
	void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;

	/// Called by RmlUi when a texture is required by the library.
	Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
	/// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels.
	Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions) override;
	/// Called by RmlUi when a loaded texture is no longer required.
	void ReleaseTexture(Rml::TextureHandle texture_handle) override;

	/// Called by RmlUi when it wants to enable or disable scissoring to clip content.
	void EnableScissorRegion(bool enable) override;
	/// Called by RmlUi when it wants to change the scissor region.
	void SetScissorRegion(Rml::Rectanglei region) override;

	/// Called by RmlUi when it wants to set the current transform matrix to a new matrix.
	void SetTransform(const Rml::Matrix4f* transform) override;

private:
	enum class shader_type_t : int { Vertex, Fragment, Unknown = -1 };
	enum class shader_id_t : int { Vertex, Fragment_WithoutTextures, Fragment_WithTextures };

	struct shader_vertex_user_data_t {
		// Member objects are order-sensitive to match shader.
		Rml::Matrix4f m_transform;
		Rml::Vector2f m_translate;
	};

	struct texture_data_t {
		VkImage m_p_vk_image;
		VkImageView m_p_vk_image_view;
		VkSampler m_p_vk_sampler;
		VkDescriptorSet m_p_vk_descriptor_set;
		VmaAllocation m_p_vma_allocation;
	};

	struct geometry_handle_t {
		int m_num_indices;

		VkDescriptorBufferInfo m_p_vertex;
		VkDescriptorBufferInfo m_p_index;
		VkDescriptorBufferInfo m_p_shader;

		// @ this is for freeing our logical blocks for VMA
		// see https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/virtual_allocator.html
		VmaVirtualAllocation m_p_vertex_allocation;
		VmaVirtualAllocation m_p_index_allocation;
		VmaVirtualAllocation m_p_shader_allocation;
	};

	struct buffer_data_t {
		VkBuffer m_p_vk_buffer;
		VmaAllocation m_p_vma_allocation;
	};

	class UploadResourceManager {
	public:
		UploadResourceManager() : m_p_device{}, m_p_fence{}, m_p_command_buffer{}, m_p_command_pool{}, m_p_graphics_queue{} {}
		~UploadResourceManager() {}

		void Initialize(VkDevice p_device, VkQueue p_queue, uint32_t queue_family_index)
		{
			RMLUI_VK_ASSERTMSG(p_queue, "you have to pass a valid VkQueue");
			RMLUI_VK_ASSERTMSG(p_device, "you have to pass a valid VkDevice for creation resources");

			m_p_device = p_device;
			m_p_graphics_queue = p_queue;

			Create_All(queue_family_index);
		}

		void Shutdown()
		{
			vkDestroyFence(m_p_device, m_p_fence, nullptr);
			vkDestroyCommandPool(m_p_device, m_p_command_pool, nullptr);
		}

		template <typename Func>
		void UploadToGPU(Func&& p_user_commands) noexcept
		{
			RMLUI_VK_ASSERTMSG(m_p_command_buffer, "you didn't initialize VkCommandBuffer");

			VkCommandBufferBeginInfo info_command = {};

			info_command.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
			info_command.pNext = nullptr;
			info_command.pInheritanceInfo = nullptr;
			info_command.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

			VkResult status = vkBeginCommandBuffer(m_p_command_buffer, &info_command);

			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkBeginCommandBuffer");

			p_user_commands(m_p_command_buffer);

			status = vkEndCommandBuffer(m_p_command_buffer);

			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "faield to vkEndCommandBuffer");

			Submit();
			Wait();
		}

	private:
		void Create_Fence() noexcept
		{
			VkFenceCreateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
			info.pNext = nullptr;
			info.flags = 0;

			VkResult status = vkCreateFence(m_p_device, &info, nullptr, &m_p_fence);

			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkCreateFence");
		}

		void Create_CommandBuffer() noexcept
		{
			RMLUI_VK_ASSERTMSG(m_p_command_pool, "you have to initialize VkCommandPool before calling this method!");

			VkCommandBufferAllocateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
			info.pNext = nullptr;
			info.commandPool = m_p_command_pool;
			info.commandBufferCount = 1;
			info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;

			VkResult status = vkAllocateCommandBuffers(m_p_device, &info, &m_p_command_buffer);

			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkAllocateCommandBuffers");
		}

		void Create_CommandPool(uint32_t queue_family_index) noexcept
		{
			VkCommandPoolCreateInfo info = {};

			info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
			info.pNext = nullptr;
			info.queueFamilyIndex = queue_family_index;
			info.flags = 0;

			VkResult status = vkCreateCommandPool(m_p_device, &info, nullptr, &m_p_command_pool);

			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkCreateCommandPool");
		}

		void Create_All(uint32_t queue_family_index) noexcept
		{
			Create_Fence();
			Create_CommandPool(queue_family_index);
			Create_CommandBuffer();
		}

		void Wait() noexcept
		{
			RMLUI_VK_ASSERTMSG(m_p_fence, "you must initialize your VkFence");

			vkWaitForFences(m_p_device, 1, &m_p_fence, VK_TRUE, UINT64_MAX);
			vkResetFences(m_p_device, 1, &m_p_fence);
			vkResetCommandPool(m_p_device, m_p_command_pool, 0);
		}

		void Submit() noexcept
		{
			VkSubmitInfo info = {};

			info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
			info.pNext = nullptr;
			info.waitSemaphoreCount = 0;
			info.signalSemaphoreCount = 0;
			info.pSignalSemaphores = nullptr;
			info.pWaitSemaphores = nullptr;
			info.pWaitDstStageMask = nullptr;
			info.pCommandBuffers = &m_p_command_buffer;
			info.commandBufferCount = 1;

			auto status = vkQueueSubmit(m_p_graphics_queue, 1, &info, m_p_fence);

			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkQueueSubmit");
		}

	private:
		VkDevice m_p_device;
		VkFence m_p_fence;
		VkCommandBuffer m_p_command_buffer;
		VkCommandPool m_p_command_pool;
		VkQueue m_p_graphics_queue;
	};

	// @ main manager for "allocating" vertex, index, uniform stuff
	class MemoryPool {
	public:
		MemoryPool();
		~MemoryPool();

		void Initialize(VkDeviceSize byte_size, VkDeviceSize device_min_uniform_alignment, VmaAllocator p_allocator, VkDevice p_device) noexcept;
		void Shutdown() noexcept;

		bool Alloc_GeneralBuffer(VkDeviceSize size, void** p_data, VkDescriptorBufferInfo* p_out, VmaVirtualAllocation* p_alloc) noexcept;
		bool Alloc_VertexBuffer(uint32_t number_of_elements, uint32_t stride_in_bytes, void** p_data, VkDescriptorBufferInfo* p_out,
			VmaVirtualAllocation* p_alloc) noexcept;
		bool Alloc_IndexBuffer(uint32_t number_of_elements, uint32_t stride_in_bytes, void** p_data, VkDescriptorBufferInfo* p_out,
			VmaVirtualAllocation* p_alloc) noexcept;

		void SetDescriptorSet(uint32_t binding_index, uint32_t size, VkDescriptorType descriptor_type, VkDescriptorSet p_set) noexcept;
		void SetDescriptorSet(uint32_t binding_index, VkDescriptorBufferInfo* p_info, VkDescriptorType descriptor_type,
			VkDescriptorSet p_set) noexcept;
		void SetDescriptorSet(uint32_t binding_index, VkSampler p_sampler, VkImageLayout layout, VkImageView p_view, VkDescriptorType descriptor_type,
			VkDescriptorSet p_set) noexcept;

		void Free_GeometryHandle(geometry_handle_t* p_valid_geometry_handle) noexcept;
		void Free_GeometryHandle_ShaderDataOnly(geometry_handle_t* p_valid_geometry_handle) noexcept;

	private:
		VkDeviceSize m_memory_total_size;
		VkDeviceSize m_device_min_uniform_alignment;
		char* m_p_data;
		VkBuffer m_p_buffer;
		VmaAllocation m_p_buffer_alloc;
		VkDevice m_p_device;
		VmaAllocator m_p_vk_allocator;
		VmaVirtualBlock m_p_block;
	};

	// If we need additional command buffers, we can add them to this list and retrieve them from the ring.
	enum class CommandBufferName { Primary, Count };

	// The command buffer ring stores a unique set of named command buffers for each bufferd frame.
	// Explanation of how to use Vulkan efficiently: https://vkguide.dev/docs/chapter-4/double_buffering/
	class CommandBufferRing {
	public:
		static constexpr uint32_t kNumFramesToBuffer = kSwapchainBackBufferCount;
		static constexpr uint32_t kNumCommandBuffersPerFrame = static_cast<uint32_t>(CommandBufferName::Count);

		CommandBufferRing();

		void Initialize(VkDevice p_device, uint32_t queue_index_graphics) noexcept;
		void Shutdown();

		void OnBeginFrame();
		VkCommandBuffer GetCommandBufferForActiveFrame(CommandBufferName named_command_buffer);

	private:
		struct CommandBuffersPerFrame {
			Rml::Array<VkCommandPool, kNumCommandBuffersPerFrame> m_command_pools;
			Rml::Array<VkCommandBuffer, kNumCommandBuffersPerFrame> m_command_buffers;
		};

		VkDevice m_p_device;
		uint32_t m_frame_index;
		CommandBuffersPerFrame* m_p_current_frame;
		Rml::Array<CommandBuffersPerFrame, kNumFramesToBuffer> m_frames;
	};

	class DescriptorPoolManager {
	public:
		DescriptorPoolManager() : m_allocated_descriptor_count{}, m_p_descriptor_pool{} {}
		~DescriptorPoolManager()
		{
			RMLUI_VK_ASSERTMSG(m_allocated_descriptor_count <= 0, "something is wrong. You didn't free some VkDescriptorSet");
		}

		void Initialize(VkDevice p_device, uint32_t count_uniform_buffer, uint32_t count_image_sampler, uint32_t count_sampler,
			uint32_t count_storage_buffer) noexcept
		{
			RMLUI_VK_ASSERTMSG(p_device, "you can't pass an invalid VkDevice here");

			Rml::Array<VkDescriptorPoolSize, 5> sizes;
			sizes[0] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, count_uniform_buffer};
			sizes[1] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, count_uniform_buffer};
			sizes[2] = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, count_image_sampler};
			sizes[3] = {VK_DESCRIPTOR_TYPE_SAMPLER, count_sampler};
			sizes[4] = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, count_storage_buffer};

			VkDescriptorPoolCreateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
			info.pNext = nullptr;
			info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
			info.maxSets = 1000;
			info.poolSizeCount = static_cast<uint32_t>(sizes.size());
			info.pPoolSizes = sizes.data();

			auto status = vkCreateDescriptorPool(p_device, &info, nullptr, &m_p_descriptor_pool);
			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkCreateDescriptorPool");
		}

		void Shutdown(VkDevice p_device)
		{
			RMLUI_VK_ASSERTMSG(p_device, "you can't pass an invalid VkDevice here");

			vkDestroyDescriptorPool(p_device, m_p_descriptor_pool, nullptr);
		}

		uint32_t Get_AllocatedDescriptorCount() const noexcept { return m_allocated_descriptor_count; }

		bool Alloc_Descriptor(VkDevice p_device, VkDescriptorSetLayout* p_layouts, VkDescriptorSet* p_sets,
			uint32_t descriptor_count_for_creation = 1) noexcept
		{
			RMLUI_VK_ASSERTMSG(p_layouts, "you have to pass a valid and initialized VkDescriptorSetLayout (probably you must create it)");
			RMLUI_VK_ASSERTMSG(p_device, "you must pass a valid VkDevice here");

			VkDescriptorSetAllocateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
			info.pNext = nullptr;
			info.descriptorPool = m_p_descriptor_pool;
			info.descriptorSetCount = descriptor_count_for_creation;
			info.pSetLayouts = p_layouts;

			auto status = vkAllocateDescriptorSets(p_device, &info, p_sets);
			RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkAllocateDescriptorSets");

			m_allocated_descriptor_count += descriptor_count_for_creation;

			return status == VkResult::VK_SUCCESS;
		}

		void Free_Descriptors(VkDevice p_device, VkDescriptorSet* p_sets, uint32_t descriptor_count = 1) noexcept
		{
			RMLUI_VK_ASSERTMSG(p_device, "you must pass a valid VkDevice here");

			if (p_sets)
			{
				m_allocated_descriptor_count -= descriptor_count;
				vkFreeDescriptorSets(p_device, m_p_descriptor_pool, descriptor_count, p_sets);
			}
		}

	private:
		int m_allocated_descriptor_count;
		VkDescriptorPool m_p_descriptor_pool;
	};

	struct PhysicalDeviceWrapper {
		VkPhysicalDevice m_p_physical_device;
		VkPhysicalDeviceProperties m_physical_device_properties;
	};

	using PhysicalDeviceWrapperList = Rml::Vector<PhysicalDeviceWrapper>;
	using LayerPropertiesList = Rml::Vector<VkLayerProperties>;
	using ExtensionPropertiesList = Rml::Vector<VkExtensionProperties>;

private:
	Rml::TextureHandle CreateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i dimensions, const Rml::String& name);

	void Initialize_Instance(Rml::Vector<const char*> required_extensions) noexcept;
	void Initialize_Device() noexcept;
	void Initialize_PhysicalDevice(VkPhysicalDeviceProperties& out_physical_device_properties) noexcept;
	void Initialize_Swapchain(VkExtent2D window_extent) noexcept;
	void Initialize_Surface(CreateSurfaceCallback create_surface_callback) noexcept;
	void Initialize_QueueIndecies() noexcept;
	void Initialize_Queues() noexcept;
	void Initialize_SyncPrimitives() noexcept;
	void Initialize_Resources(const VkPhysicalDeviceProperties& physical_device_properties) noexcept;
	void Initialize_Allocator() noexcept;

	void Destroy_Instance() noexcept;
	void Destroy_Device() noexcept;
	void Destroy_Swapchain() noexcept;
	void Destroy_Surface() noexcept;
	void Destroy_SyncPrimitives() noexcept;
	void Destroy_Resources() noexcept;
	void Destroy_Allocator() noexcept;

	void QueryInstanceLayers(LayerPropertiesList& result) noexcept;
	void QueryInstanceExtensions(ExtensionPropertiesList& result, const LayerPropertiesList& instance_layer_properties) noexcept;
	bool AddLayerToInstance(Rml::Vector<const char*>& result, const LayerPropertiesList& instance_layer_properties,
		const char* p_instance_layer_name) noexcept;
	bool AddExtensionToInstance(Rml::Vector<const char*>& result, const ExtensionPropertiesList& instance_extension_properties,
		const char* p_instance_extension_name) noexcept;
	void CreatePropertiesFor_Instance(Rml::Vector<const char*>& instance_layer_names, Rml::Vector<const char*>& instance_extension_names) noexcept;

	bool IsLayerPresent(const LayerPropertiesList& properties, const char* p_layer_name) noexcept;
	bool IsExtensionPresent(const ExtensionPropertiesList& properties, const char* p_extension_name) noexcept;

	bool AddExtensionToDevice(Rml::Vector<const char*>& result, const ExtensionPropertiesList& device_extension_properties,
		const char* p_device_extension_name) noexcept;
	void CreatePropertiesFor_Device(ExtensionPropertiesList& result) noexcept;

	void CreateReportDebugCallback() noexcept;
	void Destroy_ReportDebugCallback() noexcept;

	uint32_t GetUserAPIVersion() const noexcept;
	uint32_t GetRequiredVersionAndValidateMachine() noexcept;

	void CollectPhysicalDevices(PhysicalDeviceWrapperList& out_physical_devices) noexcept;
	const PhysicalDeviceWrapper* ChoosePhysicalDevice(const PhysicalDeviceWrapperList& physical_devices, VkPhysicalDeviceType device_type) noexcept;

	VkSurfaceFormatKHR ChooseSwapchainFormat() noexcept;
	VkSurfaceTransformFlagBitsKHR CreatePretransformSwapchain() noexcept;
	VkCompositeAlphaFlagBitsKHR ChooseSwapchainCompositeAlpha() noexcept;
	int Choose_SwapchainImageCount(uint32_t user_swapchain_count_for_creation = kSwapchainBackBufferCount, bool if_failed_choose_min = true) noexcept;
	VkPresentModeKHR GetPresentMode(VkPresentModeKHR type = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR) noexcept;
	VkSurfaceCapabilitiesKHR GetSurfaceCapabilities() noexcept;

	VkExtent2D GetValidSurfaceExtent() noexcept;

	void CreateShaders() noexcept;
	void CreateDescriptorSetLayout() noexcept;
	void CreatePipelineLayout() noexcept;
	void CreateDescriptorSets() noexcept;
	void CreateSamplers() noexcept;
	void Create_Pipelines() noexcept;
	void CreateRenderPass() noexcept;

	void CreateSwapchainFrameBuffers(const VkExtent2D& real_render_image_size) noexcept;

	// This method is called in Views, so don't call it manually
	void CreateSwapchainImages() noexcept;
	void CreateSwapchainImageViews() noexcept;

	void Create_DepthStencilImage() noexcept;
	void Create_DepthStencilImageViews() noexcept;

	void CreateResourcesDependentOnSize(const VkExtent2D& real_render_image_size) noexcept;

	buffer_data_t CreateResource_StagingBuffer(VkDeviceSize size, VkBufferUsageFlags flags) noexcept;
	void DestroyResource_StagingBuffer(const buffer_data_t& data) noexcept;

	void Destroy_Textures() noexcept;
	void Destroy_Geometries() noexcept;

	void Destroy_Texture(const texture_data_t& p_texture) noexcept;

	void DestroyResourcesDependentOnSize() noexcept;
	void DestroySwapchainImageViews() noexcept;
	void DestroySwapchainFrameBuffers() noexcept;
	void DestroyRenderPass() noexcept;
	void Destroy_Pipelines() noexcept;
	void DestroyDescriptorSets() noexcept;
	void DestroyPipelineLayout() noexcept;
	void DestroySamplers() noexcept;

	void Wait() noexcept;

	void Update_PendingForDeletion_Textures_By_Frames() noexcept;
	void Update_PendingForDeletion_Geometries() noexcept;

	void Submit() noexcept;
	void Present() noexcept;

	VkFormat Get_SupportedDepthFormat();

private:
	bool m_is_transform_enabled;
	bool m_is_apply_to_regular_geometry_stencil;
	bool m_is_use_scissor_specified;
	bool m_is_use_stencil_pipeline;

	int m_width;
	int m_height;

	uint32_t m_queue_index_present;
	uint32_t m_queue_index_graphics;
	uint32_t m_queue_index_compute;
	uint32_t m_semaphore_index;
	uint32_t m_semaphore_index_previous;
	uint32_t m_image_index;

	VkInstance m_p_instance;
	VkDevice m_p_device;
	VkPhysicalDevice m_p_physical_device;
	VkSurfaceKHR m_p_surface;
	VkSwapchainKHR m_p_swapchain;
	VmaAllocator m_p_allocator;
	// @ obtained from command list see PrepareRenderBuffer method
	VkCommandBuffer m_p_current_command_buffer;

	VkDescriptorSetLayout m_p_descriptor_set_layout_vertex_transform;
	VkDescriptorSetLayout m_p_descriptor_set_layout_texture;
	VkPipelineLayout m_p_pipeline_layout;
	VkPipeline m_p_pipeline_with_textures;
	VkPipeline m_p_pipeline_without_textures;
	VkPipeline m_p_pipeline_stencil_for_region_where_geometry_will_be_drawn;
	VkPipeline m_p_pipeline_stencil_for_regular_geometry_that_applied_to_region_with_textures;
	VkPipeline m_p_pipeline_stencil_for_regular_geometry_that_applied_to_region_without_textures;
	VkDescriptorSet m_p_descriptor_set;
	VkRenderPass m_p_render_pass;
	VkSampler m_p_sampler_linear;
	VkRect2D m_scissor;

	// @ means it captures the window size full width and full height, offset equals both x and y to 0
	VkRect2D m_scissor_original;
	VkViewport m_viewport;

	VkQueue m_p_queue_present;
	VkQueue m_p_queue_graphics;
	VkQueue m_p_queue_compute;

#ifdef RMLUI_VK_DEBUG
	VkDebugUtilsMessengerEXT m_debug_messenger;
#endif

	VkSurfaceFormatKHR m_swapchain_format;
	shader_vertex_user_data_t m_user_data_for_vertex_shader;
	texture_data_t m_texture_depthstencil;

	Rml::Matrix4f m_projection;
	Rml::Vector<VkFence> m_executed_fences;
	Rml::Vector<VkSemaphore> m_semaphores_image_available;
	Rml::Vector<VkSemaphore> m_semaphores_finished_render;
	Rml::Vector<VkFramebuffer> m_swapchain_frame_buffers;
	Rml::Vector<VkImage> m_swapchain_images;
	Rml::Vector<VkImageView> m_swapchain_image_views;
	Rml::Vector<VkShaderModule> m_shaders;
	Rml::Array<Rml::Vector<texture_data_t*>, kSwapchainBackBufferCount> m_pending_for_deletion_textures_by_frames;

	// vma handles that thing, so there's no need for frame splitting
	Rml::Vector<geometry_handle_t*> m_pending_for_deletion_geometries;

	CommandBufferRing m_command_buffer_ring;
	MemoryPool m_memory_pool;
	UploadResourceManager m_upload_manager;
	DescriptorPoolManager m_manager_descriptors;
};

#endif
