#if UF_USE_VULKAN

#include <uf/ext/vulkan/graphic.h>
#include <uf/ext/vulkan/initializers.h>
#include <uf/ext/vulkan/vulkan.h>
#include <uf/ext/openvr/openvr.h>
#include <uf/utils/camera/camera.h>
#include <uf/engine/graph/graph.h>

#include <spirv_cross/spirv_cross.hpp>
#include <spirv_cross/spirv_glsl.hpp>
#include <fstream>
#include <regex>

#define USE_SHARED_PIPELINES 0

#define VK_DEBUG_VALIDATION_MESSAGE(...)\
	VK_VALIDATION_MESSAGE(__VA_ARGS__);

namespace {
	uint32_t VERTEX_BUFFER_BIND_ID = 0;
}

uf::stl::unordered_map<ext::vulkan::GraphicDescriptor, ext::vulkan::Pipeline> ext::vulkan::Pipeline::pipelines;

PFN_vkGetBufferDeviceAddressKHR ext::vulkan::vkGetBufferDeviceAddressKHR = NULL; // = reinterpret_cast<PFN_vkGetBufferDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR"));
PFN_vkCmdBuildAccelerationStructuresKHR ext::vulkan::vkCmdBuildAccelerationStructuresKHR = NULL; // = reinterpret_cast<PFN_vkCmdBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkCmdBuildAccelerationStructuresKHR"));
PFN_vkBuildAccelerationStructuresKHR ext::vulkan::vkBuildAccelerationStructuresKHR = NULL; // = reinterpret_cast<PFN_vkBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkBuildAccelerationStructuresKHR"));
PFN_vkCreateAccelerationStructureKHR ext::vulkan::vkCreateAccelerationStructureKHR = NULL; // = reinterpret_cast<PFN_vkCreateAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR"));
PFN_vkDestroyAccelerationStructureKHR ext::vulkan::vkDestroyAccelerationStructureKHR = NULL; // = reinterpret_cast<PFN_vkDestroyAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR"));
PFN_vkGetAccelerationStructureBuildSizesKHR ext::vulkan::vkGetAccelerationStructureBuildSizesKHR = NULL; // = reinterpret_cast<PFN_vkGetAccelerationStructureBuildSizesKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureBuildSizesKHR"));
PFN_vkGetAccelerationStructureDeviceAddressKHR ext::vulkan::vkGetAccelerationStructureDeviceAddressKHR = NULL; // = reinterpret_cast<PFN_vkGetAccelerationStructureDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR"));
PFN_vkCmdTraceRaysKHR ext::vulkan::vkCmdTraceRaysKHR = NULL; // = reinterpret_cast<PFN_vkCmdTraceRaysKHR>(vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR"));
PFN_vkGetRayTracingShaderGroupHandlesKHR ext::vulkan::vkGetRayTracingShaderGroupHandlesKHR = NULL; // = reinterpret_cast<PFN_vkGetRayTracingShaderGroupHandlesKHR>(vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR"));
PFN_vkCreateRayTracingPipelinesKHR ext::vulkan::vkCreateRayTracingPipelinesKHR = NULL; // = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR"));
PFN_vkCmdWriteAccelerationStructuresPropertiesKHR ext::vulkan::vkCmdWriteAccelerationStructuresPropertiesKHR = NULL; // = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR"));
PFN_vkCmdCopyAccelerationStructureKHR ext::vulkan::vkCmdCopyAccelerationStructureKHR = NULL; // = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR"));

void ext::vulkan::Pipeline::initialize( const Graphic& graphic ) {
	return this->initialize( graphic, graphic.descriptor );
}
void ext::vulkan::Pipeline::initialize( const Graphic& graphic, const GraphicDescriptor& descriptor ) {
	if ( pipeline != VK_NULL_HANDLE ) return;

	this->device = graphic.device;
	this->descriptor = descriptor;
	Device& device = *graphic.device;

	auto shaders = graphic.material.getShaders( descriptor.pipeline );
	assert( shaders.size() > 0 );
	
	uf::stl::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings;
	uf::stl::vector<VkPushConstantRange> pushConstantRanges;
	uf::stl::vector<VkDescriptorPoolSize> poolSizes;
	uf::stl::unordered_map<VkDescriptorType, uint32_t> descriptorTypes;

	uf::stl::vector<VkPipelineColorBlendAttachmentState> blendAttachmentStates;
	uf::stl::vector<VkPipelineShaderStageCreateInfo> shaderDescriptors;
	uf::stl::vector<VkSpecializationInfo> shaderSpecializationInfos;
	uf::stl::vector<VkVertexInputBindingDescription> inputBindingDescriptions;
	uf::stl::vector<VkVertexInputAttributeDescription> attributeDescriptions;

	uf::stl::map<uint32_t, uf::stl::vector<VkDescriptorSetLayoutBinding>> pipelineBindingsBySet;
	uf::stl::map<uint32_t, uf::stl::vector<VkDescriptorBindingFlags>> pipelineBindingFlagsBySet;
	
	{
		uint32_t maxSetIndex = 0;
		bool hasDynamicBuffer = false;

		for ( auto* shader : shaders ) {
			for ( const auto& [setIndex, bindings] : shader->descriptorSetLayoutBindings ) {
				for ( auto& binding : bindings ) {
					switch ( binding.descriptorType ) {
						case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
						case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
							hasDynamicBuffer = true;
						break;
					}
				}
			}
		}

		for ( auto* shader : shaders ) {
			for ( const auto& [setIndex, bindings] : shader->descriptorSetLayoutBindings ) {
				maxSetIndex = MAX(maxSetIndex, setIndex);

				auto& pipelineBindings = pipelineBindingsBySet[setIndex];
				auto& pipelineFlags = pipelineBindingFlagsBySet[setIndex];

				for ( auto& binding : bindings ) {
					// Check if we already added this binding (e.g. Vertex and Fragment both use Set 0 Binding 0)
					auto it = std::find_if(pipelineBindings.begin(), pipelineBindings.end(),
						[&](const VkDescriptorSetLayoutBinding& b) { return b.binding == binding.binding; });

					if ( it != pipelineBindings.end() ) {
						// Merge stage flags so the single layout knows it's used in both stages
						it->stageFlags |= binding.stageFlags;
					} else {
						// New binding
						pipelineBindings.emplace_back( binding );
						auto& flags = pipelineFlags.emplace_back();

						flags |= VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT;
						if ( !hasDynamicBuffer ) flags |= VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT;
					}
				}
			}

			size_t offset = 0;
			for ( auto& pushConstant : shader->pushConstants ) {
				size_t len = pushConstant.data().len;
				if ( len <= 0 || len > device.properties.limits.maxPushConstantsSize ) {
					VK_DEBUG_VALIDATION_MESSAGE("Invalid push constant length of {} for shader {}", len, shader->filename);
				//	goto PIPELINE_INITIALIZATION_INVALID;
					len = device.properties.limits.maxPushConstantsSize;
				}
				pushConstantRanges.emplace_back(ext::vulkan::initializers::pushConstantRange(
					shader->descriptor.stage,
					len,
					offset
				));
				offset += len;
			}
		}

		descriptorSetLayouts.clear();
		descriptorSetLayouts.resize(maxSetIndex + 1, VK_NULL_HANDLE);

		for ( uint32_t set = 0; set <= maxSetIndex; ++set ) {
			auto& bindings = pipelineBindingsBySet[set];
			auto& bindingFlags = pipelineBindingFlagsBySet[set];

			VkDescriptorSetLayoutBindingFlagsCreateInfo layoutBindingFlags = {};
			layoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
			layoutBindingFlags.bindingCount = bindingFlags.size();
			layoutBindingFlags.pBindingFlags = bindingFlags.data();

			VkDescriptorSetLayoutCreateInfo descriptorLayout = ext::vulkan::initializers::descriptorSetLayoutCreateInfo(
				bindings.data(),
				bindings.size()
			);

			descriptorLayout.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT;
			descriptorLayout.pNext = &layoutBindingFlags;

			VK_CHECK_RESULT(vkCreateDescriptorSetLayout( device, &descriptorLayout, nullptr, &descriptorSetLayouts[set] ));
			VK_REGISTER_HANDLE( descriptorSetLayouts[set] );
		}

		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = ext::vulkan::initializers::pipelineLayoutCreateInfo(
			descriptorSetLayouts.data(),
			descriptorSetLayouts.size()
		);

		pPipelineLayoutCreateInfo.pushConstantRangeCount = pushConstantRanges.size();
		pPipelineLayoutCreateInfo.pPushConstantRanges = pushConstantRanges.data();
		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
		VK_REGISTER_HANDLE( pipelineLayout );
	}
	// raytrace
	{
		uf::stl::vector<VkRayTracingShaderGroupCreateInfoKHR> shaderGroups;
		for ( auto* shader : shaders ) {
			if (!(
				shader->descriptor.stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR ||
				shader->descriptor.stage == VK_SHADER_STAGE_MISS_BIT_KHR ||
				shader->descriptor.stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR ||
				shader->descriptor.stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR ||
				shader->descriptor.stage == VK_SHADER_STAGE_INTERSECTION_BIT_KHR
			)) continue;
			
			size_t shaderID = static_cast<uint32_t>(shaderDescriptors.size());
			bool isHit = shader->descriptor.stage & (VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR);

			auto& shaderGroup = shaderGroups.emplace_back();
			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
			shaderGroup.type = !isHit ? VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR : VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
			shaderGroup.generalShader = !isHit ? shaderID : VK_SHADER_UNUSED_KHR;
			
			shaderGroup.closestHitShader = (shader->descriptor.stage & VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR) ? shaderID : VK_SHADER_UNUSED_KHR;
			shaderGroup.anyHitShader = (shader->descriptor.stage & VK_SHADER_STAGE_ANY_HIT_BIT_KHR) ? shaderID : VK_SHADER_UNUSED_KHR;
			shaderGroup.intersectionShader = (shader->descriptor.stage & VK_SHADER_STAGE_INTERSECTION_BIT_KHR) ? shaderID : VK_SHADER_UNUSED_KHR;
			
			shaderDescriptors.emplace_back(shader->descriptor);
		}

		if ( !shaderGroups.empty() ) {
			VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI{};
			rayTracingPipelineCI.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR;
			rayTracingPipelineCI.stageCount = static_cast<uint32_t>(shaderDescriptors.size());
			rayTracingPipelineCI.pStages = shaderDescriptors.data();
			rayTracingPipelineCI.groupCount = static_cast<uint32_t>(shaderGroups.size());
			rayTracingPipelineCI.pGroups = shaderGroups.data();
			rayTracingPipelineCI.maxPipelineRayRecursionDepth = 1;
			rayTracingPipelineCI.layout = pipelineLayout;
			VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline));
			VK_REGISTER_HANDLE( pipeline );

			VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingPipelineProperties{};
			rayTracingPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;

			VkPhysicalDeviceProperties2 deviceProperties2{};
			deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
			deviceProperties2.pNext = &rayTracingPipelineProperties;

			vkGetPhysicalDeviceProperties2(device.physicalDevice, &deviceProperties2);

			const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize;
			const uint32_t handleSizeAligned = ALIGNED_SIZE(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
			const uint32_t groupCount = static_cast<uint32_t>(shaderGroups.size());
			const uint32_t sbtSize = groupCount * handleSizeAligned;
			const VkBufferUsageFlags bufferUsageFlags = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;

			uf::stl::vector<uint8_t> shaderHandleStorage(sbtSize);
			VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()));

			requestedAlignment = rayTracingPipelineProperties.shaderGroupBaseAlignment;
			size_t raygenBufferIndex = initializeBuffer((const void*) (shaderHandleStorage.data() + handleSizeAligned * 0), handleSize, bufferUsageFlags);
			size_t raymissBufferIndex = initializeBuffer((const void*) (shaderHandleStorage.data() + handleSizeAligned * 1), handleSize, bufferUsageFlags);
			size_t rayhitBufferIndex = initializeBuffer((const void*) (shaderHandleStorage.data() + handleSizeAligned * 2), handleSize, bufferUsageFlags);
			requestedAlignment = 0;

			Buffer raygenBuffer = buffers[raygenBufferIndex].alias();
			Buffer raymissBuffer = buffers[raymissBufferIndex].alias();
			Buffer rayhitBuffer = buffers[rayhitBufferIndex].alias();

			auto& raygenShaderSbtEntry = sbtEntries.emplace_back();
			raygenShaderSbtEntry.deviceAddress = raygenBuffer.getAddress();
			raygenShaderSbtEntry.stride = handleSizeAligned;
			raygenShaderSbtEntry.size = handleSizeAligned;

			auto& raymissShaderSbtEntry = sbtEntries.emplace_back();
			raymissShaderSbtEntry.deviceAddress = raymissBuffer.getAddress();
			raymissShaderSbtEntry.stride = handleSizeAligned;
			raymissShaderSbtEntry.size = handleSizeAligned;

			auto& rayhitShaderSbtEntry = sbtEntries.emplace_back();
			rayhitShaderSbtEntry.deviceAddress = rayhitBuffer.getAddress();
			rayhitShaderSbtEntry.stride = handleSizeAligned;
			rayhitShaderSbtEntry.size = handleSizeAligned;

			auto& raycallShaderSbtEntry = sbtEntries.emplace_back();
			
			return;
		}
	}

	// Compute
	for ( auto* shader : shaders ) {
		if ( shader->descriptor.stage != VK_SHADER_STAGE_COMPUTE_BIT ) continue;

		// Create compute shader pipelines
		VkComputePipelineCreateInfo computePipelineCreateInfo = ext::vulkan::initializers::computePipelineCreateInfo(
			pipelineLayout,
			0
		);
		computePipelineCreateInfo.stage = shader->descriptor;
		VK_CHECK_RESULT(vkCreateComputePipelines(device, device.pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipeline));
		VK_REGISTER_HANDLE( pipeline );
	
		return;
	}
	// Graphic
	{
		RenderMode& renderMode = ext::vulkan::getRenderMode( descriptor.renderMode, true );
		//UF_MSG_DEBUG("RenderMode: {} // {}: {}", descriptor.renderMode, renderMode.getName(), renderMode.getType());
		auto& renderTarget = renderMode.getRenderTarget(descriptor.renderTarget);

		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = ext::vulkan::initializers::pipelineInputAssemblyStateCreateInfo(
			descriptor.topology,
			0,
			VK_FALSE
		);
		VkPipelineRasterizationStateCreateInfo rasterizationState = ext::vulkan::initializers::pipelineRasterizationStateCreateInfo(
			descriptor.fill,
			descriptor.cullMode, //	VK_CULL_MODE_NONE,
			descriptor.frontFace,
			0
		);
		rasterizationState.lineWidth = graphic.descriptor.lineWidth;
		rasterizationState.depthBiasEnable = graphic.descriptor.depth.bias.enable;
		rasterizationState.depthBiasConstantFactor = graphic.descriptor.depth.bias.constant;
		rasterizationState.depthBiasSlopeFactor = graphic.descriptor.depth.bias.slope;
		rasterizationState.depthBiasClamp = graphic.descriptor.depth.bias.clamp;

		VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
		if ( renderMode.getType() != "Swapchain" ) {
			auto& subpass = renderTarget.passes[descriptor.subpass];
			for ( auto& color : subpass.colors ) {
				auto& attachment = renderTarget.attachments[color.attachment];
				blendAttachmentStates.emplace_back(attachment.blendState);
				samples = std::max(samples, ext::vulkan::sampleCount( attachment.descriptor.samples ));
			}
			// require blending if independentBlend is not an enabled feature
			if ( device.enabledFeatures.independentBlend == VK_FALSE ) {
				for ( size_t i = 1; i < blendAttachmentStates.size(); ++i ) {
					blendAttachmentStates[i] = blendAttachmentStates[0];
				}
			}
		} else {
			VkPipelineColorBlendAttachmentState blendAttachmentState = ext::vulkan::initializers::pipelineColorBlendAttachmentState(
				descriptor.blend.colorWriteMask,
				descriptor.blend.enabled ? VK_TRUE : VK_FALSE
			);

			if ( descriptor.blend.enabled ) {
				blendAttachmentState.srcColorBlendFactor = descriptor.blend.srcColorBlendFactor;
				blendAttachmentState.dstColorBlendFactor = descriptor.blend.dstColorBlendFactor;
				blendAttachmentState.colorBlendOp = descriptor.blend.colorBlendOp;
				blendAttachmentState.srcAlphaBlendFactor = descriptor.blend.srcAlphaBlendFactor;
				blendAttachmentState.dstAlphaBlendFactor = descriptor.blend.dstAlphaBlendFactor;
				blendAttachmentState.alphaBlendOp = descriptor.blend.alphaBlendOp;
			}
			blendAttachmentStates.emplace_back(blendAttachmentState);
		}

		VkPipelineColorBlendStateCreateInfo colorBlendState = ext::vulkan::initializers::pipelineColorBlendStateCreateInfo(
			blendAttachmentStates.size(),
			blendAttachmentStates.data()
		);
		VkPipelineDepthStencilStateCreateInfo depthStencilState = ext::vulkan::initializers::pipelineDepthStencilStateCreateInfo(
			descriptor.depth.test,
			descriptor.depth.write,
			descriptor.depth.operation
		);
		depthStencilState.minDepthBounds = descriptor.depth.min;
		depthStencilState.maxDepthBounds = descriptor.depth.max;
		VkPipelineViewportStateCreateInfo viewportState = ext::vulkan::initializers::pipelineViewportStateCreateInfo(
			1, 1, 0
		);
		VkPipelineMultisampleStateCreateInfo multisampleState = ext::vulkan::initializers::pipelineMultisampleStateCreateInfo(
			samples,
			0
		);
		// cringe
		if ( samples > 1 && descriptor.renderTarget == 1 ) {
			multisampleState.alphaToCoverageEnable = VK_TRUE;
		}
		if ( samples > 1 && device.features.sampleRateShading ) {
			multisampleState.sampleShadingEnable = VK_TRUE;
			multisampleState.minSampleShading = 0.25f;
		}

		uf::stl::vector<VkDynamicState> dynamicStateEnables = {
			VK_DYNAMIC_STATE_VIEWPORT,
			VK_DYNAMIC_STATE_SCISSOR
		};

		VkPipelineDynamicStateCreateInfo dynamicState = ext::vulkan::initializers::pipelineDynamicStateCreateInfo(
			dynamicStateEnables.data(),
			dynamicStateEnables.size(),
			0
		);

		// Vertex binding description
		size_t vertexBindID = 0;
		size_t vertexLocationID = 0;
		if ( !descriptor.inputs.vertex.attributes.empty() ) {
			for ( auto& attribute : descriptor.inputs.vertex.attributes ) {
				inputBindingDescriptions.emplace_back(ext::vulkan::initializers::vertexInputBindingDescription(
					vertexBindID, // attribute.buffer, 
					attribute.descriptor.size, 
					VK_VERTEX_INPUT_RATE_VERTEX
				));
				attributeDescriptions.emplace_back(ext::vulkan::initializers::vertexInputAttributeDescription(
					vertexBindID,
					vertexLocationID++,
					attribute.descriptor.format,
					0 // attribute.descriptor.offset
				));
				++vertexBindID;
			}
		}

		VkPipelineVertexInputStateCreateInfo vertexInputState = ext::vulkan::initializers::pipelineVertexInputStateCreateInfo();
		vertexInputState.vertexBindingDescriptionCount = inputBindingDescriptions.size();
		vertexInputState.pVertexBindingDescriptions = inputBindingDescriptions.data();
		vertexInputState.vertexAttributeDescriptionCount = attributeDescriptions.size();
		vertexInputState.pVertexAttributeDescriptions = attributeDescriptions.data();

		for ( auto* shader : shaders ) shaderDescriptors.emplace_back(shader->descriptor);

		VkGraphicsPipelineCreateInfo pipelineCreateInfo = ext::vulkan::initializers::pipelineCreateInfo(
			pipelineLayout,
			renderTarget.renderPass,
			0
		);

		pipelineCreateInfo.pVertexInputState = &vertexInputState;
		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
		pipelineCreateInfo.pRasterizationState = &rasterizationState;
		pipelineCreateInfo.pColorBlendState = &colorBlendState;
		pipelineCreateInfo.pMultisampleState = &multisampleState;
		pipelineCreateInfo.pViewportState = &viewportState;
		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
		pipelineCreateInfo.pDynamicState = &dynamicState;

		pipelineCreateInfo.stageCount = shaderDescriptors.size();
		pipelineCreateInfo.pStages = shaderDescriptors.data();

		pipelineCreateInfo.subpass = descriptor.subpass;

		VK_CHECK_RESULT(vkCreateGraphicsPipelines( device, device.pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
		VK_REGISTER_HANDLE( pipeline );
	}
	return;

PIPELINE_INITIALIZATION_INVALID:
	VK_DEBUG_VALIDATION_MESSAGE("Pipeline initialization invalid, updating next tick...");
	uf::thread::queue( uf::thread::get(uf::thread::mainThreadName), [&]{
		this->initialize( graphic, descriptor );
	});
	return;
}
void ext::vulkan::Pipeline::record( const Graphic& graphic, VkCommandBuffer commandBuffer, size_t pass, size_t draw, size_t offset ) const {
	auto shaders = graphic.material.getShaders( descriptor.pipeline );

	// create dynamic offset ranges
	STATIC_THREAD_LOCAL(uf::stl::vector<uint32_t>, dynamicOffsets);

	RenderMode& renderMode = ext::vulkan::getRenderMode(descriptor.renderMode, true);

	// to-do: properly dispatch the bind point to the pipeline because for some reason it is not
	VkPipelineBindPoint bindPoint = (VkPipelineBindPoint) descriptor.bind.point;

	bool bound = false;
	for ( auto* shader : shaders ) {
		// compute shaders
		if ( shader->descriptor.stage == VK_SHADER_STAGE_COMPUTE_BIT ) {
			if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_COMPUTE ) {
				bound = true;
			} else {
				bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
				continue;
			}
		// raytrace shaders
		} else if (
			shader->descriptor.stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_MISS_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_INTERSECTION_BIT_KHR
		) {
			if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR ) {
				bound = true;
			} else {
				bindPoint = VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR;
				continue;
			}
		// anything else
		} else {
			if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_GRAPHICS ) {
				bound = true;
			} else {
				bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
				continue;
			}
		}

		// automatically bind to our default push constants
		if ( shader->metadata.definitions.pushConstants.count("PushConstant") > 0 ) {
			struct PushConstant {
				uint32_t pass;
				uint32_t draw;
				uint32_t aux;
			} pushConstant = { pass, draw, descriptor.aux };
			vkCmdPushConstants( commandBuffer, pipelineLayout, shader->descriptor.stage, 0, sizeof(pushConstant), &pushConstant );
		} else if ( !shader->pushConstants.empty() ) {
			auto& pushConstant = shader->pushConstants.front();
			size_t size = pushConstant.size();
			void* data = pushConstant.data().data;
			if ( size && data ) {
				vkCmdPushConstants( commandBuffer, pipelineLayout, shader->descriptor.stage, 0, size, data );
			}
		}
	}

	vkCmdBindPipeline(commandBuffer, bindPoint, pipeline);
}
void ext::vulkan::Pipeline::destroy() {
	if ( aliased ) return;

/*
	if ( descriptorPool != VK_NULL_HANDLE) {
		vkDestroyDescriptorPool( *device, descriptorPool, nullptr );
		VK_UNREGISTER_HANDLE( descriptorPool );
		descriptorPool = VK_NULL_HANDLE;
	}
*/
	if ( pipelineLayout != VK_NULL_HANDLE ) {
		vkDestroyPipelineLayout( *device, pipelineLayout, nullptr );
		VK_UNREGISTER_HANDLE( pipelineLayout );
		pipelineLayout = VK_NULL_HANDLE;
	}
	if ( pipeline != VK_NULL_HANDLE ) {
		vkDestroyPipeline( *device, pipeline, nullptr );
		VK_UNREGISTER_HANDLE( pipeline );
		pipeline = VK_NULL_HANDLE;
	}
	for ( auto descriptorSetLayout : descriptorSetLayouts ) {
		if ( descriptorSetLayout != VK_NULL_HANDLE ) {
			vkDestroyDescriptorSetLayout( *device, descriptorSetLayout, nullptr );
			VK_UNREGISTER_HANDLE( descriptorSetLayout );
		}
	}
	descriptorSetLayouts.clear();

//	if ( settings::experimental::dedicatedThread ) ext::vulkan::states::rebuild = true;
/*
	if ( ext::vulkan::hasRenderMode(descriptor.renderMode, true) ) {
		RenderMode& renderMode = ext::vulkan::getRenderMode(descriptor.renderMode, true);
		renderMode.rebuild = true;
	}
*/
}

void ext::vulkan::DescriptorSets::initialize( const Graphic& graphic ) {
	initialize( graphic, graphic.descriptor );
}
void ext::vulkan::DescriptorSets::initialize( const Graphic& graphic, const GraphicDescriptor& descriptor ) {
	if ( !descriptorSets.empty() ) return;

	this->device = graphic.device;
	this->descriptor = descriptor;

	auto& pipeline = graphic.getPipeline( descriptor );
	descriptorSets.resize(pipeline.descriptorSetLayouts.size());

	for( auto i = 0; i < pipeline.descriptorSetLayouts.size(); ++i ) {
		if ( pipeline.descriptorSetLayouts[i] == VK_NULL_HANDLE ) continue;
		UF_ASSERT(device->descriptorAllocator.allocate( &descriptorSets[i], pipeline.descriptorSetLayouts[i] ));
	}
}
void ext::vulkan::DescriptorSets::record( const Graphic& graphic, VkCommandBuffer commandBuffer, size_t pass, size_t draw, size_t offset ) const {
	return record( graphic, descriptor, commandBuffer, pass, draw, offset );
}
void ext::vulkan::DescriptorSets::record( const Graphic& graphic, const GraphicDescriptor& descriptor, VkCommandBuffer commandBuffer, size_t pass, size_t draw, size_t offset ) const {
	auto shaders = graphic.material.getShaders( descriptor.pipeline );
	auto& pipeline = graphic.getPipeline( descriptor );

	// create dynamic offset ranges
	STATIC_THREAD_LOCAL(uf::stl::vector<uint32_t>, dynamicOffsets);
	static thread_local std::map<uint32_t, uint32_t> bindingStrides; bindingStrides.clear();

	RenderMode& renderMode = ext::vulkan::getRenderMode(descriptor.renderMode, true);

	bool bound = false;
	for ( auto* shader : shaders ) {
		// compute shaders
		if ( shader->descriptor.stage == VK_SHADER_STAGE_COMPUTE_BIT ) {
			if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_COMPUTE ) bound = true;
			else continue;
		// raytrace shaders
		} else if (
			shader->descriptor.stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_MISS_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_INTERSECTION_BIT_KHR
		) {
			if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR ) bound = true;
			else continue;
		// anything else
		} else {
			if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_GRAPHICS ) bound = true;
			else continue;
		}

		if ( VK_UBO_USE_N_BUFFERS ) {
			for ( const auto& [name, uniform] : shader->metadata.definitions.uniforms ) {
				if ( IS_DYNAMIC(name) ) {
					bindingStrides[uniform.binding] = uniform.size;
				}
			}		
		}		
	}

	for ( const auto& [binding, stride] : bindingStrides ) {
		dynamicOffsets.emplace_back( stride * offset );
	}

	// no matching bind point for shaders, skip
	if ( !bound ) {
		UF_MSG_DEBUG("No shaders found to bind...: {} | {}", shaders.size(), descriptor.pipeline);
	}

	// Bind descriptor sets describing shader binding points
	vkCmdBindDescriptorSets(commandBuffer, (VkPipelineBindPoint) descriptor.bind.point, pipeline.pipelineLayout, 0, descriptorSets.size(), descriptorSets.data(), dynamicOffsets.size(), dynamicOffsets.data());
}
void ext::vulkan::DescriptorSets::update( const Graphic& graphic ) {
	return this->update( graphic, descriptor );
}
void ext::vulkan::DescriptorSets::update( const Graphic& graphic, const GraphicDescriptor& descriptor, const uf::stl::vector<Shader::Metadata::Definition>& filter ) {
	//
	if ( descriptorSets.empty() ) this->initialize( graphic, descriptor );
	this->descriptor = descriptor;

	RenderMode& renderMode = ext::vulkan::getRenderMode(descriptor.renderMode, true);
	auto& renderTarget = renderMode.getRenderTarget(/*descriptor.renderTarget*/);

	auto shaders = graphic.material.getShaders( descriptor.pipeline );
	
	STATIC_THREAD_LOCAL(uf::stl::vector<VkWriteDescriptorSet>, writeDescriptorSets);

	struct Infos {
		uf::stl::vector<VkDescriptorBufferInfo> uniform;
		uf::stl::vector<VkDescriptorBufferInfo> storage;
		uf::stl::vector<VkDescriptorBufferInfo> accelerationStructure;
		uf::stl::vector<VkWriteDescriptorSetAccelerationStructureKHR> accelerationStructureInfos;

		uf::stl::vector<VkDescriptorImageInfo> image;
		uf::stl::vector<VkDescriptorImageInfo> image2D;
		uf::stl::vector<VkDescriptorImageInfo> image2DA;
		uf::stl::vector<VkDescriptorImageInfo> imageCube;
		uf::stl::vector<VkDescriptorImageInfo> image3D;
		uf::stl::vector<VkDescriptorImageInfo> imageUnknown;

		uf::stl::vector<VkDescriptorImageInfo> sampler;
		uf::stl::vector<VkDescriptorImageInfo> input;
		
		uf::stl::vector<uf::renderer::AccelerationStructure> tlases;
		uf::stl::vector<ext::vulkan::enums::Image::viewType_t> texture_types;
	};
	STATIC_THREAD_LOCAL(uf::stl::vector<Infos>, INFOS);
	INFOS.reserve( shaders.size() );

	for ( auto* shader : shaders ) {
		auto& infos = INFOS.emplace_back();

		this->collectBuffers( *shader, renderMode, graphic, [&]( const Buffer& buffer ){
			if ( buffer.usage & uf::renderer::enums::Buffer::UNIFORM ) infos.uniform.emplace_back(buffer.descriptor);
			if ( buffer.usage & uf::renderer::enums::Buffer::STORAGE ) infos.storage.emplace_back(buffer.descriptor);
		} );

		if ( descriptor.subpass < renderTarget.passes.size() ) {
			auto& subpass = renderTarget.passes[descriptor.subpass];
			for ( auto& input : subpass.inputs ) {
				infos.input.emplace_back(ext::vulkan::initializers::descriptorImageInfo( 
					renderTarget.attachments[input.attachment].views[subpass.layer],
					ext::vulkan::Texture::remapRenderpassLayout( input.layout )
				));
			}
		}

		#define INSERT_IMAGE(texture) {\
			infos.image.emplace_back(texture.descriptor);\
			switch ( texture.viewType ) {\
				case VK_IMAGE_VIEW_TYPE_2D: infos.image2D.emplace_back(texture.descriptor); break;\
				case VK_IMAGE_VIEW_TYPE_2D_ARRAY: infos.image2DA.emplace_back(texture.descriptor); break;\
				case VK_IMAGE_VIEW_TYPE_CUBE: infos.imageCube.emplace_back(texture.descriptor); break;\
				case VK_IMAGE_VIEW_TYPE_3D: infos.image3D.emplace_back(texture.descriptor); break;\
				default: infos.imageUnknown.emplace_back(texture.descriptor); break;\
			}\
		}

		for ( auto& descriptor : shader->metadata.aliases.attachments ) {
			auto texture = Texture2D::empty.alias();
			texture.sampler.descriptor.filter.min = descriptor.filter;
			texture.sampler.descriptor.filter.mag = descriptor.filter;

			auto matches = uf::string::match(descriptor.name, R"(/^(.+?)\[(\d+)\]$/)");
			auto name = matches.size() == 2 ? matches[0] : descriptor.name;
			auto view = matches.size() == 2 ? stoi(matches[1]) : -1;
			const ext::vulkan::RenderTarget::Attachment* attachment = NULL;
			if ( descriptor.renderMode ) {
				if ( descriptor.renderMode->hasAttachment(name) ) 
					attachment = &descriptor.renderMode->getAttachment(name);
			} else if ( renderMode.hasAttachment(name) ) {
				attachment = &renderMode.getAttachment(name);
			}

			if ( attachment ) {
				// subscript on name will grab the view # from attachment->views 
				if ( 0 <= view ) {
					texture.aliasAttachment( *attachment, (size_t) view );
				} else {
					texture.aliasAttachment( *attachment );
				}
			}

			if ( descriptor.layout != VK_IMAGE_LAYOUT_UNDEFINED ) {
				texture.layout = descriptor.layout;
				texture.descriptor.imageLayout = descriptor.layout;
			} else {
				texture.layout = ext::vulkan::Texture::remapRenderpassLayout( texture.layout );
				texture.descriptor.imageLayout = ext::vulkan::Texture::remapRenderpassLayout( texture.layout );

			}
			
			INSERT_IMAGE(texture);
		}

	#if 0
		for ( auto& texture : renderMode.textures ) {
			INSERT_IMAGE(texture);
		}
	#endif
		for ( auto& texture : shader->textures ) {
			INSERT_IMAGE(texture);
		}
		for ( auto& texture : graphic.material.textures ) {
			INSERT_IMAGE(texture);
		}

		for ( auto& sampler : graphic.material.samplers ) {
			infos.sampler.emplace_back(sampler.descriptor.info);
		}

		// check if we can even consume that many infos
		size_t consumes = 0;
		for ( const auto& [setIndex, bindings] : shader->descriptorSetLayoutBindings ) {
			for ( auto& layout : bindings ) {
				switch ( layout.descriptorType ) {
					// consume an texture image info
					case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
					case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
					case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: {
						consumes += layout.descriptorCount;		
						for ( auto& [ _, def ] : shader->metadata.definitions.textures ) {
							if ( def.set != setIndex || def.binding != layout.binding ) continue;
							infos.texture_types.reserve(consumes);
							for ( size_t i = 0; i < layout.descriptorCount; ++i ) infos.texture_types.emplace_back(def.type);
							break;
						}
					} break;
				}
			}
		}
		
		size_t maxTextures2D = 0;
		size_t maxTextures2DA = 0;
		size_t maxTextures3D = 0;
		size_t maxTexturesCube = 0;
		size_t maxTexturesUnknown = 0;
		for ( auto& type : infos.texture_types ) {
			if ( type == ext::vulkan::enums::Image::VIEW_TYPE_3D ) ++maxTextures3D;
			else if ( type == ext::vulkan::enums::Image::VIEW_TYPE_CUBE ) ++maxTexturesCube;
			else if ( type == ext::vulkan::enums::Image::VIEW_TYPE_2D ) ++maxTextures2D;
			else if ( type == ext::vulkan::enums::Image::VIEW_TYPE_2D_ARRAY ) ++maxTextures2DA;
			else ++maxTexturesUnknown;
		}

		while ( infos.image2D.size() < maxTextures2D ) infos.image2D.emplace_back(Texture2D::empty.descriptor);
		while ( infos.image2DA.size() < maxTextures2DA ) infos.image2DA.emplace_back(Texture2D::empty.descriptor);
		while ( infos.imageCube.size() < maxTexturesCube ) infos.imageCube.emplace_back(TextureCube::empty.descriptor);
		while ( infos.image3D.size() < maxTextures3D ) infos.image3D.emplace_back(Texture3D::empty.descriptor);
		while ( infos.imageUnknown.size() < maxTexturesUnknown ) infos.imageUnknown.emplace_back(Texture2D::empty.descriptor);

		for ( size_t i = infos.image.size(); i < consumes; ++i ) {
			ext::vulkan::enums::Image::viewType_t type = i < infos.texture_types.size() ? infos.texture_types[i] : ext::vulkan::enums::Image::viewType_t{};
			if ( type == ext::vulkan::enums::Image::VIEW_TYPE_3D ) infos.image.emplace_back(Texture3D::empty.descriptor);
			else if ( type == ext::vulkan::enums::Image::VIEW_TYPE_CUBE ) infos.image.emplace_back(TextureCube::empty.descriptor);
			else if ( type == ext::vulkan::enums::Image::VIEW_TYPE_2D ) infos.image.emplace_back(Texture2D::empty.descriptor);
			else infos.image.emplace_back(Texture2D::empty.descriptor);
		}

		if ( !graphic.accelerationStructures.tops.empty() )  {
			auto tlas = graphic.accelerationStructures.tops.front();
			if ( tlas.handle != VK_NULL_HANDLE ) infos.tlases.emplace_back(tlas);
		}

		if ( infos.accelerationStructure.empty() ) {
			uf::stl::string renderModeName = uf::renderer::hasRenderMode("Compute:RT", true) ? "Compute:RT" : "";
			auto& blitter = uf::renderer::getRenderMode(renderModeName, true).getBlitter();
			if ( !blitter.accelerationStructures.tops.empty() ) {
				infos.tlases.emplace_back(blitter.accelerationStructures.tops.front());
			}
		}

		for ( auto& tlas : infos.tlases ) {
			infos.accelerationStructure.emplace_back(tlas.buffer.descriptor);

			auto& descriptorAccelerationStructureInfo = infos.accelerationStructureInfos.emplace_back();
			descriptorAccelerationStructureInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
			descriptorAccelerationStructureInfo.accelerationStructureCount = 1;
			descriptorAccelerationStructureInfo.pAccelerationStructures = &tlas.handle;
		}
		
		auto uniformBufferInfo = infos.uniform.begin();
		auto storageBufferInfo = infos.storage.begin();
		auto accelerationStructureInfo = infos.accelerationStructure.begin();
		auto accelerationStructureInfos = infos.accelerationStructureInfos.begin();
		
		auto imageInfo = infos.image.begin();
		auto image2DInfo = infos.image2D.begin();
		auto image2DAInfo = infos.image2DA.begin();
		auto imageCubeInfo = infos.imageCube.begin();
		auto image3DInfo = infos.image3D.begin();
		auto imageUnknownInfo = infos.imageUnknown.begin();

		auto samplerInfo = infos.sampler.begin();
		auto inputInfo = infos.input.begin();

		for ( const auto& [setIndex, bindings] : shader->descriptorSetLayoutBindings ) {
			VkDescriptorSet descriptorSet = descriptorSets[setIndex];
			UF_ASSERT( descriptorSet != VK_NULL_HANDLE );

			for ( auto& layout : bindings ) {
				bool shouldWrite = filter.empty() || filter.end() != std::find_if( filter.begin(), filter.end(), [&]( auto d ) {
					return d.set == setIndex && d.binding == layout.binding;
				});

				switch ( layout.descriptorType ) {
					// consume an texture image info
					case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
					case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
					case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: {
						ext::vulkan::enums::Image::viewType_t imageType = {};
						for ( auto& [ _, def ] : shader->metadata.definitions.textures ) {
							if ( def.set != setIndex || def.binding != layout.binding ) continue;
							imageType = def.type;
							break;
						}

						if ( imageType == ext::vulkan::enums::Image::VIEW_TYPE_2D ) {
							UF_ASSERT_BREAK_MSG( image2DInfo != infos.image2D.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
							if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
								descriptorSet,
								layout.descriptorType,
								layout.binding,
								&(*image2DInfo),
								layout.descriptorCount
							));
							image2DInfo += layout.descriptorCount;
						} else if ( imageType == ext::vulkan::enums::Image::VIEW_TYPE_2D_ARRAY ) {
							UF_ASSERT_BREAK_MSG( image2DAInfo != infos.image2DA.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
							if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
								descriptorSet,
								layout.descriptorType,
								layout.binding,
								&(*image2DAInfo),
								layout.descriptorCount
							));
							image2DAInfo += layout.descriptorCount;
						} else if ( imageType == ext::vulkan::enums::Image::VIEW_TYPE_CUBE ) {
							UF_ASSERT_BREAK_MSG( imageCubeInfo != infos.imageCube.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
							if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
								descriptorSet,
								layout.descriptorType,
								layout.binding,
								&(*imageCubeInfo),
								layout.descriptorCount
							));
							imageCubeInfo += layout.descriptorCount;
						} else if ( imageType == ext::vulkan::enums::Image::VIEW_TYPE_3D ) {
							UF_ASSERT_BREAK_MSG( image3DInfo != infos.image3D.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
							if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
								descriptorSet,
								layout.descriptorType,
								layout.binding,
								&(*image3DInfo),
								layout.descriptorCount
							));
							image3DInfo += layout.descriptorCount;
						} else {
							UF_ASSERT_BREAK_MSG( imageUnknownInfo != infos.imageUnknown.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
							if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
								descriptorSet,
								layout.descriptorType,
								layout.binding,
								&(*imageUnknownInfo),
								layout.descriptorCount
							));
							imageUnknownInfo += layout.descriptorCount;
						}
					} break;
					case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: {
						UF_ASSERT_BREAK_MSG( inputInfo != infos.input.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
						if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
							descriptorSet,
							layout.descriptorType,
							layout.binding,
							&(*inputInfo),
							layout.descriptorCount
						));
						inputInfo += layout.descriptorCount;
					} break;
					case VK_DESCRIPTOR_TYPE_SAMPLER: {
						UF_ASSERT_BREAK_MSG( samplerInfo != infos.sampler.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
						if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
							descriptorSet,
							layout.descriptorType,
							layout.binding,
							&(*samplerInfo),
							layout.descriptorCount
						));
						samplerInfo += layout.descriptorCount;
					} break;
					case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
					case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: {
						UF_ASSERT_BREAK_MSG( uniformBufferInfo != infos.uniform.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
						if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
							descriptorSet,
							layout.descriptorType,
							layout.binding,
							&(*uniformBufferInfo),
							layout.descriptorCount
						));
						uniformBufferInfo += layout.descriptorCount;
					} break;
					case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
					case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: {
						UF_ASSERT_BREAK_MSG( storageBufferInfo != infos.storage.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
						if ( shouldWrite ) writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
							descriptorSet,
							layout.descriptorType,
							layout.binding,
							&(*storageBufferInfo),
							layout.descriptorCount
						));
						storageBufferInfo += layout.descriptorCount;
					} break;
					case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: {
						UF_ASSERT_BREAK_MSG( accelerationStructureInfo != infos.accelerationStructure.end(), "Filename: {}\tCount: {}", shader->filename, layout.descriptorCount )
						if ( shouldWrite ) {
							auto& writeDescriptorSet = writeDescriptorSets.emplace_back(ext::vulkan::initializers::writeDescriptorSet(
								descriptorSet,
								layout.descriptorType,
								layout.binding,
								&(*accelerationStructureInfo),
								layout.descriptorCount
							));
							writeDescriptorSet.pNext = &(*accelerationStructureInfos);
						}

						accelerationStructureInfo += layout.descriptorCount;
						accelerationStructureInfos += layout.descriptorCount;
					} break;
				}
			}
		}
	}

	// validate writeDescriptorSets
	for ( auto& descriptor : writeDescriptorSets ) {
		for ( size_t i = 0; i < descriptor.descriptorCount; ++i ) {
			if ( descriptor.pBufferInfo ) {
				if ( descriptor.pBufferInfo[i].offset % device->properties.limits.minUniformBufferOffsetAlignment != 0 ) {
					VK_DEBUG_VALIDATION_MESSAGE("Invalid descriptor for buffer: {} (Offset: {}, Range: {}), invalidating...", (void*) descriptor.pBufferInfo[i].buffer, descriptor.pBufferInfo[i].offset,  descriptor.pBufferInfo[i].range);
					goto PIPELINE_UPDATE_INVALID;
				}
			}
			if ( descriptor.pImageInfo ) {
				if (  	descriptor.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
						descriptor.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
						descriptor.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
						descriptor.descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT
				 ) {
					if ( descriptor.pImageInfo[i].imageView == VK_NULL_HANDLE || descriptor.pImageInfo[i].imageLayout == VK_IMAGE_LAYOUT_UNDEFINED ) {
						VK_DEBUG_VALIDATION_MESSAGE("Null image view or layout is undefined, replacing with fallback texture...");
				#if 0
						auto pointer = const_cast<VkDescriptorImageInfo*>(&descriptor.pImageInfo[i]);
						ext::vulkan::enums::Image::viewType_t imageType{};
						for ( auto* shader : shaders ) {
							for ( auto& [ _, def ] : shader->metadata.definitions.textures ) {
								// to-do: fix
								if ( def.set != descriptor.dstSet || def.binding != descriptor.dstBinding ) continue;
								imageType = def.type;
								break;
							}
						}
						if ( imageType == ext::vulkan::enums::Image::VIEW_TYPE_3D ) {
							*pointer = Texture3D::empty.descriptor;
						} else if ( imageType == ext::vulkan::enums::Image::VIEW_TYPE_CUBE ) {
							*pointer = TextureCube::empty.descriptor;
						} else {
							*pointer = Texture2D::empty.descriptor;
						}
				#endif
					}
				#if 0
					if ( descriptor.pImageInfo[i].layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ) {
						VK_DEBUG_VALIDATION_MESSAGE("Image layout is VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, fixing to VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL");
						auto pointer = const_cast<VkDescriptorImageInfo*>(&descriptor.pImageInfo[i]);
						pointer->layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
					}
				#endif
				}
				if ( descriptor.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || descriptor.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ) {
					if ( !descriptor.pImageInfo[i].sampler ) {
						VK_DEBUG_VALIDATION_MESSAGE("Image descriptor type is VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, yet lacks a sampler, adding default sampler...");
						auto pointer = const_cast<VkDescriptorImageInfo*>(&descriptor.pImageInfo[i]);
						pointer->sampler = Texture2D::empty.sampler.sampler;
					}
				}
			}
		}
	}
	// really needs some love
/*
	if ( this->metadata.built ) {
		renderMode.synchronize();
		bool locked = renderMode.tryMutex();
		renderMode.rebuild = true;
	//	uf::renderer::flushCommandBuffers();
		vkUpdateDescriptorSets( *device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL );
		if ( locked ) renderMode.unlockMutex();
	} else */ {
		vkUpdateDescriptorSets( *device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL );
	}
	this->metadata.built = true;
	return;

PIPELINE_UPDATE_INVALID:
//	graphic.process = false;
	VK_DEBUG_VALIDATION_MESSAGE("Pipeline update invalid, updating next tick...");
	uf::thread::queue( uf::thread::get(uf::thread::mainThreadName), [&]{
		this->update( graphic, descriptor );
	});
	return;
}
void ext::vulkan::DescriptorSets::collectBuffers( const Shader& shader, const RenderMode& renderMode, const Graphic& graphic, const std::function<void(const Buffer&)>& lambda ) const {
	// add aliased-by-name buffers
	for ( auto& descriptor : shader.metadata.aliases.buffers ) {
		auto matches = uf::string::match(descriptor.name, R"(/^(.+?)\[(\d+)\]$/)");
		auto name = matches.size() == 2 ? matches[0] : descriptor.name;
		auto view = matches.size() == 2 ? stoi(matches[1]) : -1;
		const ext::vulkan::Buffer* buffer = &descriptor.fallback;
		if ( descriptor.renderMode ) {
			if ( descriptor.renderMode->hasBuffer(name) ) 
				buffer = &descriptor.renderMode->getBuffer(name);
		} else if ( renderMode.hasBuffer(name) ) {
			buffer = &renderMode.getBuffer(name);
		}

		if ( !buffer ) continue;

		lambda( *buffer );
	}
#if 0
	// add per-rendermode buffers
	for ( auto& buffer : renderMode.buffers ) lambda( buffer );
#endif
	// add per-shader buffers
	for ( auto& buffer : shader.buffers ) lambda( buffer );
	// add per-pipeline buffers
	for ( auto& buffer : this->buffers ) lambda( buffer );
	// add per-graphics buffers
	for ( auto& buffer : graphic.buffers ) lambda( buffer );
}

void ext::vulkan::DescriptorSets::destroy() {
	// no-op
}

uf::stl::vector<const ext::vulkan::Shader*> ext::vulkan::Material::getShaders( const uf::stl::string& type ) const {
	uf::stl::unordered_map<uf::stl::string, const ext::vulkan::Shader*> map;
	uf::stl::vector<const ext::vulkan::Shader*> res;
	bool isCompute = false;
	for ( auto& shader : shaders ) {
		if ( shader.metadata.pipeline != "" && shader.metadata.pipeline != type ) continue;
		if ( shader.descriptor.stage == VK_SHADER_STAGE_COMPUTE_BIT ) isCompute = true;
	}
	for ( auto& shader : shaders ) {
		if ( shader.metadata.pipeline != "" && shader.metadata.pipeline != type ) continue;
		if ( isCompute && shader.descriptor.stage != VK_SHADER_STAGE_COMPUTE_BIT ) continue;
		map[shader.metadata.type] = &shader;
	}
	for ( auto pair : map ) res.insert( res.begin(), pair.second);
	return res;
}
void ext::vulkan::Material::initialize( Device& device ) {
	this->device = &device;
}
void ext::vulkan::Material::destroy() {
	if ( aliased ) return;

	for ( auto& texture : textures ) texture.destroy();
	for ( auto& shader : shaders ) shader.destroy();
	for ( auto& sampler : samplers ) sampler.destroy();

	shaders.clear();
	textures.clear();
	samplers.clear();
}
void ext::vulkan::Material::attachShader( const uf::stl::string& filename, VkShaderStageFlagBits stage, const uf::stl::string& pipeline ) {
	auto& shader = shaders.emplace_back();
	shader.metadata.json = metadata.json["shader"];
	shader.metadata.autoInitializeUniformBuffers = metadata.autoInitializeUniformBuffers;
	shader.metadata.autoInitializeUniformUserdatas = metadata.autoInitializeUniformUserdatas;
	shader.initialize( *device, filename, stage );

	// repoint our specialization info descriptor because our shaders will change memory locations when attaching one by one

	for ( auto& shader : shaders ) {
		shader.specializationInfo.mapEntryCount = shader.specializationMapEntries.size();
		shader.specializationInfo.pMapEntries = shader.specializationMapEntries.data();
		shader.specializationInfo.dataSize = shader.specializationConstants.size();
		shader.specializationInfo.pData = (void*) shader.specializationConstants;
		shader.descriptor.pSpecializationInfo = &shader.specializationInfo;
	}

	// check how many samplers requested
	/*
	for ( auto& layout : shader.descriptorSetLayoutBindings ) {
		if ( layout.descriptorType != VK_DESCRIPTOR_TYPE_SAMPLER ) continue;
		Sampler& sampler = samplers.emplace_back();
		sampler.initialize( *device );
	}
	*/
	
	uf::stl::string type = "unknown";
	switch ( stage ) {
		case VK_SHADER_STAGE_VERTEX_BIT: type = "vertex"; break;
		case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: type = "tessellation:control"; break;
		case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: type = "tessellation:evaluation"; break;
		case VK_SHADER_STAGE_GEOMETRY_BIT: type = "geometry"; break;
		case VK_SHADER_STAGE_FRAGMENT_BIT: type = "fragment"; break;
		case VK_SHADER_STAGE_COMPUTE_BIT: type = "compute"; break;
		case VK_SHADER_STAGE_ALL_GRAPHICS: type = "all:graphics"; break;
		case VK_SHADER_STAGE_ALL: type = "all"; break;
		case VK_SHADER_STAGE_RAYGEN_BIT_KHR: type = "ray:gen"; break;
		case VK_SHADER_STAGE_ANY_HIT_BIT_KHR: type = "ray:hit:any"; break;
		case VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR: type = "ray:hit:closest"; break;
		case VK_SHADER_STAGE_MISS_BIT_KHR: type = "ray:miss"; break;
		case VK_SHADER_STAGE_INTERSECTION_BIT_KHR: type = "ray:intersection"; break;
		case VK_SHADER_STAGE_CALLABLE_BIT_KHR: type = "ray:callable"; break;
	}
	shader.metadata.pipeline = pipeline;
	shader.metadata.type = type;

	metadata.json["shaders"][pipeline][type]["index"] = shaders.size() - 1;
	metadata.json["shaders"][pipeline][type]["filename"] = filename;
	metadata.shaders[pipeline+":"+type] = shaders.size() - 1;

	uf::hash( metadata.hashes[pipeline], filename );
}
void ext::vulkan::Material::initializeShaders( const uf::stl::vector<std::pair<uf::stl::string, VkShaderStageFlagBits>>& layout, const uf::stl::string& pipeline ) {
	shaders.clear(); shaders.reserve( layout.size() );
	for ( auto& request : layout ) {
		attachShader( request.first, request.second, pipeline );
	}
}
bool ext::vulkan::Material::hasShader( const uf::stl::string& type, const uf::stl::string& pipeline ) const {
	return metadata.shaders.count(pipeline+":"+type) > 0;
}
ext::vulkan::Shader& ext::vulkan::Material::getShader( const uf::stl::string& type, const uf::stl::string& pipeline ) {
	UF_ASSERT( hasShader(type, pipeline) );
	return shaders.at( metadata.shaders[pipeline+":"+type] );
}
const ext::vulkan::Shader& ext::vulkan::Material::getShader( const uf::stl::string& type, const uf::stl::string& pipeline ) const {
	UF_ASSERT( hasShader(type, pipeline) );
	return shaders.at( metadata.shaders.at(pipeline+":"+type) );
}
bool ext::vulkan::Material::validate() {
	bool was = true;
	for ( auto& shader : shaders ) if ( !shader.validate() ) was = false;
	return was;
}
ext::vulkan::Graphic::~Graphic() {
	this->destroy();
}
void ext::vulkan::Graphic::initialize( const uf::stl::string& renderModeName ) {
	RenderMode& renderMode = ext::vulkan::getRenderMode(renderModeName, true);
	this->descriptor.renderMode = renderModeName;
	//UF_MSG_DEBUG("RenderMode: {} // {}: {}", renderModeName, renderMode.getName(), renderMode.getType());

	auto* device = renderMode.device;
	if ( !device ) device = &ext::vulkan::device;

	material.initialize( *device );

	ext::vulkan::Buffers::initialize( *device );

	if ( this->accelerationStructures.tops.empty() ) this->accelerationStructures.tops.resize(2);
}
void ext::vulkan::Graphic::initialize( const GraphicDescriptor& descriptor ) {
	update( descriptor );
}
void ext::vulkan::Graphic::update() {
	// update descriptor
	this->descriptor.material = material.metadata.hashes[descriptor.pipeline];
	update( this->descriptor );
}
void ext::vulkan::Graphic::update( const GraphicDescriptor& descriptor ) {
	initializePipeline( descriptor );
	initializeDescriptorSet( descriptor );

	initialized = true;
	material.validate();
}
void ext::vulkan::Graphic::initializePipeline() {
	initializePipeline( this->descriptor );
}
ext::vulkan::Pipeline& ext::vulkan::Graphic::initializePipeline( const GraphicDescriptor& descriptor ) {
#if USE_SHARED_PIPELINES
	auto& pipeline = Pipeline::pipelines[descriptor];
#else
	auto& pipeline = pipelines[descriptor];
#endif
	pipeline.initialize(*this, descriptor);
	return pipeline;
}
void ext::vulkan::Graphic::initializeMesh( uf::Mesh& mesh, bool buffer ) {
	mesh.updateDescriptor();

	// copy descriptors
	descriptor.inputs.vertex = mesh.vertex;
	descriptor.inputs.index = mesh.index;
	descriptor.inputs.instance = mesh.instance;
	descriptor.inputs.indirect = mesh.indirect;

	// create buffer if not set and requested
	if ( buffer ) {
		// ensures each buffer index reflects nicely
		struct Queue {
			void* data;
			size_t size;
			VkBufferUsageFlags usage;
		};
		uf::stl::vector<Queue> queue;
		descriptor.inputs.bufferOffset = buffers.size(); // buffers.empty() ? 0 : buffers.size() - 1;
		VkBufferUsageFlags baseUsage = uf::renderer::settings::invariant::deviceAddressing ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0;
	//	VkBufferUsageFlags baseUsage = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR;

		#define PARSE_INPUT_INITIALIZE(NAME, USAGE){\
			for ( size_t i = 0; i < descriptor.inputs.NAME.attributes.size(); ++i ) {\
				auto& attribute = descriptor.inputs.NAME.attributes[i];\
				if ( attribute.pointer && attribute.length > 0 ) {\
					attribute.buffer = initializeBuffer( (const void*) attribute.pointer, attribute.length, USAGE | baseUsage );\
					this->metadata.buffers[#NAME"["+attribute.descriptor.name+"]"] = attribute.buffer;\
				} else attribute.buffer = -1;\
			}\
		}
		// allocate buffers
		auto previousRequestedAlignment = this->requestedAlignment;
		this->requestedAlignment = 16;
		PARSE_INPUT_INITIALIZE(vertex, uf::renderer::enums::Buffer::VERTEX | uf::renderer::enums::Buffer::STORAGE )
		PARSE_INPUT_INITIALIZE(index, uf::renderer::enums::Buffer::INDEX )
		PARSE_INPUT_INITIALIZE(instance, uf::renderer::enums::Buffer::VERTEX )
		PARSE_INPUT_INITIALIZE(indirect, uf::renderer::enums::Buffer::INDIRECT | uf::renderer::enums::Buffer::STORAGE )
		this->requestedAlignment = previousRequestedAlignment;
	}

	if ( mesh.instance.count == 0 && mesh.instance.attributes.empty() ) {
		descriptor.inputs.instance.count = 1;
	}
}
bool ext::vulkan::Graphic::updateMesh( uf::Mesh& mesh ) {
	mesh.updateDescriptor();

	descriptor.inputs.vertex = mesh.vertex;
	descriptor.inputs.index = mesh.index;
	descriptor.inputs.instance = mesh.instance;
	descriptor.inputs.indirect = mesh.indirect;

	// create buffer if not set and requested
	// ensures each buffer index reflects nicely
	struct Queue {
		void* data;
		size_t size;
		uf::renderer::enums::Buffer::type_t usage;
	};
	uf::stl::vector<Queue> queue;

	#define PARSE_INPUT_UPDATE(NAME, USAGE){\
		for ( size_t i = 0; i < descriptor.inputs.NAME.attributes.size(); ++i ) {\
			auto& attribute = descriptor.inputs.NAME.attributes[i];\
			if ( attribute.pointer && attribute.length > 0 ) {\
				rebuild = updateBuffer( (const void*) attribute.pointer, attribute.length, this->metadata.buffers[#NAME"["+attribute.descriptor.name+"]"] ) || rebuild;\
			} else attribute.buffer = -1;\
		}\
	}

	bool rebuild = false;
	auto previousRequestedAlignment = this->requestedAlignment;
	this->requestedAlignment = 16;
	PARSE_INPUT_UPDATE(vertex, uf::renderer::enums::Buffer::VERTEX)
	PARSE_INPUT_UPDATE(index, uf::renderer::enums::Buffer::INDEX)
	PARSE_INPUT_UPDATE(instance, uf::renderer::enums::Buffer::VERTEX)
	PARSE_INPUT_UPDATE(indirect, uf::renderer::enums::Buffer::INDIRECT)
	this->requestedAlignment = previousRequestedAlignment;

	if ( mesh.instance.count == 0 && mesh.instance.attributes.empty() ) {
		descriptor.inputs.instance.count = 1;
	}
	return rebuild;
}
void ext::vulkan::Graphic::generateBottomAccelerationStructures() {
	auto& device = *this->device;
	bool update = !this->accelerationStructures.bottoms.empty();

	VkPhysicalDeviceAccelerationStructurePropertiesKHR acclerationStructureProperties{};
	VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingPipelineProperties{};
	VkPhysicalDeviceProperties2 deviceProperties2{}; {
		acclerationStructureProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR;

		rayTracingPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
		rayTracingPipelineProperties.pNext = &acclerationStructureProperties;

		deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
		deviceProperties2.pNext = &rayTracingPipelineProperties;

		vkGetPhysicalDeviceProperties2(device.physicalDevice, &deviceProperties2);
	}

	struct BlasData {
		uf::stl::vector<VkAccelerationStructureGeometryKHR> asGeom;
		uf::stl::vector<VkAccelerationStructureBuildRangeInfoKHR> asBuildOffsetInfo;
		VkBuildAccelerationStructureFlagsKHR flags{};

		VkAccelerationStructureBuildGeometryInfoKHR buildInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR};
		VkAccelerationStructureBuildSizesInfoKHR sizeInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR};

		const VkAccelerationStructureBuildRangeInfoKHR* rangeInfo;
		uf::renderer::AccelerationStructure as;
		
		uf::renderer::AccelerationStructure previous;
  	};
  	uf::stl::vector<BlasData> blasDatas;

	// setup BLAS geometry
	{
		auto& mesh = this->descriptor.inputs;

		uf::Mesh::Attribute vertexAttribute;
		size_t vertexBufferAddress{};

		uf::Mesh::Attribute indexAttribute;
		size_t indexBufferAddress{};

		/*if ( mesh.vertex.count )*/{
			for ( auto& attribute : mesh.vertex.attributes ) if ( attribute.descriptor.name == "position" ) vertexAttribute = attribute;
			UF_ASSERT( vertexAttribute.descriptor.name == "position" );

			size_t vertexBufferIndex = (vertexAttribute.buffer) + mesh.bufferOffset;
			if ( this->metadata.buffers.count("vertexSkinned") > 0 ) vertexBufferIndex = this->metadata.buffers["vertexSkinned"];
			vertexBufferAddress = this->buffers[vertexBufferIndex].getAddress();
		}

		if ( mesh.index.count ) {
			indexAttribute = mesh.index.attributes.front();

			size_t indexBufferIndex = (indexAttribute.buffer) + mesh.bufferOffset;
			indexBufferAddress = this->buffers[indexBufferIndex].getAddress();
		}

		if ( mesh.indirect.count ) {
			uf::Mesh::Attribute indirectAttribute = mesh.indirect.attributes.front();
			const pod::DrawCommand* drawCommands = (const pod::DrawCommand*) indirectAttribute.pointer;
			for ( size_t i = 0; i < mesh.indirect.count; ++i ) {
				const auto& drawCommand = drawCommands[i];
				
				uf::Mesh::Input vertexInput = mesh.vertex;
				uf::Mesh::Input indexInput = mesh.index;

				vertexInput.first = drawCommand.vertexID;
				vertexInput.count = drawCommand.vertices;

				indexInput.first = drawCommand.indexID;
				indexInput.count = drawCommand.indices;

				auto& blasData = blasDatas.emplace_back();
			//	blasData.as.drawID 					= i;
				blasData.as.instanceID 				= drawCommand.instanceID;

				VkAccelerationStructureGeometryKHR& asGeom = blasData.asGeom.emplace_back();
				asGeom.sType 						= VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
				asGeom.geometryType					= VK_GEOMETRY_TYPE_TRIANGLES_KHR;
				asGeom.flags						= VK_GEOMETRY_OPAQUE_BIT_KHR | VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR;

				VkAccelerationStructureGeometryTrianglesDataKHR& triangles = asGeom.geometry.triangles;
				triangles.sType 					= VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
				triangles.vertexFormat				= vertexAttribute.descriptor.format;
				triangles.vertexData.deviceAddress	= vertexBufferAddress;
				triangles.vertexStride				= vertexAttribute.stride;
				triangles.maxVertex 				= vertexInput.count;

				triangles.indexType					= VK_INDEX_TYPE_NONE_KHR;
				triangles.indexData.deviceAddress	= indexBufferAddress;

				VkAccelerationStructureBuildRangeInfoKHR& offset = blasData.asBuildOffsetInfo.emplace_back();
				offset.firstVertex	 				= vertexInput.first;
				offset.primitiveCount  				= vertexInput.count / 3;
				offset.primitiveOffset 				= indexInput.first * indexInput.size;
				offset.transformOffset 				= 0;

				if ( indexInput.count ) {
					offset.primitiveCount 			= indexInput.count / 3;
					switch ( indexInput.size ) {
						case sizeof( uint8_t): triangles.indexType = VK_INDEX_TYPE_UINT8_EXT; break;
						case sizeof(uint16_t): triangles.indexType = VK_INDEX_TYPE_UINT16; break;
						case sizeof(uint32_t): triangles.indexType = VK_INDEX_TYPE_UINT32; break;
					}
				}
			}
		} else {
			uf::Mesh::Input vertexInput = mesh.vertex;
			uf::Mesh::Input indexInput = mesh.index;

			auto& blasData = blasDatas.emplace_back();
			blasData.as.instanceID 				= 0;

			VkAccelerationStructureGeometryKHR& asGeom = blasData.asGeom.emplace_back();
			asGeom.sType 						= VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
			asGeom.geometryType					= VK_GEOMETRY_TYPE_TRIANGLES_KHR;
			asGeom.flags						= VK_GEOMETRY_OPAQUE_BIT_KHR;

			VkAccelerationStructureGeometryTrianglesDataKHR& triangles = asGeom.geometry.triangles;
			triangles.sType 					= VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
			triangles.vertexFormat				= vertexAttribute.descriptor.format;
			triangles.vertexData.deviceAddress	= vertexBufferAddress;
			triangles.vertexStride				= vertexAttribute.stride;
			triangles.maxVertex 				= vertexInput.count;

			triangles.indexType					= VK_INDEX_TYPE_NONE_KHR;
			triangles.indexData.deviceAddress	= indexBufferAddress;

			VkAccelerationStructureBuildRangeInfoKHR& offset = blasData.asBuildOffsetInfo.emplace_back();
			offset.firstVertex	 				= vertexInput.first;
			offset.primitiveCount  				= vertexInput.count / 3;
			offset.primitiveOffset 				= indexInput.first * indexInput.size;
			offset.transformOffset 				= 0;

			if ( indexInput.count ) {
				offset.primitiveCount 			= indexInput.count / 3;
				switch ( indexInput.size ) {
					case sizeof( uint8_t): triangles.indexType = VK_INDEX_TYPE_UINT8_EXT; break;
					case sizeof(uint16_t): triangles.indexType = VK_INDEX_TYPE_UINT16; break;
					case sizeof(uint32_t): triangles.indexType = VK_INDEX_TYPE_UINT32; break;
				}
			}
		}
	}

	bool shouldCompact = true;

	// determine BLAS size and its scratch buffer
	VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
	if ( shouldCompact ) flags |= VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR;
	if ( this->metadata.buffers.count("vertexSkinned") > 0 ) flags |= VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
	size_t totalBlasBufferSize{};
	size_t maxScratchBufferSize{};
	for ( auto& blasData : blasDatas ) {
		blasData.buildInfo.type				= VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
	//	blasData.buildInfo.mode				= VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
		blasData.buildInfo.mode				= update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
		blasData.buildInfo.flags			= blasData.flags | flags;
		blasData.buildInfo.geometryCount	= static_cast<uint32_t>(blasData.asGeom.size());
		blasData.buildInfo.pGeometries		= blasData.asGeom.data();

		blasData.rangeInfo 					= blasData.asBuildOffsetInfo.data();

		uf::stl::vector<uint32_t> maxPrimCount(blasData.asBuildOffsetInfo.size());
		for(auto _ = 0; _ < blasData.asBuildOffsetInfo.size(); _++) maxPrimCount[_] = blasData.asBuildOffsetInfo[_].primitiveCount;
		
		uf::renderer::vkGetAccelerationStructureBuildSizesKHR(
			device,
			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
			&blasData.buildInfo,
			maxPrimCount.data(),
			&blasData.sizeInfo
		);

		blasData.sizeInfo.accelerationStructureSize = ALIGNED_SIZE(blasData.sizeInfo.accelerationStructureSize, 256);
		maxScratchBufferSize = std::max( maxScratchBufferSize, blasData.sizeInfo.buildScratchSize );
		totalBlasBufferSize += blasData.sizeInfo.accelerationStructureSize;
	}

	// create BLAS buffer and handle
	size_t blasBufferIndex{};
	bool rebuild = false;

	if ( !update ) {
		blasBufferIndex = this->initializeBuffer( NULL, totalBlasBufferSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT );
		this->metadata.buffers["blasBuffer"] = blasBufferIndex;
	} else {
		if ( this->metadata.buffers.count("blasBuffer") > 0 ) {
			blasBufferIndex = this->metadata.buffers["blasBuffer"];
		} else UF_EXCEPTION("Buffers not found: {}", "blasBuffer");

		auto& buffer = this->buffers.at( blasBufferIndex );
		if ( totalBlasBufferSize > buffer.allocationInfo.size ) {
			rebuild = true;
			buffer.destroy(true);
			buffer.initialize( NULL, totalBlasBufferSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT );
		}
	}
	
	VkQueryPool queryPool{VK_NULL_HANDLE};
	if ( shouldCompact ) {
		VkQueryPoolCreateInfo queryPoolCreateInfo{VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO};
		queryPoolCreateInfo.queryType  = VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR;
		queryPoolCreateInfo.queryCount = blasDatas.size();

		VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolCreateInfo, nullptr, &queryPool));
		VK_REGISTER_HANDLE( queryPool );
		vkResetQueryPool(device, queryPool, 0, blasDatas.size());
	}

	uint32_t queryCount{};
	{
		size_t blasBufferOffset = 0;
		scratchBuffer.alignment = acclerationStructureProperties.minAccelerationStructureScratchOffsetAlignment;
		scratchBuffer.update( NULL, maxScratchBufferSize );
		
		auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::COMPUTE);
		for ( auto i = 0; i < blasDatas.size(); ++i ) {
			auto& blasData = blasDatas[i];

			blasData.as.buffer = this->buffers[blasBufferIndex].alias();
			blasData.as.buffer.descriptor.offset = blasBufferOffset;
			blasBufferOffset += blasData.sizeInfo.accelerationStructureSize;

			if ( !update || rebuild ) {
				VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR};
				createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
				createInfo.size = blasData.sizeInfo.accelerationStructureSize;
				createInfo.buffer = blasData.as.buffer.buffer;
				createInfo.offset = blasData.as.buffer.descriptor.offset;

				VK_CHECK_RESULT(uf::renderer::vkCreateAccelerationStructureKHR(device, &createInfo, nullptr, &blasData.as.handle));
				VK_REGISTER_HANDLE( blasData.as.handle );
				
				VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
				accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
				accelerationDeviceAddressInfo.accelerationStructure = blasData.as.handle;

				blasData.as.deviceAddress = uf::renderer::vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo);

				if ( update ) {
					blasData.previous = this->accelerationStructures.bottoms[i];
					blasData.buildInfo.srcAccelerationStructure  = blasData.previous.handle;

					device.transient.ass.emplace_back(blasData.previous);
				} else {
					blasData.buildInfo.srcAccelerationStructure  = VK_NULL_HANDLE;
				}
			} else {
				blasData.as.deviceAddress = this->accelerationStructures.bottoms[i].deviceAddress;
				blasData.as.handle = this->accelerationStructures.bottoms[i].handle;

				blasData.buildInfo.srcAccelerationStructure  = blasData.as.handle;
			}

			// write to BLAS
			blasData.buildInfo.dstAccelerationStructure  = blasData.as.handle;
			blasData.buildInfo.scratchData.deviceAddress = scratchBuffer.getAddress();

			device.UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "buildBLAS" );
			uf::renderer::vkCmdBuildAccelerationStructuresKHR(
				commandBuffer,
				1,
				&blasData.buildInfo,
				&blasData.rangeInfo
			);

			// pipeline barrier here for scratch buffer
			VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
			barrier.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
			barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR;
			vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, 0, 1, &barrier, 0, nullptr, 0, nullptr);

			if ( queryPool ) uf::renderer::vkCmdWriteAccelerationStructuresPropertiesKHR(
				commandBuffer,
				1,
				&blasData.buildInfo.dstAccelerationStructure,
				VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR,
				queryPool,
				queryCount++
			);
		}
		device.flushCommandBuffer(commandBuffer);
	}

	// compact
	if ( queryPool ) {
		uf::stl::vector<VkDeviceSize> compactSizes(blasDatas.size());
		vkGetQueryPoolResults(device, queryPool, 0, (uint32_t) compactSizes.size(), compactSizes.size() * sizeof(VkDeviceSize), compactSizes.data(), sizeof(VkDeviceSize), VK_QUERY_RESULT_WAIT_BIT);

		size_t queryIndex{};
		size_t totalBlasBufferSize{};
		for ( auto& blasData : blasDatas ) {
			size_t compactedSize = compactSizes[queryIndex++];
			size_t oldSize = blasData.sizeInfo.accelerationStructureSize;

			blasData.previous = blasData.as;
			device.transient.ass.emplace_back(blasData.previous);

			blasData.sizeInfo.accelerationStructureSize = ALIGNED_SIZE(compactedSize, 256);
			totalBlasBufferSize += blasData.sizeInfo.accelerationStructureSize;
		//	UF_MSG_DEBUG("Reduced size to {}% ({} -> {} = {})", (oldSize - compactedSize) * 100.0f / oldSize, oldSize, compactedSize, oldSize - compactedSize);
		}

		ext::vulkan::Buffer oldBuffer;
		oldBuffer.alignment = acclerationStructureProperties.minAccelerationStructureScratchOffsetAlignment;
		oldBuffer.initialize( NULL, totalBlasBufferSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT );
		this->buffers[blasBufferIndex].swap(oldBuffer);
		
		size_t blasBufferOffset{};
		auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::COMPUTE);
		for ( auto& blasData : blasDatas ) {
			blasData.as.buffer = this->buffers[blasBufferIndex].alias();
			blasData.as.buffer.descriptor.offset = blasBufferOffset;
			blasBufferOffset += blasData.sizeInfo.accelerationStructureSize;

			VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR};
			createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
			createInfo.size = blasData.sizeInfo.accelerationStructureSize;
			createInfo.buffer = blasData.as.buffer.buffer;
			createInfo.offset = blasData.as.buffer.descriptor.offset;

			VK_CHECK_RESULT(uf::renderer::vkCreateAccelerationStructureKHR(device, &createInfo, nullptr, &blasData.as.handle));
			VK_REGISTER_HANDLE( blasData.as.handle );

			VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
			accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
			accelerationDeviceAddressInfo.accelerationStructure = blasData.as.handle;
			blasData.as.deviceAddress = uf::renderer::vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo);

			VkCopyAccelerationStructureInfoKHR copyInfo{VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_INFO_KHR};
			copyInfo.src  = blasData.previous.handle;
			copyInfo.dst  = blasData.as.handle;
			copyInfo.mode = VK_COPY_ACCELERATION_STRUCTURE_MODE_COMPACT_KHR;
			
			device.UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "copyBLAS" );
			uf::renderer::vkCmdCopyAccelerationStructureKHR(commandBuffer, &copyInfo);
		}
		device.flushCommandBuffer(commandBuffer);

		oldBuffer.destroy();
	}

	this->accelerationStructures.bottoms.clear();
	for ( auto& blasData : blasDatas ) {
		this->accelerationStructures.bottoms.emplace_back(blasData.as);
	}

	if ( queryPool ) {
		vkDestroyQueryPool(device, queryPool, nullptr);
		VK_UNREGISTER_HANDLE( queryPool );
	}
}
void ext::vulkan::Graphic::generateTopAccelerationStructure( const uf::stl::vector<uf::renderer::Graphic*>& graphics, const uf::stl::vector<pod::Instance>& instances, const uf::stl::vector<pod::Matrix4f>& modelMatrices ) {
	auto& device = *this->device;

	bool rebuild = false;
	bool update = this->accelerationStructures.tops[0].handle != VK_NULL_HANDLE;
	bool shouldCompact = false;

	VkPhysicalDeviceAccelerationStructurePropertiesKHR acclerationStructureProperties{};
	VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingPipelineProperties{};
	VkPhysicalDeviceProperties2 deviceProperties2{}; {
		acclerationStructureProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR;

		rayTracingPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
		rayTracingPipelineProperties.pNext = &acclerationStructureProperties;

		deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
		deviceProperties2.pNext = &rayTracingPipelineProperties;

		vkGetPhysicalDeviceProperties2(device.physicalDevice, &deviceProperties2);
	}

	VkQueryPool queryPool{VK_NULL_HANDLE};
	if ( shouldCompact ) {
		VkQueryPoolCreateInfo queryPoolCreateInfo{VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO};
		queryPoolCreateInfo.queryType  = VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR;
		queryPoolCreateInfo.queryCount = 1;

		VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolCreateInfo, nullptr, &queryPool));
		VK_REGISTER_HANDLE( queryPool );
		vkResetQueryPool(device, queryPool, 0, 1);
	}


	uf::stl::vector<VkAccelerationStructureInstanceKHR> instancesVK; instancesVK.reserve( instances.size() );
	for ( auto& graphic : graphics ) {
		for ( auto& blas : graphic->accelerationStructures.bottoms ) {
			auto& instance = instances[blas.instanceID];
			auto mat = modelMatrices[instance.objectID];
			mat = uf::matrix::transpose(mat); // might need to not do this

			auto& instanceVK = instancesVK.emplace_back();
			memcpy(&instanceVK.transform, &mat, sizeof(instanceVK.transform));
			instanceVK.instanceCustomIndex 						= blas.instanceID;
			instanceVK.accelerationStructureReference			= blas.deviceAddress;
			instanceVK.flags 									= 0; // VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
			instanceVK.mask 									= 0xFF;
			instanceVK.instanceShaderBindingTableRecordOffset	= 0;
		}
	}

	size_t instanceIndex{};
	size_t tlasBufferIndex{};
	size_t tlasBackBufferIndex{};

	// do not stage to avoid needing to wait for the transfer
	if ( !update ) {
		this->requestedAlignment = 16;
		this->staged = false;
		instanceIndex = this->initializeBuffer(
			(const void*) instancesVK.data(), instancesVK.size() * sizeof(VkAccelerationStructureInstanceKHR),
			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR
		);
		this->staged = true;
		this->requestedAlignment = 0;
		this->metadata.buffers["tlasInstance"] = instanceIndex;
	} else {
		if ( this->metadata.buffers.count("tlasInstance") > 0 ) {
			instanceIndex = this->metadata.buffers["tlasInstance"];
		} else UF_EXCEPTION("Buffers not found: {}", "tlasInstance");

		auto& buffer = this->buffers.at(instanceIndex);
		void* map = buffer.map();
		memcpy(map, instancesVK.data(), instancesVK.size() * sizeof(VkAccelerationStructureInstanceKHR));
		buffer.unmap();
	}

	size_t instanceBufferAddress = this->buffers[instanceIndex].getAddress();
	auto& tlas = this->accelerationStructures.tops[0];

	{
		VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR | VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
		if ( shouldCompact ) flags |= VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR;
		uint32_t countInstance = instancesVK.size();

		VkAccelerationStructureGeometryInstancesDataKHR instancesVk{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR};
  		instancesVk.data.deviceAddress 			= instanceBufferAddress;

  		VkAccelerationStructureGeometryKHR tlasGeometry{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR};
		tlasGeometry.geometryType				= VK_GEOMETRY_TYPE_INSTANCES_KHR;
		tlasGeometry.geometry.instances			= instancesVk;

		VkAccelerationStructureBuildGeometryInfoKHR buildInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR};
		buildInfo.flags							= flags;
		buildInfo.geometryCount					= 1;
		buildInfo.pGeometries					= &tlasGeometry;
		buildInfo.mode							= update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
		buildInfo.type							= VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
		buildInfo.srcAccelerationStructure		= VK_NULL_HANDLE;

		VkAccelerationStructureBuildSizesInfoKHR sizeInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR};
		uf::renderer::vkGetAccelerationStructureBuildSizesKHR(
			device,
			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
			&buildInfo,
			&countInstance,
			&sizeInfo
		);

		// create BLAS buffer and handle
		auto tlasBufferUsageFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
		if ( !update ) {
		//	const size_t EXTRANEOUS_SIZE = 1024 * 1024; // oversize, to avoid having to constantly resize
		//	size_t bufferSize = MAX( sizeInfo.accelerationStructureSize, EXTRANEOUS_SIZE );
			size_t bufferSize = sizeInfo.accelerationStructureSize; // MAX( sizeInfo.accelerationStructureSize, EXTRANEOUS_SIZE );

			tlasBufferIndex = this->initializeBuffer( NULL, bufferSize, tlasBufferUsageFlags);
			this->metadata.buffers["tlasBuffer"] = tlasBufferIndex;
		} else {
			if ( this->metadata.buffers.count("tlasBuffer") > 0 ) {
				tlasBufferIndex = this->metadata.buffers["tlasBuffer"];
			} else UF_EXCEPTION("Buffers not found: {}", "tlasBuffer");
		//	rebuild = rebuild || this->updateBuffer( NULL, sizeInfo.accelerationStructureSize, tlasBufferIndex );
			auto& buffer = this->buffers.at( tlasBufferIndex );
			if ( sizeInfo.accelerationStructureSize > buffer.allocationInfo.size ) {
				rebuild = true;
				buffer.destroy();
				buffer.initialize( NULL, sizeInfo.accelerationStructureSize, tlasBufferUsageFlags );
			}
		}
		
		if ( !update || rebuild ) {
			if ( update ) {
				buildInfo.srcAccelerationStructure  = tlas.handle;
				device.transient.ass.emplace_back( tlas );
			} else {
				buildInfo.srcAccelerationStructure  = VK_NULL_HANDLE;
			}

			tlas.buffer = this->buffers[tlasBufferIndex].alias();

			VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR};
			createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
			createInfo.size = sizeInfo.accelerationStructureSize;
			createInfo.buffer = tlas.buffer.buffer;

			VK_CHECK_RESULT(uf::renderer::vkCreateAccelerationStructureKHR(device, &createInfo, nullptr, &tlas.handle));
			VK_REGISTER_HANDLE( tlas.handle );

			VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
			accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
			accelerationDeviceAddressInfo.accelerationStructure = tlas.handle;
			tlas.deviceAddress = uf::renderer::vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo);
		} else {
			buildInfo.srcAccelerationStructure  = tlas.handle;
		}

		// write to TLAS
		scratchBuffer.alignment = acclerationStructureProperties.minAccelerationStructureScratchOffsetAlignment;
		scratchBuffer.update( NULL, sizeInfo.buildScratchSize );

		buildInfo.dstAccelerationStructure  = tlas.handle;
		buildInfo.scratchData.deviceAddress = scratchBuffer.getAddress();

		VkAccelerationStructureBuildRangeInfoKHR		buildOffsetInfo{countInstance, 0, 0, 0};
		const VkAccelerationStructureBuildRangeInfoKHR* rangeInfo = &buildOffsetInfo;

		auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::COMPUTE);
		device.UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "buildTLAS" );
		uf::renderer::vkCmdBuildAccelerationStructuresKHR(
			commandBuffer,
			1,
			&buildInfo,
			&rangeInfo
		);

		if ( queryPool ) uf::renderer::vkCmdWriteAccelerationStructuresPropertiesKHR(
			commandBuffer,
			1,
			&buildInfo.dstAccelerationStructure,
			VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR,
			queryPool,
			0
		);

		device.flushCommandBuffer(commandBuffer);
	}

	// compact
	if ( queryPool ) {
		VkDeviceSize compactSize{};
		vkGetQueryPoolResults(device, queryPool, 0, 1, sizeof(VkDeviceSize), &compactSize, sizeof(VkDeviceSize), VK_QUERY_RESULT_WAIT_BIT);

		auto previous = tlas;
		device.transient.ass.emplace_back(previous);

		size_t tlasBufferSize = ALIGNED_SIZE(compactSize, 256);
	//	UF_MSG_DEBUG("Reduced size to {}% ({} -> {} = {})", (float) (oldSize - compactedSize) / (float) (oldSize), oldSize, compactedSize, oldSize - compactedSize);

		ext::vulkan::Buffer oldBuffer;
		oldBuffer.initialize( NULL, tlasBufferSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | uf::renderer::enums::Buffer::STORAGE );
		this->buffers[tlasBufferIndex].swap(oldBuffer);
		
		tlas.buffer = this->buffers[tlasBufferIndex].alias();
		tlas.buffer.descriptor.offset = 0;

		VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR};
		createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
		createInfo.size = tlasBufferSize;
		createInfo.buffer = tlas.buffer.buffer;
		createInfo.offset = tlas.buffer.descriptor.offset;

		VK_CHECK_RESULT(uf::renderer::vkCreateAccelerationStructureKHR(device, &createInfo, nullptr, &tlas.handle));
		VK_REGISTER_HANDLE( tlas.handle );

		VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
		accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
		accelerationDeviceAddressInfo.accelerationStructure = tlas.handle;
		tlas.deviceAddress = uf::renderer::vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo);

		VkCopyAccelerationStructureInfoKHR copyInfo{VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_INFO_KHR};
		copyInfo.src  = previous.handle;
		copyInfo.dst  = tlas.handle;
		copyInfo.mode = VK_COPY_ACCELERATION_STRUCTURE_MODE_COMPACT_KHR;
		
		auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::COMPUTE);
		device.UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "copyTLAS" );
		uf::renderer::vkCmdCopyAccelerationStructureKHR(commandBuffer, &copyInfo);
		device.flushCommandBuffer(commandBuffer);

		oldBuffer.destroy();
	}

	if ( queryPool ) {
		vkDestroyQueryPool(device, queryPool, nullptr);
		VK_UNREGISTER_HANDLE( queryPool );
	}

	if ( !update || rebuild ) {
		auto& renderMode = ext::vulkan::getRenderMode( descriptor.renderMode, true );
		renderMode.rebuild = true;
		uf::renderer::states::rebuild = true;
	}

	// build SBT
	if ( !this->material.hasShader("ray:gen", uf::renderer::settings::pipelines::names::rt) ) {
		{
			uf::stl::string rayGenShaderFilename = uf::io::root+"/shaders/raytrace/shader.ray-gen.spv";
			uf::stl::string rayMissShaderFilename = uf::io::root+"/shaders/raytrace/shader.ray-miss.spv";
			uf::stl::string rayHitClosestShaderFilename = uf::io::root+"/shaders/raytrace/shader.ray-hit-closest.spv";
			uf::stl::string rayHitAnyShaderFilename = uf::io::root+"/shaders/raytrace/shader.ray-hit-any.spv";

			this->material.attachShader(rayGenShaderFilename, VK_SHADER_STAGE_RAYGEN_BIT_KHR, uf::renderer::settings::pipelines::names::rt);
			this->material.attachShader(rayMissShaderFilename, VK_SHADER_STAGE_MISS_BIT_KHR, uf::renderer::settings::pipelines::names::rt);
			this->material.attachShader(rayHitClosestShaderFilename, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, uf::renderer::settings::pipelines::names::rt);
		//	this->material.attachShader(rayHitAnyShaderFilename, VK_SHADER_STAGE_ANY_HIT_BIT_KHR, uf::renderer::settings::pipelines::names::rt);
		}
		{
			auto& shader = this->material.getShader("ray:gen", uf::renderer::settings::pipelines::names::rt);
			shader.buffers.emplace_back( this->buffers[tlasBufferIndex].alias() );
		}
	}
}
bool ext::vulkan::Graphic::hasPipeline( const GraphicDescriptor& descriptor ) const {
#if USE_SHARED_PIPELINES
	return Pipeline::pipelines.count( descriptor ) > 0;
#else
	return pipelines.count( descriptor ) > 0;
#endif
}
ext::vulkan::Pipeline& ext::vulkan::Graphic::getPipeline() {
	return getPipeline( descriptor );
}
const ext::vulkan::Pipeline& ext::vulkan::Graphic::getPipeline() const {
	return getPipeline( descriptor );
}
ext::vulkan::Pipeline& ext::vulkan::Graphic::getPipeline( const GraphicDescriptor& descriptor ) {
	if ( !hasPipeline(descriptor) ) return initializePipeline( descriptor );
#if USE_SHARED_PIPELINES
	return Pipeline::pipelines[descriptor];
#else
	return pipelines[descriptor];
#endif
}
const ext::vulkan::Pipeline& ext::vulkan::Graphic::getPipeline( const GraphicDescriptor& descriptor ) const {
	if ( !hasPipeline(descriptor) ) UF_EXCEPTION("does not have pipeline");
#if USE_SHARED_PIPELINES
	return Pipeline::pipelines.at(descriptor);
#else
	return pipelines.at(descriptor);
#endif
}
bool ext::vulkan::Graphic::hasDescriptorSet( const GraphicDescriptor& descriptor ) const {
	return descriptorSets.count( descriptor ) > 0;
}

void ext::vulkan::Graphic::initializeDescriptorSet() {
	initializeDescriptorSet( this->descriptor );
}
ext::vulkan::DescriptorSets& ext::vulkan::Graphic::initializeDescriptorSet( const GraphicDescriptor& descriptor ) {
	auto& pipeline = getPipeline();
	auto& descriptorSet = descriptorSets[descriptor];

	// ensure pipeline exists (because we're passing this as const)
	pipeline.initialize(*this, descriptor);

	descriptorSet.initialize(*this, descriptor);
	descriptorSet.update(*this, descriptor);

	return descriptorSet;
}

ext::vulkan::DescriptorSets& ext::vulkan::Graphic::getDescriptorSet() {
	return getDescriptorSet( descriptor );
}
const ext::vulkan::DescriptorSets& ext::vulkan::Graphic::getDescriptorSet() const {
	return getDescriptorSet( descriptor );
}
ext::vulkan::DescriptorSets& ext::vulkan::Graphic::getDescriptorSet( const GraphicDescriptor& descriptor ) {
	if ( !hasDescriptorSet(descriptor) ) return initializeDescriptorSet( descriptor );
	return descriptorSets[descriptor];
}
const ext::vulkan::DescriptorSets& ext::vulkan::Graphic::getDescriptorSet( const GraphicDescriptor& descriptor ) const {
	if ( !hasDescriptorSet(descriptor) ) UF_EXCEPTION("does not have descriptor set");
	return descriptorSets.at(descriptor);
}
void ext::vulkan::Graphic::record( VkCommandBuffer commandBuffer, size_t pass, size_t draw, size_t offset ) const {
	return this->record( commandBuffer, descriptor, pass, draw, offset );
}
void ext::vulkan::Graphic::record( VkCommandBuffer commandBuffer, const GraphicDescriptor& descriptor, size_t pass, size_t draw, size_t offset ) const {
	if ( !process ) return;
#if USE_SHARED_PIPELINES
	if ( Pipeline::pipelines.count( descriptor ) == 0 ) {
		Pipeline::pipelines[descriptor].initialize(*this, descriptor);
	}
#endif
	if ( !hasPipeline( descriptor ) ) {
	//	UF_MSG_DEBUG("{} has no valid pipeline: renderMode={}, pipeline={}", (void*) this, descriptor.renderMode, descriptor.pipeline);
		return;
	}
	if ( !hasDescriptorSet( descriptor ) ) {
	//	UF_MSG_DEBUG("{} has no valid descriptor set: renderMode={}, pipeline={}", (void*) this, descriptor.renderMode, descriptor.pipeline);
		return;
	}

	auto& pipeline = this->getPipeline( descriptor );
	auto& descriptorSet = this->getDescriptorSet( descriptor );

	if ( !descriptorSet.metadata.process ) return;
	
	pipeline.record( *this, commandBuffer, pass, draw, offset );
	descriptorSet.record( *this, descriptor, commandBuffer, pass, draw, offset );

	auto shaders = material.getShaders( descriptor.pipeline );

	uint32_t width = descriptor.bind.width ? descriptor.bind.width : ext::vulkan::settings::width;
	uint32_t height = descriptor.bind.height ? descriptor.bind.height : ext::vulkan::settings::height;
	uint32_t depth = descriptor.bind.depth ? descriptor.bind.depth : 1;

	if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR ) {
		uf::renderer::vkCmdTraceRaysKHR(
			commandBuffer,
			&pipeline.sbtEntries[0],
			&pipeline.sbtEntries[1],
			&pipeline.sbtEntries[2],
			&pipeline.sbtEntries[3],
			width,
			height,
			1
		);
		return;
	}
	if ( descriptor.bind.point == VK_PIPELINE_BIND_POINT_COMPUTE ) {
		for ( auto* shader : shaders ) {
			if ( shader->descriptor.stage != VK_SHADER_STAGE_COMPUTE_BIT ) continue;
			auto& localSize = shader->metadata.definitions.localSize;
			auto dispatch = pod::Vector3ui{
				std::ceil( (float) width / localSize.x ),
				std::ceil( (float) height / localSize.y ),
				std::ceil( (float) depth / localSize.z ),
			};

			vkCmdDispatch(commandBuffer,
				dispatch.x,
				dispatch.y,
				dispatch.z
			);
		}
		return;
	}
	for ( auto* shader : shaders ) {
		if ( shader->descriptor.stage == VK_SHADER_STAGE_COMPUTE_BIT ) return;
		if (
			shader->descriptor.stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_MISS_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR ||
			shader->descriptor.stage == VK_SHADER_STAGE_INTERSECTION_BIT_KHR
		) return;
	}

	struct {
		uf::stl::vector<VkBuffer> buffer;
		uf::stl::vector<VkDeviceSize> offset;
	} vertexInstance;

	for ( auto& attribute : descriptor.inputs.vertex.attributes ) {
		vertexInstance.buffer.emplace_back( attribute.buffer < 0 ? VK_NULL_HANDLE : buffers.at(attribute.buffer).buffer );
		vertexInstance.offset.emplace_back( attribute.offset );
	}

	for ( auto& attribute : descriptor.inputs.instance.attributes ) {
		vertexInstance.buffer.emplace_back( attribute.buffer < 0 ? VK_NULL_HANDLE : buffers.at(attribute.buffer).buffer );
		vertexInstance.offset.emplace_back( attribute.offset );
	}

	struct {
		VkBuffer buffer = NULL;
		VkDeviceSize offset = 0;
		size_t binding = 0;
		size_t index = 0;
	} index, indirect;

	for ( auto& buffer : buffers ) {
		if ( !index.buffer && buffer.usage & uf::renderer::enums::Buffer::INDEX ) index.buffer = buffer.buffer;
		if ( !indirect.buffer && buffer.usage & uf::renderer::enums::Buffer::INDIRECT ) indirect.buffer = buffer.buffer;
	}

	if ( !vertexInstance.buffer.empty() ) {
		vkCmdBindVertexBuffers( commandBuffer, 0, vertexInstance.buffer.size(), vertexInstance.buffer.data(), vertexInstance.offset.data() );
	}

	if ( index.buffer ) {
		VkIndexType indicesType = VK_INDEX_TYPE_UINT32;
		switch ( descriptor.inputs.index.size ) {
			case 1: indicesType = VK_INDEX_TYPE_UINT8_EXT; break;
			case 2: indicesType = VK_INDEX_TYPE_UINT16; break;
			case 4: indicesType = VK_INDEX_TYPE_UINT32; break;
			default:
				UF_EXCEPTION("invalid indices size of {}", (int) descriptor.inputs.index.size);
			break;
		}
		vkCmdBindIndexBuffer(commandBuffer, index.buffer, index.offset, indicesType);
	}
	const bool cpuSideIndirection = false;
	if ( index.buffer && indirect.buffer && cpuSideIndirection ) {
		auto& indirectAttribute = descriptor.inputs.indirect.attributes.front();
		const pod::DrawCommand* drawCommands = (const pod::DrawCommand*) indirectAttribute.pointer;
		for ( auto i = 0; i < descriptor.inputs.indirect.count; ++i ) {
			auto& drawCommand = drawCommands[i];
			vkCmdDrawIndexed(commandBuffer,
				drawCommand.indices, // indexCount
				drawCommand.instances, // instanceCount
				drawCommand.indexID, // firstIndex
				drawCommand.vertexID, // vertexOffset
				drawCommand.instanceID // firstInstance
			);
		}
	} else if ( index.buffer && indirect.buffer ) {
		if ( device->enabledFeatures.multiDrawIndirect || descriptor.inputs.indirect.count <= 1 ) {
			vkCmdDrawIndexedIndirect(commandBuffer, indirect.buffer,
				descriptor.inputs.indirect.offset, // offset
				descriptor.inputs.indirect.count, // drawCount
				descriptor.inputs.indirect.size // stride
			);
		} else {
			for ( auto i = 0; i < descriptor.inputs.indirect.count; ++i ) {
				vkCmdDrawIndexedIndirect(commandBuffer, indirect.buffer,
					descriptor.inputs.indirect.offset + i * descriptor.inputs.indirect.size, // offset
					1, // drawCount
					descriptor.inputs.indirect.size // stride
				);
			}
		}
	} else if ( index.buffer && !indirect.buffer ) {
		vkCmdDrawIndexed(commandBuffer,
			descriptor.inputs.index.count, // indexCount
			descriptor.inputs.instance.count, // instanceCount
			descriptor.inputs.index.first, // firstIndex
			descriptor.inputs.vertex.first, // vertexOffset
			descriptor.inputs.instance.first // firstInstance
		);
	} else if ( !vertexInstance.buffer.empty() && indirect.buffer ) {
		vkCmdDrawIndexedIndirect(commandBuffer, indirect.buffer,
			descriptor.inputs.indirect.offset, // offset
			descriptor.inputs.indirect.count, // drawCount
			descriptor.inputs.indirect.size // stride
		);
	} else/* if ( !vertexInstance.buffer.empty() && !indirect.buffer ) */{
		vkCmdDraw(commandBuffer,
			descriptor.inputs.vertex.count, // vertexCount
			descriptor.inputs.instance.count, // instanceCount
			descriptor.inputs.vertex.first, // firstVertex
			descriptor.inputs.instance.first // firstInstance
		);
	}
}
void ext::vulkan::Graphic::destroy() {
	if ( device ) {
		for ( auto& as : accelerationStructures.bottoms ) {
			if ( as.aliased ) continue;
			uf::renderer::vkDestroyAccelerationStructureKHR(*device, as.handle, nullptr);
			VK_UNREGISTER_HANDLE( as.handle );
		}
		accelerationStructures.bottoms.clear();
		
		for ( auto& as : accelerationStructures.tops ) {
			if ( as.aliased ) continue;
			uf::renderer::vkDestroyAccelerationStructureKHR(*device, as.handle, nullptr);
			VK_UNREGISTER_HANDLE( as.handle );
		}
		accelerationStructures.tops.clear();
	}
	for ( auto& pair : pipelines ) pair.second.destroy();
	pipelines.clear();
	
	for ( auto& pair : descriptorSets ) pair.second.destroy();
	descriptorSets.clear();

	material.destroy();
	ext::vulkan::Buffers::destroy();
//	ext::vulkan::states::rebuild = true;
}

#include <uf/utils/string/hash.h>
void ext::vulkan::GraphicDescriptor::parse( ext::json::Value& metadata ) {
	if ( metadata["front face"].is<uf::stl::string>() ) {
		if ( metadata["front face"].as<uf::stl::string>() == "ccw" ) frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
		else if ( metadata["front face"].as<uf::stl::string>() == "cw" ) frontFace = VK_FRONT_FACE_CLOCKWISE;
	}
	if ( metadata["cull mode"].is<uf::stl::string>() ) {
		if ( metadata["cull mode"].as<uf::stl::string>() == "back" ) cullMode = VK_CULL_MODE_BACK_BIT;
		else if ( metadata["cull mode"].as<uf::stl::string>() == "front" ) cullMode = VK_CULL_MODE_FRONT_BIT;
		else if ( metadata["cull mode"].as<uf::stl::string>() == "none" ) cullMode = VK_CULL_MODE_NONE;
		else if ( metadata["cull mode"].as<uf::stl::string>() == "both" ) cullMode = VK_CULL_MODE_FRONT_AND_BACK;
	}
	// to-do: probably parse depth op/min/max
	if ( ext::json::isObject(metadata["depth bias"]) ) {
		depth.bias.enable = VK_TRUE;
		depth.bias.constant = metadata["depth bias"]["constant"].as<float>();
		depth.bias.slope = metadata["depth bias"]["slope"].as<float>();
		depth.bias.clamp = metadata["depth bias"]["clamp"].as<float>();
	}
}
ext::vulkan::GraphicDescriptor::hash_t ext::vulkan::GraphicDescriptor::hash() const {
	size_t seed{};
	uf::hash( seed, aux, subpass, renderMode, renderTarget, material, pipeline, topology, cullMode, fill, lineWidth, frontFace, depth.test, depth.write, depth.operation, depth.bias.enable, depth.bias.constant, depth.bias.slope, depth.bias.clamp );
	return seed;
}

#endif