mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
411 lines
23 KiB
C++
411 lines
23 KiB
C++
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.cpp
|
|
// Copyright (c) 2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
//
|
|
// See WireDiscardStateProbe.h. Plumbing as in WireDepthResolveProbe.cpp: one throwaway command
|
|
// buffer, a bounded fence wait that leaks rather than idle-waits a hung GPU.
|
|
|
|
#include "WireDiscardStateProbe.h"
|
|
|
|
#include <vulkan/vulkan_core.h>
|
|
|
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
|
#include "WireDiscardStateProbeSpirv.h"
|
|
|
|
String DescribeWireDiscardStateProbe(const WireDiscardStateProbeMeasurement& measurement) {
|
|
if (!measurement.ran)
|
|
return format("not run ({})", measurement.failureReason.empty() ? "?" : measurement.failureReason);
|
|
return format("a draw inheriting viewport/scissor after a discard draw: {}/{} pixels; after a clipped "
|
|
"rasterizing draw: {}/{}",
|
|
measurement.subjectGreen, measurement.pixels, measurement.controlGreen, measurement.pixels);
|
|
}
|
|
|
|
namespace {
|
|
constexpr Uint32 kExtent = 16;
|
|
constexpr Uint32 kPixels = kExtent * kExtent;
|
|
constexpr VkFormat kColorFormat = VK_FORMAT_R8G8B8A8_UNORM;
|
|
constexpr Uint64 kFenceTimeoutNs = 5ull * 1000ull * 1000ull * 1000ull;
|
|
|
|
struct ProbeObjects {
|
|
VkDevice device = VK_NULL_HANDLE;
|
|
Vector<VkImage> images;
|
|
Vector<VkImageView> views;
|
|
Vector<VkBuffer> buffers;
|
|
Vector<VkDeviceMemory> memories;
|
|
Vector<VkFramebuffer> framebuffers;
|
|
Vector<VkPipeline> pipelines;
|
|
Vector<VkShaderModule> modules;
|
|
VkRenderPass pass = VK_NULL_HANDLE;
|
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
|
VkCommandPool commandPool = VK_NULL_HANDLE;
|
|
VkFence fence = VK_NULL_HANDLE;
|
|
|
|
void Destroy() {
|
|
for (VkPipeline pipeline : pipelines) vkDestroyPipeline(device, pipeline, nullptr);
|
|
for (VkShaderModule module : modules) vkDestroyShaderModule(device, module, nullptr);
|
|
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
|
for (VkFramebuffer framebuffer : framebuffers) vkDestroyFramebuffer(device, framebuffer, nullptr);
|
|
vkDestroyRenderPass(device, pass, nullptr);
|
|
for (VkImageView view : views) vkDestroyImageView(device, view, nullptr);
|
|
for (VkImage image : images) vkDestroyImage(device, image, nullptr);
|
|
for (VkBuffer buffer : buffers) vkDestroyBuffer(device, buffer, nullptr);
|
|
for (VkDeviceMemory memory : memories) vkFreeMemory(device, memory, nullptr);
|
|
vkDestroyCommandPool(device, commandPool, nullptr);
|
|
vkDestroyFence(device, fence, nullptr);
|
|
}
|
|
};
|
|
|
|
Optional<Uint32> PickMemoryType(const VkPhysicalDeviceMemoryProperties& properties, Uint32 allowed,
|
|
VkMemoryPropertyFlags wanted, VkMemoryPropertyFlags required) {
|
|
Optional<Uint32> fallback;
|
|
for (Uint32 i = 0; i < properties.memoryTypeCount; ++i) {
|
|
if (!(allowed & (1u << i))) continue;
|
|
const VkMemoryPropertyFlags flags = properties.memoryTypes[i].propertyFlags;
|
|
if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) continue;
|
|
if ((flags & required) != required) continue;
|
|
if ((flags & wanted) == wanted) return i;
|
|
if (!fallback) fallback = i;
|
|
}
|
|
return fallback;
|
|
}
|
|
} // namespace
|
|
|
|
WireDiscardStateProbeMeasurement RunWireDiscardStateProbe(const WireDiscardStateProbeContext& context) {
|
|
WireDiscardStateProbeMeasurement measurement;
|
|
const VkDevice device = context.device;
|
|
if (device == VK_NULL_HANDLE || context.queue == VK_NULL_HANDLE) {
|
|
measurement.failureReason = "no device or queue";
|
|
return measurement;
|
|
}
|
|
|
|
ProbeObjects objects;
|
|
objects.device = device;
|
|
Bool leak = false;
|
|
struct Teardown {
|
|
ProbeObjects& objects;
|
|
Bool& leak;
|
|
~Teardown() {
|
|
if (!leak) objects.Destroy();
|
|
}
|
|
} teardown{objects, leak};
|
|
const auto fail = [&](String reason) {
|
|
measurement.ran = false;
|
|
measurement.failureReason = Move(reason);
|
|
return measurement;
|
|
};
|
|
|
|
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
|
vkGetPhysicalDeviceMemoryProperties(context.physicalDevice, &memoryProperties);
|
|
const auto allocate = [&](const VkMemoryRequirements& requirements, VkMemoryPropertyFlags wanted,
|
|
VkMemoryPropertyFlags required, VkDeviceMemory& out, Bool* coherent) -> Bool {
|
|
const Optional<Uint32> type =
|
|
PickMemoryType(memoryProperties, requirements.memoryTypeBits, wanted, required);
|
|
if (!type) return false;
|
|
if (coherent)
|
|
*coherent = (memoryProperties.memoryTypes[*type].propertyFlags &
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
|
VkMemoryAllocateInfo info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
info.allocationSize = requirements.size;
|
|
info.memoryTypeIndex = *type;
|
|
if (vkAllocateMemory(device, &info, nullptr, &out) != VK_SUCCESS) return false;
|
|
objects.memories.push_back(out);
|
|
return true;
|
|
};
|
|
|
|
// One render pass for both lanes: clear, draw twice, hand the target to the readback copy.
|
|
{
|
|
VkAttachmentDescription attachment{};
|
|
attachment.format = kColorFormat;
|
|
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachment.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
const VkAttachmentReference reference{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
VkSubpassDescription subpass{};
|
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpass.colorAttachmentCount = 1;
|
|
subpass.pColorAttachments = &reference;
|
|
// The readback copy waits on the pass's attachment writes.
|
|
VkSubpassDependency dependency{};
|
|
dependency.srcSubpass = 0;
|
|
dependency.dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependency.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependency.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
VkRenderPassCreateInfo info{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
|
info.attachmentCount = 1;
|
|
info.pAttachments = &attachment;
|
|
info.subpassCount = 1;
|
|
info.pSubpasses = &subpass;
|
|
info.dependencyCount = 1;
|
|
info.pDependencies = &dependency;
|
|
if (vkCreateRenderPass(device, &info, nullptr, &objects.pass) != VK_SUCCESS)
|
|
return fail("vkCreateRenderPass failed");
|
|
}
|
|
|
|
// Two lanes, SUBJECT (the first draw discarded) and CONTROL (the first draw clipped away):
|
|
// each has its own target, framebuffer and readback.
|
|
struct Lane {
|
|
VkImage target = VK_NULL_HANDLE;
|
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
|
VkBuffer readback = VK_NULL_HANDLE;
|
|
VkDeviceMemory readbackMemory = VK_NULL_HANDLE;
|
|
Bool readbackCoherent = false;
|
|
VkPipeline first = VK_NULL_HANDLE;
|
|
} lanes[2];
|
|
for (Lane& lane : lanes) {
|
|
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
imageInfo.format = kColorFormat;
|
|
imageInfo.extent = {kExtent, kExtent, 1};
|
|
imageInfo.mipLevels = imageInfo.arrayLayers = 1;
|
|
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
if (vkCreateImage(device, &imageInfo, nullptr, &lane.target) != VK_SUCCESS)
|
|
return fail("vkCreateImage failed");
|
|
objects.images.push_back(lane.target);
|
|
VkMemoryRequirements requirements{};
|
|
vkGetImageMemoryRequirements(device, lane.target, &requirements);
|
|
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
|
|
if (!allocate(requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0, imageMemory, nullptr) ||
|
|
vkBindImageMemory(device, lane.target, imageMemory, 0) != VK_SUCCESS)
|
|
return fail("image memory failed");
|
|
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
viewInfo.image = lane.target;
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = kColorFormat;
|
|
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
VkImageView view = VK_NULL_HANDLE;
|
|
if (vkCreateImageView(device, &viewInfo, nullptr, &view) != VK_SUCCESS)
|
|
return fail("vkCreateImageView failed");
|
|
objects.views.push_back(view);
|
|
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
|
framebufferInfo.renderPass = objects.pass;
|
|
framebufferInfo.attachmentCount = 1;
|
|
framebufferInfo.pAttachments = &view;
|
|
framebufferInfo.width = framebufferInfo.height = kExtent;
|
|
framebufferInfo.layers = 1;
|
|
if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &lane.framebuffer) != VK_SUCCESS)
|
|
return fail("vkCreateFramebuffer failed");
|
|
objects.framebuffers.push_back(lane.framebuffer);
|
|
|
|
VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
|
bufferInfo.size = kPixels * 4;
|
|
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
if (vkCreateBuffer(device, &bufferInfo, nullptr, &lane.readback) != VK_SUCCESS)
|
|
return fail("vkCreateBuffer failed");
|
|
objects.buffers.push_back(lane.readback);
|
|
vkGetBufferMemoryRequirements(device, lane.readback, &requirements);
|
|
if (!allocate(requirements, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
|
lane.readbackMemory, &lane.readbackCoherent) ||
|
|
vkBindBufferMemory(device, lane.readback, lane.readbackMemory, 0) != VK_SUCCESS)
|
|
return fail("readback memory failed");
|
|
}
|
|
|
|
VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
|
if (vkCreatePipelineLayout(device, &layoutInfo, nullptr, &objects.pipelineLayout) != VK_SUCCESS)
|
|
return fail("vkCreatePipelineLayout failed");
|
|
const auto shader = [&](const Uint32* code, SizeT bytes, VkShaderModule& out) -> Bool {
|
|
VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
|
info.codeSize = bytes;
|
|
info.pCode = code;
|
|
if (vkCreateShaderModule(device, &info, nullptr, &out) != VK_SUCCESS) return false;
|
|
objects.modules.push_back(out);
|
|
return true;
|
|
};
|
|
VkShaderModule vertex = VK_NULL_HANDLE, fragment = VK_NULL_HANDLE;
|
|
if (!shader(kWireDiscardStateProbeVertexSpirv, sizeof(kWireDiscardStateProbeVertexSpirv), vertex) ||
|
|
!shader(kWireDiscardStateProbeFragmentSpirv, sizeof(kWireDiscardStateProbeFragmentSpirv), fragment))
|
|
return fail("vkCreateShaderModule failed");
|
|
|
|
// Every pipeline declares viewport and scissor dynamic; only `discard` differs.
|
|
const auto makePipeline = [&](Bool discard, VkPipeline& out) -> Bool {
|
|
VkPipelineShaderStageCreateInfo stages[2]{};
|
|
for (auto& stage : stages) {
|
|
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
stage.pName = "main";
|
|
}
|
|
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
stages[0].module = vertex;
|
|
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
stages[1].module = fragment;
|
|
VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
|
VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
|
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
|
VkPipelineViewportStateCreateInfo viewportState{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
|
viewportState.viewportCount = viewportState.scissorCount = 1;
|
|
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
|
raster.rasterizerDiscardEnable = discard ? VK_TRUE : VK_FALSE;
|
|
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
|
raster.cullMode = VK_CULL_MODE_NONE;
|
|
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
raster.lineWidth = 1.0f;
|
|
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
|
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
VkPipelineColorBlendAttachmentState blendAttachment{};
|
|
blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
|
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
|
blend.attachmentCount = 1;
|
|
blend.pAttachments = &blendAttachment;
|
|
constexpr VkDynamicState kDynamic[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
|
VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
|
|
dynamic.dynamicStateCount = 2;
|
|
dynamic.pDynamicStates = kDynamic;
|
|
VkGraphicsPipelineCreateInfo info{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
|
// As PipelineFactory builds it: a static discard has no fragment shader state, so no
|
|
// fragment stage (VUID-VkGraphicsPipelineCreateInfo-pStages-06894).
|
|
info.stageCount = discard ? 1u : 2u;
|
|
info.pStages = stages;
|
|
info.pVertexInputState = &vertexInput;
|
|
info.pInputAssemblyState = &assembly;
|
|
info.pViewportState = &viewportState;
|
|
info.pRasterizationState = &raster;
|
|
info.pMultisampleState = &samples;
|
|
info.pColorBlendState = &blend;
|
|
info.pDynamicState = &dynamic;
|
|
info.layout = objects.pipelineLayout;
|
|
info.renderPass = objects.pass;
|
|
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &info, nullptr, &out) != VK_SUCCESS) return false;
|
|
objects.pipelines.push_back(out);
|
|
return true;
|
|
};
|
|
VkPipeline second = VK_NULL_HANDLE;
|
|
if (!makePipeline(true, lanes[0].first) || !makePipeline(false, lanes[1].first) || !makePipeline(false, second))
|
|
return fail("pipeline creation failed");
|
|
|
|
VkCommandPoolCreateInfo commandPoolInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
|
commandPoolInfo.queueFamilyIndex = context.queueFamilyIndex;
|
|
if (vkCreateCommandPool(device, &commandPoolInfo, nullptr, &objects.commandPool) != VK_SUCCESS)
|
|
return fail("vkCreateCommandPool failed");
|
|
VkCommandBufferAllocateInfo commandInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
commandInfo.commandPool = objects.commandPool;
|
|
commandInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
commandInfo.commandBufferCount = 1;
|
|
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
|
if (vkAllocateCommandBuffers(device, &commandInfo, &cmd) != VK_SUCCESS)
|
|
return fail("vkAllocateCommandBuffers failed");
|
|
VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
if (vkBeginCommandBuffer(cmd, &beginInfo) != VK_SUCCESS) return fail("vkBeginCommandBuffer failed");
|
|
|
|
const VkClearValue black{{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
|
const VkViewport viewport{0.0f, 0.0f, static_cast<Float>(kExtent), static_cast<Float>(kExtent), 0.0f, 1.0f};
|
|
const VkRect2D scissor{{0, 0}, {kExtent, kExtent}};
|
|
for (Uint32 i = 0; i < 2; ++i) {
|
|
const Lane& lane = lanes[i];
|
|
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
|
begin.renderPass = objects.pass;
|
|
begin.framebuffer = lane.framebuffer;
|
|
begin.renderArea = scissor;
|
|
begin.clearValueCount = 1;
|
|
begin.pClearValues = &black;
|
|
vkCmdBeginRenderPass(cmd, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
|
// The first draw sets the state: discarded (SUBJECT) or a strip the vertex shader puts
|
|
// outside the clip volume (CONTROL, vertices 4-7).
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, lane.first);
|
|
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
|
vkCmdDraw(cmd, 4, 1, i == 0 ? 0 : 4, 0);
|
|
// The second draw covers the target with what it inherits.
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, second);
|
|
vkCmdDraw(cmd, 4, 1, 0, 0);
|
|
vkCmdEndRenderPass(cmd);
|
|
VkBufferImageCopy copy{};
|
|
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
copy.imageExtent = {kExtent, kExtent, 1};
|
|
vkCmdCopyImageToBuffer(cmd, lane.target, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, lane.readback, 1, ©);
|
|
}
|
|
VkMemoryBarrier hostBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
|
hostBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
hostBarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &hostBarrier, 0,
|
|
nullptr, 0, nullptr);
|
|
if (vkEndCommandBuffer(cmd) != VK_SUCCESS) return fail("vkEndCommandBuffer failed");
|
|
|
|
VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
|
if (vkCreateFence(device, &fenceInfo, nullptr, &objects.fence) != VK_SUCCESS)
|
|
return fail("vkCreateFence failed");
|
|
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
submit.commandBufferCount = 1;
|
|
submit.pCommandBuffers = &cmd;
|
|
if (const VkResult result = vkQueueSubmit(context.queue, 1, &submit, objects.fence); result != VK_SUCCESS)
|
|
return fail(format("vkQueueSubmit failed (VkResult {})", static_cast<Int>(result)));
|
|
const VkResult waited = vkWaitForFences(device, 1, &objects.fence, VK_TRUE, kFenceTimeoutNs);
|
|
if (waited == VK_TIMEOUT) {
|
|
leak = true;
|
|
measurement.fenceWaitTimedOut = true;
|
|
return fail("the probe's fence did not signal within 5 s");
|
|
}
|
|
if (waited != VK_SUCCESS) return fail(format("vkWaitForFences failed (VkResult {})", static_cast<Int>(waited)));
|
|
|
|
Uint32* green[2] = {&measurement.subjectGreen, &measurement.controlGreen};
|
|
for (Uint32 i = 0; i < 2; ++i) {
|
|
const Lane& lane = lanes[i];
|
|
void* mapped = nullptr;
|
|
if (vkMapMemory(device, lane.readbackMemory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS || !mapped)
|
|
return fail("vkMapMemory failed");
|
|
if (!lane.readbackCoherent) {
|
|
VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE};
|
|
range.memory = lane.readbackMemory;
|
|
range.size = VK_WHOLE_SIZE;
|
|
vkInvalidateMappedMemoryRanges(device, 1, &range);
|
|
}
|
|
const Uint8* pixels = static_cast<const Uint8*>(mapped);
|
|
for (Uint32 p = 0; p < kPixels; ++p)
|
|
if (pixels[p * 4] < 16 && pixels[p * 4 + 1] > 240) ++*green[i];
|
|
vkUnmapMemory(device, lane.readbackMemory);
|
|
}
|
|
measurement.pixels = kPixels;
|
|
measurement.ran = true;
|
|
return measurement;
|
|
}
|
|
|
|
WireDiscardStateProbeMeasurement RunWireDiscardStateProbeOnThrowawayDevice(
|
|
PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice,
|
|
Uint32 graphicsQueueFamilyIndex) {
|
|
WireDiscardStateProbeMeasurement measurement;
|
|
const auto createDevice =
|
|
reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
|
|
if (createDevice == nullptr) {
|
|
measurement.failureReason = "no vkCreateDevice";
|
|
return measurement;
|
|
}
|
|
const Float priority = 1.0f;
|
|
VkDeviceQueueCreateInfo queueInfo{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
|
queueInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
|
|
queueInfo.queueCount = 1;
|
|
queueInfo.pQueuePriorities = &priority;
|
|
VkDeviceCreateInfo deviceInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
|
deviceInfo.queueCreateInfoCount = 1;
|
|
deviceInfo.pQueueCreateInfos = &queueInfo;
|
|
VkDevice device = VK_NULL_HANDLE;
|
|
if (const VkResult result = createDevice(physicalDevice, &deviceInfo, nullptr, &device);
|
|
result != VK_SUCCESS || device == VK_NULL_HANDLE) {
|
|
measurement.failureReason = format("vkCreateDevice failed (VkResult {})", static_cast<Int>(result));
|
|
return measurement;
|
|
}
|
|
WireDiscardStateProbeContext context;
|
|
context.physicalDevice = physicalDevice;
|
|
context.device = device;
|
|
vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &context.queue);
|
|
context.queueFamilyIndex = graphicsQueueFamilyIndex;
|
|
measurement = RunWireDiscardStateProbe(context);
|
|
if (!measurement.fenceWaitTimedOut) vkDestroyDevice(device, nullptr);
|
|
return measurement;
|
|
}
|
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|