157 lines
6.6 KiB
C++
157 lines
6.6 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|||
|
|
|
|||
|
|
// An ImageVulkan that isn't square gets every level of its mip chain. The
|
|||
|
|
// chain was blitted with each level `size >> level` wide and high, which is
|
|||
|
|
// 0 for the shorter side once the level passes its log2: a 256×4 texture's
|
|||
|
|
// levels 3 to 8 were blitted with no height, so nothing wrote them and they
|
|||
|
|
// held whatever memory they got. A level is max(1, size >> level).
|
|||
|
|
//
|
|||
|
|
// A 256×4 and a 4×256 texture of one colour each get their full chain (9
|
|||
|
|
// levels, as the longer side needs); every texel of every level must come
|
|||
|
|
// back that colour. Needs a Vulkan device and no window. The validation
|
|||
|
|
// check only means something with CRAFTER_GRAPHICS_VALIDATION set.
|
|||
|
|
|
|||
|
|
#include <cstdlib>
|
|||
|
|
#include "vulkan/vulkan.h"
|
|||
|
|
|
|||
|
|
import Crafter.Graphics;
|
|||
|
|
import std;
|
|||
|
|
|
|||
|
|
using namespace Crafter;
|
|||
|
|
|
|||
|
|
namespace {
|
|||
|
|
|
|||
|
|
int failures = 0;
|
|||
|
|
|
|||
|
|
void Check(bool ok, std::string_view what) {
|
|||
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
|||
|
|
if (!ok) ++failures;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
VkCommandBuffer BeginCmd() {
|
|||
|
|
VkCommandBufferAllocateInfo allocInfo {
|
|||
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
|||
|
|
.commandPool = Device::commandPool,
|
|||
|
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|||
|
|
.commandBufferCount = 1,
|
|||
|
|
};
|
|||
|
|
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
|||
|
|
Device::CheckVkResult(vkAllocateCommandBuffers(Device::device, &allocInfo, &cmd));
|
|||
|
|
VkCommandBufferBeginInfo beginInfo {
|
|||
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
|||
|
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
|||
|
|
};
|
|||
|
|
Device::CheckVkResult(vkBeginCommandBuffer(cmd, &beginInfo));
|
|||
|
|
return cmd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void SubmitWait(VkCommandBuffer cmd) {
|
|||
|
|
Device::CheckVkResult(vkEndCommandBuffer(cmd));
|
|||
|
|
VkSubmitInfo submitInfo {
|
|||
|
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
|||
|
|
.commandBufferCount = 1,
|
|||
|
|
.pCommandBuffers = &cmd,
|
|||
|
|
};
|
|||
|
|
Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|||
|
|
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
|||
|
|
vkFreeCommandBuffers(Device::device, Device::commandPool, 1, &cmd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RGBA8 texel as a 4-byte word, matching VK_FORMAT_R8G8B8A8_UNORM.
|
|||
|
|
using Texel = std::uint32_t;
|
|||
|
|
constexpr Texel kColour = 0xFF3366CCu;
|
|||
|
|
constexpr std::uint8_t kLevels = 9;
|
|||
|
|
|
|||
|
|
std::uint32_t LevelSize(std::uint16_t size, std::uint32_t level) {
|
|||
|
|
return std::max<std::uint32_t>(1u, static_cast<std::uint32_t>(size) >> level);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void CheckChain(std::uint16_t width, std::uint16_t height) {
|
|||
|
|
ImageVulkan<Texel> image;
|
|||
|
|
// Room for every level, back to back.
|
|||
|
|
std::uint32_t total = 0;
|
|||
|
|
for (std::uint32_t level = 0; level < kLevels; ++level) total += LevelSize(width, level) * LevelSize(height, level);
|
|||
|
|
VulkanBuffer<Texel, true> readback;
|
|||
|
|
readback.Create(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, total, VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
|||
|
|
std::fill_n(readback.value, total, 0u);
|
|||
|
|
readback.FlushDevice();
|
|||
|
|
|
|||
|
|
VkCommandBuffer cmd = BeginCmd();
|
|||
|
|
image.Create(width, height, kLevels, cmd, VK_FORMAT_R8G8B8A8_UNORM,
|
|||
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
|||
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
|||
|
|
std::fill_n(image.buffer.value, static_cast<std::size_t>(width) * height, kColour);
|
|||
|
|
image.Update(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|||
|
|
|
|||
|
|
const VkImageMemoryBarrier toSource {
|
|||
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|||
|
|
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
|||
|
|
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
|||
|
|
.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|||
|
|
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|||
|
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|||
|
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|||
|
|
.image = image.image,
|
|||
|
|
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, kLevels, 0, 1 },
|
|||
|
|
};
|
|||
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toSource);
|
|||
|
|
std::array<VkBufferImageCopy, kLevels> regions {};
|
|||
|
|
std::uint32_t offset = 0;
|
|||
|
|
for (std::uint32_t level = 0; level < kLevels; ++level) {
|
|||
|
|
regions[level] = {
|
|||
|
|
.bufferOffset = static_cast<VkDeviceSize>(offset) * sizeof(Texel),
|
|||
|
|
.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1 },
|
|||
|
|
.imageExtent = { LevelSize(width, level), LevelSize(height, level), 1 },
|
|||
|
|
};
|
|||
|
|
offset += LevelSize(width, level) * LevelSize(height, level);
|
|||
|
|
}
|
|||
|
|
vkCmdCopyImageToBuffer(cmd, image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.buffer, kLevels, regions.data());
|
|||
|
|
const VkBufferMemoryBarrier toHost {
|
|||
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|||
|
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
|||
|
|
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
|
|||
|
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|||
|
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|||
|
|
.buffer = readback.buffer,
|
|||
|
|
.offset = 0,
|
|||
|
|
.size = VK_WHOLE_SIZE,
|
|||
|
|
};
|
|||
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1, &toHost, 0, nullptr);
|
|||
|
|
SubmitWait(cmd);
|
|||
|
|
readback.FlushHost();
|
|||
|
|
|
|||
|
|
std::vector<std::uint32_t> wrong;
|
|||
|
|
offset = 0;
|
|||
|
|
for (std::uint32_t level = 0; level < kLevels; ++level) {
|
|||
|
|
const std::uint32_t count = LevelSize(width, level) * LevelSize(height, level);
|
|||
|
|
if (!std::all_of(readback.value + offset, readback.value + offset + count, [](Texel t) { return t == kColour; })) wrong.push_back(level);
|
|||
|
|
offset += count;
|
|||
|
|
}
|
|||
|
|
std::string list;
|
|||
|
|
for (std::uint32_t level : wrong) list += std::format(" {}", level);
|
|||
|
|
Check(wrong.empty(), std::format("every level of a {}×{} texture's chain has its colour (wrong:{})", width, height, wrong.empty() ? " none" : list));
|
|||
|
|
image.Destroy();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} // namespace
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
Device::Initialize();
|
|||
|
|
Device::validationErrorCount = 0;
|
|||
|
|
Device::framesInFlight = 2;
|
|||
|
|
Device::frameCounter = 0;
|
|||
|
|
|
|||
|
|
CheckChain(256, 4);
|
|||
|
|
CheckChain(4, 256);
|
|||
|
|
// Square, as before.
|
|||
|
|
CheckChain(256, 256);
|
|||
|
|
|
|||
|
|
Device::DrainDeletions();
|
|||
|
|
if (std::getenv("CRAFTER_GRAPHICS_VALIDATION") != nullptr) {
|
|||
|
|
Check(Device::validationErrorCount == 0,
|
|||
|
|
std::format("no Vulkan validation errors across the run ({} seen)", Device::validationErrorCount));
|
|||
|
|
}
|
|||
|
|
std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed");
|
|||
|
|
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
|||
|
|
}
|