Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions projects/common/vk_renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4149,3 +4149,111 @@ uint32_t BitsPerPixel(VkFormat fmt)
return 0;
}
}

bool SaveVulkanImageAsPNG(
VulkanRenderer* pRenderer,
VkImage image,
uint32_t width,
uint32_t height,
const std::filesystem::path& absPath)
{
const size_t bufferSize = width * height * 4;

// Create a CPU-readable staging buffer
VulkanBuffer readbackBuffer = {};
VkResult vkres = CreateBuffer(
pRenderer,
bufferSize,
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_GPU_TO_CPU,
0,
&readbackBuffer);
if (vkres != VK_SUCCESS)
{
return false;
}

// Create a transient command buffer
CommandObjects cmdBuf = {};
vkres = CreateCommandBuffer(pRenderer, VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, &cmdBuf);
if (vkres != VK_SUCCESS)
{
DestroyBuffer(pRenderer, &readbackBuffer);
return false;
}

VkCommandBufferBeginInfo vkbi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
vkbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkres = vkBeginCommandBuffer(cmdBuf.CommandBuffer, &vkbi);
if (vkres != VK_SUCCESS)
{
DestroyCommandBuffer(pRenderer, &cmdBuf);
DestroyBuffer(pRenderer, &readbackBuffer);
return false;
}

// Transition: PRESENT -> TRANSFER_SRC
CmdTransitionImageLayout(
cmdBuf.CommandBuffer,
image,
GREX_ALL_SUBRESOURCES,
VK_IMAGE_ASPECT_COLOR_BIT,
RESOURCE_STATE_PRESENT,
RESOURCE_STATE_TRANSFER_SRC);

// Copy image to staging buffer
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = {0, 0, 0};
region.imageExtent = {width, height, 1};

vkCmdCopyImageToBuffer(
cmdBuf.CommandBuffer,
image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
readbackBuffer.Buffer,
1,
&region);

// Transition: TRANSFER_SRC -> PRESENT
CmdTransitionImageLayout(
cmdBuf.CommandBuffer,
image,
GREX_ALL_SUBRESOURCES,
VK_IMAGE_ASPECT_COLOR_BIT,
RESOURCE_STATE_TRANSFER_SRC,
RESOURCE_STATE_PRESENT);

vkEndCommandBuffer(cmdBuf.CommandBuffer);
ExecuteCommandBuffer(pRenderer, &cmdBuf);
vkQueueWaitIdle(pRenderer->Queue);
DestroyCommandBuffer(pRenderer, &cmdBuf);

// Map the buffer and convert BGRA -> RGBA (swapchain is B8G8R8A8_UNORM)
void* pMapped = nullptr;
vmaMapMemory(pRenderer->Allocator, readbackBuffer.Allocation, &pMapped);

BitmapRGBA8u bitmap(width, height);
const uint8_t* pSrc = static_cast<const uint8_t*>(pMapped);
uint8_t* pDst = reinterpret_cast<uint8_t*>(bitmap.GetPixels());

for (uint32_t i = 0; i < width * height; ++i)
{
pDst[i * 4 + 0] = pSrc[i * 4 + 2]; // R <- B
pDst[i * 4 + 1] = pSrc[i * 4 + 1]; // G <- G
pDst[i * 4 + 2] = pSrc[i * 4 + 0]; // B <- R
pDst[i * 4 + 3] = 255; // A <- 1 (force opaque)
}

vmaUnmapMemory(pRenderer->Allocator, readbackBuffer.Allocation);

bool ok = BitmapRGBA8u::Save(absPath, &bitmap);
DestroyBuffer(pRenderer, &readbackBuffer);
return ok;
}
9 changes: 9 additions & 0 deletions projects/common/vk_renderer.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include "config.h"
#include "bitmap.h"

#include <vulkan/vulkan.h>
#include "vk_mem_alloc.h"

#include <dxcapi.h>
#include <filesystem>

#define GREX_ALL_SUBRESOURCES 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS

Expand Down Expand Up @@ -583,3 +585,10 @@ extern PFN_vkCmdSetDescriptorBufferOffsetsEXT fn_vkCmdSetDescriptorBuffe
extern PFN_vkCmdDrawMeshTasksEXT fn_vkCmdDrawMeshTasksEXT;
extern PFN_vkCmdPushDescriptorSetKHR fn_vkCmdPushDescriptorSetKHR;
extern PFN_vkCmdDrawMeshTasksNV fn_vkCmdDrawMeshTasksNV;

bool SaveVulkanImageAsPNG(
VulkanRenderer* pRenderer,
VkImage image,
uint32_t width,
uint32_t height,
const std::filesystem::path& absPath);
19 changes: 17 additions & 2 deletions projects/geometry/101_color_cube_vulkan/101_color_cube_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ int main(int argc, char** argv)
// *************************************************************************
std::vector<VkImageView> imageViews;
std::vector<VkImageView> depthViews;
std::vector<VkImage> images;
{
std::vector<VkImage> images;
CHECK_CALL(GetSwapchainImages(renderer.get(), images));

for (auto& image : images)
Expand Down Expand Up @@ -286,9 +286,11 @@ int main(int argc, char** argv)

window->ResetTimer();

uint32_t frameIndex = 0;

while (window->PollEvents())
{
if (args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
if (args.screenshotFrame < 0 && args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
{
break;
}
Expand Down Expand Up @@ -371,11 +373,24 @@ int main(int argc, char** argv)
assert(false && "WaitForGpu failed");
}

if (!args.screenshotPath.empty() &&
(args.screenshotFrame < 0 || (int)frameIndex == args.screenshotFrame))
{
SaveVulkanImageAsPNG(renderer.get(), images[imageIndex], gWindowWidth, gWindowHeight, args.screenshotPath);
args.screenshotPath.clear();
if (args.screenshotFrame >= 0)
{
break;
}
}

if (!SwapchainPresent(renderer.get(), imageIndex))
{
assert(false && "SwapchainPresent failed");
break;
}

++frameIndex;
}

return 0;
Expand Down
2 changes: 2 additions & 0 deletions projects/geometry/101_color_cube_vulkan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ add_executable(
${TARGET_NAME}
${TARGET_NAME}.cpp
${GREX_PROJECTS_COMMON_DIR}/config.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.cpp
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.h
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.cpp
${GREX_PROJECTS_COMMON_DIR}/window.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,11 @@ int main(int argc, char** argv)

window->ResetTimer();

uint32_t frameIndex = 0;

while (window->PollEvents())
{
if (args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
if (args.screenshotFrame < 0 && args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
{
break;
}
Expand Down Expand Up @@ -483,12 +485,25 @@ int main(int argc, char** argv)
break;
}

if (!args.screenshotPath.empty() &&
(args.screenshotFrame < 0 || (int)frameIndex == args.screenshotFrame))
{
SaveVulkanImageAsPNG(renderer.get(), images[bufferIndex], gWindowWidth, gWindowHeight, args.screenshotPath);
args.screenshotPath.clear();
if (args.screenshotFrame >= 0)
{
break;
}
}

// Present
if (!SwapchainPresent(renderer.get(), bufferIndex))
{
assert(false && "SwapchainPresent failed");
break;
}

++frameIndex;
}

return 0;
Expand Down
2 changes: 2 additions & 0 deletions projects/geometry/102_cornell_box_vulkan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ add_executable(
${TARGET_NAME}
${TARGET_NAME}.cpp
${GREX_PROJECTS_COMMON_DIR}/config.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.cpp
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.h
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.cpp
${GREX_PROJECTS_COMMON_DIR}/window.h
Expand Down
19 changes: 17 additions & 2 deletions projects/geometry/103_cone_vulkan/103_cone_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ int main(int argc, char** argv)
// *************************************************************************
std::vector<VkImageView> imageViews;
std::vector<VkImageView> depthViews;
std::vector<VkImage> images;
{
std::vector<VkImage> images;
CHECK_CALL(GetSwapchainImages(renderer.get(), images));

for (auto& image : images)
Expand Down Expand Up @@ -304,9 +304,11 @@ int main(int argc, char** argv)

window->ResetTimer();

uint32_t frameIndex = 0;

while (window->PollEvents())
{
if (args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
if (args.screenshotFrame < 0 && args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
{
break;
}
Expand Down Expand Up @@ -399,12 +401,25 @@ int main(int argc, char** argv)
break;
}

if (!args.screenshotPath.empty() &&
(args.screenshotFrame < 0 || (int)frameIndex == args.screenshotFrame))
{
SaveVulkanImageAsPNG(renderer.get(), images[bufferIndex], gWindowWidth, gWindowHeight, args.screenshotPath);
args.screenshotPath.clear();
if (args.screenshotFrame >= 0)
{
break;
}
}

// Present
if (!SwapchainPresent(renderer.get(), bufferIndex))
{
assert(false && "SwapchainPresent failed");
break;
}

++frameIndex;
}

return 0;
Expand Down
2 changes: 2 additions & 0 deletions projects/geometry/103_cone_vulkan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ add_executable(
${TARGET_NAME}
${TARGET_NAME}.cpp
${GREX_PROJECTS_COMMON_DIR}/config.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.cpp
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.h
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.cpp
${GREX_PROJECTS_COMMON_DIR}/window.h
Expand Down
17 changes: 16 additions & 1 deletion projects/geometry/104_debug_tbn_vulkan/104_debug_tbn_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,11 @@ int main(int argc, char** argv)

window->ResetTimer();

uint32_t frameIndex = 0;

while (window->PollEvents())
{
if (args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
if (args.screenshotFrame < 0 && args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
{
break;
}
Expand Down Expand Up @@ -532,12 +534,25 @@ int main(int argc, char** argv)
break;
}

if (!args.screenshotPath.empty() &&
(args.screenshotFrame < 0 || (int)frameIndex == args.screenshotFrame))
{
SaveVulkanImageAsPNG(renderer.get(), images[bufferIndex], gWindowWidth, gWindowHeight, args.screenshotPath);
args.screenshotPath.clear();
if (args.screenshotFrame >= 0)
{
break;
}
}

// Present
if (!SwapchainPresent(renderer.get(), bufferIndex))
{
assert(false && "SwapchainPresent failed");
break;
}

++frameIndex;
}

return 0;
Expand Down
2 changes: 2 additions & 0 deletions projects/geometry/104_debug_tbn_vulkan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ add_executable(
${TARGET_NAME}
${TARGET_NAME}.cpp
${GREX_PROJECTS_COMMON_DIR}/config.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.h
${GREX_PROJECTS_COMMON_DIR}/bitmap.cpp
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.h
${GREX_PROJECTS_COMMON_DIR}/vk_renderer.cpp
${GREX_PROJECTS_COMMON_DIR}/window.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ int main(int argc, char** argv)
// *************************************************************************
std::vector<VkImageView> imageViews;
std::vector<VkImageView> depthViews;
std::vector<VkImage> images;
{
std::vector<VkImage> images;
CHECK_CALL(GetSwapchainImages(renderer.get(), images));

for (auto& image : images)
Expand Down Expand Up @@ -259,9 +259,11 @@ int main(int argc, char** argv)

window->ResetTimer();

uint32_t frameIndex = 0;

while (window->PollEvents())
{
if (args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
if (args.screenshotFrame < 0 && args.autoExitSeconds >= 0 && window->GetElapsedSeconds() >= args.autoExitSeconds)
{
break;
}
Expand Down Expand Up @@ -326,11 +328,24 @@ int main(int argc, char** argv)
assert(false && "WaitForGpu failed");
}

if (!args.screenshotPath.empty() &&
(args.screenshotFrame < 0 || (int)frameIndex == args.screenshotFrame))
{
SaveVulkanImageAsPNG(renderer.get(), images[imageIndex], gWindowWidth, gWindowHeight, args.screenshotPath);
args.screenshotPath.clear();
if (args.screenshotFrame >= 0)
{
break;
}
}

if (!SwapchainPresent(renderer.get(), imageIndex))
{
assert(false && "SwapchainPresent failed");
break;
}

++frameIndex;
}

return 0;
Expand Down
Loading