/* * Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de * Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #ifdef VULKAN #include #include #else #include #endif // #ifdef VULKAN #include #include #include #include #include #ifndef VULKAN #include "wfd_nvsci.h" #include #include "pipeline_cache.hpp" #include "utils.h" #endif // #ifndef VULKAN #define LOG(fn) if (err == VK_SUCCESS) std::cout << fn << " OK" << std::endl; \ else std::cout << fn << " ERROR!!" << std::endl; \ assert(err == VK_SUCCESS); #ifndef VULKAN struct VkSCSciBufInterface { PFN_vkGetPhysicalDeviceSciBufAttributesNV fpGetPhysicalDeviceSciBufAttributesNV; PFN_vkGetMemorySciBufNV fpGetMemorySciBufNV; } vkscSciBufInterface; static PFN_vkGetDeviceProcAddr g_gdpa = NULL; #define ERR_EXIT(err_msg, err_class) \ do { \ printf("%s\n", err_msg); \ fflush(stdout); \ exit(1); \ } while (0) #define GET_DEVICE_PROC_ADDR(dev, entrypoint) \ { \ if (!g_gdpa) \ g_gdpa = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr( \ instance, "vkGetDeviceProcAddr"); \ vkscSciBufInterface.fp##entrypoint = \ (PFN_vk##entrypoint)g_gdpa(dev, "vk" #entrypoint); \ if (vkscSciBufInterface.fp##entrypoint == NULL) { \ ERR_EXIT("vkGetDeviceProcAddr failed to find vk" #entrypoint, \ "vkGetDeviceProcAddr Failure"); \ } \ } #define GET_INSTANCE_DEVICE_PROC_ADDR(entrypoint) \ { \ vkscSciBufInterface.fp##entrypoint = \ (PFN_vk##entrypoint)vkGetInstanceProcAddr(instance, "vk" #entrypoint); \ if (vkscSciBufInterface.fp##entrypoint == NULL) { \ ERR_EXIT("vkGetInstanceProcAddr failed to find vk" #entrypoint, \ "vkGetInstanceProcAddr Failure"); \ } \ } #endif // #ifndef VULKAN static VkInstance instance; static VkRenderPass renderPass; static VkCommandBuffer drawCmdBuffer; static VkCommandBuffer copyCmdBuffer; static VkCommandPool cmdPool; static VkFramebuffer frameBuffer; static VkDeviceMemory offscreenImageMem; static VkDeviceMemory copyImageMem; static VkImage offscreenImage; static VkImage copyImage; static VkImageView offscreenImageView; static VkPipeline pipeline; static VkPipelineLayout pipelineLayout; static int width = 1920; static int height = 1080; static VkQueue queue = VK_NULL_HANDLE; static VkDevice dev = VK_NULL_HANDLE; static std::vector physdevs; static VkPipelineShaderStageCreateInfo stageInfo[2] = { { }, { } }; static VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM; static VkPhysicalDeviceMemoryProperties deviceMemoryProperties; static VkPhysicalDeviceProperties deviceProperties; static bool outputImageFile = false; // VKSC specific stuff: #ifndef VULKAN static size_t startCacheSize = 0; static void* startCacheData = nullptr; static VkDeviceObjectReservationCreateInfo devObjectResCreateInfo; static VkPipelineOfflineCreateInfo pipelineOfflineCreateInfo; static VkPipelineCache pipelineCache; static VkPipelineCacheCreateInfo pipelineCacheCreateInfo; static VkPipelineCacheCreateInfo* pPipelineCacheCreateInfos = &pipelineCacheCreateInfo; static uint32_t pipelineCacheCreateInfoCount = 1; static bool useExternalCache = false; static VkDeviceSize const PIPELINE_SIZE = 1024U * 1024U; static VkPipelinePoolSize pipelinePoolSizes {VK_STRUCTURE_TYPE_PIPELINE_POOL_SIZE, nullptr, PIPELINE_SIZE, 1}; // nvsci stuff: static NvSciBufModule sciBufModule = nullptr; static NvSciBufObj sciBufObj = nullptr; // openwfd stuff static bool enableWfd = false; #else static const char* jsonGenLayerName = "VK_LAYER_KHRONOS_json_gen"; VkResult checkJsonLayerSupport(); #endif // #ifndef VULKAN static uint32_t getMemoryType(uint32_t typeBits, VkFlags properties) { for (uint32_t i = 0; i < 32; i++) { if ((typeBits & 1) == 1) { if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { return i; } } typeBits >>= 1; } // todo : throw error return 0; } #ifndef VULKAN static VkResult initNvSci() { GET_INSTANCE_DEVICE_PROC_ADDR(GetPhysicalDeviceSciBufAttributesNV); GET_DEVICE_PROC_ADDR(dev, GetMemorySciBufNV); NvSciError err = NvSciBufModuleOpen(&sciBufModule); if (err != NvSciError_Success) return VK_ERROR_UNKNOWN; return VK_SUCCESS; } static VkResult getAttrListFromVkImage(const VkImageCreateInfo& imageInfo, const VkMemoryRequirements& memReqs, NvSciBufAttrList attrList) { // Check the alignment has to be power of 2 assert(__builtin_popcountl(memReqs.alignment) == 1); NvSciBufType bufType = NvSciBufType_Image; NvSciBufAttrValAccessPerm perm = NvSciBufAccessPerm_ReadWrite; // Controlled by application NvSciBufAttrValImageLayoutType layout = imageInfo.tiling == VK_IMAGE_TILING_OPTIMAL ? NvSciBufImage_BlockLinearType : NvSciBufImage_PitchLinearType; NvSciBufAttrValImageScanType planescantype = NvSciBufScan_ProgressiveType; uint32_t planeCount = 1; NvSciBufAttrValColorFmt colorFormat = NvSciColor_A8R8G8B8; // TODO: make a conversion table uint32_t height = imageInfo.extent.height; uint32_t width = imageInfo.extent.width; bool needCpuAccess = memReqs.memoryTypeBits & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; NvSciBufAttrKeyValuePair pairArray[] = { { NvSciBufImageAttrKey_Layout, (void*) &layout, sizeof(layout) }, { NvSciBufImageAttrKey_PlaneCount, (void*) &planeCount, sizeof(planeCount) }, { NvSciBufImageAttrKey_PlaneColorFormat, (void*) &colorFormat, sizeof(colorFormat) }, { NvSciBufImageAttrKey_PlaneHeight, (void*) &height, sizeof(height) }, { NvSciBufImageAttrKey_PlaneWidth, (void*) &width, sizeof(width) }, { NvSciBufImageAttrKey_ScanType, (void*) &planescantype, sizeof(planescantype) }, { NvSciBufGeneralAttrKey_Types, (void*) &bufType, sizeof(bufType) }, { NvSciBufGeneralAttrKey_NeedCpuAccess, (void*) &needCpuAccess, sizeof(needCpuAccess) }, { NvSciBufGeneralAttrKey_RequiredPerm, (void*) &perm, sizeof(perm) }, }; // Application fills the public key-value pairs NvSciError err = NvSciBufAttrListSetAttrs(attrList, pairArray, sizeof(pairArray)/sizeof(NvSciBufAttrKeyValuePair)); if (err != NvSciError_Success) return VK_ERROR_UNKNOWN; // Get GPU id VkResult res = vkscSciBufInterface.fpGetPhysicalDeviceSciBufAttributesNV(physdevs[0], attrList); if (res != VK_SUCCESS) return VK_ERROR_INITIALIZATION_FAILED; return VK_SUCCESS; } static void displayImage() { WFDResources wfdRes; wfdRes.WfdInit(width, height); wfdRes.BindNvSciBufObj(&sciBufObj, 1); // Display on scrren for 500 frames for (uint32_t i = 0U; i < 500; i++) { wfdRes.FlipSubmit(0); } printf("Display image with OpenWFD\n"); } static VkResult setupNvSciBufVkDeviceMemory(VkImageCreateInfo imageInfo, VkMemoryRequirements memReqs, VkDeviceMemory* outMemory) { printf("vkimage mem reqs, type: %u, align = %lu, size = %lu\n", memReqs.memoryTypeBits, memReqs.alignment, memReqs.size); // setup NvSciBufAttrList NvSciBufAttrList unreconciledList[2], reconciledList, conflictList; NvSciBufAttrListCreate(sciBufModule, &unreconciledList[0]); NvSciBufAttrListCreate(sciBufModule, &unreconciledList[1]); VkResult err = getAttrListFromVkImage(imageInfo, memReqs, unreconciledList[0]); assert(err == VK_SUCCESS); bool ret = WFDResources::InitializeRGB(width, height, unreconciledList[1], imageInfo.tiling == VK_IMAGE_TILING_LINEAR ? NvSciBufImage_PitchLinearType : NvSciBufImage_BlockLinearType); assert(ret == true); NvSciError sciErr = NvSciBufAttrListReconcile(unreconciledList, 2, &reconciledList, &conflictList); assert(sciErr == NvSciError_Success); NvSciBufAttrListFree(unreconciledList[0]); NvSciBufAttrListFree(unreconciledList[1]); NvSciBufObjAlloc(reconciledList, &sciBufObj); // Update allocation size NvSciBufAttrKeyValuePair pairArray[] = { { NvSciBufImageAttrKey_Size, nullptr, 0 }, { NvSciBufImageAttrKey_Alignment, nullptr, 0 }, { NvSciBufImageAttrKey_PlanePitch, nullptr, 0 } }; NvSciBufAttrListGetAttrs(reconciledList, pairArray, sizeof(pairArray) / sizeof(NvSciBufAttrKeyValuePair)); memReqs.size = *static_cast(pairArray[0].value); memReqs.alignment = *static_cast(pairArray[1].value); uint64_t pitch = *static_cast(pairArray[2].value); printf("NvSciBuf image size = %lu, alignement = %lu, pitch = %lu\n", memReqs.size, memReqs.alignment, pitch); VkImportMemorySciBufInfoNV importSciBufInfo = { .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_SCI_BUF_INFO_NV, .pNext = nullptr, .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_SCI_BUF_BIT_NV, .handle = sciBufObj }; VkMemoryAllocateInfo memAllocInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &importSciBufInfo, .allocationSize = 0, // ignore the allocate size when importing NvSciBufObj .memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) }; err = vkAllocateMemory(dev, &memAllocInfo, nullptr, outMemory); assert(err == VK_SUCCESS); return err; } static VkResult fillDeviceObjResInfo() { devObjectResCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_OBJECT_RESERVATION_CREATE_INFO, devObjectResCreateInfo.pNext = nullptr; devObjectResCreateInfo.pipelineCacheCreateInfoCount = pipelineCacheCreateInfoCount; devObjectResCreateInfo.pPipelineCacheCreateInfos = pPipelineCacheCreateInfos; devObjectResCreateInfo.pipelinePoolSizeCount = 0; devObjectResCreateInfo.pPipelinePoolSizes = 0; devObjectResCreateInfo.semaphoreRequestCount = 0; devObjectResCreateInfo.commandBufferRequestCount = 4; devObjectResCreateInfo.fenceRequestCount = 0; devObjectResCreateInfo.deviceMemoryRequestCount = 2; devObjectResCreateInfo.bufferRequestCount = 0; devObjectResCreateInfo.imageRequestCount = 2; devObjectResCreateInfo.eventRequestCount = 0; devObjectResCreateInfo.queryPoolRequestCount = 0; devObjectResCreateInfo.bufferViewRequestCount = 0; devObjectResCreateInfo.imageViewRequestCount = 1; devObjectResCreateInfo.layeredImageViewRequestCount = 0; devObjectResCreateInfo.pipelineCacheRequestCount = 1; devObjectResCreateInfo.pipelineLayoutRequestCount = 1; devObjectResCreateInfo.renderPassRequestCount = 1; devObjectResCreateInfo.graphicsPipelineRequestCount = 1; devObjectResCreateInfo.computePipelineRequestCount = 0; devObjectResCreateInfo.descriptorSetLayoutRequestCount = 0; devObjectResCreateInfo.samplerRequestCount = 0; devObjectResCreateInfo.descriptorPoolRequestCount = 0; devObjectResCreateInfo.descriptorSetRequestCount = 0; devObjectResCreateInfo.framebufferRequestCount = 1; devObjectResCreateInfo.commandPoolRequestCount = 1; devObjectResCreateInfo.samplerYcbcrConversionRequestCount = 0; devObjectResCreateInfo.surfaceRequestCount = 0; devObjectResCreateInfo.swapchainRequestCount = 0; devObjectResCreateInfo.displayModeRequestCount = 0; devObjectResCreateInfo.subpassDescriptionRequestCount = 0; devObjectResCreateInfo.attachmentDescriptionRequestCount = 0; devObjectResCreateInfo.descriptorSetLayoutBindingLimit = 0; devObjectResCreateInfo.maxImageViewMipLevels = 0; devObjectResCreateInfo.maxImageViewArrayLayers = 0; devObjectResCreateInfo.maxLayeredImageViewMipLevels = 0; devObjectResCreateInfo.maxOcclusionQueriesPerPool = 0; devObjectResCreateInfo.maxPipelineStatisticsQueriesPerPool = 0; devObjectResCreateInfo.maxTimestampQueriesPerPool = 0; devObjectResCreateInfo.pipelinePoolSizeCount = 1; devObjectResCreateInfo.pPipelinePoolSizes = &pipelinePoolSizes; return VK_SUCCESS; } // We have different binaries for different platforms. static void getCacheFile() { switch (deviceProperties.deviceID) { case 0xA5BA03D7: // gv11b startCacheData = (void*)cache_file_gv11b; startCacheSize = cache_size_gv11b; break; case 0x97BA03D7: // ga10b startCacheData = (void*)cache_file_ga10b; startCacheSize = cache_size_ga10b; break; case 0x97EA03D7: // ga10f startCacheData = (void*)cache_file_ga10f; startCacheSize = cache_size_ga10f; break; default: assert(!"Invalid chip"); break; } } static void loadPipelineCaches() { std::string readFileName("data/pipeline/vksc_01tri/pipeline_cache.bin"); std::cout << " Trying to read cache from: " << readFileName << std::endl; FILE *pReadFile = useExternalCache ? fopen(readFileName.c_str(), "rb") : nullptr; if (pReadFile) { fseek(pReadFile, 0, SEEK_END); startCacheSize = ftell(pReadFile); rewind(pReadFile); startCacheData = (char *)malloc(sizeof(char) * startCacheSize); if (startCacheData == nullptr) { fputs("Memory error", stderr); exit(EXIT_FAILURE); } size_t result = fread(startCacheData, 1, startCacheSize, pReadFile); if (result != startCacheSize) { fputs("Reading error", stderr); free(startCacheData); exit(EXIT_FAILURE); } fclose(pReadFile); std::cout << " Pipeline cache HIT!" << std::endl; std::cout << " cacheData loaded from " << readFileName.c_str() << std::endl; } else { std::cout << "Error opening cache file, using embedded binary." << std::endl; getCacheFile(); } pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; pipelineCacheCreateInfo.pNext = NULL; pipelineCacheCreateInfo.initialDataSize = startCacheSize; pipelineCacheCreateInfo.pInitialData = startCacheData; pipelineCacheCreateInfo.flags = VK_PIPELINE_CACHE_CREATE_READ_ONLY_BIT | VK_PIPELINE_CACHE_CREATE_USE_APPLICATION_STORAGE_BIT; } static VkResult createPipelineCaches() { VkResult err = vkCreatePipelineCache(dev, &pipelineCacheCreateInfo, nullptr, &pipelineCache); LOG("vkCreatePipelineCache"); return err; } #else // #ifndef VULKAN VkResult checkJsonLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); for (uint32_t i = 0; i < layerCount; i++) { if (!strncmp(availableLayers[i].layerName, jsonGenLayerName, strlen(jsonGenLayerName))) { return VK_SUCCESS; } } return VK_ERROR_INITIALIZATION_FAILED; } static VkShaderModule loadSPIRVShader(std::string filename) { size_t shaderSize; char* shaderCode = NULL; std::ifstream is(filename, std::ios::binary | std::ios::in | std::ios::ate); if (is.is_open()) { shaderSize = is.tellg(); is.seekg(0, std::ios::beg); // Copy file contents into a buffer shaderCode = new char[shaderSize]; is.read(shaderCode, shaderSize); is.close(); assert(shaderSize > 0); } if (shaderCode) { // Create a new shader module that will be used for pipeline creation VkShaderModuleCreateInfo moduleCreateInfo{}; moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; moduleCreateInfo.codeSize = shaderSize; moduleCreateInfo.pCode = (uint32_t*)shaderCode; VkShaderModule shaderModule; VkResult err = (vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &shaderModule)); LOG("vkCreateShaderModule"); delete[] shaderCode; return shaderModule; } else { std::cerr << "Error: Could not open shader file \"" << filename << "\"" << std::endl; return VK_NULL_HANDLE; } } #endif // #ifndef VULKAN static VkResult CreateShaderStageInfo() { // In VKSC, the shader compilation is offline. stageInfo[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stageInfo[0].stage = VK_SHADER_STAGE_VERTEX_BIT; stageInfo[0].pName = "main"; stageInfo[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stageInfo[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stageInfo[1].pName = "main"; #ifndef VULKAN stageInfo[0].module = VK_NULL_HANDLE; stageInfo[1].module = VK_NULL_HANDLE; #else stageInfo[0].module = loadSPIRVShader("data/shaders/vksc_01tri/vk_01tri_pipeline_0.vert.spv"); stageInfo[1].module = loadSPIRVShader("data/shaders/vksc_01tri/vk_01tri_pipeline_0.frag.spv"); #endif // #ifndef VULKAN return VK_SUCCESS; } static VkResult setupRenderPass() { // No depth attachment in this simple test. VkResult err; VkAttachmentDescription attachments[1] = {}; // Color attachment attachments[0].format = colorFormat; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference colorReference = {}; colorReference.attachment = 0; colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.flags = 0; subpass.inputAttachmentCount = 0; subpass.pInputAttachments = NULL; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorReference; subpass.pResolveAttachments = NULL; subpass.pDepthStencilAttachment = NULL; subpass.preserveAttachmentCount = 0; subpass.pPreserveAttachments = NULL; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.pNext = NULL; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = attachments; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 0; renderPassInfo.pDependencies = NULL; err = vkCreateRenderPass(dev, &renderPassInfo, NULL, &renderPass); LOG("vkCreateRenderPass"); return err; } // We have one offsreen buffer we render into. static VkResult setupRenderBuffer() { VkResult err; VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; #ifdef VULKAN imageInfo.pNext = nullptr; #else VkExternalMemoryImageCreateInfo externalMemInfo = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, .pNext = nullptr, .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_SCI_BUF_BIT_NV }; imageInfo.pNext = &externalMemInfo; #endif // #ifndef VULKAN imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = colorFormat; imageInfo.extent.width = width; imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; err = vkCreateImage(dev, &imageInfo, nullptr, &offscreenImage); assert(err == VK_SUCCESS); VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(dev, offscreenImage, &memReqs); #ifndef VULKAN if (enableWfd) { err = setupNvSciBufVkDeviceMemory(imageInfo, memReqs, &offscreenImageMem); } else #endif // #ifndef VULKAN { VkMemoryAllocateInfo memAllocInfo {}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, &offscreenImageMem); } assert(err == VK_SUCCESS); LOG("vkAllocateMemory"); err = vkBindImageMemory(dev, offscreenImage, offscreenImageMem, 0); LOG("vkBindImageMemory"); VkImageViewCreateInfo colorAttachmentView = {}; colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; colorAttachmentView.pNext = NULL; colorAttachmentView.format = colorFormat; colorAttachmentView.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; colorAttachmentView.subresourceRange.baseMipLevel = 0; colorAttachmentView.subresourceRange.levelCount = 1; colorAttachmentView.subresourceRange.baseArrayLayer = 0; colorAttachmentView.subresourceRange.layerCount = 1; colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D; colorAttachmentView.flags = 0; colorAttachmentView.image = offscreenImage; err = vkCreateImageView(dev, &colorAttachmentView, NULL, &offscreenImageView); LOG("vkCreateImageView"); return err; } static VkResult setupFrameBuffer() { VkResult err; VkImageView attachments[1]; err = setupRenderBuffer(); assert(err == VK_SUCCESS); attachments[0] = offscreenImageView; VkFramebufferCreateInfo frameBufferCreateInfo = {}; frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; frameBufferCreateInfo.pNext = NULL; frameBufferCreateInfo.renderPass = renderPass; frameBufferCreateInfo.attachmentCount = 1; frameBufferCreateInfo.pAttachments = attachments; frameBufferCreateInfo.width = width; frameBufferCreateInfo.height = height; frameBufferCreateInfo.layers = 1; err = vkCreateFramebuffer(dev, &frameBufferCreateInfo, NULL, &frameBuffer); LOG("vkCreateFramebuffer"); return err; } static VkResult buildCommandBuffers() { VkResult err; VkCommandBufferBeginInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; VkClearColorValue clearColor = { { 1.0, 0.0, 0.0, 1.0 } }; VkClearRect rect = { { {0,0}, {(uint32_t)width, (uint32_t)height} }, 0, 1 }; VkClearAttachment clearAttachment = { }; clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; clearAttachment.colorAttachment = 0; clearAttachment.clearValue.color = clearColor; VkClearValue clearValues[2]; clearValues[0].color = { {1.0f, 1.0f, 1.0f, 0.99f} }; clearValues[1].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = {}; renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassBeginInfo.pNext = NULL; renderPassBeginInfo.renderPass = renderPass; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = width; renderPassBeginInfo.renderArea.extent.height = height; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; renderPassBeginInfo.framebuffer = frameBuffer; err = vkBeginCommandBuffer(drawCmdBuffer, &cmdBufInfo); LOG("vkBeginCommandBuffer"); vkCmdBeginRenderPass(drawCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); { // Update dynamic viewport state VkViewport viewport = {}; viewport.height = (float)height; viewport.width = (float)width; viewport.minDepth = (float)0.0f; viewport.maxDepth = (float)1.0f; vkCmdSetViewport(drawCmdBuffer, 0, 1, &viewport); // Update dynamic scissor state VkRect2D scissor = {}; scissor.extent.width = width; scissor.extent.height = height; scissor.offset.x = 0; scissor.offset.y = 0; vkCmdSetScissor(drawCmdBuffer, 0, 1, &scissor); vkCmdBindPipeline(drawCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdClearAttachments(drawCmdBuffer, 1, &clearAttachment, 1, &rect); vkCmdDraw(drawCmdBuffer, 3, 1, 0, 1); } vkCmdEndRenderPass(drawCmdBuffer); err = vkEndCommandBuffer(drawCmdBuffer); LOG("vkEndCommandBuffer"); return err; } static VkResult draw() { VkResult err; VkPipelineStageFlags pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pWaitDstStageMask = &pipelineStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffer; err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); LOG("vkQueueSubmit"); err = vkQueueWaitIdle(queue); LOG("vkQueueWaitIdle"); return err; } static VkResult createCommandPool() { VkResult err; #ifndef VULKAN VkCommandPoolMemoryReservationCreateInfo memReserveInfo{}; memReserveInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_MEMORY_RESERVATION_CREATE_INFO; memReserveInfo.pNext = NULL; memReserveInfo.commandPoolReservedSize = 1536ULL * 1024ULL; // A 1.5 MB placeholder (default) // This value cannot exceed the VkDeviceObjectReservationCreateInfo::commandBufferRequestCount memReserveInfo.commandPoolMaxCommandBuffers = 4U; #endif // #ifndef VULKAN VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; #ifndef VULKAN cmdPoolInfo.pNext = &memReserveInfo; #endif // #ifndef VULKAN cmdPoolInfo.queueFamilyIndex = 0; // TODO: queueNodeIndex; cmdPoolInfo.flags = 0; // VUID-VkCommandPoolCreateInfo-commandPoolResetCommandBuffer-05001 err = vkCreateCommandPool(dev, &cmdPoolInfo, NULL, &cmdPool); return err; } static VkResult createCommandBuffers() { VkResult err; if (createCommandPool() != VK_SUCCESS) { std::cout << "Error creating cmdpool" << std::endl; assert(0); } VkCommandBufferAllocateInfo cmdBufAllocateInfo = {}; cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufAllocateInfo.pNext = NULL; cmdBufAllocateInfo.commandPool = cmdPool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = 1;//(uint32_t) drawCmdBuffers.size(); err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, &drawCmdBuffer); return err; } static VkResult setupPipeline() { VkResult err; VkPipelineLayoutCreateInfo layoutCreateInfo = {}; layoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; err = vkCreatePipelineLayout(dev, &layoutCreateInfo, nullptr, &pipelineLayout); LOG("vkCreatePipelineLayout"); VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineCreateInfo.renderPass = renderPass; pipelineCreateInfo.layout = pipelineLayout; VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {}; inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; // Viewport state VkPipelineViewportStateCreateInfo viewportState = {}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; pipelineCreateInfo.pViewportState = &viewportState; VkPipelineDynamicStateCreateInfo dynamicState = {}; std::vector dynamicStateEnables; dynamicStateEnables.push_back(VK_DYNAMIC_STATE_VIEWPORT); dynamicStateEnables.push_back(VK_DYNAMIC_STATE_SCISSOR); dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStateEnables.data(); dynamicState.dynamicStateCount = static_cast(dynamicStateEnables.size()); pipelineCreateInfo.pDynamicState = &dynamicState; VkPipelineMultisampleStateCreateInfo multisampleState = {}; multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleState.pSampleMask = NULL; multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; pipelineCreateInfo.pMultisampleState = &multisampleState; // Rasterization state VkPipelineRasterizationStateCreateInfo rasterizationState = {}; rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; rasterizationState.cullMode = VK_CULL_MODE_NONE; rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizationState.depthClampEnable = VK_FALSE; rasterizationState.rasterizerDiscardEnable = VK_FALSE; rasterizationState.depthBiasEnable = VK_FALSE; rasterizationState.lineWidth = 1.0f; pipelineCreateInfo.pRasterizationState = &rasterizationState; VkPipelineColorBlendStateCreateInfo colorBlendState = {}; colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; VkPipelineColorBlendAttachmentState blendAttachmentState[1] = {}; blendAttachmentState[0].colorWriteMask = 0xf; blendAttachmentState[0].blendEnable = VK_FALSE; colorBlendState.attachmentCount = 1; colorBlendState.pAttachments = blendAttachmentState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.stageCount = 2; pipelineCreateInfo.pStages = stageInfo; #ifndef VULKAN char graphicsUUID[VK_UUID_SIZE]; err = readPipelineUUID("data/pipeline/vksc_01tri/vk_01tri_pipeline_0.json", graphicsUUID); LOG("readPipelineUUID"); pipelineOfflineCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_OFFLINE_CREATE_INFO; memcpy(pipelineOfflineCreateInfo.pipelineIdentifier, graphicsUUID, VK_UUID_SIZE); // Obtained in json. pipelineOfflineCreateInfo.poolEntrySize = PIPELINE_SIZE; pipelineCreateInfo.pNext = &pipelineOfflineCreateInfo; err = vkCreateGraphicsPipelines(dev, pipelineCache, 1, &pipelineCreateInfo, NULL, &pipeline); #else pipelineCreateInfo.pNext = nullptr; err = vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipelineCreateInfo, NULL, &pipeline); #endif // #ifndef VULKAN LOG("vkCreateGraphicsPipelines"); return err; } static VkResult setupCopyImage() { VkResult err; VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageInfo.extent.width = width; imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_LINEAR; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; err = vkCreateImage(dev, &imageInfo, nullptr, ©Image); assert(err == VK_SUCCESS); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(dev, copyImage, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); err = vkAllocateMemory(dev, &memAllocInfo, nullptr, ©ImageMem); LOG("vkAllocateMemory"); err = vkBindImageMemory(dev, copyImage, copyImageMem, 0); LOG("vkBindImageMemory"); VkCommandBufferAllocateInfo cmdBufAllocateInfo = {}; cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufAllocateInfo.pNext = NULL; cmdBufAllocateInfo.commandPool = cmdPool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = 1; err = vkAllocateCommandBuffers(dev, &cmdBufAllocateInfo, ©CmdBuffer); return err; } static void writeOutPPMFile(uint8_t const * components, int const width, int const height, int const bytesPerPixel) { // Taken from Sascha Willems' screenshot app. Write out ppm file. std::ofstream file("tri.ppm", std::ios::out | std::ios::binary); if (file.fail()) { std::cerr << "Failed to open output file tri.ppm. Skipping file output!" << std::endl; return; } file << "P3\n" << width << " " << height << "\n" << 255 << "\n"; for (int x = 0; x < width * height; x++) { file << (unsigned int)components[2] << " " << (unsigned int)components[1] << " " << (unsigned int)components[0] << "\n"; components += bytesPerPixel; } file.close(); std::cout << "wrote out file." << std::endl; } static VkResult issueCopyImage() { VkResult err; VkCommandBufferBeginInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; err = vkBeginCommandBuffer(copyCmdBuffer, &cmdBufInfo); LOG("vkBeginCommandBuffer copy"); { // Otherwise use image copy (requires us to manually flip components) VkImageCopy imageCopyRegion{}; imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageCopyRegion.srcSubresource.layerCount = 1; imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageCopyRegion.dstSubresource.layerCount = 1; imageCopyRegion.extent.width = width; imageCopyRegion.extent.height = height; imageCopyRegion.extent.depth = 1; // Issue the copy command vkCmdCopyImage( copyCmdBuffer, offscreenImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopyRegion); } err = vkEndCommandBuffer(copyCmdBuffer); LOG("vkEndCommandBuffer copy"); VkPipelineStageFlags pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pWaitDstStageMask = &pipelineStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = ©CmdBuffer; err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); LOG("vkQueueSubmit"); err = vkQueueWaitIdle(queue); LOG("vkQueueWaitIdle"); // We're trying to render to a pitch linear buffer and read it back. uint8_t* imageData {nullptr}; err = vkMapMemory(dev, copyImageMem, 0, VK_WHOLE_SIZE, 0, (void**)&imageData); LOG("vkMapMemory"); if (outputImageFile) { writeOutPPMFile(imageData, width, height, 4); } return err; } static VkResult cleanup() { vkDestroyFramebuffer(dev, frameBuffer, nullptr); vkDestroyRenderPass(dev, renderPass, nullptr); vkDestroyImageView(dev, offscreenImageView, nullptr); vkDestroyImage(dev, offscreenImage, nullptr); vkDestroyImage(dev, copyImage, nullptr); vkDestroyPipelineLayout(dev, pipelineLayout, nullptr); vkDestroyPipeline(dev, pipeline, nullptr); vkFreeCommandBuffers(dev, cmdPool, 1, &drawCmdBuffer); vkFreeCommandBuffers(dev, cmdPool, 1, ©CmdBuffer); #ifndef VULKAN vkDestroyPipelineCache(dev, pipelineCache, nullptr); if (enableWfd) { NvSciBufModuleClose(sciBufModule); NvSciBufObjFree(sciBufObj); } #else vkFreeMemory(dev, offscreenImageMem, nullptr); vkFreeMemory(dev, copyImageMem, nullptr); vkDestroyShaderModule(dev, stageInfo[0].module, nullptr); vkDestroyShaderModule(dev, stageInfo[1].module, nullptr); vkDestroyCommandPool(dev, cmdPool, nullptr); #endif // #ifndef VULKAN vkDestroyDevice(dev, NULL); vkDestroyInstance(instance, NULL); return VK_SUCCESS; } static VkResult createInstanceAndDevice() { VkResult err = VK_SUCCESS; #ifdef VULKAN err = checkJsonLayerSupport(); LOG("checkJsonLayerSupport"); #endif // #ifdef VULKAN //Create a Vulkan instance VkInstanceCreateInfo instanceCreateInfo = {}; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instanceCreateInfo.pNext = NULL; instanceCreateInfo.pApplicationInfo = NULL; instanceCreateInfo.enabledExtensionCount = 0; instanceCreateInfo.ppEnabledExtensionNames = NULL; instanceCreateInfo.enabledLayerCount = 0; instanceCreateInfo.ppEnabledLayerNames = NULL; #ifdef VULKAN VkLayerInstanceLink layerLink = { .pNext = nullptr, .pfnNextGetInstanceProcAddr = nullptr, // this has to be really vulkan entry point .pfnNextGetPhysicalDeviceProcAddr = nullptr }; // enable json_gen layer for instance VkLayerInstanceCreateInfo layerInfo = {}; layerInfo.sType = VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO; layerInfo.function = VK_LAYER_LINK_INFO; layerInfo.u.pLayerInfo = &layerLink; instanceCreateInfo.pNext = &layerInfo; instanceCreateInfo.enabledLayerCount = 1; instanceCreateInfo.ppEnabledLayerNames = &jsonGenLayerName; #endif // #ifdef VULKAN err = vkCreateInstance(&instanceCreateInfo, NULL, &instance); assert(err == VK_SUCCESS); uint32_t physdevCount = 0; err = vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr); assert(err == VK_SUCCESS); #ifdef VULKAN assert(physdevCount > 0); #else assert(physdevCount == 1); #endif physdevs.resize(physdevCount); err = vkEnumeratePhysicalDevices(instance, &physdevCount, physdevs.data()); assert(err == VK_SUCCESS); vkGetPhysicalDeviceProperties(physdevs[0], &deviceProperties); float priority = 1.0f; VkDeviceQueueCreateInfo queueInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, NULL, 0, // flags 0, // queueFamilyIndex 1, // queueCount &priority // pQueuePriorities }; #ifndef VULKAN std::vector extNamesDev; if (enableWfd) { extNamesDev.push_back(VK_NV_EXTERNAL_MEMORY_SCI_BUF_EXTENSION_NAME); } #endif // #ifndef VULKAN VkDeviceCreateInfo deviceInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, NULL, 0, 1, &queueInfo, 0, NULL, #ifndef VULKAN (uint32_t)extNamesDev.size(), extNamesDev.data(), #else 0, nullptr, #endif // #ifndef VULKAN NULL }; #ifndef VULKAN loadPipelineCaches(); err = fillDeviceObjResInfo(); assert(err == VK_SUCCESS); deviceInfo.pNext = &devObjectResCreateInfo; #else // enable json_gen layer for device VkLayerDeviceLink layerDevInfo = {}; VkLayerDeviceCreateInfo devLayerCreateInfo = {}; devLayerCreateInfo.sType = VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO; devLayerCreateInfo.pNext = &layerDevInfo; devLayerCreateInfo.function = VK_LAYER_LINK_INFO; devLayerCreateInfo.u.pLayerInfo = &layerDevInfo; deviceInfo.pNext = &devLayerCreateInfo; deviceInfo.enabledLayerCount = 1; deviceInfo.ppEnabledLayerNames = &jsonGenLayerName; #endif // #ifndef VULKAN err = vkCreateDevice(physdevs[0], &deviceInfo, NULL, &dev); assert(err == VK_SUCCESS); vkGetPhysicalDeviceMemoryProperties(physdevs[0], &deviceMemoryProperties); vkGetDeviceQueue(dev, 0, 0, &queue); return err; } static void printTestInfo(bool printUsageOnly = false) { std::cout << "\nUsage: \n"; std::cout << "--help / -h : Print information about vksc_01tri\n"; std::cout << "--wfd / -w : Run the test with openwfd enabled (default is to check output against expected hash value)\n"; std::cout << "--cache / -c : Load the cache from tri.bin (default is to load from embedded cache)" << std::endl; std::cout << "--output / -o : The test outputs screenshot of rendered image to a file named tri.ppm, if this flag is passed." << std::endl; } static bool parseCommandLineArgs(int argc, char **argv) { bool earlyExit = false; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { printTestInfo(); earlyExit = true; #ifndef VULKAN } else if (!strcmp(argv[i], "-w") || !strcmp(argv[i], "--wfd")) { enableWfd = true; } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache")) { useExternalCache = true; #endif } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { outputImageFile = true; } else { std::cerr << "\nInvalid arguments specified.\n"; printTestInfo(); } } #ifndef VULKAN if (enableWfd && outputImageFile) { std::cerr << "Both of --output/-o and --wfd/-w flags are set. --wfd takes precendence." << std::endl; printTestInfo(); outputImageFile = false; } #endif // #ifndef VULKAN return earlyExit; } int main(int argc, char** argv) { bool earlyExit = parseCommandLineArgs(argc, argv); if (earlyExit) return 0; VkResult err = VK_SUCCESS; err = createInstanceAndDevice(); assert(err == VK_SUCCESS); #ifndef VULKAN if (enableWfd) { err = initNvSci(); assert(err == VK_SUCCESS); } #endif // #ifndef VULKAN err = setupRenderPass(); assert(err == VK_SUCCESS); err = CreateShaderStageInfo(); assert(err == VK_SUCCESS); err = createCommandBuffers(); assert(err == VK_SUCCESS); err = setupFrameBuffer(); assert(err == VK_SUCCESS); #ifndef VULKAN err = createPipelineCaches(); assert(err == VK_SUCCESS); if (!enableWfd) #endif // #ifndef VULKAN { err = setupCopyImage(); assert(err == VK_SUCCESS); } err = setupPipeline(); assert(err == VK_SUCCESS); err = buildCommandBuffers(); assert(err == VK_SUCCESS); err = draw(); assert(err == VK_SUCCESS); #ifndef VULKAN if(!enableWfd) #endif // #ifndef VULKAN { err = issueCopyImage(); assert(err == VK_SUCCESS); } #ifndef VULKAN if (enableWfd) displayImage(); #endif // #ifndef VULKAN err = cleanup(); assert(err == VK_SUCCESS); if (err != VK_SUCCESS) { return -1; } return 0; }