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
|
||||
// volk (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and
|
||||
// each frame clears the swap-chain image to the frame-counter colour with vkCmdClearColorImage
|
||||
// (no pipeline, no shaders, no SPIR-V) and presents. The whole image encodes the frame number,
|
||||
// so it animates and a dropped/stale frame is detectable by the capture test. Clear-only keeps
|
||||
// this to one image-clear per frame; richer per-rect drawing would need a render pass.
|
||||
// Vulkan backend for the mock game. Brings up a real Vulkan instance/device/swap chain via volk
|
||||
// (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and renders
|
||||
// the same pattern as the other backends -- an animated background, a moving vertical bar, and the
|
||||
// top-left frame-counter block -- so a dropped/duplicated/stale frame is detectable by the capture
|
||||
// test just like the D3D/GL paths. The per-rect draw uses a render pass whose load clears the
|
||||
// background and vkCmdClearAttachments for the bar + block, so it still needs no pipeline / shaders.
|
||||
#include "render_backend.hpp"
|
||||
|
||||
#include <vector>
|
||||
@@ -42,10 +42,8 @@ class VkBackend : public RenderBackend {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pick_device() || !create_device() || !create_swapchain() || !create_commands()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return pick_device() && create_device() && create_swapchain() && create_render_pass() && create_framebuffers()
|
||||
&& create_commands();
|
||||
}
|
||||
|
||||
void render_and_present(std::uint32_t frame) override
|
||||
@@ -57,38 +55,42 @@ class VkBackend : public RenderBackend {
|
||||
|
||||
std::uint32_t idx = 0;
|
||||
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) {
|
||||
return;
|
||||
return; // OUT_OF_DATE / SUBOPTIMAL / error: skip (the mock window isn't resized in practice)
|
||||
}
|
||||
vkResetFences(device_, 1, &in_flight_);
|
||||
|
||||
const FramePattern pat = frame_pattern(frame, extent_.width);
|
||||
|
||||
VkCommandBuffer cb = cmd_;
|
||||
vkResetCommandBuffer(cb, 0);
|
||||
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
vkBeginCommandBuffer(cb, &bi);
|
||||
|
||||
barrier(cb, images_[idx], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
// The render pass's load op clears the whole attachment to the animated background; the
|
||||
// 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;
|
||||
frame_to_rgb(frame, r, g, b); // whole image encodes the frame -> animates + decodable
|
||||
VkClearColorValue cc{};
|
||||
cc.float32[0] = r / 255.0f;
|
||||
cc.float32[1] = g / 255.0f;
|
||||
cc.float32[2] = b / 255.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);
|
||||
// Moving vertical bar (full height) then the top-left frame-counter block, cleared as
|
||||
// rectangular regions of the bound attachment (no pipeline needed).
|
||||
clear_rect(cb, {{static_cast<std::int32_t>(pat.bar_x), 0}, {kBarWidth, extent_.height}},
|
||||
{{1.0f, 1.0f, 1.0f, 1.0f}});
|
||||
clear_rect(cb, {{0, 0}, {kFrameBlock, kFrameBlock}},
|
||||
{{pat.code_r / 255.0f, pat.code_g / 255.0f, pat.code_b / 255.0f, 1.0f}});
|
||||
|
||||
barrier(cb, images_[idx], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
|
||||
vkCmdEndRenderPass(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};
|
||||
si.waitSemaphoreCount = 1;
|
||||
si.pWaitSemaphores = &acquire_sem_;
|
||||
@@ -114,6 +116,14 @@ class VkBackend : public RenderBackend {
|
||||
{
|
||||
if (device_ != VK_NULL_HANDLE) {
|
||||
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)
|
||||
vkDestroyFence(device_, in_flight_, nullptr);
|
||||
if (acquire_sem_ != VK_NULL_HANDLE)
|
||||
@@ -133,6 +143,20 @@ class VkBackend : public RenderBackend {
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
std::uint32_t n = 0;
|
||||
@@ -197,6 +221,7 @@ class VkBackend : public RenderBackend {
|
||||
}
|
||||
}
|
||||
format_ = chosen.format;
|
||||
extent_ = caps.currentExtent.width != 0xFFFFFFFFu ? caps.currentExtent : VkExtent2D{width_, height_};
|
||||
|
||||
std::uint32_t want = caps.minImageCount + 1;
|
||||
if (caps.maxImageCount > 0 && want > caps.maxImageCount) {
|
||||
@@ -207,10 +232,11 @@ class VkBackend : public RenderBackend {
|
||||
sc.minImageCount = want;
|
||||
sc.imageFormat = chosen.format;
|
||||
sc.imageColorSpace = chosen.colorSpace;
|
||||
sc.imageExtent = caps.currentExtent.width != 0xFFFFFFFFu ? caps.currentExtent : VkExtent2D{width_, height_};
|
||||
sc.imageExtent = extent_;
|
||||
sc.imageArrayLayers = 1;
|
||||
// TRANSFER_DST so we can clear it; TRANSFER_SRC so the capture hook can copy it out.
|
||||
sc.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
// COLOR_ATTACHMENT so the render pass can draw into it; TRANSFER_SRC so the capture hook can
|
||||
// copy it out.
|
||||
sc.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
sc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
sc.preTransform = caps.currentTransform;
|
||||
sc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
@@ -240,6 +266,70 @@ class VkBackend : public RenderBackend {
|
||||
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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 height_ = 0;
|
||||
VkInstance instance_ = VK_NULL_HANDLE;
|
||||
@@ -288,7 +363,11 @@ class VkBackend : public RenderBackend {
|
||||
VkQueue queue_ = VK_NULL_HANDLE;
|
||||
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
||||
VkFormat format_ = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent_ = {0, 0};
|
||||
std::vector<VkImage> images_;
|
||||
std::vector<VkImageView> views_;
|
||||
std::vector<VkFramebuffer> framebuffers_;
|
||||
VkRenderPass render_pass_ = VK_NULL_HANDLE;
|
||||
VkCommandPool pool_ = VK_NULL_HANDLE;
|
||||
VkCommandBuffer cmd_ = VK_NULL_HANDLE;
|
||||
VkSemaphore acquire_sem_ = VK_NULL_HANDLE;
|
||||
|
||||
Reference in New Issue
Block a user