104 lines
4.3 KiB
C++
104 lines
4.3 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// The swapchain images are created with TRANSFER_DST where the surface
|
||
|
|
// allows it, so a pass can blit or clear into them: the swapchain used to be
|
||
|
|
// STORAGE | TRANSFER_SRC only, which left a compute shader as the only way to
|
||
|
|
// put an offscreen image on screen. This checks the usage the Window records,
|
||
|
|
// then clears the swapchain image from a pass, reads the frame back and
|
||
|
|
// checks the colour.
|
||
|
|
//
|
||
|
|
// Needs a live Wayland compositor and a Vulkan device. The validation check
|
||
|
|
// only means something with CRAFTER_GRAPHICS_VALIDATION set.
|
||
|
|
|
||
|
|
#include <cstdlib>
|
||
|
|
#include "vulkan/vulkan.h"
|
||
|
|
|
||
|
|
// PNG decode for the read-back frame. The library only compiles the writer
|
||
|
|
// (Window::CaptureNextFrame); STB_IMAGE_STATIC keeps the reader private to this file.
|
||
|
|
#define STB_IMAGE_IMPLEMENTATION
|
||
|
|
#define STB_IMAGE_STATIC
|
||
|
|
#define STBI_ONLY_PNG
|
||
|
|
#include "../../lib/stb_image.h"
|
||
|
|
|
||
|
|
import Crafter.Graphics;
|
||
|
|
import std;
|
||
|
|
using namespace Crafter;
|
||
|
|
namespace fs = std::filesystem;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
void Check(bool ok, std::string_view what) {
|
||
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||
|
|
if (!ok) ++failures;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clears the frame's swapchain image with vkCmdClearColorImage, a transfer
|
||
|
|
// write.
|
||
|
|
struct ClearPass : RenderPass {
|
||
|
|
VkClearColorValue colour = {{0.2f, 0.4f, 0.8f, 1.0f}};
|
||
|
|
|
||
|
|
void Record(VkCommandBuffer cmd, std::uint32_t, Window& window) override {
|
||
|
|
const VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||
|
|
vkCmdClearColorImage(cmd, window.images[window.currentBuffer], VK_IMAGE_LAYOUT_GENERAL, &colour, 1, &range);
|
||
|
|
}
|
||
|
|
|
||
|
|
VkPipelineStageFlags SwapchainStage() const override {
|
||
|
|
return VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
Device::Initialize();
|
||
|
|
Device::validationErrorCount = 0;
|
||
|
|
|
||
|
|
Window window(320, 240, "SwapchainTransferDst test");
|
||
|
|
VkSurfaceCapabilitiesKHR caps;
|
||
|
|
Device::CheckVkResult(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(Device::physDevice, window.vulkanSurface, &caps));
|
||
|
|
const bool surfaceAllows = (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0;
|
||
|
|
|
||
|
|
Check((window.swapchainUsage & (VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT))
|
||
|
|
== (VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
|
||
|
|
"the swapchain keeps STORAGE and TRANSFER_SRC");
|
||
|
|
Check(((window.swapchainUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0) == surfaceAllows,
|
||
|
|
std::format("TRANSFER_DST is used exactly when the surface allows it ({})", surfaceAllows ? "it does" : "it doesn't"));
|
||
|
|
if (!surfaceAllows) {
|
||
|
|
std::println("SKIP the clear-and-read-back check (this surface has no TRANSFER_DST)");
|
||
|
|
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
|
||
|
|
window.StartInit();
|
||
|
|
window.FinishInit();
|
||
|
|
ClearPass clear;
|
||
|
|
window.passes.push_back(&clear);
|
||
|
|
for (int i = 0; i < Window::numFrames * 2; ++i) window.Render();
|
||
|
|
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||
|
|
|
||
|
|
const fs::path out = fs::temp_directory_path() / "crafter-swapchain-transfer-dst.png";
|
||
|
|
std::error_code ec;
|
||
|
|
fs::remove(out, ec);
|
||
|
|
window.CaptureNextFrame(out);
|
||
|
|
window.Render();
|
||
|
|
if (std::getenv("CRAFTER_GRAPHICS_VALIDATION") != nullptr) {
|
||
|
|
Check(Device::validationErrorCount == 0,
|
||
|
|
std::format("no Vulkan validation errors while clearing and capturing ({} seen)", Device::validationErrorCount));
|
||
|
|
}
|
||
|
|
int width = 0, height = 0, channels = 0;
|
||
|
|
stbi_uc* pixels = stbi_load(out.string().c_str(), &width, &height, &channels, STBI_rgb_alpha);
|
||
|
|
Check(pixels != nullptr, "the frame was read back");
|
||
|
|
if (pixels != nullptr) {
|
||
|
|
const stbi_uc* p = pixels + (static_cast<std::size_t>(height / 2) * width + width / 2) * 4;
|
||
|
|
// 0.2, 0.4, 0.8 in a UNORM image.
|
||
|
|
const bool cleared = std::abs(p[0] - 51) <= 2 && std::abs(p[1] - 102) <= 2 && std::abs(p[2] - 204) <= 2;
|
||
|
|
Check(cleared, std::format("the cleared colour reached the frame (got {}, {}, {})", p[0], p[1], p[2]));
|
||
|
|
stbi_image_free(pixels);
|
||
|
|
}
|
||
|
|
|
||
|
|
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||
|
|
std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed");
|
||
|
|
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||
|
|
}
|