Render the same pattern in the Vulkan mock backend as the others
The Vulkan backend was the odd one out: it cleared the whole swapchain image to the frame-counter colour (vkCmdClearColorImage), while every other backend draws an animated background + a moving vertical bar + a top-left frame-counter block. Bring it in line. Use a render pass (loadOp CLEAR paints the animated background, final layout PRESENT_SRC so no manual barriers) plus vkCmdClearAttachments to clear the bar and block rects -- so it renders the full FramePattern while still needing no pipeline / shaders / SPIR-V, matching the clear-based character of the dx11/dx12/gl backends. Swapchain images gain COLOR_ATTACHMENT usage (keep TRANSFER_SRC for the capture hook). No new dependency: render pass / framebuffer / image view / ClearAttachments are core Vulkan, already available through the vendored volk submodule. mock_game_test decodes the vk frame-counter block correctly through the capture layer, same as before.
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
// Vulkan backend for the mock game. Brings up a real Vulkan instance/device/swap chain via
|
// Vulkan backend for the mock game. Brings up a real Vulkan instance/device/swap chain via volk
|
||||||
// volk (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and
|
// (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and renders
|
||||||
// each frame clears the swap-chain image to the frame-counter colour with vkCmdClearColorImage
|
// the same pattern as the other backends -- an animated background, a moving vertical bar, and the
|
||||||
// (no pipeline, no shaders, no SPIR-V) and presents. The whole image encodes the frame number,
|
// top-left frame-counter block -- so a dropped/duplicated/stale frame is detectable by the capture
|
||||||
// so it animates and a dropped/stale frame is detectable by the capture test. Clear-only keeps
|
// test just like the D3D/GL paths. The per-rect draw uses a render pass whose load clears the
|
||||||
// this to one image-clear per frame; richer per-rect drawing would need a render pass.
|
// background and vkCmdClearAttachments for the bar + block, so it still needs no pipeline / shaders.
|
||||||
#include "render_backend.hpp"
|
#include "render_backend.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -42,10 +42,8 @@ class VkBackend : public RenderBackend {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pick_device() || !create_device() || !create_swapchain() || !create_commands()) {
|
return pick_device() && create_device() && create_swapchain() && create_render_pass() && create_framebuffers()
|
||||||
return false;
|
&& create_commands();
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_and_present(std::uint32_t frame) override
|
void render_and_present(std::uint32_t frame) override
|
||||||
@@ -57,38 +55,42 @@ class VkBackend : public RenderBackend {
|
|||||||
|
|
||||||
std::uint32_t idx = 0;
|
std::uint32_t idx = 0;
|
||||||
VkResult acq = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, acquire_sem_, VK_NULL_HANDLE, &idx);
|
VkResult acq = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, acquire_sem_, VK_NULL_HANDLE, &idx);
|
||||||
if (acq == VK_ERROR_OUT_OF_DATE_KHR || acq == VK_SUBOPTIMAL_KHR) {
|
|
||||||
return; // skip this frame (the mock window isn't resized in practice)
|
|
||||||
}
|
|
||||||
if (acq != VK_SUCCESS) {
|
if (acq != VK_SUCCESS) {
|
||||||
return;
|
return; // OUT_OF_DATE / SUBOPTIMAL / error: skip (the mock window isn't resized in practice)
|
||||||
}
|
}
|
||||||
vkResetFences(device_, 1, &in_flight_);
|
vkResetFences(device_, 1, &in_flight_);
|
||||||
|
|
||||||
|
const FramePattern pat = frame_pattern(frame, extent_.width);
|
||||||
|
|
||||||
VkCommandBuffer cb = cmd_;
|
VkCommandBuffer cb = cmd_;
|
||||||
vkResetCommandBuffer(cb, 0);
|
vkResetCommandBuffer(cb, 0);
|
||||||
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||||
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
vkBeginCommandBuffer(cb, &bi);
|
vkBeginCommandBuffer(cb, &bi);
|
||||||
|
|
||||||
barrier(cb, images_[idx], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0,
|
// The render pass's load op clears the whole attachment to the animated background; the
|
||||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
// pass's final layout is PRESENT_SRC_KHR, so no manual image barriers are needed.
|
||||||
|
VkClearValue clear{};
|
||||||
|
clear.color = {{pat.bg_r / 255.0f, pat.bg_g / 255.0f, pat.bg_b / 255.0f, 1.0f}};
|
||||||
|
VkRenderPassBeginInfo rpbi{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||||
|
rpbi.renderPass = render_pass_;
|
||||||
|
rpbi.framebuffer = framebuffers_[idx];
|
||||||
|
rpbi.renderArea = {{0, 0}, extent_};
|
||||||
|
rpbi.clearValueCount = 1;
|
||||||
|
rpbi.pClearValues = &clear;
|
||||||
|
vkCmdBeginRenderPass(cb, &rpbi, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
std::uint8_t r = 0, g = 0, b = 0;
|
// Moving vertical bar (full height) then the top-left frame-counter block, cleared as
|
||||||
frame_to_rgb(frame, r, g, b); // whole image encodes the frame -> animates + decodable
|
// rectangular regions of the bound attachment (no pipeline needed).
|
||||||
VkClearColorValue cc{};
|
clear_rect(cb, {{static_cast<std::int32_t>(pat.bar_x), 0}, {kBarWidth, extent_.height}},
|
||||||
cc.float32[0] = r / 255.0f;
|
{{1.0f, 1.0f, 1.0f, 1.0f}});
|
||||||
cc.float32[1] = g / 255.0f;
|
clear_rect(cb, {{0, 0}, {kFrameBlock, kFrameBlock}},
|
||||||
cc.float32[2] = b / 255.0f;
|
{{pat.code_r / 255.0f, pat.code_g / 255.0f, pat.code_b / 255.0f, 1.0f}});
|
||||||
cc.float32[3] = 1.0f;
|
|
||||||
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
||||||
vkCmdClearColorImage(cb, images_[idx], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cc, 1, &range);
|
|
||||||
|
|
||||||
barrier(cb, images_[idx], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
vkCmdEndRenderPass(cb);
|
||||||
VK_ACCESS_TRANSFER_WRITE_BIT, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
|
|
||||||
vkEndCommandBuffer(cb);
|
vkEndCommandBuffer(cb);
|
||||||
|
|
||||||
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||||
si.waitSemaphoreCount = 1;
|
si.waitSemaphoreCount = 1;
|
||||||
si.pWaitSemaphores = &acquire_sem_;
|
si.pWaitSemaphores = &acquire_sem_;
|
||||||
@@ -114,6 +116,14 @@ class VkBackend : public RenderBackend {
|
|||||||
{
|
{
|
||||||
if (device_ != VK_NULL_HANDLE) {
|
if (device_ != VK_NULL_HANDLE) {
|
||||||
vkDeviceWaitIdle(device_);
|
vkDeviceWaitIdle(device_);
|
||||||
|
for (VkFramebuffer fb : framebuffers_)
|
||||||
|
if (fb != VK_NULL_HANDLE)
|
||||||
|
vkDestroyFramebuffer(device_, fb, nullptr);
|
||||||
|
for (VkImageView v : views_)
|
||||||
|
if (v != VK_NULL_HANDLE)
|
||||||
|
vkDestroyImageView(device_, v, nullptr);
|
||||||
|
if (render_pass_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroyRenderPass(device_, render_pass_, nullptr);
|
||||||
if (in_flight_ != VK_NULL_HANDLE)
|
if (in_flight_ != VK_NULL_HANDLE)
|
||||||
vkDestroyFence(device_, in_flight_, nullptr);
|
vkDestroyFence(device_, in_flight_, nullptr);
|
||||||
if (acquire_sem_ != VK_NULL_HANDLE)
|
if (acquire_sem_ != VK_NULL_HANDLE)
|
||||||
@@ -133,6 +143,20 @@ class VkBackend : public RenderBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Clear one rectangular region of the bound color attachment to `color`.
|
||||||
|
void clear_rect(VkCommandBuffer cb, VkRect2D rect, VkClearColorValue color)
|
||||||
|
{
|
||||||
|
VkClearAttachment att{};
|
||||||
|
att.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
att.colorAttachment = 0;
|
||||||
|
att.clearValue.color = color;
|
||||||
|
VkClearRect cr{};
|
||||||
|
cr.rect = rect;
|
||||||
|
cr.baseArrayLayer = 0;
|
||||||
|
cr.layerCount = 1;
|
||||||
|
vkCmdClearAttachments(cb, 1, &att, 1, &cr);
|
||||||
|
}
|
||||||
|
|
||||||
bool pick_device()
|
bool pick_device()
|
||||||
{
|
{
|
||||||
std::uint32_t n = 0;
|
std::uint32_t n = 0;
|
||||||
@@ -197,6 +221,7 @@ class VkBackend : public RenderBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
format_ = chosen.format;
|
format_ = chosen.format;
|
||||||
|
extent_ = caps.currentExtent.width != 0xFFFFFFFFu ? caps.currentExtent : VkExtent2D{width_, height_};
|
||||||
|
|
||||||
std::uint32_t want = caps.minImageCount + 1;
|
std::uint32_t want = caps.minImageCount + 1;
|
||||||
if (caps.maxImageCount > 0 && want > caps.maxImageCount) {
|
if (caps.maxImageCount > 0 && want > caps.maxImageCount) {
|
||||||
@@ -207,10 +232,11 @@ class VkBackend : public RenderBackend {
|
|||||||
sc.minImageCount = want;
|
sc.minImageCount = want;
|
||||||
sc.imageFormat = chosen.format;
|
sc.imageFormat = chosen.format;
|
||||||
sc.imageColorSpace = chosen.colorSpace;
|
sc.imageColorSpace = chosen.colorSpace;
|
||||||
sc.imageExtent = caps.currentExtent.width != 0xFFFFFFFFu ? caps.currentExtent : VkExtent2D{width_, height_};
|
sc.imageExtent = extent_;
|
||||||
sc.imageArrayLayers = 1;
|
sc.imageArrayLayers = 1;
|
||||||
// TRANSFER_DST so we can clear it; TRANSFER_SRC so the capture hook can copy it out.
|
// COLOR_ATTACHMENT so the render pass can draw into it; TRANSFER_SRC so the capture hook can
|
||||||
sc.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
// copy it out.
|
||||||
|
sc.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
sc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
sc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
sc.preTransform = caps.currentTransform;
|
sc.preTransform = caps.currentTransform;
|
||||||
sc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
sc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||||
@@ -240,6 +266,70 @@ class VkBackend : public RenderBackend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool create_render_pass()
|
||||||
|
{
|
||||||
|
VkAttachmentDescription color{};
|
||||||
|
color.format = format_;
|
||||||
|
color.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // clears to the animated background each frame
|
||||||
|
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // ready to present, no manual barrier
|
||||||
|
|
||||||
|
VkAttachmentReference ref{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||||
|
VkSubpassDescription sub{};
|
||||||
|
sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
|
sub.colorAttachmentCount = 1;
|
||||||
|
sub.pColorAttachments = &ref;
|
||||||
|
|
||||||
|
// Order the acquire against the attachment's first write.
|
||||||
|
VkSubpassDependency dep{};
|
||||||
|
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
dep.dstSubpass = 0;
|
||||||
|
dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
dep.srcAccessMask = 0;
|
||||||
|
dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
|
||||||
|
VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||||
|
rpci.attachmentCount = 1;
|
||||||
|
rpci.pAttachments = &color;
|
||||||
|
rpci.subpassCount = 1;
|
||||||
|
rpci.pSubpasses = ⊂
|
||||||
|
rpci.dependencyCount = 1;
|
||||||
|
rpci.pDependencies = &dep;
|
||||||
|
return vkCreateRenderPass(device_, &rpci, nullptr, &render_pass_) == VK_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_framebuffers()
|
||||||
|
{
|
||||||
|
views_.resize(images_.size(), VK_NULL_HANDLE);
|
||||||
|
framebuffers_.resize(images_.size(), VK_NULL_HANDLE);
|
||||||
|
for (std::size_t i = 0; i < images_.size(); ++i) {
|
||||||
|
VkImageViewCreateInfo ivci{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||||
|
ivci.image = images_[i];
|
||||||
|
ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
ivci.format = format_;
|
||||||
|
ivci.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
|
if (vkCreateImageView(device_, &ivci, nullptr, &views_[i]) != VK_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||||
|
fbci.renderPass = render_pass_;
|
||||||
|
fbci.attachmentCount = 1;
|
||||||
|
fbci.pAttachments = &views_[i];
|
||||||
|
fbci.width = extent_.width;
|
||||||
|
fbci.height = extent_.height;
|
||||||
|
fbci.layers = 1;
|
||||||
|
if (vkCreateFramebuffer(device_, &fbci, nullptr, &framebuffers_[i]) != VK_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool create_commands()
|
bool create_commands()
|
||||||
{
|
{
|
||||||
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||||
@@ -263,21 +353,6 @@ class VkBackend : public RenderBackend {
|
|||||||
&& vkCreateFence(device_, &fi, nullptr, &in_flight_) == VK_SUCCESS;
|
&& vkCreateFence(device_, &fi, nullptr, &in_flight_) == VK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void barrier(VkCommandBuffer cb, VkImage img, VkImageLayout from, VkImageLayout to, VkAccessFlags src_access,
|
|
||||||
VkAccessFlags dst_access, VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage)
|
|
||||||
{
|
|
||||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
||||||
b.srcAccessMask = src_access;
|
|
||||||
b.dstAccessMask = dst_access;
|
|
||||||
b.oldLayout = from;
|
|
||||||
b.newLayout = to;
|
|
||||||
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
||||||
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
||||||
b.image = img;
|
|
||||||
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
||||||
vkCmdPipelineBarrier(cb, src_stage, dst_stage, 0, 0, nullptr, 0, nullptr, 1, &b);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t width_ = 0;
|
std::uint32_t width_ = 0;
|
||||||
std::uint32_t height_ = 0;
|
std::uint32_t height_ = 0;
|
||||||
VkInstance instance_ = VK_NULL_HANDLE;
|
VkInstance instance_ = VK_NULL_HANDLE;
|
||||||
@@ -288,7 +363,11 @@ class VkBackend : public RenderBackend {
|
|||||||
VkQueue queue_ = VK_NULL_HANDLE;
|
VkQueue queue_ = VK_NULL_HANDLE;
|
||||||
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
||||||
VkFormat format_ = VK_FORMAT_UNDEFINED;
|
VkFormat format_ = VK_FORMAT_UNDEFINED;
|
||||||
|
VkExtent2D extent_ = {0, 0};
|
||||||
std::vector<VkImage> images_;
|
std::vector<VkImage> images_;
|
||||||
|
std::vector<VkImageView> views_;
|
||||||
|
std::vector<VkFramebuffer> framebuffers_;
|
||||||
|
VkRenderPass render_pass_ = VK_NULL_HANDLE;
|
||||||
VkCommandPool pool_ = VK_NULL_HANDLE;
|
VkCommandPool pool_ = VK_NULL_HANDLE;
|
||||||
VkCommandBuffer cmd_ = VK_NULL_HANDLE;
|
VkCommandBuffer cmd_ = VK_NULL_HANDLE;
|
||||||
VkSemaphore acquire_sem_ = VK_NULL_HANDLE;
|
VkSemaphore acquire_sem_ = VK_NULL_HANDLE;
|
||||||
|
|||||||
Reference in New Issue
Block a user