#include "vulkan_pipeline_utl.h" #include "vulkan_utl.h" #include "vulkan_framebuffer_utl.h" #include "vulkan_texture_utl.h" #include "vulkan_contructors.h" extern "C" { #include #include #include #include #include #include #include } #include #include #include // use glfw for temporary swapchain window // todo: GET RID OF THIS, IT SOMEHOW INCLUDES GL!!! #include #define GLFW_EXPOSE_NATIVE_X11 #include #define MAX_SWAPCHAIN_IMAGES 8 // workaround for ffmpeg stable api nonsence with incompatible pointers between versions struct PointerWrap { void *ptr; template PointerWrap(T p):ptr((void*)p){} template operator T() const { return (T)ptr; } }; struct Vertex { float position[3]; float color[3]; }; struct DecoderImage { VkImage image; VkDeviceMemory image_memory; // todo: deduplicate shared parameters VkDeviceSize memory_offset_plane0, memory_offset_plane1; VkDeviceSize stride0, stride1; VkImageView image_view; unsigned char *pMappedData; }; void CreateSoftwareImage(VkInstance inst, VulkanDevice &dev, DecoderImage &image, const VkSamplerYcbcrConversionInfo &ycbcr_info, VkFormat format, unsigned int width, unsigned int height) { CallWith(Image2dInfo(VK_IMAGE_USAGE_SAMPLED_BIT, format, width, height, $(flags) = VK_IMAGE_CREATE_DISJOINT_BIT, $(tiling) = VK_IMAGE_TILING_LINEAR, $(initialLayout) = VK_IMAGE_LAYOUT_PREINITIALIZED ), vkCreateImage(dev.device, &ref,NULL, &image.image)); VkImagePlaneMemoryRequirementsInfo plane_info = $M(VkImagePlaneMemoryRequirementsInfo{VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO}, $(planeAspect) = VK_IMAGE_ASPECT_PLANE_0_BIT); VkImageMemoryRequirementsInfo2 image_info = $M(VkImageMemoryRequirementsInfo2{VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2}, $(image) = image.image); VkMemoryRequirements2 image_reqs = $M(VkMemoryRequirements2{VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2}); image_info.pNext = &plane_info; PFN_vkGetImageMemoryRequirements2 pvkGetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)vkGetInstanceProcAddr(inst, "vkGetImageMemoryRequirements2KHR"); pvkGetImageMemoryRequirements2(dev.device, &image_info, &image_reqs); VkDeviceSize image_size = image_reqs.memoryRequirements.size; uint32_t image_bits = image_reqs.memoryRequirements.memoryTypeBits; image.memory_offset_plane0 = 0; image.memory_offset_plane1 = image_size; // todo: should not we align plane offset here plane_info.planeAspect = VK_IMAGE_ASPECT_PLANE_1_BIT; pvkGetImageMemoryRequirements2(dev.device, &image_info, &image_reqs); image_size += image_reqs.memoryRequirements.size; image_bits = image_bits | image_reqs.memoryRequirements.memoryTypeBits; VkMemoryAllocateInfo ainfo = AllocateInfo(image_size); dev.GetMemoryType(image_bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &ainfo.memoryTypeIndex); vkAllocateMemory(dev.device, &ainfo, NULL, &image.image_memory); VkBindImagePlaneMemoryInfo plane0i = {VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO, NULL, VK_IMAGE_ASPECT_PLANE_0_BIT}; VkBindImagePlaneMemoryInfo plane1i = {VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO, NULL, VK_IMAGE_ASPECT_PLANE_1_BIT}; VkBindImageMemoryInfo bindInfo[2] = {{VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, &plane0i}, {VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, &plane1i}}; $F(bindInfo[0], $(image) = image.image, $(memory) = image.image_memory, $(memoryOffset) = image.memory_offset_plane0); $F(bindInfo[1], $(image) = image.image, $(memory) = image.image_memory, $(memoryOffset) = image.memory_offset_plane1); PFN_vkBindImageMemory2 pvkBindImageMemory2 = (PFN_vkBindImageMemory2)vkGetInstanceProcAddr(inst, "vkBindImageMemory2KHR"); pvkBindImageMemory2(dev.device, 2, bindInfo); CallWith($M(VkImageViewCreateInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, &ycbcr_info}, $(image) = image.image, $(viewType) = VK_IMAGE_VIEW_TYPE_2D, $(format) = format, $(subresourceRange) = SubresourceRange()), vkCreateImageView(dev.device, &ref, NULL, &image.image_view)); vkMapMemory(dev.device, image.image_memory, 0, image_size, 0, (void**)&image.pMappedData); VkCommandBuffer cbuf = dev.CreateCommandBuffer(); VulkanTexture::SetImageLayout(cbuf, image.image, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); dev.FlushCommandBuffer(cbuf,dev.defaultQueue); VkImageSubresource plane = {VK_IMAGE_ASPECT_PLANE_0_BIT}; VkSubresourceLayout layout = {}; vkGetImageSubresourceLayout(dev.device, image.image, &plane, &layout); image.stride0 = layout.rowPitch; plane.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; vkGetImageSubresourceLayout(dev.device, image.image, &plane, &layout); image.stride1 = layout.rowPitch; } #define MAX_DECODER_FRAMES 4 static struct FFmpegDecoder { DecoderImage images[MAX_DECODER_FRAMES]; VkSamplerYcbcrConversion ycbcr_sampler_conversion; VkSampler ycbcr_sampler; // todo: align thread-shared data to unshare with immutable volatile int decode_index, present_index; pthread_t thread = 0; pthread_mutex_t mutex; pthread_cond_t cond; // todo: should keep availiable frames list/mask here (atomic uint64_t?) } gFF; static int WaitActiveFrame() { // todo: where frame selection should be done? pthread_mutex_lock(&gFF.mutex); while(gFF.decode_index <= gFF.present_index) pthread_cond_wait(&gFF.cond, &gFF.mutex); int ret = ++gFF.present_index; pthread_mutex_unlock(&gFF.mutex); return ret; } template static enum AVPixelFormat my_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt) { return p010?AV_PIX_FMT_YUV420P10LE:AV_PIX_FMT_YUV420P; } template void ConvertFrame(AVFrame *frame, const DecoderImage &img) { constexpr uint8_t pad = sizeof(pixel) > 1?6:0; for(int i = 0; i < frame->height; i++) { pixel *data = (pixel*)(img.pMappedData + img.stride0 * i); pixel *src = (pixel*)(frame->data[0] + frame->linesize[0]*i); for(int j = 0; j < frame->linesize[0]/sizeof(pixel); j++) data[j] = src[j] << pad; } // ffmpeg cannot output 2 planes for(int i = 0; i < frame->height / 2; i++) { pixel *data = (pixel*)(img.pMappedData + img.memory_offset_plane1 + img.stride1*i); pixel *src1 = (pixel*)(frame->data[1] + frame->linesize[1]*i); pixel *src2 = (pixel*)(frame->data[2] + frame->linesize[2]*i); for(int j = 0; j < frame->linesize[1]/sizeof(pixel); j++) { data[j*2] = src1[j] << pad; data[j*2+1] = src2[j] << pad; } } } template static void *DecoderThread(void*) { AVFormatContext *input_ctx = NULL; AVCodec *decoder = NULL; AVCodecContext *decoder_ctx = NULL; AVPacket *packet = NULL; int video; avformat_open_input(&input_ctx, "out.265", NULL, NULL); avformat_find_stream_info(input_ctx, NULL); video = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, PointerWrap(&decoder), 0); decoder_ctx = avcodec_alloc_context3(decoder); decoder_ctx->get_format = my_get_format; avcodec_open2(decoder_ctx, decoder, NULL); packet = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); int idx = 0; while(true) { av_read_frame(input_ctx, packet); if(packet->stream_index == video) { int res = avcodec_send_packet(decoder_ctx, packet); if(res >= 0) { res = avcodec_receive_frame(decoder_ctx, frame); if(res < 0) break; idx++; if constexpr(p010) ConvertFrame(frame,gFF.images[idx & 3]); else ConvertFrame(frame,gFF.images[idx & 3]); pthread_mutex_lock(&gFF.mutex); gFF.decode_index = idx; pthread_cond_signal(&gFF.cond); pthread_mutex_unlock(&gFF.mutex); printf("frame %d\n", idx); usleep(20000); } else break; } av_packet_unref(packet); } return NULL; } static void SetupFFMpeg(VkInstance inst, VulkanDevice &dev, unsigned int width, unsigned int height, bool p010) { PFN_vkCreateSamplerYcbcrConversion pvkCreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)vkGetInstanceProcAddr(inst, "vkCreateSamplerYcbcrConversionKHR"); VkFormat format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; if (p010) { VkFormatProperties format_properties; vkGetPhysicalDeviceFormatProperties(dev.physicalDevice, VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, &format_properties); // intel does not support 10 bit, causing validation errors (but works!), but 16 bit layout is very similar; // todo: option to ignore this check if (format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT) format = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16; else format = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM; } CallWith($M(VkSamplerYcbcrConversionCreateInfo{VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO}, $(format) = format, $(ycbcrModel) = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY, $(ycbcrRange) = VK_SAMPLER_YCBCR_RANGE_ITU_FULL, $(xChromaOffset) = VK_CHROMA_LOCATION_MIDPOINT, // zero $(yChromaOffset) = VK_CHROMA_LOCATION_MIDPOINT, $(chromaFilter) = VK_FILTER_LINEAR), // zero pvkCreateSamplerYcbcrConversion(dev.device, &ref, NULL, &gFF.ycbcr_sampler_conversion)); $Sc ycbcr_info = $M(VkSamplerYcbcrConversionInfo{VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO}, $(conversion) = gFF.ycbcr_sampler_conversion); CallWith($M(VkSamplerCreateInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, &ycbcr_info}, $(magFilter) = VK_FILTER_LINEAR, $(minFilter) = VK_FILTER_LINEAR, $(mipmapMode) = VK_SAMPLER_MIPMAP_MODE_NEAREST, // zero $(addressModeU) = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, $(addressModeV) = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, $(addressModeW) = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, $(maxLod) = 1.0f, $(borderColor) = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK), vkCreateSampler(dev.device, &ref, NULL, &gFF.ycbcr_sampler); ); for(int i = 0; i < MAX_DECODER_FRAMES; i++) CreateSoftwareImage(inst, dev, gFF.images[i], ycbcr_info, format, width, height); pthread_mutex_init(&gFF.mutex, NULL); pthread_cond_init(&gFF.cond, NULL); pthread_create(&gFF.thread, NULL, p010?&DecoderThread:&DecoderThread, NULL); } #define USE_SAMPLER 1 // decoder outputs frames mapped to random VkImage from decoder pool // reconstruction source frames match decoder frame index (but not image index) #define RECONSTRUCTION_SOURCE_FRAMES 4 struct ReconsructionSource { VkImage image; VkDeviceMemory image_memory; VkImageView image_view; VkSampler image_sampler; unsigned char *pMappedData; VkCommandBuffer copyCmdBuf; }; static struct { ReconsructionSource sources[RECONSTRUCTION_SOURCE_FRAMES]; unsigned int frame_idx; FILE *reconstructionStream; VulkanBuffer stagingBuffer; VulkanDevice *device; // todo: separate thread // todo: separate queue family if needed // todo: staging buffer when linear not supported } gReconstruction; void SetupReconstructionImages(VulkanDevice &dev, VkFormat format, unsigned int width, unsigned int height) { dev.CreateBuffer(gReconstruction.stagingBuffer, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, ((1024 + 3) >> 2) * ((height + 3) >> 2)*8); gReconstruction.stagingBuffer.Map(); for(int i = 0; i < RECONSTRUCTION_SOURCE_FRAMES; i++) { ReconsructionSource &image = gReconstruction.sources[i]; CallWith(Image2dInfo((USE_SAMPLER?VK_IMAGE_USAGE_SAMPLED_BIT:VK_IMAGE_USAGE_STORAGE_BIT) |VK_IMAGE_USAGE_TRANSFER_DST_BIT , format, width, height, $(tiling) = VK_IMAGE_TILING_OPTIMAL, $(initialLayout) = VK_IMAGE_LAYOUT_UNDEFINED ), vkCreateImage(dev.device, &ref,NULL, &image.image)); VkMemoryRequirements mem_reqs; vkGetImageMemoryRequirements(dev.device, image.image, &mem_reqs); VkMemoryAllocateInfo info = AllocateInfo(mem_reqs.size); dev.GetMemoryType(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, &info.memoryTypeIndex); vkAllocateMemory(dev.device, &info, NULL, &image.image_memory); vkMapMemory(dev.device, image.image_memory, 0, mem_reqs.size, 0, (void**)&image.pMappedData); vkBindImageMemory(dev.device, image.image, image.image_memory, 0); CallWith($M(VkImageViewCreateInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}, $(image) = image.image, $(viewType) = VK_IMAGE_VIEW_TYPE_2D, $(format) = format, $(subresourceRange) = SubresourceRange()), vkCreateImageView(dev.device, &ref, NULL, &image.image_view)); #if USE_SAMPLER CallWith($M(VkSamplerCreateInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}, $(magFilter) = VK_FILTER_NEAREST, $(minFilter) = VK_FILTER_NEAREST, $(mipmapMode) = VK_SAMPLER_MIPMAP_MODE_NEAREST, // zero $(addressModeU) = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, $(addressModeV) = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, $(addressModeW) = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, $(maxLod) = 1.0f, $(borderColor) = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK), vkCreateSampler(dev.device, &ref, NULL, &image.image_sampler); ); #endif VkCommandBuffer cbuf = dev.CreateCommandBuffer(); VulkanTexture::SetImageLayout(cbuf, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); dev.FlushCommandBuffer(cbuf,dev.defaultQueue); image.copyCmdBuf = dev.CreateCommandBuffer(); VulkanTexture::SetImageLayout(image.copyCmdBuf, image.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); VkBufferImageCopy cp = VkBufferImageCopy { .bufferOffset = 0, .bufferRowLength = 1024, .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .imageExtent = { .width = width, .height = height, .depth = 1, }, }; vkCmdCopyBufferToImage(image.copyCmdBuf, gReconstruction.stagingBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &cp); VulkanTexture::SetImageLayout(image.copyCmdBuf, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); vkEndCommandBuffer(image.copyCmdBuf); } gReconstruction.reconstructionStream = fopen("reconstruction.bin", "rb"); gReconstruction.device = &dev; } //#include #include "xpack/libxpack.h" static xpack_decompressor *dec; int ReadReconstructionFrame() { if(!dec) dec = xpack_alloc_decompressor(); uint32_t length = 0; ReconsructionSource &image = gReconstruction.sources[++gReconstruction.frame_idx & 3]; fread(&length, sizeof(int), 1, gReconstruction.reconstructionStream); #if 1 #if 1 static char buffer[((1024 + 3) >> 2) * ((540 + 3) >> 2)*8+16]; fread(buffer, 1, length, gReconstruction.reconstructionStream); //LZ4_decompress_safe(buffer,(char*)gReconstruction.stagingBuffer.mapped, length, ((1024 + 3) >> 2) * ((540 + 3) >> 2)*8); size_t length2; xpack_decompress(dec, buffer, length, gReconstruction.stagingBuffer.mapped, ((1024 + 3) >> 2) * ((540 + 3) >> 2)*8+16, &length2); #else fread(gReconstruction.stagingBuffer.mapped, 1, length, gReconstruction.reconstructionStream); #endif CallWith(SubmitInfo( image.copyCmdBuf), vkQueueSubmit(gReconstruction.device->defaultQueue, 1, &ref, NULL)); #else fread(image.pMappedData, 1, length, gReconstruction.reconstructionStream); #endif return gReconstruction.frame_idx; } // if separate reconstruction disabled, each decoder/reconstruction source combo generates command buffer for each swapchain image // separate reconstruction: // reconstruction target frame index always match reconstruction source frames #define RECONSTRUCTION_TARGET_FRAMES 4 // each reconstruction target builds command buffer for each decoder image // foveation pipeline builds command buffer for each swapchain image/reprojection targer pair // reconstruction // sample one decoder pixel and one reconstruction pixel // map decoder pixels to 0-511 (or 0-2) and reconstruction to -256-255 (-1-1) (divide by two?) // graphics // depending on even/noteven coordinates, add or substract pixels and write to output struct GraphicsApplicationPipeline: BaseVulkanPipeline { void Init(VkDevice dev, VkRenderPass renderPass, VkSampler *pImmutableSamplers, int count) { device = dev; CreateDescriptorSetLayout( //BasicBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,1,VK_SHADER_STAGE_VERTEX_BIT) BasicBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,1,VK_SHADER_STAGE_FRAGMENT_BIT, pImmutableSamplers) ); CreatePool(count, //BasicPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) BasicPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, count) ); VkShaderModule vp, fp; CreateGraphicsPipeline(renderPass, Stages( ShaderFromFile(vp, "quad.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), ShaderFromFile(fp, "quad.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)), VertexBindings(), VertexAttributes(), DynamicStates( VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR ), DepthStencil(true, true), AssemblyTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN) //RasterMode(VK_FRONT_FACE_CLOCKWISE) ); vkDestroyShaderModule(device, vp, NULL); vkDestroyShaderModule(device, fp, NULL); } void UpdateDescriptors(VkDescriptorSet dstSet, VkImageView imageView, VkSampler sampler) { WriteDescriptors( //BufferWrite(dstSet, 0, buffer) ImageWrite(dstSet, 0, ImageDescriptor(imageView, VK_IMAGE_LAYOUT_GENERAL, sampler), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) ); } }; // compute // write 4 output pixels for each source pair struct ComputeApplicationPipeline: BaseVulkanPipeline { void Init(VkDevice dev, VkRenderPass renderPass, VkSampler *pImmutableSamplers, int count1, int count2, int count3, unsigned int width, unsigned int height, unsigned int fwidth, unsigned int fheight) { device = dev; CreateDescriptorSetLayout( BasicBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,1,VK_SHADER_STAGE_COMPUTE_BIT, pImmutableSamplers), #if USE_SAMPLER BasicBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,1,VK_SHADER_STAGE_COMPUTE_BIT), #else BasicBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,VK_SHADER_STAGE_COMPUTE_BIT), #endif BasicBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,1,VK_SHADER_STAGE_COMPUTE_BIT) ); CreatePool((count1 + count3) * count2, BasicPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, count2 * count3*3), #if USE_SAMPLER // BasicPoolSize(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, count3 * count2 * count1), BasicPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, count3 * count2 * count1) #else BasicPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, count3 * count2 * count1*2) #endif ); // todo: wrapper for specialization arrays... float specData[2] = {(float)width,(float)height}; VkSpecializationMapEntry specs[2] = {{0, 0, sizeof(float)},1,sizeof(float),sizeof(float)}; VkSpecializationInfo sinfo = {2, specs, sizeof(specData), specData }; VkShaderModule shader; // todo: combined reconstruction/foveation shader? (maybe ineffective for compute pipeline) CreateComputePipeline(ShaderFromFile(shader, "reconstruction.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT, &sinfo)); // todo: should not we destroy shader internally? vkDestroyShaderModule(device, shader, NULL); } void UpdateDescriptors(VkDescriptorSet dstSet, VkImageView imageView, VkSampler sampler, VkImageView imageView1, VkImageView imageView2, VkSampler sampler2) { WriteDescriptors( ImageWrite(dstSet, 0, ImageDescriptor(imageView, VK_IMAGE_LAYOUT_GENERAL, sampler), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER), ImageWrite(dstSet, 1, ImageDescriptor(imageView1, VK_IMAGE_LAYOUT_GENERAL, sampler2), USE_SAMPLER?VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:VK_DESCRIPTOR_TYPE_STORAGE_IMAGE), ImageWrite(dstSet, 2, ImageDescriptor(imageView2, VK_IMAGE_LAYOUT_GENERAL))); } }; // todo: how should be compute defoveation done (input/output resolution?) struct GraphicsApplication { VulkanContext context; VulkanDevice dev; GLFWwindow *window = NULL; // swapchain stuff VkSwapchainKHR swapchain; VkSurfaceKHR surface; VkImage swapchainImages[MAX_SWAPCHAIN_IMAGES]; unsigned int numSwapchainImages; VulkanFramebuffer swapchainFbs[MAX_SWAPCHAIN_IMAGES]; VkFence chainFences[MAX_SWAPCHAIN_IMAGES]; VkSemaphore swapchainRenderSemaphore[MAX_SWAPCHAIN_IMAGES], swapchainPresentSemaphore[MAX_SWAPCHAIN_IMAGES]; // direct reconstruction to swapchain VkCommandBuffer commandBuffers[MAX_SWAPCHAIN_IMAGES * MAX_DECODER_FRAMES]; // reconstruction to temporary framebuffer VulkanFramebuffer reconstructionFbs[RECONSTRUCTION_TARGET_FRAMES]; VkImage reconstructionImages[RECONSTRUCTION_TARGET_FRAMES]; VkDeviceMemory reconstructionImagesMem[RECONSTRUCTION_TARGET_FRAMES]; VkCommandBuffer reconstructionCommandBuffers[MAX_DECODER_FRAMES * RECONSTRUCTION_TARGET_FRAMES]; VkCommandBuffer presentCommandBuffers[MAX_SWAPCHAIN_IMAGES * RECONSTRUCTION_TARGET_FRAMES]; VkSemaphore reconstructionSemaphore[RECONSTRUCTION_TARGET_FRAMES]; struct UBO { }; GraphicsApplicationPipeline graphicsPipeline; ComputeApplicationPipeline computePipeline; VkDescriptorSet descriptorSet; VkDescriptorSet swapchainDescriptorSets[MAX_SWAPCHAIN_IMAGES*MAX_DECODER_FRAMES]; VkDescriptorSet reconstructionDescriptorSets[RECONSTRUCTION_TARGET_FRAMES*MAX_DECODER_FRAMES]; VkDescriptorSet decodeDescriptorSet[MAX_DECODER_FRAMES]; VulkanBuffer uboBuf; VulkanBuffer stagingVert; VulkanBuffer stagingInd; //VkFence swapchainFence; void CreateWindow(const char* windowTitle, int& outWidth, int& outHeight, bool resizable) { if (!glfwInit()) return; glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // render full screen without overlapping taskbar GLFWmonitor* monitor = glfwGetPrimaryMonitor(); window = glfwCreateWindow(outWidth, outHeight, windowTitle, nullptr, nullptr); if (!window) { glfwTerminate(); return; } glfwGetWindowSize(window, &outWidth, &outHeight); glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int, int action, int) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { glfwSetWindowShouldClose(window, GLFW_TRUE); } }); glfwSetErrorCallback([](int error, const char* description) { printf("GLFW Error (%i): %s\n", error, description); }); } void DestroyWindow() { if(window) glfwDestroyWindow(window); glfwTerminate(); } void CreateSwapchain(uint32_t width, uint32_t height, bool graphics) { VK_CHECK_RESULT(CallWith($M(VkXlibSurfaceCreateInfoKHR{VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR}, $(dpy) = glfwGetX11Display(), $(window) = glfwGetX11Window(window)), vkCreateXlibSurfaceKHR(context.instance, &ref, nullptr, &surface))); CallWith($M( VkSwapchainCreateInfoKHR{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}, $(surface) = surface, $(minImageCount) = 4, $(imageFormat) = VK_FORMAT_B8G8R8A8_UNORM, $(imageColorSpace) = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, $(imageExtent) = VkExtent2D{width, height}, $(imageArrayLayers),$(queueFamilyIndexCount),$(clipped), $(imageUsage) = VK_IMAGE_USAGE_TRANSFER_DST_BIT | (graphics?VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT:VK_IMAGE_USAGE_STORAGE_BIT), $(pQueueFamilyIndices) = &dev.defaultFamilyIndex, $(preTransform) = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, $(compositeAlpha) = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, $(presentMode) = VK_PRESENT_MODE_FIFO_KHR), vkCreateSwapchainKHR(dev.device, &ref, nullptr, &swapchain)); numSwapchainImages = MAX_SWAPCHAIN_IMAGES; VK_CHECK_RESULT(vkGetSwapchainImagesKHR(dev.device, swapchain, &numSwapchainImages, swapchainImages)); VkSemaphoreCreateInfo semaphoreCreateInfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; for(int i = 0; i < numSwapchainImages; i++) { swapchainFbs[i].Create(dev.device); if(graphics) { swapchainFbs[i].CreateDepthAttachment(dev, VK_FORMAT_D32_SFLOAT, width, height); swapchainFbs[i].Init(swapchainImages[i], VK_FORMAT_B8G8R8A8_UNORM, width, height); } else { CallWith($M(VkImageViewCreateInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}, $(image) = swapchainImages[i], $(viewType) = VK_IMAGE_VIEW_TYPE_2D, $(format) = VK_FORMAT_B8G8R8A8_UNORM, $(subresourceRange) = SubresourceRange()), vkCreateImageView(dev.device, &ref, NULL, &swapchainFbs[i].color_view)); } VK_CHECK_RESULT(vkCreateSemaphore(dev.device, &semaphoreCreateInfo, nullptr, &swapchainPresentSemaphore[i])); VK_CHECK_RESULT(vkCreateSemaphore(dev.device, &semaphoreCreateInfo, nullptr, &swapchainRenderSemaphore[i])); } } unsigned int AcquireImage(int sem_index, VkFence fence = NULL) { unsigned int index; vkAcquireNextImageKHR(dev.device, swapchain, UINT64_MAX, swapchainRenderSemaphore[sem_index], fence, &index); return index; } void PresentImage(unsigned int index) { CallWith(PresentInfo(&swapchain, &index, &swapchainPresentSemaphore[index]), vkQueuePresentKHR(dev.defaultQueue, &ref)); } void DestroySwapchain() { for(int i = 0; i < numSwapchainImages; i++) { swapchainFbs[i].Destroy(); vkDestroySemaphore(dev.device, swapchainPresentSemaphore[i], NULL); vkDestroySemaphore(dev.device, swapchainRenderSemaphore[i], NULL); } vkDestroySwapchainKHR(dev.device, swapchain, NULL); vkDestroySurfaceKHR(context.instance,surface,NULL); } void waitFence(int chidx) { VK_CHECK_RESULT(vkWaitForFences(dev.device, 1, &chainFences[chidx], VK_TRUE, 100000000000)); } void Run() { #ifndef NDEBUG #define ENABLE_VALIDATION_LAYERS 1 #else #define ENABLE_VALIDATION_LAYERS 0 #endif bool compute = true; bool separateReconstruction = true; context.Create("streamingengine", "vulkan-playground-server", ENABLE_VALIDATION_LAYERS); dev.Create(context.FindPhysicalDevice(), compute?VK_QUEUE_COMPUTE_BIT : VK_QUEUE_GRAPHICS_BIT); dev.CreateDevice(context); int width = 1920; int height = 1080; // todo: reconstruction frames source resolution may be lower (another way doing foveated rendering) // this might be useful, because foveated stream high-frequency part is much detailed // on edges and may increase bandwidth to much int rwidth_out = 1920; // fov int rheight_out = 1080; // fov int rwidth_in = 1920/2; int rheight_in = 1080/2; SetupReconstructionImages(dev, VK_FORMAT_BC1_RGB_UNORM_BLOCK, rwidth_in, rheight_in); CreateWindow("demo", width, height, false); CreateSwapchain(width,height, !compute); printf("%d swapchain images\n", numSwapchainImages); dev.CreateAndMap(uboBuf, sizeof(UBO)); SetupFFMpeg(context.instance, dev, rwidth_in, rheight_in, true); for(int i = 0; i < RECONSTRUCTION_TARGET_FRAMES; i++) { VkSemaphoreCreateInfo semaphoreCreateInfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; VK_CHECK_RESULT(vkCreateSemaphore(dev.device, &semaphoreCreateInfo, nullptr, &reconstructionSemaphore[i])); CallWith(Image2dInfo(VK_IMAGE_USAGE_STORAGE_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT|VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_FORMAT_B8G8R8A8_UNORM, rwidth_out, rheight_out), vkCreateImage(dev.device, &ref,NULL, &reconstructionImages[i])); VkMemoryRequirements mem_reqs; vkGetImageMemoryRequirements(dev.device, reconstructionImages[i], &mem_reqs); VkMemoryAllocateInfo mem_alloc = AllocateInfo(mem_reqs.size); if (!dev.GetMemoryType(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex)) printf("Could not find memory type.\n"); VK_CHECK_RESULT(vkAllocateMemory(dev.device, &mem_alloc, NULL, &reconstructionImagesMem[i])); VK_CHECK_RESULT(vkBindImageMemory(dev.device, reconstructionImages[i], reconstructionImagesMem[i], 0)); } if(compute) { computePipeline.Init(dev.device, swapchainFbs[0].render_pass, &gFF.ycbcr_sampler, numSwapchainImages, MAX_DECODER_FRAMES, RECONSTRUCTION_TARGET_FRAMES, rwidth_in, rheight_in, width, height); for(int i = 0; i < numSwapchainImages; i++) { for(int j = 0; j < MAX_DECODER_FRAMES; j++) { int ci = MAX_DECODER_FRAMES * i + j; swapchainDescriptorSets[ci] = computePipeline.AllocateSingleDescriptorSet(); // todo: reconstruction sources are incorrect here! computePipeline.UpdateDescriptors(swapchainDescriptorSets[ci], gFF.images[j].image_view, gFF.ycbcr_sampler, gReconstruction.sources[i].image_view, swapchainFbs[i].color_view, gReconstruction.sources[i].image_sampler); } } for(int i = 0; i < RECONSTRUCTION_TARGET_FRAMES; i++) { CallWith($M(VkImageViewCreateInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}, $(image) = reconstructionImages[i], $(viewType) = VK_IMAGE_VIEW_TYPE_2D, $(format) = VK_FORMAT_B8G8R8A8_UNORM, $(subresourceRange) = SubresourceRange()), vkCreateImageView(dev.device, &ref, NULL, &reconstructionFbs[i].color_view)); for(int j = 0; j < MAX_DECODER_FRAMES; j++) { int ci = MAX_DECODER_FRAMES * i + j; reconstructionDescriptorSets[ci] = computePipeline.AllocateSingleDescriptorSet(); computePipeline.UpdateDescriptors(reconstructionDescriptorSets[ci], gFF.images[j].image_view, gFF.ycbcr_sampler, gReconstruction.sources[i].image_view, reconstructionFbs[i].color_view, gReconstruction.sources[i].image_sampler); } } } else { graphicsPipeline.Init(dev.device, swapchainFbs[0].render_pass, &gFF.ycbcr_sampler, MAX_DECODER_FRAMES); for(int j = 0; j < MAX_DECODER_FRAMES; j++) { decodeDescriptorSet[j] = graphicsPipeline.AllocateSingleDescriptorSet(); graphicsPipeline.UpdateDescriptors(decodeDescriptorSet[j], gFF.images[j].image_view, gFF.ycbcr_sampler); } for(int i = 0; i < RECONSTRUCTION_TARGET_FRAMES; i++) { reconstructionFbs[i].Create(dev.device); reconstructionFbs[i].CreateDepthAttachment(dev,VK_FORMAT_D32_SFLOAT, rwidth_out, rheight_out); reconstructionFbs[i].Init(reconstructionImages[i],VK_FORMAT_B8G8R8A8_UNORM, rwidth_out, rheight_out); } } for(int i = 0; i < numSwapchainImages; i++) { for(int j = 0; j < MAX_DECODER_FRAMES; j++) { int ci = MAX_DECODER_FRAMES * i + j; commandBuffers[ci] = dev.CreateCommandBuffer(); //VulkanTexture::SetImageLayout(commandBuffers[i], swapchainImages[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); //vkCmdClearColorImage(commandBuffers[i], swapchainImages[i],VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, &color, 1, &range ); if(!compute) { swapchainFbs[i].BeginRenderPass(commandBuffers[ci]); swapchainFbs[i].SetViewportAndScissor(commandBuffers[ci]); vkCmdBindDescriptorSets(commandBuffers[ci],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline.pipelineLayout, 0, 1, &decodeDescriptorSet[j], 0, nullptr); vkCmdBindPipeline(commandBuffers[ci], VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline.pipeline); // VkDeviceSize offset = 0; //vkCmdBindVertexBuffers(commandBuffers[i],0, 1, &stagingVert.buffer, &offset); //vkCmdBindIndexBuffer(commandBuffers[i], stagingInd.buffer, 0, VK_INDEX_TYPE_UINT32); //vkCmdDrawIndexed(commandBuffers[i], 3, 1, 0, 0, 0); vkCmdDraw(commandBuffers[ci], 4, 1, 0, 0); vkCmdEndRenderPass(commandBuffers[ci]); VulkanTexture::SetImageLayout(commandBuffers[ci], swapchainImages[i], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); } else { const int WIDTH = rwidth_in; const int HEIGHT = rheight_in; const int WORKGROUP_SIZE = 32; // Workgroup size in compute shader. VulkanTexture::SetImageLayout(commandBuffers[ci], swapchainImages[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); vkCmdBindPipeline(commandBuffers[ci], VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.pipeline); vkCmdBindDescriptorSets(commandBuffers[ci], VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.pipelineLayout, 0, 1, &swapchainDescriptorSets[ci], 0, NULL); vkCmdDispatch(commandBuffers[ci], (uint32_t)ceil(WIDTH / float(WORKGROUP_SIZE)), (uint32_t)ceil(HEIGHT / float(WORKGROUP_SIZE)), 1); VulkanTexture::SetImageLayout(commandBuffers[ci], swapchainImages[i], VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); } vkEndCommandBuffer(commandBuffers[ci]); } VK_CHECK_RESULT(CallWith(FenceInfo(VK_FENCE_CREATE_SIGNALED_BIT), vkCreateFence(dev.device, &ref, NULL, &chainFences[i]))); } for(int i = 0; i < numSwapchainImages; i++) { for(int j = 0; j < RECONSTRUCTION_TARGET_FRAMES; j++) { int ci = RECONSTRUCTION_TARGET_FRAMES*i+j; presentCommandBuffers[ci] = dev.CreateCommandBuffer(); //VulkanTexture::SetImageLayout(presentCommandBuffers[ci], reconstructionImages[j], VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); VulkanTexture::SetImageLayout(presentCommandBuffers[ci], swapchainImages[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); VkImageCopy regions = $M(VkImageCopy{}, $(srcSubresource) = SubresourceLayers(), $(dstSubresource) = SubresourceLayers(), $(extent) = VkExtent3D{(unsigned int)rwidth_out, (unsigned int)rheight_out, 1}); vkCmdCopyImage(presentCommandBuffers[ci], reconstructionImages[j],VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapchainImages[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ions); VulkanTexture::SetImageLayout(presentCommandBuffers[ci], swapchainImages[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); vkEndCommandBuffer(presentCommandBuffers[ci]); } } for(int i = 0; i < RECONSTRUCTION_TARGET_FRAMES; i++) { for(int j = 0; j < MAX_DECODER_FRAMES; j++) { int ci = RECONSTRUCTION_TARGET_FRAMES*i+j; reconstructionCommandBuffers[ci] = dev.CreateCommandBuffer(); if(!compute) { VulkanTexture::SetImageLayout(reconstructionCommandBuffers[ci], reconstructionImages[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); reconstructionFbs[i].BeginRenderPass(reconstructionCommandBuffers[ci]); reconstructionFbs[i].SetViewportAndScissor(reconstructionCommandBuffers[ci]); vkCmdBindDescriptorSets(reconstructionCommandBuffers[ci],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline.pipelineLayout, 0, 1, &decodeDescriptorSet[j], 0, nullptr); vkCmdBindPipeline(reconstructionCommandBuffers[ci], VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline.pipeline); // VkDeviceSize offset = 0; //vkCmdBindVertexBuffers(commandBuffers[i],0, 1, &stagingVert.buffer, &offset); //vkCmdBindIndexBuffer(commandBuffers[i], stagingInd.buffer, 0, VK_INDEX_TYPE_UINT32); //vkCmdDrawIndexed(commandBuffers[i], 3, 1, 0, 0, 0); vkCmdDraw(reconstructionCommandBuffers[ci], 4, 1, 0, 0); vkCmdEndRenderPass(reconstructionCommandBuffers[ci]); VulkanTexture::SetImageLayout(reconstructionCommandBuffers[ci], reconstructionImages[i], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); } else { const int WIDTH = rwidth_in; const int HEIGHT = rheight_in; const int WORKGROUP_SIZE = 32; // Workgroup size in compute shader. //VulkanTexture::SetImageLayout(reconstructionCommandBuffers[ci], swapchainImages[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); VulkanTexture::SetImageLayout(reconstructionCommandBuffers[ci], reconstructionImages[i], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); vkCmdBindPipeline(reconstructionCommandBuffers[ci], VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.pipeline); vkCmdBindDescriptorSets(reconstructionCommandBuffers[ci], VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.pipelineLayout, 0, 1, &reconstructionDescriptorSets[ci], 0, NULL); vkCmdDispatch(reconstructionCommandBuffers[ci], (uint32_t)ceil(WIDTH / float(WORKGROUP_SIZE)), (uint32_t)ceil(HEIGHT / float(WORKGROUP_SIZE)), 1); VulkanTexture::SetImageLayout(reconstructionCommandBuffers[ci], reconstructionImages[i], VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); } vkEndCommandBuffer(reconstructionCommandBuffers[ci]); } } uint32_t idx = 0; int sem_idx = 0; int ridx = 0; while(true) { sem_idx = (sem_idx + 1) % numSwapchainImages; waitFence(sem_idx); idx = AcquireImage(sem_idx); int decoder_idx = WaitActiveFrame() & 3; VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; vkResetFences(dev.device,1, &chainFences[sem_idx]); if(separateReconstruction) { ridx = ReadReconstructionFrame() & 3; CallWith( SubmitInfo( reconstructionCommandBuffers[ridx * MAX_DECODER_FRAMES + decoder_idx],$(waitSemaphoreCount), $(signalSemaphoreCount), $(pWaitSemaphores) = &swapchainRenderSemaphore[sem_idx], $(pSignalSemaphores) = &reconstructionSemaphore[ridx], $(pWaitDstStageMask) = &waitDstStageMask), vkQueueSubmit(dev.defaultQueue, 1, &ref, NULL)); CallWith( SubmitInfo( presentCommandBuffers[idx * RECONSTRUCTION_TARGET_FRAMES + ridx],$(waitSemaphoreCount), $(signalSemaphoreCount), $(pWaitSemaphores) = &reconstructionSemaphore[ridx], $(pSignalSemaphores) = &swapchainPresentSemaphore[idx], $(pWaitDstStageMask) = &waitDstStageMask), vkQueueSubmit(dev.defaultQueue, 1, &ref, chainFences[sem_idx])); //ridx = (ridx + 1) & 3; } else { CallWith( SubmitInfo( commandBuffers[idx*MAX_DECODER_FRAMES + decoder_idx],$(waitSemaphoreCount), $(signalSemaphoreCount), $(pWaitSemaphores) = &swapchainRenderSemaphore[sem_idx], $(pSignalSemaphores) = &swapchainPresentSemaphore[idx], $(pWaitDstStageMask) = &waitDstStageMask), vkQueueSubmit(dev.defaultQueue, 1, &ref, chainFences[sem_idx])); } PresentImage(idx); } vkDeviceWaitIdle(dev.device); graphicsPipeline.Destroy(); stagingVert.Destroy(); stagingInd.Destroy(); uboBuf.Destroy(); DestroySwapchain(); DestroyWindow(); dev.Destroy(); context.Destroy(); } }; int main() { GraphicsApplication app; app.Run(); return EXIT_SUCCESS; }