mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend] (P7): every silent exit on the Magma wire draw path has a name, a tally and a decline line
Rule I admits two shapes for a refusal on the wire arm, a decline and a named Fatal. The draw path had a third one the rule does not allow: roughly forty bare `return false` statements with no log, no counter and no census entry, so a dropped draw left no trace anywhere - which is how the OpenRA device divergence survived four device windows with client and server logs that were line-identical between a passing and a failing run. WireDeclines.def names every one of them; WireDeclineTally.h expands that into a counter table plus MGL_WIRE_DECLINE_AT (the tally always moves, the log is once per site), in the shape of PipeApplier.h's ServerVerbSink tallies and for the same reason (R-16: a probe may not arm against a stub). Covered: SetupWireDraw and DispatchWireCompute (25), the three exits of PrepareWireTextureResources, the four of SyncTextureResourceByHandle, and UploadPendingWireLevels' five false exits PLUS the two that only `continue` - those report SUCCESS while leaving the entry pending, i.e. a draw that samples a level which was never uploaded, so they are counted apart. Also the pending-upload gauges (entries, bytes, peak bytes), sampled at the funnel, because a refused upload does not consume its entry and the level shadow behind it stays alive. Shutdown dumps the tally and is silent when it is all zero: a green lane stays quiet and a lane that dropped a draw cannot. Plus the drain probe behind MOBILEGL_MAGMA_WIREBUF_PROBE, which is what measured that a host replay reaches the mid-frame frame-boundary drain zero times.
This commit is contained in:
@@ -33,6 +33,8 @@
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
// P7 wave 0: the seam WireDescriptorFatal dies through. See MG_Pipe/PipeSessionFail.h.
|
||||
#include <MG_Pipe/PipeSessionFail.h>
|
||||
// P7 wave 2 package B3: rule I's tally for this file's silent wire-draw exits.
|
||||
#include "WireDeclineTally.h"
|
||||
#endif
|
||||
#include <vulkan/utility/vk_format_utils.h>
|
||||
#include <algorithm>
|
||||
@@ -2944,7 +2946,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool UniformManager::PrepareWireTextureResources(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj) {
|
||||
if (!program.IsWire()) return true;
|
||||
if (programObj.declinedDescriptors || m_textureManager == nullptr) return false;
|
||||
if (programObj.declinedDescriptors || m_textureManager == nullptr) {
|
||||
MGL_WIRE_DECLINE_AT(DescriptorDeclined, "the program carries a descriptor MobileGL could not resolve");
|
||||
return false;
|
||||
}
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
// Resolve writable images first: aliases sampled by the same draw must
|
||||
// see the final STORAGE-capable allocation before any view is built.
|
||||
@@ -2958,11 +2963,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const Uint32 count = BindingDescriptorCount(programObj, binding);
|
||||
for (Uint32 element = 0; element < count; ++element) {
|
||||
const Int location = baseLocation + static_cast<Int>(element);
|
||||
if (baseLocation < 0 || !program.UniformLocationsAliasSameUniform(baseLocation, location))
|
||||
if (baseLocation < 0 || !program.UniformLocationsAliasSameUniform(baseLocation, location)) {
|
||||
MGL_WIRE_DECLINE_AT(SamplerLocationAlias,
|
||||
"binding %u element %u does not alias one sampler uniform", binding,
|
||||
element);
|
||||
return false;
|
||||
}
|
||||
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >=
|
||||
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false;
|
||||
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) {
|
||||
MGL_WIRE_DECLINE_AT(SamplerUnitRange, "sampler/image unit %d is out of range", unit);
|
||||
return false;
|
||||
}
|
||||
const auto handle = storage ? state.BoundShaderImages[unit].Res
|
||||
: state.BoundSamplerViews[unit].Texture;
|
||||
// Null is the wire's unbound/incomplete binding. Its native
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_Remote/Server/StagedTextureStore.h>
|
||||
#include "../DirectVulkan.h"
|
||||
// P7 wave 2 package B3: rule I's tally for the silent exits on this file's wire arm.
|
||||
#include "WireDeclineTally.h"
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
@@ -2385,6 +2387,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: pending upload for level %u exceeds the "
|
||||
"image's %u levels; left pending",
|
||||
handle.Slot, handle.Gen, static_cast<Uint32>(level), resource.mipLevels);
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadLevelBeyondChain);
|
||||
continue;
|
||||
}
|
||||
const IntVec3 glExtent = store.LevelExtentOrUndefined(key, uploadTarget, level);
|
||||
@@ -2405,9 +2408,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
texelSize.y() != static_cast<Int>(std::max(resource.extent.height >> level, 1u)) ||
|
||||
(volume && texelSize.z() != static_cast<Int>(std::max(resource.depth >> level, 1u))) ||
|
||||
(!volume && Uint64(ResolveUploadArrayLayer(static_cast<TextureUploadTarget>(uploadTarget))) +
|
||||
static_cast<Uint64>(texelSize.z()) > resource.arrayLayers))
|
||||
static_cast<Uint64>(texelSize.z()) > resource.arrayLayers)) {
|
||||
// NOT `incomplete`: this arm reports SUCCESS while leaving the entry pending,
|
||||
// so the draw proceeds against texels that were never uploaded. Counted
|
||||
// separately for exactly that reason (B3).
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadLevelExtentMismatch);
|
||||
continue;
|
||||
}
|
||||
if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) {
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadEmptyLevel);
|
||||
incomplete = true;
|
||||
continue;
|
||||
}
|
||||
@@ -2423,6 +2432,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
"level %u left pending",
|
||||
handle.Slot, handle.Gen, static_cast<Int>(resource.format),
|
||||
static_cast<Uint32>(level));
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadConversion);
|
||||
incomplete = true;
|
||||
continue;
|
||||
}
|
||||
@@ -2435,6 +2445,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
(resource.aspect & VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
if (vkuFormatIsCompressed(resource.format) || !levelTexels ||
|
||||
item.byteSize % levelTexels != 0 || item.byteSize / levelTexels == 0) {
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadTexelArithmetic);
|
||||
incomplete = true;
|
||||
continue;
|
||||
}
|
||||
@@ -2454,6 +2465,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (offset[axis] < 0 || extent[axis] <= 0 ||
|
||||
static_cast<Uint64>(offset[axis]) + static_cast<Uint64>(extent[axis]) >
|
||||
static_cast<Uint64>(texelSize[axis])) {
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadRegionOutOfBounds);
|
||||
incomplete = true;
|
||||
return;
|
||||
}
|
||||
@@ -2496,7 +2508,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
addRegion({region.X, region.Y, region.Z, region.W, region.H, region.D});
|
||||
}
|
||||
}
|
||||
if (incomplete) return false;
|
||||
// B3: the pending gauge is sampled here, on every pass through the funnel, because a
|
||||
// refusal below does NOT consume the entries - they and their level shadows stay
|
||||
// alive for the next draw to trip over (and, on a heavy-texture trace, to accumulate).
|
||||
{
|
||||
Uint64 pendingBytes = 0;
|
||||
for (const auto& pending : record.PendingUploads) {
|
||||
pendingBytes += static_cast<Uint64>(
|
||||
store.LevelByteSize(MG_Remote::Server::StagedTextureStore::KeyForHandle(handle),
|
||||
pending.UploadTarget, pending.Level));
|
||||
}
|
||||
WireDeclineTally::SetPendingGauge(static_cast<Uint64>(record.PendingUploads.size()), pendingBytes);
|
||||
}
|
||||
if (incomplete) {
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadIncomplete);
|
||||
return false;
|
||||
}
|
||||
if (items.empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -2505,7 +2532,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Submit older renderer work before adding the new bytes to the batch,
|
||||
// otherwise Draw(old T), TexSubImage(T), Draw(new T) can upload before
|
||||
// the first draw. A clean texture never reaches this submission boundary.
|
||||
if (pVulkanRenderer && !pVulkanRenderer->FlushWirePendingCommandsForTextureUpdate()) return false;
|
||||
if (pVulkanRenderer && !pVulkanRenderer->FlushWirePendingCommandsForTextureUpdate()) {
|
||||
WireDeclineTally::Count(WireDeclineSite::UploadFlushFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
// UploadDirtyMipLevels' batching rules, verbatim: flush an open batch that already
|
||||
// writes this image and was touched by the open recording, and bound the staging bytes
|
||||
@@ -2659,7 +2689,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 level = 0, layer = 0;
|
||||
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
|
||||
const auto storage = ResolveWireTextureStorage(handle, level, layer, &viewFormat);
|
||||
if (MG_Pipe::MGPipeHandleIsNull(storage)) return nullptr;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(storage)) {
|
||||
WireDeclineTally::Count(WireDeclineSite::TexResolveStorage);
|
||||
return nullptr;
|
||||
}
|
||||
if (storage != handle) {
|
||||
auto* resource = SyncTextureResourceByHandle(storage, false, requireStorage);
|
||||
if (!resource) return nullptr;
|
||||
@@ -2698,12 +2731,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return &resource;
|
||||
}
|
||||
if (!SyncWireTextureShape(record, resource, requireStorage)) {
|
||||
WireDeclineTally::Count(WireDeclineSite::TexShapeSync);
|
||||
return nullptr;
|
||||
}
|
||||
if (!renderbuffer && !UploadPendingWireLevels(handle, record, resource)) {
|
||||
WireDeclineTally::Count(WireDeclineSite::TexUploadPendingLevels);
|
||||
return nullptr;
|
||||
}
|
||||
if (requireStorage && (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) == 0) {
|
||||
WireDeclineTally::Count(WireDeclineSite::TexStorageUsage);
|
||||
return nullptr;
|
||||
}
|
||||
if (requireStorage && (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) == 0) return nullptr;
|
||||
resource.syncedWireSerial = record.Serial;
|
||||
return &resource;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
// P5c (G6): the named-blit arm's endpoint resolution runs inside the frontend-keyed scope.
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
// P7 wave 2 package B3: rule I's tally for WireDraw.inc's silent draw drops.
|
||||
#include "WireDeclineTally.h"
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -3453,6 +3455,9 @@ void main() {
|
||||
VK_VERIFY(vkDeviceWaitIdle(m_device));
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P7 wave 2 package B3: the session's decline tally. Silent when nothing declined, so a
|
||||
// green lane stays quiet and a lane that dropped a draw cannot (rule I's observable).
|
||||
WireDeclineTally::Dump("shutdown");
|
||||
DestroyWireDrawPass();
|
||||
CollectWireObjects(m_submitCounter, true);
|
||||
ClearAllWireDrawPassCaches();
|
||||
@@ -13688,6 +13693,23 @@ void main() {
|
||||
// loops still rewind the arena and age their caches every 8 iterations -
|
||||
// bounded by 8 iterations' transient usage.
|
||||
++m_drainsSinceLastPresent;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// B3 probe: a drain that reaches here found the GPU caught up and nothing recording.
|
||||
// Every 8th one is treated as a FRAME BOUNDARY - mid-frame. See the note.
|
||||
{
|
||||
static const Bool probe = [] {
|
||||
const char* value = std::getenv("MOBILEGL_MAGMA_WIREBUF_PROBE");
|
||||
return value && value[0] == '1';
|
||||
}();
|
||||
if (probe) {
|
||||
MGLOG_I("WBUF drain#%llu%s submit=%llu completed=%llu",
|
||||
static_cast<unsigned long long>(m_drainsSinceLastPresent),
|
||||
(m_drainsSinceLastPresent % 8) == 0 ? " ***FRAME-BOUNDARY-WORK***" : "",
|
||||
static_cast<unsigned long long>(m_submitCounter),
|
||||
static_cast<unsigned long long>(m_completedSubmitCounter));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if ((m_drainsSinceLastPresent % 8) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h
|
||||
// Copyright (c) 2025-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>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
// P7 wave 2 package B3: the per-site tally behind CONTRACT-P7 §0 rule I.
|
||||
//
|
||||
// Shape is PipeApplier.h's ServerVerbSink tallies, and for the same reason (R-16: a probe may
|
||||
// not arm against a stub). "The retrace passed" and "the retrace silently dropped nine draws"
|
||||
// produce the same exit code and the same log, so the lane has to assert the number that only
|
||||
// this path can move.
|
||||
//
|
||||
// Counters are process-wide and monotone. On the spawn arm they live in the SERVER process,
|
||||
// which is where the draw path runs - a client-side reader would always see zero, so the
|
||||
// two-process case asserts pixels and reads these from the server's own dump.
|
||||
enum class WireDeclineSite : Uint32 {
|
||||
#define MGL_WIRE_DECLINE(name) name,
|
||||
#include "WireDeclines.def"
|
||||
#undef MGL_WIRE_DECLINE
|
||||
Count
|
||||
};
|
||||
|
||||
inline const char* WireDeclineSiteName(WireDeclineSite site) {
|
||||
switch (site) {
|
||||
#define MGL_WIRE_DECLINE(name) \
|
||||
case WireDeclineSite::name: \
|
||||
return #name;
|
||||
#include "WireDeclines.def"
|
||||
#undef MGL_WIRE_DECLINE
|
||||
case WireDeclineSite::Count:
|
||||
break;
|
||||
}
|
||||
return "Unnamed";
|
||||
}
|
||||
|
||||
class WireDeclineTally {
|
||||
public:
|
||||
static constexpr SizeT kSiteCount = static_cast<SizeT>(WireDeclineSite::Count);
|
||||
|
||||
static void Count(WireDeclineSite site) {
|
||||
Slots()[static_cast<SizeT>(site)].fetch_add(1, std::memory_order_relaxed);
|
||||
Total().fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static Uint64 Get(WireDeclineSite site) {
|
||||
return Slots()[static_cast<SizeT>(site)].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static Uint64 TotalDeclines() { return Total().load(std::memory_order_relaxed); }
|
||||
|
||||
// The memory half (package M1's thread): a refused upload does not consume its
|
||||
// PendingUpload entry, so the entry and the level shadow behind it stay alive. These are
|
||||
// GAUGES, not counters - the last observed live depth, sampled where the refusal happens -
|
||||
// so a monotone climb across frames is the signature that a refusal is also a leak.
|
||||
static void SetPendingGauge(Uint64 entries, Uint64 bytes) {
|
||||
PendingEntries().store(entries, std::memory_order_relaxed);
|
||||
PendingBytes().store(bytes, std::memory_order_relaxed);
|
||||
Uint64 peak = PeakPendingBytes().load(std::memory_order_relaxed);
|
||||
while (bytes > peak && !PeakPendingBytes().compare_exchange_weak(peak, bytes, std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
static Uint64 PendingUploadEntries() { return PendingEntries().load(std::memory_order_relaxed); }
|
||||
static Uint64 PendingUploadBytes() { return PendingBytes().load(std::memory_order_relaxed); }
|
||||
static Uint64 PeakPendingUploadBytes() { return PeakPendingBytes().load(std::memory_order_relaxed); }
|
||||
|
||||
static void Reset() {
|
||||
for (SizeT i = 0; i < kSiteCount; ++i) Slots()[i].store(0, std::memory_order_relaxed);
|
||||
Total().store(0, std::memory_order_relaxed);
|
||||
PendingEntries().store(0, std::memory_order_relaxed);
|
||||
PendingBytes().store(0, std::memory_order_relaxed);
|
||||
PeakPendingBytes().store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// One line per site that moved, plus the pending gauges. Called at session teardown and
|
||||
// from the forced-condition harness; silent when nothing declined, so a green lane stays
|
||||
// quiet and a lane that dropped a draw cannot.
|
||||
static void Dump(const char* tag) {
|
||||
if (TotalDeclines() == 0 && PeakPendingUploadBytes() == 0) return;
|
||||
MGLOG_I("MGWIRE-DECLINES[%s] total=%llu pendingEntries=%llu pendingBytes=%llu peakPendingBytes=%llu", tag,
|
||||
static_cast<unsigned long long>(TotalDeclines()),
|
||||
static_cast<unsigned long long>(PendingUploadEntries()),
|
||||
static_cast<unsigned long long>(PendingUploadBytes()),
|
||||
static_cast<unsigned long long>(PeakPendingUploadBytes()));
|
||||
for (SizeT i = 0; i < kSiteCount; ++i) {
|
||||
const Uint64 value = Slots()[i].load(std::memory_order_relaxed);
|
||||
if (!value) continue;
|
||||
MGLOG_I("MGWIRE-DECLINES[%s] %s=%llu", tag, WireDeclineSiteName(static_cast<WireDeclineSite>(i)),
|
||||
static_cast<unsigned long long>(value));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::atomic<Uint64>* Slots() {
|
||||
static std::atomic<Uint64> slots[kSiteCount]{};
|
||||
return slots;
|
||||
}
|
||||
static std::atomic<Uint64>& Total() {
|
||||
static std::atomic<Uint64> total{0};
|
||||
return total;
|
||||
}
|
||||
static std::atomic<Uint64>& PendingEntries() {
|
||||
static std::atomic<Uint64> value{0};
|
||||
return value;
|
||||
}
|
||||
static std::atomic<Uint64>& PendingBytes() {
|
||||
static std::atomic<Uint64> value{0};
|
||||
return value;
|
||||
}
|
||||
static std::atomic<Uint64>& PeakPendingBytes() {
|
||||
static std::atomic<Uint64> value{0};
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
// Every decline goes through this: the tally moves ALWAYS, the log line is once per site.
|
||||
// `return MGL_WIRE_DECLINE_FALSE(Site, "why", ...)` reads as the statement it replaces.
|
||||
#define MGL_WIRE_DECLINE_AT(site, fmt, ...) \
|
||||
do { \
|
||||
::MobileGL::MG_Backend::DirectVulkan::WireDeclineTally::Count( \
|
||||
::MobileGL::MG_Backend::DirectVulkan::WireDeclineSite::site); \
|
||||
MGLOG_E_ONCE("Magma wire draw declined [" #site "]: " fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
#endif // MOBILEGL_BUILD_DISAGGREGATED
|
||||
@@ -0,0 +1,90 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclines.def
|
||||
// Copyright (c) 2025-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
|
||||
|
||||
// P7 wave 2 package B3. THE NAMES OF EVERY WAY A MAGMA WIRE DRAW CAN DECLINE.
|
||||
//
|
||||
// CONTRACT-P7 §0 rule I says a refusal on the wire arm is either a named decline or a named
|
||||
// Fatal. Before B3 the draw path had a third shape the rule does not admit: a bare
|
||||
// `return false` with no log, no counter and no census entry - roughly twenty of them in
|
||||
// SetupWireDraw alone, plus the texture-sync chain behind PrepareWireTextureResources. A draw
|
||||
// that took one of those exits left NO trace anywhere, which is precisely why the OpenRA
|
||||
// device divergence survived four device windows with client and server logs that were
|
||||
// LINE-IDENTICAL between a passing and a failing run.
|
||||
//
|
||||
// Each row below is one such exit. The name is what the decline logs and what the tally
|
||||
// counts, so "which exit did this frame take" is answerable from a counter dump instead of
|
||||
// from a bisect. Rows are append-only: the integration lane asserts specific counters moved.
|
||||
//
|
||||
// MGL_WIRE_DECLINE(Name)
|
||||
|
||||
// ---- SetupWireDraw, in statement order -------------------------------------------------
|
||||
MGL_WIRE_DECLINE(ProgramLinkOrSpirv)
|
||||
MGL_WIRE_DECLINE(PointSizeCapability)
|
||||
MGL_WIRE_DECLINE(TextureResourcesUnprepared)
|
||||
MGL_WIRE_DECLINE(VertexFormatUnsupported)
|
||||
MGL_WIRE_DECLINE(VertexAttributeWindow)
|
||||
MGL_WIRE_DECLINE(VertexStreamCountZero)
|
||||
MGL_WIRE_DECLINE(VertexStreamRead)
|
||||
MGL_WIRE_DECLINE(VertexStreamConverter)
|
||||
MGL_WIRE_DECLINE(VertexTransientUpload)
|
||||
MGL_WIRE_DECLINE(IndexViewMissing)
|
||||
MGL_WIRE_DECLINE(IndexTypeWidth)
|
||||
MGL_WIRE_DECLINE(IndexWindow)
|
||||
MGL_WIRE_DECLINE(IndexStreamRead)
|
||||
MGL_WIRE_DECLINE(IndexTransientUpload)
|
||||
MGL_WIRE_DECLINE(IndexSliceInvalid)
|
||||
MGL_WIRE_DECLINE(UniformBufferBinding)
|
||||
MGL_WIRE_DECLINE(NoAttachments)
|
||||
MGL_WIRE_DECLINE(PipelineUnavailable)
|
||||
MGL_WIRE_DECLINE(CurrentAttributePayload)
|
||||
MGL_WIRE_DECLINE(CurrentAttributeUpload)
|
||||
MGL_WIRE_DECLINE(BeginRenderPass)
|
||||
|
||||
// ---- DispatchWireCompute ----------------------------------------------------------------
|
||||
MGL_WIRE_DECLINE(ComputeProgramLinkOrSpirv)
|
||||
MGL_WIRE_DECLINE(ComputeTextureResourcesUnprepared)
|
||||
MGL_WIRE_DECLINE(ComputePipelineUnavailable)
|
||||
MGL_WIRE_DECLINE(ComputeUniformBufferBinding)
|
||||
|
||||
// ---- UniformManager::PrepareWireTextureResources ----------------------------------------
|
||||
MGL_WIRE_DECLINE(DescriptorDeclined)
|
||||
MGL_WIRE_DECLINE(SamplerLocationAlias)
|
||||
MGL_WIRE_DECLINE(SamplerUnitRange)
|
||||
|
||||
// ---- VkTextureManager::SyncTextureResourceByHandle --------------------------------------
|
||||
MGL_WIRE_DECLINE(TexResolveStorage)
|
||||
MGL_WIRE_DECLINE(TexViewFormat)
|
||||
MGL_WIRE_DECLINE(TexNoApplierRecord)
|
||||
MGL_WIRE_DECLINE(TexRecordDeadOrStale)
|
||||
MGL_WIRE_DECLINE(TexShapeSync)
|
||||
MGL_WIRE_DECLINE(TexUploadPendingLevels)
|
||||
MGL_WIRE_DECLINE(TexStorageUsage)
|
||||
|
||||
// ---- VkTextureManager::UploadPendingWireLevels ------------------------------------------
|
||||
// The five `incomplete`/false exits the investigation named, plus the two that leave an
|
||||
// entry pending WITHOUT failing (a `continue`) - those are the ones that can starve a draw
|
||||
// of its texels while still reporting success, so they are counted separately.
|
||||
MGL_WIRE_DECLINE(UploadLevelBeyondChain)
|
||||
MGL_WIRE_DECLINE(UploadLevelExtentMismatch)
|
||||
MGL_WIRE_DECLINE(UploadEmptyLevel)
|
||||
MGL_WIRE_DECLINE(UploadConversion)
|
||||
MGL_WIRE_DECLINE(UploadTexelArithmetic)
|
||||
MGL_WIRE_DECLINE(UploadRegionOutOfBounds)
|
||||
MGL_WIRE_DECLINE(UploadIncomplete)
|
||||
MGL_WIRE_DECLINE(UploadFlushFailed)
|
||||
|
||||
// ---- VkTextureManager::SyncWireTextureShape ---------------------------------------------
|
||||
MGL_WIRE_DECLINE(ShapeBufferBacked)
|
||||
MGL_WIRE_DECLINE(ShapeViewOfStorage)
|
||||
MGL_WIRE_DECLINE(ShapeNoLevels)
|
||||
MGL_WIRE_DECLINE(ShapeNoVulkanShape)
|
||||
MGL_WIRE_DECLINE(ShapeNoVkFormat)
|
||||
MGL_WIRE_DECLINE(ShapeSampleCount)
|
||||
MGL_WIRE_DECLINE(ShapePreserveFlushFailed)
|
||||
MGL_WIRE_DECLINE(ShapeImageFlagsUnsupported)
|
||||
MGL_WIRE_DECLINE(ShapeCreateImageFailed)
|
||||
@@ -203,7 +203,10 @@
|
||||
Flags<DrawSetupAspect> aspects, const DrawCmdParam& drawParams, const IndexBufferView* indices) {
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto program = WireProgramSource(state.BoundShaderCso);
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) return false;
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
|
||||
MGL_WIRE_DECLINE_AT(ProgramLinkOrSpirv, "program is not linked or carries no SPIR-V");
|
||||
return false;
|
||||
}
|
||||
if (aspects & DrawSetupAspect::IndirectDrawBuffer)
|
||||
WireBufferLegacyFatal();
|
||||
MakeXfbWritesVisible();
|
||||
@@ -224,10 +227,16 @@
|
||||
flags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex;
|
||||
programObject = &m_programFactory->GetOrCreateProgram(program, flags);
|
||||
}
|
||||
if (programObject->pointSizeCapabilityUnsupported) return false;
|
||||
if (programObject->pointSizeCapabilityUnsupported) {
|
||||
MGL_WIRE_DECLINE_AT(PointSizeCapability, "the program writes gl_PointSize and the device cannot express it");
|
||||
return false;
|
||||
}
|
||||
const auto& programObj = *programObject;
|
||||
m_currentDrawXfbCaptureDeclined = programObj.xfbCaptureDeclined;
|
||||
if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) return false;
|
||||
if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) {
|
||||
MGL_WIRE_DECLINE_AT(TextureResourcesUnprepared, "a sampled/storage image could not be made ready; see the Tex* tally");
|
||||
return false;
|
||||
}
|
||||
// Lazy image uploads/promotions can submit the old recording. Resolve
|
||||
// ALL attachments before descriptor binding captures the current cmd.
|
||||
for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
|
||||
@@ -277,6 +286,7 @@
|
||||
"mask 0x%x whose enabled array has no supported vertex format",
|
||||
program.GetExternalIndex(),
|
||||
input.unsupportedAttribMask & programObj.activeVertexInputLocationMask);
|
||||
WireDeclineTally::Count(WireDeclineSite::VertexFormatUnsupported);
|
||||
return false;
|
||||
}
|
||||
Vector<BufferSlice> vertexSlices(input.bindings.size());
|
||||
@@ -291,7 +301,10 @@
|
||||
const VkDeviceSize offset = input.bindingBaseOffsets[binding];
|
||||
const auto type = static_cast<DataType>(attr.Type);
|
||||
const SizeT elementSize = VertexInputStateFactory::GetAttributeByteSize(type, attr.Size, attr.IsBgra);
|
||||
if (offset > slice.size || elementSize > slice.size - offset) return false;
|
||||
if (offset > slice.size || elementSize > slice.size - offset) {
|
||||
MGL_WIRE_DECLINE_AT(VertexAttributeWindow, "an enabled attribute's first element lies outside its buffer");
|
||||
return false;
|
||||
}
|
||||
const auto conversion = input.bindingConversions[binding];
|
||||
if (conversion != VertexInputStateFactory::VertexStreamConversion::None) {
|
||||
const SizeT stride = static_cast<SizeT>(attr.Stride);
|
||||
@@ -300,9 +313,15 @@
|
||||
// name arbitrary vertices, so preserve the entire valid source stream.
|
||||
if (stride && !(aspects & DrawSetupAspect::IndexBuffer) && !resource.Divisor)
|
||||
count = std::min<SizeT>(count, static_cast<SizeT>(drawParams.firstVertex) + drawParams.vertexCount);
|
||||
if (!count) return false;
|
||||
if (!count) {
|
||||
MGL_WIRE_DECLINE_AT(VertexStreamCountZero, "the converted vertex stream would hold no elements");
|
||||
return false;
|
||||
}
|
||||
Vector<Uint8> source((count - 1) * stride + elementSize);
|
||||
if (!m_bufferManager.ReadWireBuffer(resource.Res, offset, source.size(), source.data())) return false;
|
||||
if (!m_bufferManager.ReadWireBuffer(resource.Res, offset, source.size(), source.data())) {
|
||||
MGL_WIRE_DECLINE_AT(VertexStreamRead, "the vertex bytes to convert could not be read back");
|
||||
return false;
|
||||
}
|
||||
Vector<Uint8> converted;
|
||||
// P7 wave 2 package C, CONTRACT-P7 §3.2 `vertex-format-conversion`: DECLINE.
|
||||
// The converter is asked only for a conversion BuildWireVertexInput already
|
||||
@@ -318,10 +337,14 @@
|
||||
"location=%u type=%u size=%u conversion=%d",
|
||||
location, attr.Type, static_cast<Uint32>(attr.Size),
|
||||
static_cast<Int>(conversion));
|
||||
WireDeclineTally::Count(WireDeclineSite::VertexStreamConverter);
|
||||
return false;
|
||||
}
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
|
||||
converted.data(), converted.size(), 16, vertexSlices[binding])) return false;
|
||||
converted.data(), converted.size(), 16, vertexSlices[binding])) {
|
||||
MGL_WIRE_DECLINE_AT(VertexTransientUpload, "the converted vertex stream did not fit the transient arena");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
slice.offset += offset;
|
||||
slice.size -= offset;
|
||||
@@ -332,9 +355,15 @@
|
||||
BufferSlice indexSlice{};
|
||||
VkIndexType indexType = VK_INDEX_TYPE_MAX_ENUM;
|
||||
if (aspects & DrawSetupAspect::IndexBuffer) {
|
||||
if (!indices) return false;
|
||||
if (!indices) {
|
||||
MGL_WIRE_DECLINE_AT(IndexViewMissing, "an indexed draw arrived with no index view");
|
||||
return false;
|
||||
}
|
||||
const SizeT width = MG_Util::GetGLTypeSize(indices->indexType);
|
||||
if (width != 1 && width != 2 && width != 4) return false;
|
||||
if (width != 1 && width != 2 && width != 4) {
|
||||
MGL_WIRE_DECLINE_AT(IndexTypeWidth, "the index type is not 1, 2 or 4 bytes wide");
|
||||
return false;
|
||||
}
|
||||
indexType = width == 4 ? VK_INDEX_TYPE_UINT32 : width == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT8;
|
||||
const auto& restart = MGB_CTX->GetRenderStateParameters();
|
||||
const Uint32 fixed = MG_Util::FixedRestartIndexForGLType(indices->indexType);
|
||||
@@ -350,10 +379,16 @@
|
||||
MagmaWireFatal("index-buffer-resource");
|
||||
const Uint64 offset = state.IndexBuffer.Offset + indices->indexByteOffset;
|
||||
if (offset < state.IndexBuffer.Offset || offset > indexSlice.size ||
|
||||
indices->indexByteSize > indexSlice.size - offset) return false;
|
||||
indices->indexByteSize > indexSlice.size - offset) {
|
||||
MGL_WIRE_DECLINE_AT(IndexWindow, "the index range lies outside its buffer");
|
||||
return false;
|
||||
}
|
||||
if (replaceRestart || expandByte || offset % width) {
|
||||
copied.resize(indices->indexByteSize);
|
||||
if (!m_bufferManager.ReadWireBuffer(state.IndexBuffer.Res, offset, copied.size(), copied.data())) return false;
|
||||
if (!m_bufferManager.ReadWireBuffer(state.IndexBuffer.Res, offset, copied.size(), copied.data())) {
|
||||
MGL_WIRE_DECLINE_AT(IndexStreamRead, "the indices to rewrite could not be read back");
|
||||
return false;
|
||||
}
|
||||
bytes = copied.data();
|
||||
}
|
||||
indexSlice.offset += offset;
|
||||
@@ -377,9 +412,15 @@
|
||||
if (expandByte) indexType = VK_INDEX_TYPE_UINT16;
|
||||
}
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(),
|
||||
bytes, uploadSize, 16, indexSlice)) return false;
|
||||
bytes, uploadSize, 16, indexSlice)) {
|
||||
MGL_WIRE_DECLINE_AT(IndexTransientUpload, "the rewritten indices did not fit the transient arena");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!indexSlice.IsValid()) {
|
||||
MGL_WIRE_DECLINE_AT(IndexSliceInvalid, "no usable index slice was resolved");
|
||||
return false;
|
||||
}
|
||||
if (!indexSlice.IsValid()) return false;
|
||||
}
|
||||
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
@@ -387,7 +428,10 @@
|
||||
// Resolve it before constructing framebuffer views so both roles of that image
|
||||
// name the same allocation even on its first storage-image use.
|
||||
if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) return false;
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) {
|
||||
MGL_WIRE_DECLINE_AT(UniformBufferBinding, "the program's uniform/storage bindings could not be bound");
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<VkAttachmentDescription> attachments;
|
||||
Vector<VkImageViewCreateInfo> viewInfos;
|
||||
@@ -459,7 +503,10 @@
|
||||
depth.attachment = add(fbo->Depth, VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
else if (fbo->Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
||||
depth.attachment = add(fbo->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
if (attachments.empty()) return false;
|
||||
if (attachments.empty()) {
|
||||
MGL_WIRE_DECLINE_AT(NoAttachments, "the draw framebuffer resolved to no attachments");
|
||||
return false;
|
||||
}
|
||||
passKey.width = extent.width;
|
||||
passKey.height = extent.height;
|
||||
passKey.layers = layers;
|
||||
@@ -516,7 +563,10 @@
|
||||
const Uint64 compatibilityId = m_wireRenderPassCompatibility.Intern(pipelinePassKey);
|
||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass,
|
||||
ResolvePrimitiveRestartEnable(aspects, indices), compatibilityId);
|
||||
if (pipeline == VK_NULL_HANDLE) return false;
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
MGL_WIRE_DECLINE_AT(PipelineUnavailable, "no graphics pipeline could be created for this draw");
|
||||
return false;
|
||||
}
|
||||
for (Uint32 i = 0; i < vertexSlices.size(); ++i)
|
||||
vkCmdBindVertexBuffers(frame.commandBuffer, i, 1, &vertexSlices[i].buffer, &vertexSlices[i].offset);
|
||||
Uint32 binding = static_cast<Uint32>(vertexSlices.size());
|
||||
@@ -527,14 +577,23 @@
|
||||
const void* data = nullptr;
|
||||
VkDeviceSize size = 0;
|
||||
if (!TryGetCurrentVertexAttributeUploadPayload(MGB_CTX->GetCurrentVertexAttribute(location),
|
||||
programObj.vertexInputTypes[location], format, data, size)) return false;
|
||||
programObj.vertexInputTypes[location], format, data, size)) {
|
||||
MGL_WIRE_DECLINE_AT(CurrentAttributePayload, "a disabled array's current attribute value has no upload payload");
|
||||
return false;
|
||||
}
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
|
||||
data, size, 16, slice)) return false;
|
||||
data, size, 16, slice)) {
|
||||
MGL_WIRE_DECLINE_AT(CurrentAttributeUpload, "the current attribute value did not fit the transient arena");
|
||||
return false;
|
||||
}
|
||||
vkCmdBindVertexBuffers(frame.commandBuffer, binding++, 1, &slice.buffer, &slice.offset);
|
||||
}
|
||||
if (indexSlice.IsValid()) vkCmdBindIndexBuffer(frame.commandBuffer, indexSlice.buffer, indexSlice.offset, indexType);
|
||||
if (!VkRenderPassManager::BeginRenderPass(frame.commandBuffer, pass)) return false;
|
||||
if (!VkRenderPassManager::BeginRenderPass(frame.commandBuffer, pass)) {
|
||||
MGL_WIRE_DECLINE_AT(BeginRenderPass, "the render pass for this draw could not be begun");
|
||||
return false;
|
||||
}
|
||||
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
ApplyDynamicDrawStateTail(frame, pass.extent, fbo->IsDefault,
|
||||
ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin));
|
||||
@@ -546,14 +605,26 @@
|
||||
RetireWireDrawPass();
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
const auto program = WireProgramSource(MG_Pipe::MGPipeApplier().DispatchProgram);
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) return;
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
|
||||
MGL_WIRE_DECLINE_AT(ComputeProgramLinkOrSpirv, "the compute program is not linked or carries no SPIR-V");
|
||||
return;
|
||||
}
|
||||
const auto& programObj = m_programFactory->GetOrCreateProgram(program, {});
|
||||
if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) return;
|
||||
if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) {
|
||||
MGL_WIRE_DECLINE_AT(ComputeTextureResourcesUnprepared, "a sampled/storage image could not be made ready for the dispatch");
|
||||
return;
|
||||
}
|
||||
const auto pipeline = GetOrCreateComputePipeline(programObj);
|
||||
if (!pipeline) return;
|
||||
if (!pipeline) {
|
||||
MGL_WIRE_DECLINE_AT(ComputePipelineUnavailable, "no compute pipeline could be created for this dispatch");
|
||||
return;
|
||||
}
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE)) return;
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE)) {
|
||||
MGL_WIRE_DECLINE_AT(ComputeUniformBufferBinding, "the dispatch's uniform/storage bindings could not be bound");
|
||||
return;
|
||||
}
|
||||
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
vkCmdDispatch(frame.commandBuffer, x, y, z);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user