diff --git a/include/renderer.h b/include/renderer.h index b1edb3d..246ab0a 100644 --- a/include/renderer.h +++ b/include/renderer.h @@ -47,6 +47,8 @@ public: RenderTarget* createSurfaceRenderTarget(VkSurfaceKHR surface, RenderTarget* oldRenderTarget = nullptr); void destroyRenderTarget(RenderTarget* target); + + void takeScreenshot(RenderTarget* target); VkShaderModule createShader(const char* path); diff --git a/src/main.cpp b/src/main.cpp index fdac39a..70315ce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -168,6 +168,10 @@ int main(int, char*[]) { target = renderer->createSurfaceRenderTarget(surface, target); } + + if(event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_F12) { + renderer->takeScreenshot(target); + } } renderer->render(world, target); diff --git a/src/renderer.cpp b/src/renderer.cpp index 54a1db0..8701389 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -151,7 +151,7 @@ RenderTarget* Renderer::createSurfaceRenderTarget(VkSurfaceKHR surface, RenderTa swapchainCreateInfo.imageFormat = surfaceFormats[chosenFormat].format; swapchainCreateInfo.imageExtent = surfaceCapabilities.currentExtent; swapchainCreateInfo.imageArrayLayers = 1; - swapchainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + swapchainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; swapchainCreateInfo.queueFamilyIndexCount = 1; swapchainCreateInfo.pQueueFamilyIndices = &queueIndices.presentation; swapchainCreateInfo.preTransform = surfaceCapabilities.currentTransform; @@ -403,6 +403,210 @@ void Renderer::destroyRenderTarget(RenderTarget* target) { delete target; } +void Renderer::takeScreenshot(RenderTarget* target) { + VkImageCreateInfo imageCreateInfo = {}; + imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageCreateInfo.extent.width = target->extent.width; + imageCreateInfo.extent.height = target->extent.height; + imageCreateInfo.extent.depth = 1; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; + imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + VkImage image = nullptr; + vkCreateImage(device_, &imageCreateInfo, nullptr, &image); + + VkMemoryRequirements memoryRequirements = {}; + vkGetImageMemoryRequirements(device_, image, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + + VkDeviceMemory imageMemory = nullptr; + vkAllocateMemory(device_, &allocateInfo, nullptr, &imageMemory); + vkBindImageMemory(device_, image, imageMemory, 0); + + VkCommandBufferAllocateInfo bufferAllocateInfo = {}; + bufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + bufferAllocateInfo.commandPool = commandPool_; + bufferAllocateInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer = nullptr; + vkAllocateCommandBuffers(device_, &bufferAllocateInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + // change screenshot image to transfer dst layout + { + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageMemoryBarrier.image = image; + imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageMemoryBarrier.subresourceRange.layerCount = 1; + imageMemoryBarrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + } + + const VkImage srcImage = target->swapchainImages[0]; // FIXME: use previous image + + // change offscreen image to transfer src layout + { + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + imageMemoryBarrier.image = srcImage; + imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageMemoryBarrier.subresourceRange.layerCount = 1; + imageMemoryBarrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + } + + VkImageCopy imageCopy = {}; + imageCopy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageCopy.srcSubresource.layerCount = 1; + imageCopy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageCopy.dstSubresource.layerCount = 1; + imageCopy.extent.width = target->extent.width; + imageCopy.extent.height = target->extent.height; + imageCopy.extent.depth = 1; + + // Issue the copy command + vkCmdCopyImage( + commandBuffer, + srcImage, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &imageCopy); + + // change screenshot image from transfer dst to general (for mapping memory) + { + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.image = image; + imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageMemoryBarrier.subresourceRange.layerCount = 1; + imageMemoryBarrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + } + + // change offscreen image layout back to normal + { + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + imageMemoryBarrier.image = srcImage; + imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageMemoryBarrier.subresourceRange.layerCount = 1; + imageMemoryBarrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + } + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkFenceCreateInfo fenceCreateInfo = {}; + fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + VkFence fence = nullptr; + vkCreateFence(device_, &fenceCreateInfo, nullptr, &fence); + + vkQueueSubmit(graphicsQueue_, 1, &submitInfo, fence); + + vkWaitForFences(device_, 1, &fence, true, -1); + vkDestroyFence(device_, fence, nullptr); + + vkFreeCommandBuffers(device_, commandPool_, 1, &commandBuffer); + + VkImageSubresource subResource = {}; + subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + VkSubresourceLayout subResourceLayout = {}; + vkGetImageSubresourceLayout(device_, image, &subResource, &subResourceLayout); + + const char* data = nullptr; + vkMapMemory(device_, imageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data); + data += subResourceLayout.offset; + + std::ofstream file("screenshot.ppm", std::ios::out | std::ios::binary); + file << "P6\n" << target->extent.width << "\n" << target->extent.height << "\n" << 255 << "\n"; + + for(uint32_t y = 0; y < target->extent.height; y++) { + const unsigned int* row = reinterpret_cast(data); + for(uint32_t x = 0; x < target->extent.width; x++) { + file.write(reinterpret_cast(row), 3); + row++; + } + + data += subResourceLayout.rowPitch; + } + + file.close(); + + vkUnmapMemory(device_, imageMemory); + + vkFreeMemory(device_, imageMemory, nullptr); + vkDestroyImage(device_, image, nullptr); +} + VkShaderModule Renderer::createShader(const char* path) { std::ifstream file(path, std::ios::ate | std::ios::binary);