mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Magma): re-emit dynamic state after rasterizer-discard draws
This commit is contained in:
@@ -735,6 +735,7 @@ list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.cpp
|
||||
# P5 b1: the block-granularity persistent-map push the record arm makes mandatory (T2).
|
||||
MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp
|
||||
# P13 W5: the verb emitters and the monolith verb port, and the client GPU-write set
|
||||
|
||||
@@ -397,6 +397,12 @@ namespace MobileGL::MG_Config {
|
||||
// a device probe at renderer bring-up (one region vs. per-slice regions); ForceOn splits
|
||||
// on any device; ForceOff is the negative control that replays the dropped slices.
|
||||
QuirkOverride MagmaSplitSliceCopies = QuirkOverride::Auto;
|
||||
// MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD: DirectVulkan sets the dynamic state again after
|
||||
// every rasterizer-discard draw (WireDiscardStateProbe.h). On Adreno 750 a draw with a
|
||||
// rasterizerDiscardEnable pipeline drops the dynamic viewport/scissor, and later draws that
|
||||
// rely on it draw nothing. Auto defers to a device probe at renderer bring-up; ForceOn
|
||||
// re-emits on any device; ForceOff is the negative control that replays the lost state.
|
||||
QuirkOverride MagmaReemitStateAfterDiscard = QuirkOverride::Auto;
|
||||
// --- MGPipe (the disaggregation plan's explicit frontend/backend boundary) ---
|
||||
// MOBILEGL_PIPE_PUSH: per-subsystem bitmask selecting which state the frontend
|
||||
// PUSHES over MGPipe instead of leaving the backend to pull it out of GLContext.
|
||||
|
||||
@@ -422,6 +422,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
features.MagmaPrimGenQueryReroute = QueryEnvQuirkOverride("MOBILEGL_MAGMA_PRIMGEN_QUERY_REROUTE");
|
||||
features.MagmaE5RawReads = QueryEnvQuirkOverride("MOBILEGL_MAGMA_E5_RAW_READS");
|
||||
features.MagmaSplitSliceCopies = QueryEnvQuirkOverride("MOBILEGL_MAGMA_SPLIT_SLICE_COPIES");
|
||||
features.MagmaReemitStateAfterDiscard = QueryEnvQuirkOverride("MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD");
|
||||
// MGPipe. Nothing here needs adding to an allow-list: InitializeAcceptedEnvVariables
|
||||
// accepts every MOBILEGL_ / LIBGL_ prefixed variable in the environment, so a name
|
||||
// that starts with MOBILEGL_ is visible to these queries by construction.
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "WireColorBlitFilter.h"
|
||||
#include "WireDepthResolveArm.h"
|
||||
#include "WireCopySliceProbe.h"
|
||||
#include "WireDiscardStateProbe.h"
|
||||
#include "WireDepthResolveProbe.h"
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
@@ -3523,6 +3524,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
shadow.dynamicTailValid = false;
|
||||
}
|
||||
|
||||
// A rasterizer-discard draw consumes the dynamic state it was recorded with on some drivers
|
||||
// (WireDiscardStateProbe.h): every dynamic value is forgotten, so the next draw's tail sets its own
|
||||
// again instead of trusting what the shadow says the command buffer holds. Binds stay valid.
|
||||
void VulkanRenderer::ForgetDynamicStateAfterDiscardDraw() {
|
||||
auto& shadow = *g_dynamicStateShadow;
|
||||
shadow.viewportValid = false;
|
||||
shadow.scissorValid = false;
|
||||
shadow.blendConstantsValid = false;
|
||||
shadow.depthBiasValid = false;
|
||||
shadow.lineWidthValid = false;
|
||||
shadow.stencilValid = false;
|
||||
shadow.fragmentShadingRateValid = false;
|
||||
shadow.dynamicTailValid = false;
|
||||
}
|
||||
|
||||
void VulkanRenderer::ApplyDynamicDrawStateTail(FrameContext::FrameData& frame, const IntVec2& extent,
|
||||
Bool isDefaultFbo, Uint32 viewportCount) {
|
||||
auto& shadow = *g_dynamicStateShadow;
|
||||
@@ -8528,6 +8544,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Same terms again.
|
||||
ArmWireE5RawReads();
|
||||
ArmWireCopySliceSplit();
|
||||
ArmWireDiscardStateReemit();
|
||||
}
|
||||
|
||||
// P7 gate 5 (g5-msprobe): THE ARM ORDER OF THE MULTISAMPLE DEPTH/STENCIL RESOLVE IS A MEASURED
|
||||
@@ -8709,6 +8726,47 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
armed ? "are split one slice per region" : "go as one region");
|
||||
}
|
||||
|
||||
// THE RE-EMIT AFTER A DISCARD DRAW IS ARMED BY MEASUREMENT (WireDiscardStateProbe.h), once per
|
||||
// device identity, or forced by MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD.
|
||||
void VulkanRenderer::ArmWireDiscardStateReemit() {
|
||||
const MG_Config::QuirkOverride setting = MG_Config::Features.MagmaReemitStateAfterDiscard;
|
||||
Bool armed = setting == MG_Config::QuirkOverride::ForceOn;
|
||||
if (setting == MG_Config::QuirkOverride::Auto) {
|
||||
struct Entry {
|
||||
WireDepthResolveDeviceIdentity identity;
|
||||
WireDiscardStateProbeVerdict verdict;
|
||||
};
|
||||
static std::mutex s_mutex;
|
||||
static Vector<Entry> s_entries;
|
||||
const WireDepthResolveDeviceIdentity identity = MakeWireDepthResolveDeviceIdentity(
|
||||
m_physicalDevice.properties, false, WireDepthResolveProbeKnob::Measure);
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
const Entry* found = nullptr;
|
||||
for (const Entry& entry : s_entries)
|
||||
if (entry.identity == identity) found = &entry;
|
||||
if (found == nullptr) {
|
||||
WireDiscardStateProbeContext context;
|
||||
context.physicalDevice = m_physicalDevice.handle;
|
||||
context.device = m_device;
|
||||
context.queue = m_graphicsQueue;
|
||||
context.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
|
||||
const WireDiscardStateProbeMeasurement measurement = RunWireDiscardStateProbe(context);
|
||||
const WireDiscardStateProbeVerdict verdict = EvaluateWireDiscardStateProbe(measurement);
|
||||
if (measurement.fenceWaitTimedOut)
|
||||
MGLOG_W("DirectVulkan: the discard state probe timed out waiting on its own submission; its "
|
||||
"Vulkan objects are deliberately leaked");
|
||||
MGLOG_I("DirectVulkan: discard state probe verdict=%s: %s", WireDiscardStateProbeVerdictName(verdict),
|
||||
DescribeWireDiscardStateProbe(measurement).c_str());
|
||||
s_entries.push_back({identity, verdict});
|
||||
found = &s_entries.back();
|
||||
}
|
||||
armed = found->verdict == WireDiscardStateProbeVerdict::DiscardDrawDropsState;
|
||||
}
|
||||
m_wireReemitStateAfterDiscard = armed;
|
||||
MGLOG_I("DirectVulkan: dynamic state %s",
|
||||
armed ? "is set again after every rasterizer-discard draw" : "carries across discard draws");
|
||||
}
|
||||
|
||||
void VulkanRenderer::ArmPrimGenReroute() {
|
||||
using namespace MG_Util::SelfTest;
|
||||
m_primGenRerouteKind = PrimGenRerouteKind::None;
|
||||
|
||||
@@ -799,6 +799,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// CopyImageSubData then records one region per slice. Set by ArmWireCopySliceSplit at bring-up.
|
||||
Bool m_wireSplitCrossFormatSliceCopies = false;
|
||||
void ArmWireCopySliceSplit();
|
||||
// A rasterizer-discard draw drops the dynamic viewport/scissor it was recorded with on some
|
||||
// drivers (WireDiscardStateProbe.h): there the draw path forgets the dynamic-state shadow after
|
||||
// each discard draw (ForgetDynamicStateAfterDiscardDraw). Set by ArmWireDiscardStateReemit.
|
||||
Bool m_wireReemitStateAfterDiscard = false;
|
||||
void ArmWireDiscardStateReemit();
|
||||
void ForgetDynamicStateAfterDiscardDraw();
|
||||
void ArmWireE5RawReads();
|
||||
public:
|
||||
// The kit when the route is armed, else nullptr. VkTextureManager asks before a level copy.
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
// 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};
|
||||
info.stageCount = 2;
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
#version 450
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = vec4(0.0, 1.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.h
|
||||
// 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
|
||||
#pragma once
|
||||
|
||||
#include <Config.h>
|
||||
#include <Includes.h>
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
// DYNAMIC STATE ACROSS A RASTERIZER-DISCARD DRAW, and the probe that decides whether a device needs it
|
||||
// set again.
|
||||
//
|
||||
// Dynamic viewport and scissor are command-buffer state: set once, they hold for every later draw
|
||||
// whose pipeline declares them dynamic, across pipeline binds and render passes. Magma's draw path
|
||||
// relies on that - its dynamic-state shadow skips a vkCmdSetViewport/vkCmdSetScissor whose value the
|
||||
// command buffer already holds. On Adreno 750 a DRAW with a rasterizerDiscardEnable pipeline drops
|
||||
// that state: the next draw that inherits it instead of setting it again draws nothing, in the same
|
||||
// render pass or a later one. Binding the discard pipeline and setting the state without drawing
|
||||
// with it is harmless, and so is the same draw with a rasterizing pipeline.
|
||||
//
|
||||
// The GL pattern is a vertex shader that writes with imageStore under GL_RASTERIZER_DISCARD, then a
|
||||
// draw that reads what it wrote (KHR-GL4x.shader_image_load_store.advanced-sync-imageAccess and
|
||||
// -vertexArray): the second draw has the same viewport and scissor, so the shadow sets neither, and
|
||||
// it is lost.
|
||||
//
|
||||
// Where the probe below measured the defect, the draw path forgets the dynamic-state shadow after
|
||||
// every discard draw (VulkanRenderer::ForgetDynamicStateAfterDiscardDraw), so the next draw sets its
|
||||
// own. Armed by VulkanRenderer::ArmWireDiscardStateReemit at bring-up and reported in the POST's
|
||||
// "Known Driver Bugs" (MG_Util/SelfTest/DriverBugProbes.cpp); MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD
|
||||
// forces it.
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct WireDiscardStateProbeMeasurement {
|
||||
Bool ran = false;
|
||||
Bool fenceWaitTimedOut = false;
|
||||
String failureReason;
|
||||
// Pixels of each lane's 16x16 target.
|
||||
Uint32 pixels = 0;
|
||||
// SUBJECT: a discard draw, then a full-target draw that inherits its viewport and scissor.
|
||||
// Green pixels (all of them, when correct).
|
||||
Uint32 subjectGreen = 0;
|
||||
// CONTROL: the same with the first draw rasterizing (clipped away). Must be all green.
|
||||
Uint32 controlGreen = 0;
|
||||
};
|
||||
|
||||
enum class WireDiscardStateProbeVerdict : Uint8 {
|
||||
NotRun,
|
||||
Inconclusive,
|
||||
Clean,
|
||||
// The defect: the draw after the discard draw lost the state, the one after the clipped draw
|
||||
// did not.
|
||||
DiscardDrawDropsState,
|
||||
};
|
||||
|
||||
inline const char* WireDiscardStateProbeVerdictName(WireDiscardStateProbeVerdict verdict) {
|
||||
switch (verdict) {
|
||||
case WireDiscardStateProbeVerdict::NotRun: return "not-run";
|
||||
case WireDiscardStateProbeVerdict::Inconclusive: return "inconclusive";
|
||||
case WireDiscardStateProbeVerdict::Clean: return "clean";
|
||||
case WireDiscardStateProbeVerdict::DiscardDrawDropsState: return "discard-draw-drops-state";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
inline WireDiscardStateProbeVerdict EvaluateWireDiscardStateProbe(const WireDiscardStateProbeMeasurement& m) {
|
||||
if (!m.ran || m.fenceWaitTimedOut || m.pixels == 0) return WireDiscardStateProbeVerdict::NotRun;
|
||||
if (m.controlGreen != m.pixels) return WireDiscardStateProbeVerdict::Inconclusive;
|
||||
return m.subjectGreen != m.pixels ? WireDiscardStateProbeVerdict::DiscardDrawDropsState
|
||||
: WireDiscardStateProbeVerdict::Clean;
|
||||
}
|
||||
|
||||
String DescribeWireDiscardStateProbe(const WireDiscardStateProbeMeasurement& measurement);
|
||||
|
||||
struct WireDiscardStateProbeContext {
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
// Must be otherwise idle for the probe's duration.
|
||||
VkQueue queue = VK_NULL_HANDLE;
|
||||
Uint32 queueFamilyIndex = 0;
|
||||
};
|
||||
|
||||
// One submission on its own command pool, a bounded fence wait (on timeout every object is
|
||||
// leaked and the measurement says so), teardown on every other path.
|
||||
WireDiscardStateProbeMeasurement RunWireDiscardStateProbe(const WireDiscardStateProbeContext& context);
|
||||
|
||||
// The POST's entry: a throwaway device with one graphics queue and no features.
|
||||
WireDiscardStateProbeMeasurement RunWireDiscardStateProbeOnThrowawayDevice(
|
||||
PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice,
|
||||
Uint32 graphicsQueueFamilyIndex);
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
@@ -0,0 +1,8 @@
|
||||
#version 450
|
||||
// The discard state probe's draws (WireDiscardStateProbe.h): vertices 0-3 are a full-viewport strip,
|
||||
// vertices 4-7 the same strip far outside the clip volume (the control's first draw).
|
||||
void main() {
|
||||
const vec2 corners[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
const vec2 offset = gl_VertexIndex >= 4 ? vec2(8.0) : vec2(0.0);
|
||||
gl_Position = vec4(corners[gl_VertexIndex & 3] + offset, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// Generated from WireDiscardStateProbe.vert / WireDiscardStateProbe.frag (Vulkan 1.0 / SPIR-V 1.0) by
|
||||
// scripts/bake_internal_shaders.py, which runs MG_Test's BakedInternalShadersTest with the IN-TREE
|
||||
// glslang and splices the words it emits into the arrays below. Do not edit the words by hand:
|
||||
// MOBILEGL_BAKED_INTERNAL_SHADERS (the same test, run as a gate) recompiles every source in its table
|
||||
// and reds on the first word that disagrees.
|
||||
#pragma once
|
||||
|
||||
static constexpr Uint32 kWireDiscardStateProbeVertexSpirv[] = {
|
||||
0x07230203u,
|
||||
0x00010000u,
|
||||
0x0008000bu,
|
||||
0x00000037u,
|
||||
0x00000000u,
|
||||
0x00020011u,
|
||||
0x00000001u,
|
||||
0x0006000bu,
|
||||
0x00000001u,
|
||||
0x4c534c47u,
|
||||
0x6474732eu,
|
||||
0x3035342eu,
|
||||
0x00000000u,
|
||||
0x0003000eu,
|
||||
0x00000000u,
|
||||
0x00000001u,
|
||||
0x0007000fu,
|
||||
0x00000000u,
|
||||
0x00000004u,
|
||||
0x6e69616du,
|
||||
0x00000000u,
|
||||
0x0000000cu,
|
||||
0x0000001eu,
|
||||
0x00030003u,
|
||||
0x00000002u,
|
||||
0x000001c2u,
|
||||
0x00040005u,
|
||||
0x00000004u,
|
||||
0x6e69616du,
|
||||
0x00000000u,
|
||||
0x00040005u,
|
||||
0x00000009u,
|
||||
0x7366666fu,
|
||||
0x00007465u,
|
||||
0x00060005u,
|
||||
0x0000000cu,
|
||||
0x565f6c67u,
|
||||
0x65747265u,
|
||||
0x646e4978u,
|
||||
0x00007865u,
|
||||
0x00060005u,
|
||||
0x0000001cu,
|
||||
0x505f6c67u,
|
||||
0x65567265u,
|
||||
0x78657472u,
|
||||
0x00000000u,
|
||||
0x00060006u,
|
||||
0x0000001cu,
|
||||
0x00000000u,
|
||||
0x505f6c67u,
|
||||
0x7469736fu,
|
||||
0x006e6f69u,
|
||||
0x00070006u,
|
||||
0x0000001cu,
|
||||
0x00000001u,
|
||||
0x505f6c67u,
|
||||
0x746e696fu,
|
||||
0x657a6953u,
|
||||
0x00000000u,
|
||||
0x00070006u,
|
||||
0x0000001cu,
|
||||
0x00000002u,
|
||||
0x435f6c67u,
|
||||
0x4470696cu,
|
||||
0x61747369u,
|
||||
0x0065636eu,
|
||||
0x00070006u,
|
||||
0x0000001cu,
|
||||
0x00000003u,
|
||||
0x435f6c67u,
|
||||
0x446c6c75u,
|
||||
0x61747369u,
|
||||
0x0065636eu,
|
||||
0x00030005u,
|
||||
0x0000001eu,
|
||||
0x00000000u,
|
||||
0x00050005u,
|
||||
0x0000002du,
|
||||
0x65646e69u,
|
||||
0x6c626178u,
|
||||
0x00000065u,
|
||||
0x00040047u,
|
||||
0x0000000cu,
|
||||
0x0000000bu,
|
||||
0x0000002au,
|
||||
0x00030047u,
|
||||
0x0000001cu,
|
||||
0x00000002u,
|
||||
0x00050048u,
|
||||
0x0000001cu,
|
||||
0x00000000u,
|
||||
0x0000000bu,
|
||||
0x00000000u,
|
||||
0x00050048u,
|
||||
0x0000001cu,
|
||||
0x00000001u,
|
||||
0x0000000bu,
|
||||
0x00000001u,
|
||||
0x00050048u,
|
||||
0x0000001cu,
|
||||
0x00000002u,
|
||||
0x0000000bu,
|
||||
0x00000003u,
|
||||
0x00050048u,
|
||||
0x0000001cu,
|
||||
0x00000003u,
|
||||
0x0000000bu,
|
||||
0x00000004u,
|
||||
0x00020013u,
|
||||
0x00000002u,
|
||||
0x00030021u,
|
||||
0x00000003u,
|
||||
0x00000002u,
|
||||
0x00030016u,
|
||||
0x00000006u,
|
||||
0x00000020u,
|
||||
0x00040017u,
|
||||
0x00000007u,
|
||||
0x00000006u,
|
||||
0x00000002u,
|
||||
0x00040020u,
|
||||
0x00000008u,
|
||||
0x00000007u,
|
||||
0x00000007u,
|
||||
0x00040015u,
|
||||
0x0000000au,
|
||||
0x00000020u,
|
||||
0x00000001u,
|
||||
0x00040020u,
|
||||
0x0000000bu,
|
||||
0x00000001u,
|
||||
0x0000000au,
|
||||
0x0004003bu,
|
||||
0x0000000bu,
|
||||
0x0000000cu,
|
||||
0x00000001u,
|
||||
0x0004002bu,
|
||||
0x0000000au,
|
||||
0x0000000eu,
|
||||
0x00000004u,
|
||||
0x00020014u,
|
||||
0x0000000fu,
|
||||
0x0004002bu,
|
||||
0x00000006u,
|
||||
0x00000011u,
|
||||
0x41000000u,
|
||||
0x0005002cu,
|
||||
0x00000007u,
|
||||
0x00000012u,
|
||||
0x00000011u,
|
||||
0x00000011u,
|
||||
0x0004002bu,
|
||||
0x00000006u,
|
||||
0x00000013u,
|
||||
0x00000000u,
|
||||
0x0005002cu,
|
||||
0x00000007u,
|
||||
0x00000014u,
|
||||
0x00000013u,
|
||||
0x00000013u,
|
||||
0x00040017u,
|
||||
0x00000015u,
|
||||
0x0000000fu,
|
||||
0x00000002u,
|
||||
0x00040017u,
|
||||
0x00000018u,
|
||||
0x00000006u,
|
||||
0x00000004u,
|
||||
0x00040015u,
|
||||
0x00000019u,
|
||||
0x00000020u,
|
||||
0x00000000u,
|
||||
0x0004002bu,
|
||||
0x00000019u,
|
||||
0x0000001au,
|
||||
0x00000001u,
|
||||
0x0004001cu,
|
||||
0x0000001bu,
|
||||
0x00000006u,
|
||||
0x0000001au,
|
||||
0x0006001eu,
|
||||
0x0000001cu,
|
||||
0x00000018u,
|
||||
0x00000006u,
|
||||
0x0000001bu,
|
||||
0x0000001bu,
|
||||
0x00040020u,
|
||||
0x0000001du,
|
||||
0x00000003u,
|
||||
0x0000001cu,
|
||||
0x0004003bu,
|
||||
0x0000001du,
|
||||
0x0000001eu,
|
||||
0x00000003u,
|
||||
0x0004002bu,
|
||||
0x0000000au,
|
||||
0x0000001fu,
|
||||
0x00000000u,
|
||||
0x0004002bu,
|
||||
0x00000019u,
|
||||
0x00000020u,
|
||||
0x00000004u,
|
||||
0x0004001cu,
|
||||
0x00000021u,
|
||||
0x00000007u,
|
||||
0x00000020u,
|
||||
0x0004002bu,
|
||||
0x00000006u,
|
||||
0x00000022u,
|
||||
0xbf800000u,
|
||||
0x0005002cu,
|
||||
0x00000007u,
|
||||
0x00000023u,
|
||||
0x00000022u,
|
||||
0x00000022u,
|
||||
0x0004002bu,
|
||||
0x00000006u,
|
||||
0x00000024u,
|
||||
0x3f800000u,
|
||||
0x0005002cu,
|
||||
0x00000007u,
|
||||
0x00000025u,
|
||||
0x00000024u,
|
||||
0x00000022u,
|
||||
0x0005002cu,
|
||||
0x00000007u,
|
||||
0x00000026u,
|
||||
0x00000022u,
|
||||
0x00000024u,
|
||||
0x0005002cu,
|
||||
0x00000007u,
|
||||
0x00000027u,
|
||||
0x00000024u,
|
||||
0x00000024u,
|
||||
0x0007002cu,
|
||||
0x00000021u,
|
||||
0x00000028u,
|
||||
0x00000023u,
|
||||
0x00000025u,
|
||||
0x00000026u,
|
||||
0x00000027u,
|
||||
0x0004002bu,
|
||||
0x0000000au,
|
||||
0x0000002au,
|
||||
0x00000003u,
|
||||
0x00040020u,
|
||||
0x0000002cu,
|
||||
0x00000007u,
|
||||
0x00000021u,
|
||||
0x00040020u,
|
||||
0x00000035u,
|
||||
0x00000003u,
|
||||
0x00000018u,
|
||||
0x00050036u,
|
||||
0x00000002u,
|
||||
0x00000004u,
|
||||
0x00000000u,
|
||||
0x00000003u,
|
||||
0x000200f8u,
|
||||
0x00000005u,
|
||||
0x0004003bu,
|
||||
0x00000008u,
|
||||
0x00000009u,
|
||||
0x00000007u,
|
||||
0x0004003bu,
|
||||
0x0000002cu,
|
||||
0x0000002du,
|
||||
0x00000007u,
|
||||
0x0004003du,
|
||||
0x0000000au,
|
||||
0x0000000du,
|
||||
0x0000000cu,
|
||||
0x000500afu,
|
||||
0x0000000fu,
|
||||
0x00000010u,
|
||||
0x0000000du,
|
||||
0x0000000eu,
|
||||
0x00050050u,
|
||||
0x00000015u,
|
||||
0x00000016u,
|
||||
0x00000010u,
|
||||
0x00000010u,
|
||||
0x000600a9u,
|
||||
0x00000007u,
|
||||
0x00000017u,
|
||||
0x00000016u,
|
||||
0x00000012u,
|
||||
0x00000014u,
|
||||
0x0003003eu,
|
||||
0x00000009u,
|
||||
0x00000017u,
|
||||
0x0004003du,
|
||||
0x0000000au,
|
||||
0x00000029u,
|
||||
0x0000000cu,
|
||||
0x000500c7u,
|
||||
0x0000000au,
|
||||
0x0000002bu,
|
||||
0x00000029u,
|
||||
0x0000002au,
|
||||
0x0003003eu,
|
||||
0x0000002du,
|
||||
0x00000028u,
|
||||
0x00050041u,
|
||||
0x00000008u,
|
||||
0x0000002eu,
|
||||
0x0000002du,
|
||||
0x0000002bu,
|
||||
0x0004003du,
|
||||
0x00000007u,
|
||||
0x0000002fu,
|
||||
0x0000002eu,
|
||||
0x0004003du,
|
||||
0x00000007u,
|
||||
0x00000030u,
|
||||
0x00000009u,
|
||||
0x00050081u,
|
||||
0x00000007u,
|
||||
0x00000031u,
|
||||
0x0000002fu,
|
||||
0x00000030u,
|
||||
0x00050051u,
|
||||
0x00000006u,
|
||||
0x00000032u,
|
||||
0x00000031u,
|
||||
0x00000000u,
|
||||
0x00050051u,
|
||||
0x00000006u,
|
||||
0x00000033u,
|
||||
0x00000031u,
|
||||
0x00000001u,
|
||||
0x00070050u,
|
||||
0x00000018u,
|
||||
0x00000034u,
|
||||
0x00000032u,
|
||||
0x00000033u,
|
||||
0x00000013u,
|
||||
0x00000024u,
|
||||
0x00050041u,
|
||||
0x00000035u,
|
||||
0x00000036u,
|
||||
0x0000001eu,
|
||||
0x0000001fu,
|
||||
0x0003003eu,
|
||||
0x00000036u,
|
||||
0x00000034u,
|
||||
0x000100fdu,
|
||||
0x00010038u,
|
||||
};
|
||||
|
||||
static constexpr Uint32 kWireDiscardStateProbeFragmentSpirv[] = {
|
||||
0x07230203u,
|
||||
0x00010000u,
|
||||
0x0008000bu,
|
||||
0x0000000du,
|
||||
0x00000000u,
|
||||
0x00020011u,
|
||||
0x00000001u,
|
||||
0x0006000bu,
|
||||
0x00000001u,
|
||||
0x4c534c47u,
|
||||
0x6474732eu,
|
||||
0x3035342eu,
|
||||
0x00000000u,
|
||||
0x0003000eu,
|
||||
0x00000000u,
|
||||
0x00000001u,
|
||||
0x0006000fu,
|
||||
0x00000004u,
|
||||
0x00000004u,
|
||||
0x6e69616du,
|
||||
0x00000000u,
|
||||
0x00000009u,
|
||||
0x00030010u,
|
||||
0x00000004u,
|
||||
0x00000007u,
|
||||
0x00030003u,
|
||||
0x00000002u,
|
||||
0x000001c2u,
|
||||
0x00040005u,
|
||||
0x00000004u,
|
||||
0x6e69616du,
|
||||
0x00000000u,
|
||||
0x00040005u,
|
||||
0x00000009u,
|
||||
0x6f6c6f63u,
|
||||
0x00000072u,
|
||||
0x00040047u,
|
||||
0x00000009u,
|
||||
0x0000001eu,
|
||||
0x00000000u,
|
||||
0x00020013u,
|
||||
0x00000002u,
|
||||
0x00030021u,
|
||||
0x00000003u,
|
||||
0x00000002u,
|
||||
0x00030016u,
|
||||
0x00000006u,
|
||||
0x00000020u,
|
||||
0x00040017u,
|
||||
0x00000007u,
|
||||
0x00000006u,
|
||||
0x00000004u,
|
||||
0x00040020u,
|
||||
0x00000008u,
|
||||
0x00000003u,
|
||||
0x00000007u,
|
||||
0x0004003bu,
|
||||
0x00000008u,
|
||||
0x00000009u,
|
||||
0x00000003u,
|
||||
0x0004002bu,
|
||||
0x00000006u,
|
||||
0x0000000au,
|
||||
0x00000000u,
|
||||
0x0004002bu,
|
||||
0x00000006u,
|
||||
0x0000000bu,
|
||||
0x3f800000u,
|
||||
0x0007002cu,
|
||||
0x00000007u,
|
||||
0x0000000cu,
|
||||
0x0000000au,
|
||||
0x0000000bu,
|
||||
0x0000000au,
|
||||
0x0000000bu,
|
||||
0x00050036u,
|
||||
0x00000002u,
|
||||
0x00000004u,
|
||||
0x00000000u,
|
||||
0x00000003u,
|
||||
0x000200f8u,
|
||||
0x00000005u,
|
||||
0x0003003eu,
|
||||
0x00000009u,
|
||||
0x0000000cu,
|
||||
0x000100fdu,
|
||||
0x00010038u,
|
||||
};
|
||||
@@ -982,6 +982,11 @@
|
||||
if (m_fragmentShadingRate.Pipeline) {
|
||||
ShadowedSetFragmentShadingRate(frame.commandBuffer, s_vkCmdSetFragmentShadingRateKHR);
|
||||
}
|
||||
// This draw's state is set; where a discard draw then loses it (WireDiscardStateProbe.h),
|
||||
// the next draw must set its own rather than inherit.
|
||||
if (m_wireReemitStateAfterDiscard &&
|
||||
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard))
|
||||
ForgetDynamicStateAfterDiscardDraw();
|
||||
// Storage images this draw writes are what the next sampled-image barrier must cover.
|
||||
if (programObj.hasStorageImages) BumpWireImageWriteEpoch();
|
||||
if (MG_Util::PipeStats::Enabled()) MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::Draws, 1);
|
||||
|
||||
@@ -62,6 +62,7 @@ using Uint32 = std::uint32_t;
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h>
|
||||
|
||||
struct BakedShader {
|
||||
const char* name; // ctest entry name, and the bake file's stem
|
||||
@@ -107,6 +108,19 @@ const BakedShader kBakedShaders[] = {
|
||||
"kWireE5RawCopy3DSpirv", EShLangCompute,
|
||||
glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0,
|
||||
kWireE5RawCopy3DSpirv),
|
||||
// The rasterizer-discard state probe (WireDiscardStateProbe.h).
|
||||
MGL_BAKED_ROW("WireDiscardStateProbeVertex",
|
||||
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.vert",
|
||||
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h",
|
||||
"kWireDiscardStateProbeVertexSpirv", EShLangVertex,
|
||||
glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0,
|
||||
kWireDiscardStateProbeVertexSpirv),
|
||||
MGL_BAKED_ROW("WireDiscardStateProbeFragment",
|
||||
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.frag",
|
||||
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h",
|
||||
"kWireDiscardStateProbeFragmentSpirv", EShLangFragment,
|
||||
glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0,
|
||||
kWireDiscardStateProbeFragmentSpirv),
|
||||
MGL_BAKED_ROW("WireYuvConvertVertex",
|
||||
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvert.vert",
|
||||
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -2578,11 +2579,34 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
return DriverBugFinding{"Cross-format 2D-array <-> 3D image copies drop slices",
|
||||
DriverBugVerdict::Fixed, Move(detail)};
|
||||
}
|
||||
|
||||
// The same probe the Magma renderer runs at bring-up (VulkanRenderer::ArmWireDiscardStateReemit).
|
||||
Optional<DriverBugFinding> ProbeDiscardStateBug(const VulkanDriverBugProbeContext& context) {
|
||||
using namespace MG_Backend::DirectVulkan;
|
||||
if (context.getInstanceProcAddr == nullptr) return std::nullopt;
|
||||
const WireDiscardStateProbeMeasurement measurement = RunWireDiscardStateProbeOnThrowawayDevice(
|
||||
context.getInstanceProcAddr, context.instance, context.physicalDevice,
|
||||
context.graphicsQueueFamilyIndex);
|
||||
const WireDiscardStateProbeVerdict verdict = EvaluateWireDiscardStateProbe(measurement);
|
||||
MGLOG_I("Driver POST: discard state probe verdict=%s: %s", WireDiscardStateProbeVerdictName(verdict),
|
||||
DescribeWireDiscardStateProbe(measurement).c_str());
|
||||
if (verdict != WireDiscardStateProbeVerdict::DiscardDrawDropsState) return std::nullopt;
|
||||
String detail =
|
||||
"a draw with a rasterizerDiscardEnable pipeline drops the dynamic viewport/scissor, so a later "
|
||||
"draw that inherits them draws nothing, while after a rasterizing draw it does: ";
|
||||
detail += DescribeWireDiscardStateProbe(measurement);
|
||||
detail +=
|
||||
". Magma sets the dynamic state again after every discard draw "
|
||||
"(MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD=0 turns it off)";
|
||||
return DriverBugFinding{"Rasterizer-discard draws drop dynamic viewport/scissor",
|
||||
DriverBugVerdict::Fixed, Move(detail)};
|
||||
}
|
||||
using VulkanDriverBugProbeFn = Optional<DriverBugFinding> (*)(const VulkanDriverBugProbeContext&);
|
||||
constexpr VulkanDriverBugProbeFn kVulkanDriverBugProbes[] = {
|
||||
&ProbeDepthStencilResolvePassBug,
|
||||
&ProbeE5TransferReadNormalizeBug,
|
||||
&ProbeCrossFormatSliceCopyBug,
|
||||
&ProbeDiscardStateBug,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user