diff --git a/src/render/render.cpp b/src/render/render.cpp index 00cddba..8243d77 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -1595,113 +1595,211 @@ PlumageRender::PlumageRender() void PlumageRender::writeImageToFile(std::string filePath) { - bool surpportBlit = true; + bool screenshotSaved = false; + bool supportsBlit = true; - + // Check blit support for source and destination + VkFormatProperties formatProps; - char* imageData; - // create dst image to copy - VkImageCreateInfo imageCreateInfo(vks::initializers::imageCreateInfo()); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageCreateInfo.extent.width = width; - imageCreateInfo.extent.height = height; - imageCreateInfo.extent.depth = 1; - imageCreateInfo.arrayLayers = 1; - imageCreateInfo.mipLevels = 1; - imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; - imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + // Check if the device supports blitting from optimal images (the swapchain images are in optimal format) + vkGetPhysicalDeviceFormatProperties(physicalDevice, swapChain.colorFormat, &formatProps); + if (!(formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) { + std::cerr << "Device does not support blitting from optimal tiled images, using copy instead of blit!" << std::endl; + supportsBlit = false; + } + // Check if the device supports blitting to linear images + vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_R8G8B8A8_UNORM, &formatProps); + if (!(formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) { + std::cerr << "Device does not support blitting to linear tiled images, using copy instead of blit!" << std::endl; + supportsBlit = false; + } + + // Source for the copy is the last rendered swapchain image + VkImage srcImage = swapChain.images[currentBuffer]; + + // Create the linear tiled destination image to copy to and to read the memory from + VkImageCreateInfo imageCreateCI(vks::initializers::imageCreateInfo()); + imageCreateCI.imageType = VK_IMAGE_TYPE_2D; + // Note that vkCmdBlitImage (if supported) will also do format conversions if the swapchain color format would differ + imageCreateCI.format = VK_FORMAT_R8G8B8A8_UNORM; + imageCreateCI.extent.width = width; + imageCreateCI.extent.height = height; + imageCreateCI.extent.depth = 1; + imageCreateCI.arrayLayers = 1; + imageCreateCI.mipLevels = 1; + imageCreateCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateCI.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateCI.tiling = VK_IMAGE_TILING_LINEAR; + imageCreateCI.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + // Create the image VkImage dstImage; - VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &dstImage)); - - // get memory ready to store the image + VK_CHECK_RESULT(vkCreateImage(device, &imageCreateCI, nullptr, &dstImage)); + // Create memory to back up the image VkMemoryRequirements memRequirements; VkMemoryAllocateInfo memAllocInfo(vks::initializers::memoryAllocateInfo()); VkDeviceMemory dstImageMemory; vkGetImageMemoryRequirements(device, dstImage, &memRequirements); memAllocInfo.allocationSize = memRequirements.size; - memAllocInfo.memoryTypeIndex = getMemoryTypeIndex(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - - // allocate and bind memory + // Memory must be host visible to copy from + memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory)); VK_CHECK_RESULT(vkBindImageMemory(device, dstImage, dstImageMemory, 0)); - // blit image and copy to host visualable memory - VkCommandBufferAllocateInfo cmdBufferAllocInfo; - VkCommandBuffer copyCmd; - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufferAllocInfo, ©Cmd)); - VkCommandBufferBeginInfo cmdBufferBeginInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufferBeginInfo)); + // Do the actual blit from the swapchain image to our host visible destination image + VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - vks::tools::insertImageMemoryBarrier(copyCmd, dstImage, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1 }); + // Transition destination image to transfer destination layout + vks::tools::insertImageMemoryBarrier( + copyCmd, + dstImage, + 0, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); - 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; + // Transition swapchain image from present to transfer source layout + vks::tools::insertImageMemoryBarrier( + copyCmd, + srcImage, + VK_ACCESS_MEMORY_READ_BIT, + VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); - vkCmdCopyImage(copyCmd, offscreen.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopyRegion); + // If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB) + if (supportsBlit) + { + // Define the region to blit (we will blit the whole swapchain image) + VkOffset3D blitSize; + blitSize.x = width; + blitSize.y = height; + blitSize.z = 1; + VkImageBlit imageBlitRegion{}; + imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlitRegion.srcSubresource.layerCount = 1; + imageBlitRegion.srcOffsets[1] = blitSize; + imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlitRegion.dstSubresource.layerCount = 1; + imageBlitRegion.dstOffsets[1] = blitSize; - // transition dst image to general layout for map memory - vks::tools::insertImageMemoryBarrier(copyCmd, dstImage, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,VkImageSubresourceRange{VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}); + // Issue the blit command + vkCmdBlitImage( + copyCmd, + srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &imageBlitRegion, + VK_FILTER_NEAREST); + } + else + { + // 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; - VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); + // Issue the copy command + vkCmdCopyImage( + copyCmd, + srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &imageCopyRegion); + } - submitWork(copyCmd, queue); + // Transition destination image to general layout, which is the required layout for mapping the image memory later on + vks::tools::insertImageMemoryBarrier( + copyCmd, + dstImage, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_MEMORY_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); - // Get layout of the image - VkImageSubresource subResource{}; - subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + // Transition back the swap chain image after the blit is done + vks::tools::insertImageMemoryBarrier( + copyCmd, + srcImage, + VK_ACCESS_TRANSFER_READ_BIT, + VK_ACCESS_MEMORY_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); + + vulkanDevice->flushCommandBuffer(copyCmd, queue); + + // Get layout of the image (including row pitch) + VkImageSubresource subResource{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 }; VkSubresourceLayout subResourceLayout; - vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout); - // Map image memory - vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&imageData); - - imageData += subResourceLayout.offset; + // Map image memory so we can start copying from it + const char* data; + vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data); + data += subResourceLayout.offset; std::ofstream file(filePath, std::ios::out | std::ios::binary); // ppm header file << "P6\n" << width << "\n" << height << "\n" << 255 << "\n"; - std::vector formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM }; - const bool colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), VK_FORMAT_R8G8B8A8_UNORM) != formatsBGR.end()); + // If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components + bool colorSwizzle = false; + // Check if source is BGR + // Note: Not complete, only contains most common and basic BGR surface formats for demonstration purposes + if (!supportsBlit) + { + std::vector formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM }; + colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), swapChain.colorFormat) != formatsBGR.end()); + } // ppm binary pixel data - for (int32_t y = 0; y < height; y++) { - unsigned int* row = (unsigned int*)imageData; - for (int32_t x = 0; x < width; x++) { - if (colorSwizzle) { + for (uint32_t y = 0; y < height; y++) + { + unsigned int* row = (unsigned int*)data; + for (uint32_t x = 0; x < width; x++) + { + if (colorSwizzle) + { file.write((char*)row + 2, 1); file.write((char*)row + 1, 1); file.write((char*)row, 1); } - else { + else + { file.write((char*)row, 3); } row++; } - imageData += subResourceLayout.rowPitch; + data += subResourceLayout.rowPitch; } file.close(); - std::cout << "Framebuffer image saved to " << filePath << std::endl; + std::cout << "Screenshot saved to disk" << std::endl; // Clean up resources vkUnmapMemory(device, dstImageMemory); vkFreeMemory(device, dstImageMemory, nullptr); vkDestroyImage(device, dstImage, nullptr); - vkQueueWaitIdle(queue); + screenshotSaved = true; }