mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 04:04:07 +02:00
Submit the overlay through a persistent Vulkan texture
This commit is contained in:
1 parent
8417212ed2
commit
4c043c9962
13 files changed
+750
-26
No files matched your search
@@ -0,0 +1,134 @@
|
||||
// Explicit offscreen Vulkan check. Not registered with CTest: --run opts into
|
||||
// GPU access. No OpenVR initialization, desktop surface, microphone or input.
|
||||
#include "overlay_texture.hpp"
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
void check(VkResult result, const char* operation) {
|
||||
if (result != VK_SUCCESS) throw std::runtime_error(std::string(operation) + ": " + std::to_string(result));
|
||||
}
|
||||
struct Readback {
|
||||
VkDevice device{};
|
||||
VkBuffer buffer{};
|
||||
VkDeviceMemory memory{};
|
||||
VkCommandPool pool{};
|
||||
void* mapped = nullptr;
|
||||
~Readback() {
|
||||
if (device) vkDeviceWaitIdle(device);
|
||||
if (mapped) vkUnmapMemory(device, memory);
|
||||
if (pool) vkDestroyCommandPool(device, pool, nullptr);
|
||||
if (buffer) vkDestroyBuffer(device, buffer, nullptr);
|
||||
if (memory) vkFreeMemory(device, memory, nullptr);
|
||||
}
|
||||
void verify(const vr::VRVulkanTextureData_t& texture, std::span<const unsigned char> expected) {
|
||||
device = texture.m_pDevice;
|
||||
VkBufferCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
info.size = expected.size();
|
||||
info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
check(vkCreateBuffer(device, &info, nullptr, &buffer), "create readback buffer");
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetBufferMemoryRequirements(device, buffer, &requirements);
|
||||
VkPhysicalDeviceMemoryProperties types{};
|
||||
vkGetPhysicalDeviceMemoryProperties(texture.m_pPhysicalDevice, &types);
|
||||
uint32_t type = 0;
|
||||
while (type < types.memoryTypeCount && (!(requirements.memoryTypeBits & (1u << type)) ||
|
||||
!(types.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))) ++type;
|
||||
if (type == types.memoryTypeCount) throw std::runtime_error("No host-visible readback memory");
|
||||
VkMemoryAllocateInfo allocation{};
|
||||
allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocation.memoryTypeIndex = type;
|
||||
check(vkAllocateMemory(device, &allocation, nullptr, &memory), "allocate readback memory");
|
||||
check(vkBindBufferMemory(device, buffer, memory, 0), "bind readback memory");
|
||||
VkCommandPoolCreateInfo pool_info{};
|
||||
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
pool_info.queueFamilyIndex = texture.m_nQueueFamilyIndex;
|
||||
check(vkCreateCommandPool(device, &pool_info, nullptr, &pool), "create readback command pool");
|
||||
VkCommandBufferAllocateInfo command_info{};
|
||||
command_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
command_info.commandPool = pool;
|
||||
command_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
command_info.commandBufferCount = 1;
|
||||
VkCommandBuffer command{};
|
||||
check(vkAllocateCommandBuffers(device, &command_info, &command), "allocate readback commands");
|
||||
VkCommandBufferBeginInfo begin{};
|
||||
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
check(vkBeginCommandBuffer(command, &begin), "begin readback");
|
||||
VkBufferImageCopy copy{};
|
||||
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
copy.imageExtent = {texture.m_nWidth, texture.m_nHeight, 1};
|
||||
vkCmdCopyImageToBuffer(command, std::bit_cast<VkImage>(texture.m_nImage),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, 1, ©);
|
||||
VkBufferMemoryBarrier barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = buffer;
|
||||
barrier.size = VK_WHOLE_SIZE;
|
||||
vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
check(vkEndCommandBuffer(command), "end readback");
|
||||
VkSubmitInfo submit{};
|
||||
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit.commandBufferCount = 1;
|
||||
submit.pCommandBuffers = &command;
|
||||
check(vkQueueSubmit(texture.m_pQueue, 1, &submit, VK_NULL_HANDLE), "submit readback");
|
||||
check(vkQueueWaitIdle(texture.m_pQueue), "wait for readback");
|
||||
check(vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &mapped), "map readback");
|
||||
if (!(types.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
||||
VkMappedMemoryRange range{};
|
||||
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
range.memory = memory;
|
||||
range.size = VK_WHOLE_SIZE;
|
||||
check(vkInvalidateMappedMemoryRanges(device, 1, &range), "invalidate readback");
|
||||
}
|
||||
if (std::memcmp(mapped, expected.data(), expected.size()))
|
||||
throw std::runtime_error("RGBA readback differs from uploaded pixels");
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 2 || std::string_view(argv[1]) != "--run") {
|
||||
std::cout << "Use --run for an offscreen GPU upload/readback check. No OpenVR, audio or input.\n";
|
||||
return argc == 1 || (argc == 2 && std::string_view(argv[1]) == "--help") ? 0 : 2;
|
||||
}
|
||||
try {
|
||||
frameyap::OverlayTexture texture(1000, 680, {}, [](VkInstance instance) {
|
||||
uint32_t count = 0;
|
||||
check(vkEnumeratePhysicalDevices(instance, &count, nullptr), "enumerate GPUs");
|
||||
if (!count) throw std::runtime_error("No Vulkan GPU");
|
||||
std::vector<VkPhysicalDevice> devices(count);
|
||||
check(vkEnumeratePhysicalDevices(instance, &count, devices.data()), "enumerate GPUs");
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
vkGetPhysicalDeviceProperties(devices.front(), &properties);
|
||||
std::cout << "Vulkan device: " << properties.deviceName << '\n';
|
||||
return devices.front();
|
||||
}, [](VkPhysicalDevice) { return std::vector<std::string>{}; });
|
||||
std::vector<unsigned char> pixels(1000 * 680 * 4);
|
||||
uint64_t image = 0;
|
||||
for (unsigned frame = 0; frame < 8; ++frame) {
|
||||
for (size_t i = 0; i < pixels.size(); ++i) pixels[i] = (i * 17 + (i / 4000) * 31 + frame * 67) & 255;
|
||||
texture.upload(pixels);
|
||||
const auto submitted = texture.texture();
|
||||
const auto& description = *static_cast<vr::VRVulkanTextureData_t*>(submitted.handle);
|
||||
if (frame && description.m_nImage != image) throw std::runtime_error("Texture was recreated");
|
||||
image = description.m_nImage;
|
||||
Readback readback;
|
||||
readback.verify(description, pixels);
|
||||
}
|
||||
std::cout << "8 exact 1000x680 RGBA readbacks; one persistent Vulkan image.\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n'; return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// Link against these Vulkan fakes, never the loader. CTest must not touch a GPU
|
||||
// or initialize OpenVR. Model queued transfers so reuse before completion fails.
|
||||
#include "overlay_texture.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
template<class T> T handle(uintptr_t value) { return std::bit_cast<T>(value); }
|
||||
std::set<uintptr_t> live;
|
||||
uintptr_t next_handle = 10;
|
||||
unsigned calls = 0, fail_at = 0, images = 0, submits = 0, flushes = 0;
|
||||
bool coherent = true;
|
||||
std::map<VkDeviceMemory, std::vector<unsigned char>> allocations;
|
||||
VkDeviceMemory image_memory{}, buffer_memory{};
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
std::vector<std::function<void()>> recorded, pending;
|
||||
std::vector<std::vector<unsigned char>> observed;
|
||||
VkResult result() { return ++calls == fail_at ? VK_ERROR_OUT_OF_DEVICE_MEMORY : VK_SUCCESS; }
|
||||
template<class T> VkResult create(T* out) {
|
||||
auto status = result();
|
||||
if (status == VK_SUCCESS) { *out = handle<T>(next_handle++); live.insert(std::bit_cast<uintptr_t>(*out)); }
|
||||
return status;
|
||||
}
|
||||
template<class T> void destroy(T resource) { assert(live.erase(std::bit_cast<uintptr_t>(resource)) == 1); }
|
||||
void complete() { for (auto& work : pending) work(); pending.clear(); }
|
||||
void reset() {
|
||||
assert(live.empty() && allocations.empty() && pending.empty());
|
||||
calls = images = submits = flushes = 0;
|
||||
recorded.clear(); observed.clear(); layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
const std::vector<std::string> instance_extensions{"VK_KHR_external_memory_capabilities"};
|
||||
auto select_device = [](VkInstance instance) {
|
||||
assert(live.contains(std::bit_cast<uintptr_t>(instance)));
|
||||
return handle<VkPhysicalDevice>(2);
|
||||
};
|
||||
auto device_extensions = [](VkPhysicalDevice physical) {
|
||||
assert(physical == handle<VkPhysicalDevice>(2));
|
||||
return std::vector<std::string>{"VK_KHR_external_memory"};
|
||||
};
|
||||
void consume(frameyap::OverlayTexture& texture) {
|
||||
const auto t = texture.texture();
|
||||
assert(t.eType == vr::TextureType_Vulkan && t.eColorSpace == vr::ColorSpace_Gamma);
|
||||
const auto& data = *static_cast<vr::VRVulkanTextureData_t*>(t.handle);
|
||||
assert(data.m_nWidth == 2 && data.m_nHeight == 2 && data.m_nSampleCount == 1);
|
||||
assert(data.m_nFormat == VK_FORMAT_R8G8B8A8_UNORM && data.m_nQueueFamilyIndex == 1);
|
||||
assert(data.m_pPhysicalDevice == handle<VkPhysicalDevice>(2));
|
||||
assert(data.m_pQueue == handle<VkQueue>(3));
|
||||
assert(live.contains(data.m_nImage));
|
||||
// Emulate SetOverlayTexture enqueuing a read AFTER our upload, on our queue.
|
||||
pending.push_back([] {
|
||||
assert(layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
auto& bytes = allocations.at(image_memory);
|
||||
observed.emplace_back(bytes.begin(), bytes.begin() + 16);
|
||||
});
|
||||
}
|
||||
template<class F> void throws(F f) {
|
||||
bool threw = false;
|
||||
try { f(); } catch (const std::runtime_error&) { threw = true; }
|
||||
assert(threw);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* info, const VkAllocationCallbacks*, VkInstance* out) {
|
||||
assert(info->enabledExtensionCount == 1);
|
||||
assert(std::string(info->ppEnabledExtensionNames[0]) == instance_extensions[0]);
|
||||
return create(out);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks*) {
|
||||
assert(live.size() == 1); destroy(instance);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties(VkPhysicalDevice, VkPhysicalDeviceProperties* properties) {
|
||||
properties->limits.maxImageDimension2D = 4096;
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice, uint32_t* count, VkQueueFamilyProperties* out) {
|
||||
if (!out) { *count = 2; return; }
|
||||
assert(*count == 2);
|
||||
out[0] = {}; out[0].queueFlags = VK_QUEUE_TRANSFER_BIT; out[0].queueCount = 1;
|
||||
out[1] = {}; out[1].queueFlags = VK_QUEUE_GRAPHICS_BIT; out[1].queueCount = 1;
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physical, const VkDeviceCreateInfo* info,
|
||||
const VkAllocationCallbacks*, VkDevice* out) {
|
||||
assert(physical == handle<VkPhysicalDevice>(2));
|
||||
assert(info->queueCreateInfoCount == 1 && info->pQueueCreateInfos->queueFamilyIndex == 1);
|
||||
assert(info->enabledExtensionCount == 1);
|
||||
assert(std::string(info->ppEnabledExtensionNames[0]) == "VK_KHR_external_memory");
|
||||
return create(out);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks*) {
|
||||
assert(live.size() == 2 && allocations.empty() && pending.empty()); destroy(device);
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkDeviceWaitIdle(VkDevice) { complete(); return VK_SUCCESS; }
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue(VkDevice, uint32_t family, uint32_t index, VkQueue* queue) {
|
||||
assert(family == 1 && index == 0); *queue = handle<VkQueue>(3);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties* memory) {
|
||||
memory->memoryTypeCount = 2;
|
||||
memory->memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
memory->memoryTypes[1].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
(coherent ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT : 0);
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(VkDevice, const VkImageCreateInfo* info, const VkAllocationCallbacks*, VkImage* out) {
|
||||
assert(info->format == VK_FORMAT_R8G8B8A8_UNORM);
|
||||
assert(info->extent.width == 2 && info->extent.height == 2 && info->extent.depth == 1);
|
||||
assert(info->mipLevels == 1 && info->arrayLayers == 1 && info->samples == VK_SAMPLE_COUNT_1_BIT);
|
||||
assert(info->tiling == VK_IMAGE_TILING_OPTIMAL);
|
||||
assert(info->usage == (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT));
|
||||
++images; return create(out);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyImage(VkDevice, VkImage image, const VkAllocationCallbacks*) { destroy(image); }
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements(VkDevice, VkImage, VkMemoryRequirements* requirements) {
|
||||
*requirements = {256, 256, 1};
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory(VkDevice, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks*, VkDeviceMemory* out) {
|
||||
auto status = create(out);
|
||||
if (status == VK_SUCCESS) allocations[*out].resize(info->allocationSize);
|
||||
return status;
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkFreeMemory(VkDevice, VkDeviceMemory memory, const VkAllocationCallbacks*) {
|
||||
assert(allocations.erase(memory) == 1); destroy(memory);
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice, VkImage, VkDeviceMemory memory, VkDeviceSize) {
|
||||
image_memory = memory; return result();
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(VkDevice, const VkBufferCreateInfo* info, const VkAllocationCallbacks*, VkBuffer* out) {
|
||||
assert(info->size == 16 && info->usage == VK_BUFFER_USAGE_TRANSFER_SRC_BIT); return create(out);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(VkDevice, VkBuffer buffer, const VkAllocationCallbacks*) { destroy(buffer); }
|
||||
VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements(VkDevice, VkBuffer, VkMemoryRequirements* requirements) {
|
||||
*requirements = {256, 256, 2};
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice, VkBuffer, VkDeviceMemory memory, VkDeviceSize) {
|
||||
buffer_memory = memory; return result();
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory(VkDevice, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size,
|
||||
VkMemoryMapFlags, void** out) {
|
||||
assert(offset == 0 && size == VK_WHOLE_SIZE);
|
||||
auto status = result();
|
||||
if (status == VK_SUCCESS) *out = allocations.at(memory).data();
|
||||
return status;
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkUnmapMemory(VkDevice, VkDeviceMemory memory) { assert(allocations.contains(memory)); }
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkFlushMappedMemoryRanges(VkDevice, uint32_t count, const VkMappedMemoryRange* range) {
|
||||
assert(!coherent && count == 1 && range->offset == 0 && range->size == VK_WHOLE_SIZE);
|
||||
assert(range->memory == buffer_memory); ++flushes; return result();
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool(VkDevice, const VkCommandPoolCreateInfo* info, const VkAllocationCallbacks*, VkCommandPool* out) {
|
||||
assert(info->queueFamilyIndex == 1); return create(out);
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool(VkDevice, VkCommandPool pool, const VkAllocationCallbacks*) { destroy(pool); }
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers(VkDevice, const VkCommandBufferAllocateInfo*, VkCommandBuffer* out) {
|
||||
auto status = result(); if (status == VK_SUCCESS) *out = handle<VkCommandBuffer>(4); return status;
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle(VkQueue queue) {
|
||||
assert(queue == handle<VkQueue>(3)); auto status = result(); if (status == VK_SUCCESS) complete(); return status;
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer(VkCommandBuffer, VkCommandBufferResetFlags) {
|
||||
assert(pending.empty()); recorded.clear(); return result();
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { return result(); }
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(VkCommandBuffer, VkPipelineStageFlags, VkPipelineStageFlags,
|
||||
VkDependencyFlags, uint32_t, const VkMemoryBarrier*, uint32_t, const VkBufferMemoryBarrier*,
|
||||
uint32_t count, const VkImageMemoryBarrier* barriers) {
|
||||
assert(count == 1);
|
||||
const auto b = barriers[0];
|
||||
assert(b.srcQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED && b.dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED);
|
||||
assert(b.subresourceRange.aspectMask == VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
assert(b.subresourceRange.levelCount == 1 && b.subresourceRange.layerCount == 1);
|
||||
recorded.push_back([b] { assert(layout == b.oldLayout); layout = b.newLayout; });
|
||||
}
|
||||
VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage(VkCommandBuffer, VkBuffer, VkImage, VkImageLayout target,
|
||||
uint32_t count, const VkBufferImageCopy* copy) {
|
||||
assert(count == 1 && target == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
assert(copy->bufferRowLength == 0 && copy->bufferImageHeight == 0 && copy->bufferOffset == 0);
|
||||
assert(copy->imageExtent.width == 2 && copy->imageExtent.height == 2 && copy->imageExtent.depth == 1);
|
||||
recorded.push_back([] {
|
||||
assert(layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
std::copy_n(allocations.at(buffer_memory).begin(), 16, allocations.at(image_memory).begin());
|
||||
});
|
||||
}
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer(VkCommandBuffer) { return result(); }
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(VkQueue queue, uint32_t count, const VkSubmitInfo* submit, VkFence) {
|
||||
assert(queue == handle<VkQueue>(3) && count == 1 && submit->commandBufferCount == 1);
|
||||
auto status = result();
|
||||
if (status == VK_SUCCESS) { pending.insert(pending.end(), recorded.begin(), recorded.end()); ++submits; }
|
||||
return status;
|
||||
}
|
||||
} // extern C
|
||||
|
||||
int main() {
|
||||
const std::array<unsigned char, 16> first{255,0,0,255, 0,255,0,128, 0,0,255,0, 255,255,255,255};
|
||||
const std::array<unsigned char, 16> second{0,0,0,0, 4,5,6,7, 8,9,10,11, 12,13,14,15};
|
||||
for (bool use_coherent : {true, false}) {
|
||||
reset(); coherent = use_coherent;
|
||||
{
|
||||
frameyap::OverlayTexture texture(2, 2, instance_extensions, select_device, device_extensions);
|
||||
throws([&] { texture.texture(); });
|
||||
throws([&] { texture.upload(std::span(first).first(15)); });
|
||||
texture.upload(first);
|
||||
const auto descriptor = texture.texture();
|
||||
const auto image = static_cast<vr::VRVulkanTextureData_t*>(descriptor.handle)->m_nImage;
|
||||
consume(texture);
|
||||
texture.upload(second); // must drain first read before replacing staging bytes
|
||||
assert(observed.size() == 1 && std::ranges::equal(observed[0], first));
|
||||
assert(texture.texture().handle == descriptor.handle);
|
||||
assert(static_cast<vr::VRVulkanTextureData_t*>(descriptor.handle)->m_nImage == image);
|
||||
consume(texture);
|
||||
assert(images == 1 && submits == 2 && flushes == (coherent ? 0u : 2u));
|
||||
}
|
||||
assert(observed.size() == 2 && std::ranges::equal(observed[1], second));
|
||||
assert(live.empty() && allocations.empty());
|
||||
const auto operation_count = calls;
|
||||
// Every fallible allocation/upload operation must unwind all owned
|
||||
// resources, including a failure while a previous frame is pending.
|
||||
for (unsigned failure = 1; failure <= operation_count; ++failure) {
|
||||
reset(); fail_at = failure;
|
||||
throws([&] {
|
||||
frameyap::OverlayTexture texture(2, 2, instance_extensions, select_device, device_extensions);
|
||||
texture.upload(first); consume(texture);
|
||||
texture.upload(second); consume(texture);
|
||||
});
|
||||
assert(live.empty() && allocations.empty() && pending.empty());
|
||||
}
|
||||
fail_at = 0;
|
||||
}
|
||||
reset();
|
||||
throws([&] { frameyap::OverlayTexture texture(0, 2, instance_extensions, select_device, device_extensions); });
|
||||
assert(calls == 0); // invalid sizes must not initialize Vulkan
|
||||
throws([&] {
|
||||
frameyap::OverlayTexture texture(2, 2, instance_extensions,
|
||||
[](VkInstance) { return VkPhysicalDevice{}; }, device_extensions);
|
||||
});
|
||||
assert(live.empty()); // never pick an unrelated GPU when SteamVR returns none
|
||||
}
|
||||
Reference in new issue
Block a user