mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Remote] (Disaggregated): carry native query results and complete XFB object deletion
This commit is contained in:
@@ -1422,7 +1422,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated &&
|
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated &&
|
||||||
!query->pausedPrimitivesCountedByGpu && MGB_CTX_LIVE) {
|
!query->pausedPrimitivesCountedByGpu &&
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
MG_Config::Transport == MG_Config::TransportMode::Monolith &&
|
||||||
|
#endif
|
||||||
|
MGB_CTX_LIVE) {
|
||||||
primitives += MGB_CTX->GetTransformFeedbackPausedPrimitiveCounter() -
|
primitives += MGB_CTX->GetTransformFeedbackPausedPrimitiveCounter() -
|
||||||
query->pausedPrimitiveSnapshot;
|
query->pausedPrimitiveSnapshot;
|
||||||
}
|
}
|
||||||
@@ -1470,6 +1474,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten;
|
query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten;
|
||||||
query->rendererGeneration = GetRendererGeneration();
|
query->rendererGeneration = GetRendererGeneration();
|
||||||
query->pausedPrimitiveSnapshot =
|
query->pausedPrimitiveSnapshot =
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
MG_Config::Transport == MG_Config::TransportMode::Monolith &&
|
||||||
|
#endif
|
||||||
MGB_CTX_LIVE ? MGB_CTX->GetTransformFeedbackPausedPrimitiveCounter() : 0;
|
MGB_CTX_LIVE ? MGB_CTX->GetTransformFeedbackPausedPrimitiveCounter() : 0;
|
||||||
// Read AFTER StartXfbQueryCapture, which is where a failed reroute-pool creation
|
// Read AFTER StartXfbQueryCapture, which is where a failed reroute-pool creation
|
||||||
// disarms: the answer is then what this span will actually do for every draw.
|
// disarms: the answer is then what this span will actually do for every draw.
|
||||||
|
|||||||
@@ -187,6 +187,22 @@ namespace MobileGL::MG_Backend {
|
|||||||
serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) {
|
serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) {
|
||||||
capBits |= MG_Pipe::kCapBackendOwnsXfbCapture;
|
capBits |= MG_Pipe::kCapBackendOwnsXfbCapture;
|
||||||
}
|
}
|
||||||
|
if (const auto* backend = loop.Backend()) {
|
||||||
|
const auto& gl = backend->GetBackendFunctions().GL;
|
||||||
|
// The query owner publishes only complete native query/reply paths.
|
||||||
|
// Timer hardware support is refreshed when caps are published after
|
||||||
|
// make-current; no CPU primitive-accounting preference is advertised.
|
||||||
|
if (gl.IsQueryResultAvailable && gl.GetQueryResult64 && gl.DeleteBackendQuery &&
|
||||||
|
gl.FenceSync && gl.ClientWaitSync && gl.DeleteSync) {
|
||||||
|
if (gl.BeginXfbPrimitivesQuery && gl.EndXfbPrimitivesQuery)
|
||||||
|
capBits |= MG_Pipe::kCapXfbPrimitivesQuery;
|
||||||
|
if (gl.BeginOcclusionQuery && gl.EndOcclusionQuery)
|
||||||
|
capBits |= MG_Pipe::kCapOcclusionQuery;
|
||||||
|
if (gl.IsTimerQuerySupported && gl.BeginTimeElapsedQuery &&
|
||||||
|
gl.EndTimeElapsedQuery && gl.QueryCounterTimestamp)
|
||||||
|
capBits |= MG_Pipe::kCapTimerQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Each backend has an independent implementation-readiness gate.
|
// Each backend has an independent implementation-readiness gate.
|
||||||
// The runtime RunAhead knob can decline the feature, never create it.
|
// The runtime RunAhead knob can decline the feature, never create it.
|
||||||
const Bool runAheadReady = MG_Config::ActiveBackendType == BackendType::DirectVulkan
|
const Bool runAheadReady = MG_Config::ActiveBackendType == BackendType::DirectVulkan
|
||||||
|
|||||||
@@ -154,7 +154,16 @@
|
|||||||
X(FenceStatus, GetSyncStatus) \
|
X(FenceStatus, GetSyncStatus) \
|
||||||
X(FenceWait, ClientWaitSync) \
|
X(FenceWait, ClientWaitSync) \
|
||||||
X(FenceDestroy, DeleteSync) \
|
X(FenceDestroy, DeleteSync) \
|
||||||
X(FenceWaitServer, WaitSync)
|
X(FenceWaitServer, WaitSync) \
|
||||||
|
X(QueryCreate, BeginXfbPrimitivesQuery) \
|
||||||
|
X(QueryBegin, BeginXfbPrimitivesQuery) \
|
||||||
|
X(QueryEnd, EndXfbPrimitivesQuery) \
|
||||||
|
X(QueryAvailable, IsQueryResultAvailable) \
|
||||||
|
X(QueryResult, GetQueryResult64) \
|
||||||
|
X(QueryDestroy, DeleteBackendQuery) \
|
||||||
|
X(QueryCounter, QueryCounterTimestamp) \
|
||||||
|
X(QueryTimestamp, GetGpuTimestampNs) \
|
||||||
|
X(DeleteStreamOutput, DeleteTransformFeedback)
|
||||||
|
|
||||||
// X(Op, Why) - verb-shaped calls that are deliberately NOT stamp points.
|
// X(Op, Why) - verb-shaped calls that are deliberately NOT stamp points.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
// sat catalogued-and-dead since P4a. 80 unique records.
|
// sat catalogued-and-dead since P4a. 80 unique records.
|
||||||
// ---------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
#define MGP_CALL_LIST_DOCUMENTED_COUNT 80
|
#define MGP_CALL_LIST_DOCUMENTED_COUNT 81
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
#define MGP_CALL_LIST(X) \
|
#define MGP_CALL_LIST(X) \
|
||||||
@@ -337,7 +337,9 @@
|
|||||||
/* ---- the same validate point: a rebuild inside the verb must already see the bindings. ---- */ \
|
/* ---- the same validate point: a rebuild inside the verb must already see the bindings. ---- */ \
|
||||||
/* ---- kWaitNone: nothing about it is read back, and under run-ahead a kCtxState row that ---- */ \
|
/* ---- kWaitNone: nothing about it is read back, and under run-ahead a kCtxState row that ---- */ \
|
||||||
/* ---- names only handles and values is exactly what rule F admits. ---- */ \
|
/* ---- names only handles and values is exactly what rule F admits. ---- */ \
|
||||||
X(SetProgramBindings, MGPProgramBindings, kCtxState, kVarTail|kHostSpan, kWaitNone)
|
X(SetProgramBindings, MGPProgramBindings, kCtxState, kVarTail|kHostSpan, kWaitNone) \
|
||||||
|
/* Deletion carries the GL argument and its never-reused capture identity. */ \
|
||||||
|
X(DeleteStreamOutput, MGPStreamOutputBind, kCtxVerb, kNone, kWaitNone)
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"):
|
// Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"):
|
||||||
|
|||||||
@@ -292,12 +292,21 @@ constexpr MGPipeVerb MGPipeVerbForWireOp(MGPWireOp op) {
|
|||||||
case MGPWireOp::FenceWait: return MGPipeVerb::ClientWaitSync;
|
case MGPWireOp::FenceWait: return MGPipeVerb::ClientWaitSync;
|
||||||
case MGPWireOp::FenceDestroy: return MGPipeVerb::DeleteSync;
|
case MGPWireOp::FenceDestroy: return MGPipeVerb::DeleteSync;
|
||||||
case MGPWireOp::FenceWaitServer: return MGPipeVerb::WaitSync;
|
case MGPWireOp::FenceWaitServer: return MGPipeVerb::WaitSync;
|
||||||
|
case MGPWireOp::QueryCreate: return MGPipeVerb::BeginXfbPrimitivesQuery;
|
||||||
|
case MGPWireOp::QueryBegin: return MGPipeVerb::BeginXfbPrimitivesQuery;
|
||||||
|
case MGPWireOp::QueryEnd: return MGPipeVerb::EndXfbPrimitivesQuery;
|
||||||
|
case MGPWireOp::QueryAvailable: return MGPipeVerb::IsQueryResultAvailable;
|
||||||
|
case MGPWireOp::QueryResult: return MGPipeVerb::GetQueryResult64;
|
||||||
|
case MGPWireOp::QueryDestroy: return MGPipeVerb::DeleteBackendQuery;
|
||||||
|
case MGPWireOp::QueryCounter: return MGPipeVerb::QueryCounterTimestamp;
|
||||||
|
case MGPWireOp::QueryTimestamp: return MGPipeVerb::GetGpuTimestampNs;
|
||||||
|
case MGPWireOp::DeleteStreamOutput: return MGPipeVerb::DeleteTransformFeedback;
|
||||||
default:
|
default:
|
||||||
return MGPipeVerb::kVerbCount;
|
return MGPipeVerb::kVerbCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr SizeT kMGPipeVerbBoundaryOpCount = 23;
|
inline constexpr SizeT kMGPipeVerbBoundaryOpCount = 32;
|
||||||
inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3;
|
inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3;
|
||||||
|
|
||||||
// ---- THE §7 ALLOWLIST, DERIVED RATHER THAN WRITTEN (ID-116) --------------------------
|
// ---- THE §7 ALLOWLIST, DERIVED RATHER THAN WRITTEN (ID-116) --------------------------
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct MGPipeScreen {
|
|||||||
void (*ApplierReset)(const MGPApplierReset* payload);
|
void (*ApplierReset)(const MGPApplierReset* payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
// context: 68 calls. A null entry means the backend does not implement this
|
// context: 69 calls. A null entry means the backend does not implement this
|
||||||
// call and the frontend keeps its own path (plan B section 4.1).
|
// call and the frontend keeps its own path (plan B section 4.1).
|
||||||
struct MGPipeContext {
|
struct MGPipeContext {
|
||||||
void (*QueryCreate)(const MGPQueryDesc* payload);
|
void (*QueryCreate)(const MGPQueryDesc* payload);
|
||||||
@@ -100,11 +100,12 @@ struct MGPipeContext {
|
|||||||
void (*ObjectDeath)(const MGPHandleOnly* payload);
|
void (*ObjectDeath)(const MGPHandleOnly* payload);
|
||||||
void (*SetContextValues)(const MGPContextValues* payload);
|
void (*SetContextValues)(const MGPContextValues* payload);
|
||||||
void (*SetProgramBindings)(const MGPProgramBindings* payload, const void* varTail, Uint32 varTailCount);
|
void (*SetProgramBindings)(const MGPProgramBindings* payload, const void* varTail, Uint32 varTailCount);
|
||||||
|
void (*DeleteStreamOutput)(const MGPStreamOutputBind* payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr SizeT kMGPipeScreenCallCount = 12;
|
inline constexpr SizeT kMGPipeScreenCallCount = 12;
|
||||||
inline constexpr SizeT kMGPipeContextCallCount = 68;
|
inline constexpr SizeT kMGPipeContextCallCount = 69;
|
||||||
inline constexpr SizeT kMGPipeCallCount = 80;
|
inline constexpr SizeT kMGPipeCallCount = 81;
|
||||||
|
|
||||||
// A table that is not exactly its call count of function pointers has grown a
|
// A table that is not exactly its call count of function pointers has grown a
|
||||||
// member that no generator knows about.
|
// member that no generator knows about.
|
||||||
|
|||||||
@@ -336,3 +336,7 @@ inline void MGP_SetContextValues(const MGPContextValues* payload) {
|
|||||||
inline void MGP_SetProgramBindings(const MGPProgramBindings* payload, const void* varTail, Uint32 varTailCount) {
|
inline void MGP_SetProgramBindings(const MGPProgramBindings* payload, const void* varTail, Uint32 varTailCount) {
|
||||||
gMGPipeContext.SetProgramBindings(payload, varTail, varTailCount);
|
gMGPipeContext.SetProgramBindings(payload, varTail, varTailCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void MGP_DeleteStreamOutput(const MGPStreamOutputBind* payload) {
|
||||||
|
gMGPipeContext.DeleteStreamOutput(payload);
|
||||||
|
}
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ enum class MGPWireOp : Uint16 {
|
|||||||
ObjectDeath = 78,
|
ObjectDeath = 78,
|
||||||
SetContextValues = 79,
|
SetContextValues = 79,
|
||||||
SetProgramBindings = 80,
|
SetProgramBindings = 80,
|
||||||
kOpCount = 81,
|
DeleteStreamOutput = 81,
|
||||||
|
kOpCount = 82,
|
||||||
};
|
};
|
||||||
|
|
||||||
// THE FLAGS, EXPORTED ONCE, INDEXED BY OPCODE (P5 R-13.4). MGPWireRecHeader::Flags is
|
// THE FLAGS, EXPORTED ONCE, INDEXED BY OPCODE (P5 R-13.4). MGPWireRecHeader::Flags is
|
||||||
@@ -252,6 +253,7 @@ inline constexpr Uint32 kMGPipeCallFlags[static_cast<SizeT>(MGPWireOp::kOpCount)
|
|||||||
/* 78 ObjectDeath */ static_cast<Uint32>(kNone),
|
/* 78 ObjectDeath */ static_cast<Uint32>(kNone),
|
||||||
/* 79 SetContextValues */ static_cast<Uint32>(kNone),
|
/* 79 SetContextValues */ static_cast<Uint32>(kNone),
|
||||||
/* 80 SetProgramBindings */ static_cast<Uint32>(kVarTail | kHostSpan),
|
/* 80 SetProgramBindings */ static_cast<Uint32>(kVarTail | kHostSpan),
|
||||||
|
/* 81 DeleteStreamOutput */ static_cast<Uint32>(kNone),
|
||||||
};
|
};
|
||||||
static_assert(sizeof(kMGPipeCallFlags) / sizeof(kMGPipeCallFlags[0]) ==
|
static_assert(sizeof(kMGPipeCallFlags) / sizeof(kMGPipeCallFlags[0]) ==
|
||||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||||
@@ -352,6 +354,7 @@ inline constexpr Uint8 kMGPipeCallClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
|||||||
/* 78 ObjectDeath */ static_cast<Uint8>(kCtxObject),
|
/* 78 ObjectDeath */ static_cast<Uint8>(kCtxObject),
|
||||||
/* 79 SetContextValues */ static_cast<Uint8>(kCtxState),
|
/* 79 SetContextValues */ static_cast<Uint8>(kCtxState),
|
||||||
/* 80 SetProgramBindings */ static_cast<Uint8>(kCtxState),
|
/* 80 SetProgramBindings */ static_cast<Uint8>(kCtxState),
|
||||||
|
/* 81 DeleteStreamOutput */ static_cast<Uint8>(kCtxVerb),
|
||||||
};
|
};
|
||||||
static_assert(sizeof(kMGPipeCallClasses) / sizeof(kMGPipeCallClasses[0]) ==
|
static_assert(sizeof(kMGPipeCallClasses) / sizeof(kMGPipeCallClasses[0]) ==
|
||||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||||
@@ -456,6 +459,7 @@ inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
|||||||
/* 78 ObjectDeath */ static_cast<Uint8>(kWaitNone),
|
/* 78 ObjectDeath */ static_cast<Uint8>(kWaitNone),
|
||||||
/* 79 SetContextValues */ static_cast<Uint8>(kWaitNone),
|
/* 79 SetContextValues */ static_cast<Uint8>(kWaitNone),
|
||||||
/* 80 SetProgramBindings */ static_cast<Uint8>(kWaitNone),
|
/* 80 SetProgramBindings */ static_cast<Uint8>(kWaitNone),
|
||||||
|
/* 81 DeleteStreamOutput */ static_cast<Uint8>(kWaitNone),
|
||||||
};
|
};
|
||||||
static_assert(sizeof(kMGPipeWaitClasses) / sizeof(kMGPipeWaitClasses[0]) ==
|
static_assert(sizeof(kMGPipeWaitClasses) / sizeof(kMGPipeWaitClasses[0]) ==
|
||||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||||
@@ -1162,6 +1166,14 @@ static_assert(sizeof(MGPWireRec_SetProgramBindings) ==
|
|||||||
((sizeof(MGPWireRecHeader) + sizeof(MGPProgramBindings) + 7u) & ~SizeT(7u)),
|
((sizeof(MGPWireRecHeader) + sizeof(MGPProgramBindings) + 7u) & ~SizeT(7u)),
|
||||||
"MGPWireRec_SetProgramBindings gained padding; the wire format moved");
|
"MGPWireRec_SetProgramBindings gained padding; the wire format moved");
|
||||||
|
|
||||||
|
struct alignas(8) MGPWireRec_DeleteStreamOutput {
|
||||||
|
MGPWireRecHeader Header;
|
||||||
|
MGPStreamOutputBind Payload;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MGPWireRec_DeleteStreamOutput) ==
|
||||||
|
((sizeof(MGPWireRecHeader) + sizeof(MGPStreamOutputBind) + 7u) & ~SizeT(7u)),
|
||||||
|
"MGPWireRec_DeleteStreamOutput gained padding; the wire format moved");
|
||||||
|
|
||||||
[[noreturn]] inline void MGPipeWireProtocolFatal(const char* call, Uint64 size, Uint64 remaining) {
|
[[noreturn]] inline void MGPipeWireProtocolFatal(const char* call, Uint64 size, Uint64 remaining) {
|
||||||
MGLOG_F("MGPipe: protocol corruption applying %s: size=%llu remaining=%llu", call,
|
MGLOG_F("MGPipe: protocol corruption applying %s: size=%llu remaining=%llu", call,
|
||||||
static_cast<unsigned long long>(size), static_cast<unsigned long long>(remaining));
|
static_cast<unsigned long long>(size), static_cast<unsigned long long>(remaining));
|
||||||
@@ -1451,6 +1463,9 @@ inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size,
|
|||||||
case MGPWireOp::SetProgramBindings:
|
case MGPWireOp::SetProgramBindings:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetProgramBindings, "SetProgramBindings");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetProgramBindings, "SetProgramBindings");
|
||||||
break;
|
break;
|
||||||
|
case MGPWireOp::DeleteStreamOutput:
|
||||||
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteStreamOutput, "DeleteStreamOutput");
|
||||||
|
break;
|
||||||
case MGPWireOp::kInvalid:
|
case MGPWireOp::kInvalid:
|
||||||
case MGPWireOp::kOpCount:
|
case MGPWireOp::kOpCount:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "PersistentMapTracker.h"
|
#include "PersistentMapTracker.h"
|
||||||
|
|
||||||
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||||
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
#include <MG_Util/Debug/Log.h>
|
#include <MG_Util/Debug/Log.h>
|
||||||
#include <MG_Util/Metrics/PipeStats.h>
|
#include <MG_Util/Metrics/PipeStats.h>
|
||||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||||
@@ -915,11 +916,9 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// split form - the server writes the reply into the buffer resource and the client
|
// split form - the server writes the reply into the buffer resource and the client
|
||||||
// marks it GPU-written (b1's MarkReadPixelsPackBuffer becoming the producer contract
|
// marks it GPU-written (b1's MarkReadPixelsPackBuffer becoming the producer contract
|
||||||
// §3 names) - is a P6 ROADMAP item. In P5 it is class C's shape (R-4), refused here.
|
// §3 names) - is a P6 ROADMAP item. In P5 it is class C's shape (R-4), refused here.
|
||||||
if (MG_State::pGLContext != nullptr &&
|
const auto pbo = MG_State::pGLContext != nullptr
|
||||||
MG_State::pGLContext->GetBufferBindingSlot(::MobileGL::BufferTarget::PixelPack)
|
? MG_State::pGLContext->GetBufferBindingSlot(::MobileGL::BufferTarget::PixelPack).GetBoundObject()
|
||||||
.GetBoundObject()) {
|
: SharedPtr<MG_State::GLState::BufferObject>{};
|
||||||
UnmigratedVerbFatal("ReadPixels+PACK_BUFFER");
|
|
||||||
}
|
|
||||||
|
|
||||||
BeforeReadOnlyVerb();
|
BeforeReadOnlyVerb();
|
||||||
|
|
||||||
@@ -972,7 +971,7 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
|
|
||||||
Int32 status = 0;
|
Int32 status = 0;
|
||||||
Uint64 replySize = 0;
|
Uint64 replySize = 0;
|
||||||
if (ReadbackPackStateIsTight(width, bytesPerPixel, pack)) {
|
if (!pbo && ReadbackPackStateIsTight(width, bytesPerPixel, pack)) {
|
||||||
// THE COMMON CASE, AND IT KEEPS THE ZERO-COPY. A neutral pack state means the
|
// THE COMMON CASE, AND IT KEEPS THE ZERO-COPY. A neutral pack state means the
|
||||||
// destination layout IS the tight layout, so the reply lands straight in the
|
// destination layout IS the tight layout, so the reply lands straight in the
|
||||||
// application's buffer and there is no bounce at all. It is a fast path for the
|
// application's buffer and there is no bounce at all. It is a fast path for the
|
||||||
@@ -998,10 +997,25 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// pointer at all (the bounce is the only thing that held the partial bytes).
|
// pointer at all (the bounce is the only thing that held the partial bytes).
|
||||||
RequireReadbackReplyComplete(status, replySize, tight);
|
RequireReadbackReplyComplete(status, replySize, tight);
|
||||||
ApplyReadbackByteSwap(bounce.data(), tight, type, pack);
|
ApplyReadbackByteSwap(bounce.data(), tight, type, pack);
|
||||||
|
if (pbo) {
|
||||||
|
// pixels is an offset, never a host pointer. Upload only the
|
||||||
|
// requested rows so PACK padding and untouched bytes survive.
|
||||||
|
const Uint64 alignment = std::max<Int>(pack.Alignment, 1);
|
||||||
|
const Uint64 rowPixels = pack.RowLength > 0 ? pack.RowLength : width;
|
||||||
|
const Uint64 stride = (rowPixels * bytesPerPixel + alignment - 1) / alignment * alignment;
|
||||||
|
const Uint64 start = reinterpret_cast<SizeT>(pixels) + pack.SkipRows * stride +
|
||||||
|
pack.SkipPixels * bytesPerPixel;
|
||||||
|
const SizeT rowBytes = static_cast<SizeT>(width) * bytesPerPixel;
|
||||||
|
for (Int y = 0; y < height; ++y)
|
||||||
|
pbo->UploadSubData({bounce.data() + y * rowBytes, rowBytes}, start + y * stride);
|
||||||
|
return;
|
||||||
|
}
|
||||||
ScatterTightReadbackIntoPackState(bounce.data(), pixels, width, height, bytesPerPixel,
|
ScatterTightReadbackIntoPackState(bounce.data(), pixels, width, height, bytesPerPixel,
|
||||||
pack);
|
pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "TextureReadbackEmit.inc"
|
||||||
|
|
||||||
void EmitPresent() {
|
void EmitPresent() {
|
||||||
ClientSession& session = RequireSession("Present");
|
ClientSession& session = RequireSession("Present");
|
||||||
BeforeReadOnlyVerb();
|
BeforeReadOnlyVerb();
|
||||||
@@ -1565,7 +1579,9 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
g_fenceProxies.erase(proxy);
|
g_fenceProxies.erase(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLASS C - 16 slots remain after d1/i1/t2/f1; each names its first blocker.
|
#include "QueryEmit.inc"
|
||||||
|
|
||||||
|
// CLASS C - each remaining slot names its first blocker.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
//
|
//
|
||||||
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
|
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
|
||||||
@@ -1614,35 +1630,19 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// not by omission (unmeasured; the driver object leaks on the server until P9's XFB
|
// not by omission (unmeasured; the driver object leaks on the server until P9's XFB
|
||||||
// namespace work, and a bind of name 0 is what the backend does on delete of the bound
|
// namespace work, and a bind of name 0 is what the backend does on delete of the bound
|
||||||
// one, DirectGLES.cpp:1422). It therefore still aborts by its own name.
|
// one, DirectGLES.cpp:1422). It therefore still aborts by its own name.
|
||||||
#define MGR_UNMIGRATED_T2_SLOTS(X) \
|
#define MGR_UNMIGRATED_T2_SLOTS(X)
|
||||||
X(DeleteTransformFeedback, void, (GLuint))
|
|
||||||
|
|
||||||
#define MGR_UNMIGRATED_F1_SLOTS(X)
|
#define MGR_UNMIGRATED_F1_SLOTS(X)
|
||||||
|
|
||||||
// The wave-3 tail. SetSwapInterval is hand-written below (it is not a GL.* slot).
|
// The wave-3 tail. SetSwapInterval is hand-written below (it is not a GL.* slot).
|
||||||
#define MGR_UNMIGRATED_TAIL_SLOTS(X) \
|
#define MGR_UNMIGRATED_TAIL_SLOTS(X)
|
||||||
X(GetTexImage, void, (GLenum, GLint, GLenum, GLenum, GLvoid*)) \
|
|
||||||
X(GetTextureImage, void, \
|
|
||||||
(const SharedPtr<MG_State::GLState::ITextureObject>&, TextureUploadTarget, GLint, GLenum, \
|
|
||||||
GLenum, GLsizei, GLvoid*)) \
|
|
||||||
X(EndTimeElapsedQuery, void, (MG_Backend::BackendQueryHandle)) \
|
|
||||||
X(DeleteBackendQuery, void, (MG_Backend::BackendQueryHandle)) \
|
|
||||||
X(EndOcclusionQuery, void, (MG_Backend::BackendQueryHandle)) \
|
|
||||||
X(EndXfbPrimitivesQuery, void, (MG_Backend::BackendQueryHandle))
|
|
||||||
|
|
||||||
// The non-void ones, kept apart only because the macro body differs: a [[noreturn]]
|
// The non-void ones, kept apart only because the macro body differs: a [[noreturn]]
|
||||||
// call is a complete body for a void slot and for a value-returning one alike, but a
|
// call is a complete body for a void slot and for a value-returning one alike, but a
|
||||||
// compiler that does not see UnmigratedVerbFatal's attribute through the macro would
|
// compiler that does not see UnmigratedVerbFatal's attribute through the macro would
|
||||||
// warn on the second. It does see it; they are split for readability. All ten are the
|
// warn on the second. It does see it; they are split for readability. All ten are the
|
||||||
// wave-3 tail.
|
// wave-3 tail.
|
||||||
#define MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X) \
|
#define MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X)
|
||||||
X(BeginTimeElapsedQuery, MG_Backend::BackendQueryHandle, ()) \
|
|
||||||
X(QueryCounterTimestamp, MG_Backend::BackendQueryHandle, ()) \
|
|
||||||
X(IsQueryResultAvailable, Bool, (MG_Backend::BackendQueryHandle)) \
|
|
||||||
X(GetQueryResult64, Bool, (MG_Backend::BackendQueryHandle, Bool, Uint64*)) \
|
|
||||||
X(BeginOcclusionQuery, MG_Backend::BackendQueryHandle, ()) \
|
|
||||||
X(BeginXfbPrimitivesQuery, MG_Backend::BackendQueryHandle, (Bool)) \
|
|
||||||
X(GetGpuTimestampNs, Int64, ())
|
|
||||||
|
|
||||||
// The union, for the places that want every class-C row at once (the definitions and
|
// The union, for the places that want every class-C row at once (the definitions and
|
||||||
// the assignments). A package never edits THIS; it edits its own list above.
|
// the assignments). A package never edits THIS; it edits its own list above.
|
||||||
@@ -1686,13 +1686,14 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
|
constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
|
||||||
constexpr Uint32 kEmittedSlotsD1 = 19;
|
constexpr Uint32 kEmittedSlotsD1 = 19;
|
||||||
constexpr Uint32 kEmittedSlotsI1 = 7;
|
constexpr Uint32 kEmittedSlotsI1 = 7;
|
||||||
constexpr Uint32 kEmittedSlotsT2 = 6;
|
constexpr Uint32 kEmittedSlotsT2 = 7;
|
||||||
constexpr Uint32 kEmittedSlotsF1 = 11;
|
constexpr Uint32 kEmittedSlotsF1 = 11;
|
||||||
constexpr Uint32 kEmittedSlotsTail = 1; // BlitNamedFramebuffer
|
constexpr Uint32 kEmittedSlotsTail = 3; // BlitNamedFramebuffer, both texture readbacks
|
||||||
constexpr Uint32 kEmittedSlotsSync = 5;
|
constexpr Uint32 kEmittedSlotsSync = 5;
|
||||||
|
constexpr Uint32 kEmittedSlotsQueries = 11;
|
||||||
constexpr Uint32 kEmittedSlots =
|
constexpr Uint32 kEmittedSlots =
|
||||||
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1 +
|
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1 +
|
||||||
kEmittedSlotsTail + kEmittedSlotsSync;
|
kEmittedSlotsTail + kEmittedSlotsSync + kEmittedSlotsQueries;
|
||||||
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
|
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
|
||||||
|
|
||||||
// EACH PACKAGE'S OWNERSHIP, PINNED. A package that flips a slot removes one row and
|
// EACH PACKAGE'S OWNERSHIP, PINNED. A package that flips a slot removes one row and
|
||||||
@@ -1703,7 +1704,7 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
static_assert(kUnmigratedI1 + kEmittedSlotsI1 == 7, "i1 owns the 7 image/compute/barrier/copy/SSBO slots");
|
static_assert(kUnmigratedI1 + kEmittedSlotsI1 == 7, "i1 owns the 7 image/compute/barrier/copy/SSBO slots");
|
||||||
static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots");
|
static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots");
|
||||||
static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots");
|
static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots");
|
||||||
static_assert(kUnmigratedTail + kEmittedSlotsTail + kEmittedSlotsSync == 20,
|
static_assert(kUnmigratedTail + kEmittedSlotsTail + kEmittedSlotsSync + kEmittedSlotsQueries == 20,
|
||||||
"the original wave-3 tail owns 20 slots");
|
"the original wave-3 tail owns 20 slots");
|
||||||
static_assert(kUnmigratedSlots + kEmittedSlots == 69, "class B and C own 69 slots");
|
static_assert(kUnmigratedSlots + kEmittedSlots == 69, "class B and C own 69 slots");
|
||||||
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
|
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
|
||||||
@@ -1729,6 +1730,18 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
table.GL.GetSyncStatus = &EmitGetSyncStatus;
|
table.GL.GetSyncStatus = &EmitGetSyncStatus;
|
||||||
table.GL.WaitSync = &EmitWaitSync;
|
table.GL.WaitSync = &EmitWaitSync;
|
||||||
table.GL.DeleteSync = &EmitDeleteSync;
|
table.GL.DeleteSync = &EmitDeleteSync;
|
||||||
|
table.GL.BeginTimeElapsedQuery = &EmitBeginTimeElapsedQuery;
|
||||||
|
table.GL.EndTimeElapsedQuery = &EmitEndQuery;
|
||||||
|
table.GL.QueryCounterTimestamp = &EmitQueryCounterTimestamp;
|
||||||
|
table.GL.BeginOcclusionQuery = &EmitBeginOcclusionQuery;
|
||||||
|
table.GL.EndOcclusionQuery = &EmitEndQuery;
|
||||||
|
table.GL.BeginXfbPrimitivesQuery = &EmitBeginXfbPrimitivesQuery;
|
||||||
|
table.GL.EndXfbPrimitivesQuery = &EmitEndQuery;
|
||||||
|
table.GL.IsQueryResultAvailable = &EmitIsQueryResultAvailable;
|
||||||
|
table.GL.GetQueryResult64 = &EmitGetQueryResult64;
|
||||||
|
table.GL.DeleteBackendQuery = &EmitDeleteBackendQuery;
|
||||||
|
table.GL.GetGpuTimestampNs = &EmitGetGpuTimestampNs;
|
||||||
|
table.GL.DeleteTransformFeedback = &EmitDeleteTransformFeedback;
|
||||||
|
|
||||||
// ---- class A
|
// ---- class A
|
||||||
table.GL.GetIntegeri_v = &AnswerGetIntegeri_v;
|
table.GL.GetIntegeri_v = &AnswerGetIntegeri_v;
|
||||||
@@ -1791,6 +1804,8 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
|
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
|
||||||
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
|
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
|
||||||
table.GL.GenerateMipmap = &EmitGenerateMipmap;
|
table.GL.GenerateMipmap = &EmitGenerateMipmap;
|
||||||
|
table.GL.GetTexImage = &EmitGetTexImage;
|
||||||
|
table.GL.GetTextureImage = &EmitGetTextureImage;
|
||||||
|
|
||||||
// ---- class B, P5b package i1 (kEmittedSlotsI1 = 7)
|
// ---- class B, P5b package i1 (kEmittedSlotsI1 = 7)
|
||||||
table.GL.BindImageTexture = &EmitBindImageTexture;
|
table.GL.BindImageTexture = &EmitBindImageTexture;
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Opaque local proxies carry only {slot, generation, target} across the wire.
|
||||||
|
struct RemoteQueryProxy { MG_Pipe::MGPQueryDesc Desc{}; };
|
||||||
|
std::mutex g_queryProxyMutex;
|
||||||
|
UnorderedMap<MG_Backend::BackendQueryHandle, UniquePtr<RemoteQueryProxy>> g_queryProxies;
|
||||||
|
|
||||||
|
const MG_Pipe::MGPQueryDesc& QueryDesc(MG_Backend::BackendQueryHandle handle) {
|
||||||
|
const auto found = g_queryProxies.find(handle);
|
||||||
|
if (found == g_queryProxies.end())
|
||||||
|
Wire::WireProtocolFatal("Query.proxy", "unknown local query proxy");
|
||||||
|
return found->second->Desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Result>
|
||||||
|
Result ReadQueryReply(ClientSession& session, MG_Pipe::MGPWireOp op, const void* request, Uint64 size) {
|
||||||
|
Result result{};
|
||||||
|
Int32 status = Wire::ReplySink::kStatusError;
|
||||||
|
Uint64 returned = 0;
|
||||||
|
session.EmitAndWait(op, request, size, nullptr, 0, &result, sizeof(result), &status, &returned);
|
||||||
|
if (status != Wire::ReplySink::kStatusOk || returned != sizeof(result))
|
||||||
|
Wire::WireProtocolFatal("Query.reply", "missing or malformed backend query reply");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_Backend::BackendQueryHandle EmitCreateQuery(GLenum kind, Bool counter) {
|
||||||
|
auto& session = RequireSession("QueryBegin");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryProxyMutex);
|
||||||
|
BeforeReadOnlyVerb();
|
||||||
|
auto proxy = MakeUnique<RemoteQueryProxy>();
|
||||||
|
proxy->Desc.Query = MG_Pipe::MGPipeSlots().Allocate(MG_Pipe::MGPipeKind::Query);
|
||||||
|
proxy->Desc.Kind = kind;
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::QueryCreate, &proxy->Desc, sizeof(proxy->Desc),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
session.EmitAndWait(counter ? MG_Pipe::MGPWireOp::QueryCounter : MG_Pipe::MGPWireOp::QueryBegin,
|
||||||
|
&proxy->Desc, sizeof(proxy->Desc), nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
auto* local = proxy.get();
|
||||||
|
g_queryProxies.emplace(local, Move(proxy));
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
MG_Backend::BackendQueryHandle EmitBeginTimeElapsedQuery() { return EmitCreateQuery(GL_TIME_ELAPSED, false); }
|
||||||
|
MG_Backend::BackendQueryHandle EmitQueryCounterTimestamp() { return EmitCreateQuery(GL_TIMESTAMP, true); }
|
||||||
|
MG_Backend::BackendQueryHandle EmitBeginOcclusionQuery() { return EmitCreateQuery(GL_SAMPLES_PASSED, false); }
|
||||||
|
MG_Backend::BackendQueryHandle EmitBeginXfbPrimitivesQuery(Bool generated) {
|
||||||
|
return EmitCreateQuery(generated ? GL_PRIMITIVES_GENERATED : GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, false);
|
||||||
|
}
|
||||||
|
void EmitEndQuery(MG_Backend::BackendQueryHandle handle) {
|
||||||
|
auto& session = RequireSession("QueryEnd");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryProxyMutex);
|
||||||
|
const auto& desc = QueryDesc(handle);
|
||||||
|
BeforeReadOnlyVerb();
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::QueryEnd, &desc, sizeof(desc), nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
Bool EmitIsQueryResultAvailable(MG_Backend::BackendQueryHandle handle) {
|
||||||
|
auto& session = RequireSession("IsQueryResultAvailable");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryProxyMutex);
|
||||||
|
const MG_Pipe::MGPHandleOnly request{QueryDesc(handle).Query, static_cast<Uint32>(MG_Pipe::MGPipeKind::Query), 0};
|
||||||
|
const auto result = ReadQueryReply<Uint32>(session, MG_Pipe::MGPWireOp::QueryAvailable, &request, sizeof(request));
|
||||||
|
if (result > 1) Wire::WireProtocolFatal("QueryAvailable.reply", "availability is not boolean");
|
||||||
|
return result != 0;
|
||||||
|
}
|
||||||
|
Bool EmitGetQueryResult64(MG_Backend::BackendQueryHandle handle, Bool wait, Uint64* value) {
|
||||||
|
auto& session = RequireSession("GetQueryResult64");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryProxyMutex);
|
||||||
|
MG_Pipe::MGPQueryResultRequest request{};
|
||||||
|
request.Query = QueryDesc(handle).Query;
|
||||||
|
request.Wait = wait ? 1 : 0;
|
||||||
|
const auto reply = ReadQueryReply<Wire::QueryResultReply>(session, MG_Pipe::MGPWireOp::QueryResult,
|
||||||
|
&request, sizeof(request));
|
||||||
|
if (reply.Produced > 1 || reply.Reserved)
|
||||||
|
Wire::WireProtocolFatal("QueryResult.reply", "invalid result metadata");
|
||||||
|
if (!reply.Produced) return false;
|
||||||
|
if (value) *value = reply.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void EmitDeleteBackendQuery(MG_Backend::BackendQueryHandle handle) {
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryProxyMutex);
|
||||||
|
const auto query = QueryDesc(handle).Query;
|
||||||
|
if (auto* session = ClientSession::Active(); session && session->Started()) {
|
||||||
|
const MG_Pipe::MGPHandleOnly request{query, static_cast<Uint32>(MG_Pipe::MGPipeKind::Query), 0};
|
||||||
|
session->EmitAndWait(MG_Pipe::MGPWireOp::QueryDestroy, &request, sizeof(request),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Query, query);
|
||||||
|
g_queryProxies.erase(handle);
|
||||||
|
}
|
||||||
|
Int64 EmitGetGpuTimestampNs() {
|
||||||
|
auto& session = RequireSession("GetGpuTimestampNs");
|
||||||
|
BeforeReadOnlyVerb();
|
||||||
|
const MG_Pipe::MGPTimestampRequest request{};
|
||||||
|
return ReadQueryReply<Int64>(session, MG_Pipe::MGPWireOp::QueryTimestamp, &request, sizeof(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitDeleteTransformFeedback(GLuint name) {
|
||||||
|
auto& session = RequireSession("DeleteTransformFeedback");
|
||||||
|
BeforeReadOnlyVerb();
|
||||||
|
const auto& context = *MG_State::pGLContext;
|
||||||
|
const MG_Pipe::MGPStreamOutputBind object{name, 0, context.GetTransformFeedbackLifetimeId(name)};
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::DeleteStreamOutput, &object, sizeof(object),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
if (context.GetBoundTransformFeedbackName() == name) {
|
||||||
|
const MG_Pipe::MGPStreamOutputBind fallback{0, 0, context.GetTransformFeedbackLifetimeId(0)};
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::BindStreamOutput, &fallback, sizeof(fallback),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
// §5.2). The same dependency ServerLoop.cpp already takes for CreateBackend; a server built
|
// §5.2). The same dependency ServerLoop.cpp already takes for CreateBackend; a server built
|
||||||
// on Magma simply holds no twins in these tables and every release resolves to nothing.
|
// on Magma simply holds no twins in these tables and every release resolves to nothing.
|
||||||
#include <MG_Backend/DirectGLES/Managers.h>
|
#include <MG_Backend/DirectGLES/Managers.h>
|
||||||
|
#include <MG_Backend/DirectGLES/DirectGLES.h>
|
||||||
|
#include <MG_Backend/DirectVulkan/DirectVulkan.h>
|
||||||
#include <MG_Remote/Client/ClientSession.h>
|
#include <MG_Remote/Client/ClientSession.h>
|
||||||
#include <MG_Pipe/PipeApply.h>
|
#include <MG_Pipe/PipeApply.h>
|
||||||
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||||
@@ -72,7 +74,10 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
void ServerVerbSink::SetBackend(MG_Backend::BackendObject* backend) {
|
void ServerVerbSink::SetBackend(MG_Backend::BackendObject* backend) {
|
||||||
if (m_backend != backend) ReleaseFences();
|
if (m_backend != backend) {
|
||||||
|
ReleaseQueries();
|
||||||
|
ReleaseFences();
|
||||||
|
}
|
||||||
m_backend = backend;
|
m_backend = backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,20 +187,27 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
m_fences.clear();
|
m_fences.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "QueryServer.inc"
|
||||||
|
|
||||||
Bool ServerVerbSink::OnClear(const MG_Pipe::MGPClear& clear) {
|
Bool ServerVerbSink::OnClear(const MG_Pipe::MGPClear& clear) {
|
||||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("clear");
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("clear");
|
||||||
if (table == nullptr) return false;
|
if (table == nullptr) return false;
|
||||||
const MG_Backend::GLFunctionsTable& gl = table->GL;
|
const MG_Backend::GLFunctionsTable& gl = table->GL;
|
||||||
|
|
||||||
// Named records precede the verb; bound-form backends re-sync the live draw binding.
|
// The named framebuffer record already crossed before this verb. Scope
|
||||||
if (!MG_Pipe::MGPipeHandleIsNull(clear.Fbo) &&
|
// only the server's resolved draw target; the next verb still observes
|
||||||
clear.Fbo != MG_Pipe::MGPipeApplier().BoundFramebuffer[0]) {
|
// the application's unchanged binding. Both backends sync from this
|
||||||
const char* slot = clear.Kind == kMGPClearKindDepthStencil ? "ClearNamedFramebufferfi+UNBOUND" :
|
// handle and never need the frontend FramebufferObject.
|
||||||
clear.ValueClass == kMGPClearValueClassInt ? "ClearNamedFramebufferiv+UNBOUND" :
|
auto& applier = MG_Pipe::MGPipeApplier();
|
||||||
clear.ValueClass == kMGPClearValueClassUint ? "ClearNamedFramebufferuiv+UNBOUND" :
|
struct ScopedClearTarget {
|
||||||
"ClearNamedFramebufferfv+UNBOUND";
|
MG_Pipe::MGPipeApplierState& State;
|
||||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"%s\"}", slot);
|
MG_Pipe::MGPipeHandle Saved;
|
||||||
std::abort();
|
~ScopedClearTarget() { State.BoundFramebuffer[0] = Saved; }
|
||||||
|
} clearTarget{applier, applier.BoundFramebuffer[0]};
|
||||||
|
if (!MG_Pipe::MGPipeHandleIsNull(clear.Fbo)) {
|
||||||
|
if (!applier.FramebufferRecordFor(clear.Fbo))
|
||||||
|
Wire::WireProtocolFatal("Clear.Fbo", "missing named framebuffer record");
|
||||||
|
applier.BoundFramebuffer[0] = clear.Fbo;
|
||||||
}
|
}
|
||||||
switch (clear.Kind) {
|
switch (clear.Kind) {
|
||||||
case kMGPClearKindWhole:
|
case kMGPClearKindWhole:
|
||||||
@@ -332,6 +344,41 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnGetTextureImage(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
|
||||||
|
Wire::ReplySink* replies) {
|
||||||
|
if (!replies) Wire::WireProtocolFatal("GetTextureImage.reply", "missing reply sink");
|
||||||
|
const SizeT bpp = MG_Util::GetInputBytesPerPixel(
|
||||||
|
MG_Util::ConvertGLEnumToTextureInputFormat(info.Format),
|
||||||
|
MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type));
|
||||||
|
if (!bpp || !info.Box.W || !info.Box.H || !info.Box.D || info.Box.X || info.Box.Y || info.Box.Z) {
|
||||||
|
Wire::WireProtocolFatal("GetTextureImage.extent", "invalid tight image extent");
|
||||||
|
}
|
||||||
|
const Uint64 tight = static_cast<Uint64>(info.Box.W) * info.Box.H * info.Box.D * bpp;
|
||||||
|
if (tight / bpp / info.Box.W / info.Box.H != info.Box.D ||
|
||||||
|
info.DstOffset > tight || !info.DstSize || info.DstSize > tight - info.DstOffset)
|
||||||
|
Wire::WireProtocolFatal("GetTextureImage.range", "invalid reply window");
|
||||||
|
auto image = info;
|
||||||
|
image.DstOffset = 0;
|
||||||
|
image.DstSize = tight;
|
||||||
|
Vector<Uint8> bytes;
|
||||||
|
Bool ok = false;
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (m_backend && m_backend->GetBackendType() == BackendType::DirectGLES)
|
||||||
|
ok = MG_Backend::DirectGLES::ReadTextureImageWire(image, bytes);
|
||||||
|
else if (m_backend && m_backend->GetBackendType() == BackendType::DirectVulkan &&
|
||||||
|
MG_Backend::DirectVulkan::pVulkanRenderer)
|
||||||
|
ok = MG_Backend::DirectVulkan::pVulkanRenderer->ReadTextureImageWire(image, bytes);
|
||||||
|
#endif
|
||||||
|
if (!ok || bytes.size() != tight) {
|
||||||
|
replies->PostReply(seq, Wire::ReplySink::kStatusError, nullptr, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
replies->PostReply(seq, Wire::ReplySink::kStatusOk, bytes.data() + info.DstOffset, info.DstSize);
|
||||||
|
m_readbackBytes += info.DstSize;
|
||||||
|
++m_readbacks;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
|
Bool ServerVerbSink::OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
|
||||||
Wire::ReplySink* replies) {
|
Wire::ReplySink* replies) {
|
||||||
if (replies == nullptr) {
|
if (replies == nullptr) {
|
||||||
@@ -1008,7 +1055,6 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
Bool ServerVerbSink::OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) {
|
Bool ServerVerbSink::OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) {
|
||||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("bind_stream_output");
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("bind_stream_output");
|
||||||
if (table == nullptr) return false;
|
if (table == nullptr) return false;
|
||||||
if (table->GL.BindTransformFeedback == nullptr) return false;
|
|
||||||
// THE GL NAME IS THE ARGUMENT, NOT THE LifetimeId BESIDE IT. Espryt keys its driver
|
// THE GL NAME IS THE ARGUMENT, NOT THE LifetimeId BESIDE IT. Espryt keys its driver
|
||||||
// objects by the GL name (XfbImpl::g_xfbObjects[name], DirectGLES.cpp:1401) and creates
|
// objects by the GL name (XfbImpl::g_xfbObjects[name], DirectGLES.cpp:1401) and creates
|
||||||
// the ES object on first bind; passing the lifetime id would index a map that has never
|
// the ES object on first bind; passing the lifetime id would index a map that has never
|
||||||
@@ -1016,11 +1062,25 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// identifies the P5f capture snapshot on this side. Keeping it separate preserves
|
// identifies the P5f capture snapshot on this side. Keeping it separate preserves
|
||||||
// the backend GL-name argument while Begin/End find the right server-owned span.
|
// the backend GL-name argument while Begin/End find the right server-owned span.
|
||||||
MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId = bind.LifetimeId;
|
MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId = bind.LifetimeId;
|
||||||
table->GL.BindTransformFeedback(static_cast<GLuint>(bind.GlName));
|
if (table->GL.BindTransformFeedback) table->GL.BindTransformFeedback(static_cast<GLuint>(bind.GlName));
|
||||||
++m_streamOutputBinds;
|
++m_streamOutputBinds;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnDeleteStreamOutput(const MG_Pipe::MGPStreamOutputBind& object) {
|
||||||
|
const auto* table = Table("DeleteTransformFeedback");
|
||||||
|
if (!table) return false;
|
||||||
|
if (!object.GlName || object.Pad0)
|
||||||
|
Wire::WireProtocolFatal("DeleteStreamOutput.name", "default object or reserved field");
|
||||||
|
auto& state = MG_Pipe::MGPipeApplier();
|
||||||
|
if (object.LifetimeId) {
|
||||||
|
state.StreamOutputSpans.erase(object.LifetimeId);
|
||||||
|
if (state.BoundStreamOutputLifetimeId == object.LifetimeId) state.BoundStreamOutputLifetimeId = 0;
|
||||||
|
}
|
||||||
|
if (table->GL.DeleteTransformFeedback) table->GL.DeleteTransformFeedback(object.GlName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) {
|
Bool ServerVerbSink::OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) {
|
||||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("patch_parameter");
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("patch_parameter");
|
||||||
if (table == nullptr) return false;
|
if (table == nullptr) return false;
|
||||||
|
|||||||
@@ -141,11 +141,22 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) override;
|
Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) override;
|
||||||
Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) override;
|
Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) override;
|
||||||
void ReleaseFences();
|
void ReleaseFences();
|
||||||
|
Bool OnQueryCreate(const MG_Pipe::MGPQueryDesc&) override;
|
||||||
|
Bool OnQueryBegin(const MG_Pipe::MGPQueryDesc&) override;
|
||||||
|
Bool OnQueryEnd(const MG_Pipe::MGPQueryDesc&) override;
|
||||||
|
Bool OnQueryCounter(const MG_Pipe::MGPQueryDesc&) override;
|
||||||
|
Bool OnQueryAvailable(const MG_Pipe::MGPHandleOnly&, Uint32&) override;
|
||||||
|
Bool OnQueryResult(const MG_Pipe::MGPQueryResultRequest&, Wire::QueryResultReply&) override;
|
||||||
|
Bool OnQueryDestroy(const MG_Pipe::MGPHandleOnly&) override;
|
||||||
|
Bool OnQueryTimestamp(const MG_Pipe::MGPTimestampRequest&, Int64&) override;
|
||||||
|
void ReleaseQueries();
|
||||||
Bool OnClear(const MG_Pipe::MGPClear& clear) override;
|
Bool OnClear(const MG_Pipe::MGPClear& clear) override;
|
||||||
Bool OnBlit(const MG_Pipe::MGPBlit& blit) override;
|
Bool OnBlit(const MG_Pipe::MGPBlit& blit) override;
|
||||||
Bool OnPresent(const MG_Pipe::MGPPresent& present) override;
|
Bool OnPresent(const MG_Pipe::MGPPresent& present) override;
|
||||||
Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
|
Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
|
||||||
Wire::ReplySink* replies) override;
|
Wire::ReplySink* replies) override;
|
||||||
|
Bool OnGetTextureImage(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
|
||||||
|
Wire::ReplySink* replies) override;
|
||||||
Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
|
Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
|
||||||
const MG_Pipe::MGHostSpan* userIndices,
|
const MG_Pipe::MGHostSpan* userIndices,
|
||||||
const MG_Pipe::MGPDrawIndirect* indirect) override;
|
const MG_Pipe::MGPDrawIndirect* indirect) override;
|
||||||
@@ -179,6 +190,7 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
|
Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
|
||||||
Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
|
Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
|
||||||
Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) override;
|
Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) override;
|
||||||
|
Bool OnDeleteStreamOutput(const MG_Pipe::MGPStreamOutputBind& object) override;
|
||||||
Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) override;
|
Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) override;
|
||||||
Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) override;
|
Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) override;
|
||||||
Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) override;
|
Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) override;
|
||||||
@@ -273,6 +285,18 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
};
|
};
|
||||||
FenceEntry& FindFence(MG_Pipe::MGPipeHandle handle);
|
FenceEntry& FindFence(MG_Pipe::MGPipeHandle handle);
|
||||||
UnorderedMap<Uint32, FenceEntry> m_fences;
|
UnorderedMap<Uint32, FenceEntry> m_fences;
|
||||||
|
struct QueryEntry {
|
||||||
|
Uint32 Gen = 0;
|
||||||
|
Uint32 Kind = 0;
|
||||||
|
Bool Live = false;
|
||||||
|
Bool Active = false;
|
||||||
|
MG_Backend::BackendQueryHandle Native = nullptr;
|
||||||
|
MG_Backend::BackendSyncHandle Completion = nullptr;
|
||||||
|
};
|
||||||
|
QueryEntry& FindQuery(MG_Pipe::MGPipeHandle handle);
|
||||||
|
void EndNativeQuery(QueryEntry& entry);
|
||||||
|
Bool QueryGpuComplete(QueryEntry& entry, Bool wait);
|
||||||
|
UnorderedMap<Uint32, QueryEntry> m_queries;
|
||||||
MG_Backend::BackendObject* m_backend = nullptr;
|
MG_Backend::BackendObject* m_backend = nullptr;
|
||||||
Uint64 m_clears = 0;
|
Uint64 m_clears = 0;
|
||||||
Uint64 m_draws = 0;
|
Uint64 m_draws = 0;
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// Server-owned query identities and actual backend query/fence results.
|
||||||
|
ServerVerbSink::QueryEntry& ServerVerbSink::FindQuery(MG_Pipe::MGPipeHandle handle) {
|
||||||
|
const auto found = m_queries.find(handle.Slot);
|
||||||
|
if (!handle.Slot || found == m_queries.end() || !found->second.Live || found->second.Gen != handle.Gen)
|
||||||
|
Wire::WireProtocolFatal("Query.handle", "missing, destroyed or stale query handle");
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryCreate(const MG_Pipe::MGPQueryDesc& desc) {
|
||||||
|
if (!Table("QueryCreate")) return false;
|
||||||
|
if (!desc.Query.Slot || desc.Stream != 0)
|
||||||
|
Wire::WireProtocolFatal("QueryCreate.identity", "reserved handle or unsupported stream");
|
||||||
|
switch (desc.Kind) {
|
||||||
|
case GL_TIME_ELAPSED: case GL_TIMESTAMP: case GL_SAMPLES_PASSED:
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: case GL_PRIMITIVES_GENERATED: break;
|
||||||
|
default: Wire::WireProtocolFatal("QueryCreate.Kind", "unknown query target");
|
||||||
|
}
|
||||||
|
const Bool seen = m_queries.find(desc.Query.Slot) != m_queries.end();
|
||||||
|
auto& entry = m_queries[desc.Query.Slot];
|
||||||
|
if (entry.Live || (seen && desc.Query.Gen <= entry.Gen))
|
||||||
|
Wire::WireProtocolFatal("QueryCreate.handle", "duplicate or stale query generation");
|
||||||
|
entry = QueryEntry{};
|
||||||
|
entry.Gen = desc.Query.Gen;
|
||||||
|
entry.Kind = desc.Kind;
|
||||||
|
entry.Live = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryBegin(const MG_Pipe::MGPQueryDesc& desc) {
|
||||||
|
auto& entry = FindQuery(desc.Query);
|
||||||
|
const auto* table = Table("QueryBegin");
|
||||||
|
if (!table) return false;
|
||||||
|
if (desc.Stream || desc.Kind != entry.Kind || entry.Active || entry.Native)
|
||||||
|
Wire::WireProtocolFatal("QueryBegin.state", "query target or lifecycle mismatch");
|
||||||
|
const auto& gl = table->GL;
|
||||||
|
switch (entry.Kind) {
|
||||||
|
case GL_TIME_ELAPSED:
|
||||||
|
if (!gl.BeginTimeElapsedQuery) return false;
|
||||||
|
entry.Native = gl.BeginTimeElapsedQuery();
|
||||||
|
break;
|
||||||
|
case GL_SAMPLES_PASSED:
|
||||||
|
if (!gl.BeginOcclusionQuery) return false;
|
||||||
|
entry.Native = gl.BeginOcclusionQuery();
|
||||||
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
||||||
|
case GL_PRIMITIVES_GENERATED:
|
||||||
|
if (!gl.BeginXfbPrimitivesQuery) return false;
|
||||||
|
entry.Native = gl.BeginXfbPrimitivesQuery(entry.Kind == GL_PRIMITIVES_GENERATED);
|
||||||
|
break;
|
||||||
|
default: Wire::WireProtocolFatal("QueryBegin.Kind", "target is not a begin/end query");
|
||||||
|
}
|
||||||
|
// A failed native allocation remains a real, unreadable query. Returning a
|
||||||
|
// null proxy to the frontend would silently select CPU primitive accounting.
|
||||||
|
entry.Active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerVerbSink::EndNativeQuery(QueryEntry& entry) {
|
||||||
|
if (!entry.Active || !m_backend) return;
|
||||||
|
const auto& gl = m_backend->GetBackendFunctions().GL;
|
||||||
|
if (entry.Native) {
|
||||||
|
switch (entry.Kind) {
|
||||||
|
case GL_TIME_ELAPSED: if (gl.EndTimeElapsedQuery) gl.EndTimeElapsedQuery(entry.Native); break;
|
||||||
|
case GL_SAMPLES_PASSED: if (gl.EndOcclusionQuery) gl.EndOcclusionQuery(entry.Native); break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
||||||
|
case GL_PRIMITIVES_GENERATED:
|
||||||
|
if (gl.EndXfbPrimitivesQuery) gl.EndXfbPrimitivesQuery(entry.Native);
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.Active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryEnd(const MG_Pipe::MGPQueryDesc& desc) {
|
||||||
|
auto& entry = FindQuery(desc.Query);
|
||||||
|
const auto* table = Table("QueryEnd");
|
||||||
|
if (!table) return false;
|
||||||
|
if (desc.Stream || desc.Kind != entry.Kind || !entry.Active)
|
||||||
|
Wire::WireProtocolFatal("QueryEnd.state", "query target or lifecycle mismatch");
|
||||||
|
EndNativeQuery(entry);
|
||||||
|
if (entry.Native && table->GL.FenceSync) entry.Completion = table->GL.FenceSync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryCounter(const MG_Pipe::MGPQueryDesc& desc) {
|
||||||
|
auto& entry = FindQuery(desc.Query);
|
||||||
|
const auto* table = Table("QueryCounter");
|
||||||
|
if (!table || !table->GL.QueryCounterTimestamp) return false;
|
||||||
|
if (desc.Stream || desc.Kind != GL_TIMESTAMP || entry.Kind != GL_TIMESTAMP || entry.Native || entry.Active)
|
||||||
|
Wire::WireProtocolFatal("QueryCounter.state", "query target or lifecycle mismatch");
|
||||||
|
entry.Native = table->GL.QueryCounterTimestamp();
|
||||||
|
if (entry.Native && table->GL.FenceSync) entry.Completion = table->GL.FenceSync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::QueryGpuComplete(QueryEntry& entry, Bool wait) {
|
||||||
|
if (!m_backend || !entry.Native || entry.Active) return false;
|
||||||
|
const auto& gl = m_backend->GetBackendFunctions().GL;
|
||||||
|
if (!entry.Completion || !gl.ClientWaitSync || !gl.IsQueryResultAvailable) return false;
|
||||||
|
// Polling may submit the pending batch, but never waits for GPU completion.
|
||||||
|
// A blocking result explicitly submits/waits, including present-less workloads.
|
||||||
|
const GLenum completion = gl.ClientWaitSync(entry.Completion, GL_SYNC_FLUSH_COMMANDS_BIT,
|
||||||
|
wait ? GL_TIMEOUT_IGNORED : 0);
|
||||||
|
if (completion != GL_ALREADY_SIGNALED && completion != GL_CONDITION_SATISFIED) return false;
|
||||||
|
return gl.IsQueryResultAvailable(entry.Native);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryAvailable(const MG_Pipe::MGPHandleOnly& request, Uint32& result) {
|
||||||
|
if (request.Kind != static_cast<Uint32>(MG_Pipe::MGPipeKind::Query))
|
||||||
|
Wire::WireProtocolFatal("Query.Kind", "expected query namespace");
|
||||||
|
auto& entry = FindQuery(request.Handle);
|
||||||
|
result = QueryGpuComplete(entry, false) ? 1 : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryResult(const MG_Pipe::MGPQueryResultRequest& request, Wire::QueryResultReply& reply) {
|
||||||
|
if (request.Wait > 1) Wire::WireProtocolFatal("QueryResult.Wait", "wait must be boolean");
|
||||||
|
auto& entry = FindQuery(request.Query);
|
||||||
|
const auto* table = Table("QueryResult");
|
||||||
|
if (!table || !table->GL.GetQueryResult64) return false;
|
||||||
|
reply = {};
|
||||||
|
if (QueryGpuComplete(entry, request.Wait != 0))
|
||||||
|
reply.Produced = table->GL.GetQueryResult64(entry.Native, request.Wait != 0, &reply.Value) ? 1 : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryDestroy(const MG_Pipe::MGPHandleOnly& request) {
|
||||||
|
if (request.Kind != static_cast<Uint32>(MG_Pipe::MGPipeKind::Query))
|
||||||
|
Wire::WireProtocolFatal("Query.Kind", "expected query namespace");
|
||||||
|
auto& entry = FindQuery(request.Handle);
|
||||||
|
const auto* table = Table("QueryDestroy");
|
||||||
|
if (!table) return false;
|
||||||
|
EndNativeQuery(entry);
|
||||||
|
if (entry.Native && table->GL.DeleteBackendQuery) table->GL.DeleteBackendQuery(entry.Native);
|
||||||
|
if (entry.Completion && table->GL.DeleteSync) table->GL.DeleteSync(entry.Completion);
|
||||||
|
entry.Native = nullptr;
|
||||||
|
entry.Completion = nullptr;
|
||||||
|
entry.Live = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnQueryTimestamp(const MG_Pipe::MGPTimestampRequest& request, Int64& result) {
|
||||||
|
if (request.Reserved) Wire::WireProtocolFatal("QueryTimestamp.Reserved", "reserved field is not zero");
|
||||||
|
const auto* table = Table("QueryTimestamp");
|
||||||
|
if (!table || !table->GL.QueryCounterTimestamp || !table->GL.GetQueryResult64 ||
|
||||||
|
!table->GL.DeleteBackendQuery || !table->GL.FenceSync || !table->GL.DeleteSync) return false;
|
||||||
|
QueryEntry entry{};
|
||||||
|
entry.Native = table->GL.QueryCounterTimestamp();
|
||||||
|
if (entry.Native) entry.Completion = table->GL.FenceSync();
|
||||||
|
Uint64 timestamp = 0;
|
||||||
|
const Bool produced = QueryGpuComplete(entry, true) &&
|
||||||
|
table->GL.GetQueryResult64(entry.Native, true, ×tamp);
|
||||||
|
if (entry.Native) table->GL.DeleteBackendQuery(entry.Native);
|
||||||
|
if (entry.Completion) table->GL.DeleteSync(entry.Completion);
|
||||||
|
if (produced) result = static_cast<Int64>(timestamp);
|
||||||
|
return produced;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerVerbSink::ReleaseQueries() {
|
||||||
|
if (m_backend) {
|
||||||
|
const auto& gl = m_backend->GetBackendFunctions().GL;
|
||||||
|
for (auto& [slot, entry] : m_queries) {
|
||||||
|
if (!entry.Live) continue;
|
||||||
|
EndNativeQuery(entry);
|
||||||
|
if (entry.Native && gl.DeleteBackendQuery) gl.DeleteBackendQuery(entry.Native);
|
||||||
|
if (entry.Completion && gl.DeleteSync) gl.DeleteSync(entry.Completion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_queries.clear();
|
||||||
|
}
|
||||||
@@ -632,7 +632,10 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
|
|
||||||
// R-8/C-4: bits 32..47 of CallMask are the CONSUMER MASK, and this is the only place
|
// R-8/C-4: bits 32..47 of CallMask are the CONSUMER MASK, and this is the only place
|
||||||
// they are produced.
|
// they are produced.
|
||||||
const Uint64 callMask = CallMask();
|
Uint64 callMask = CallMask();
|
||||||
|
const auto timerSupported = m_backend->GetBackendFunctions().GL.IsTimerQuerySupported;
|
||||||
|
if ((callMask & MG_Pipe::kCapTimerQuery) && (!timerSupported || !timerSupported()))
|
||||||
|
callMask &= ~static_cast<Uint64>(MG_Pipe::kCapTimerQuery);
|
||||||
|
|
||||||
::flatbuffers::FlatBufferBuilder builder(4096);
|
::flatbuffers::FlatBufferBuilder builder(4096);
|
||||||
auto dynamicVector =
|
auto dynamicVector =
|
||||||
|
|||||||
@@ -243,7 +243,8 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
X(ApplierReset, MGPApplierReset) \
|
X(ApplierReset, MGPApplierReset) \
|
||||||
X(ObjectDeath, MGPHandleOnly) \
|
X(ObjectDeath, MGPHandleOnly) \
|
||||||
X(SetContextValues, MGPContextValues) \
|
X(SetContextValues, MGPContextValues) \
|
||||||
X(SetProgramBindings, MGPProgramBindings)
|
X(SetProgramBindings, MGPProgramBindings) \
|
||||||
|
X(DeleteStreamOutput, MGPStreamOutputBind)
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -1657,16 +1658,37 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
ok ? &result : nullptr, ok ? sizeof(result) : 0);
|
ok ? &result : nullptr, ok ? sizeof(result) : 0);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
// Query migration follows the measured first blockers.
|
|
||||||
case MGPWireOp::QueryCreate:
|
case MGPWireOp::QueryCreate:
|
||||||
|
return m_verbs && m_verbs->OnQueryCreate(*static_cast<const MGPQueryDesc*>(payload));
|
||||||
case MGPWireOp::QueryBegin:
|
case MGPWireOp::QueryBegin:
|
||||||
|
return m_verbs && m_verbs->OnQueryBegin(*static_cast<const MGPQueryDesc*>(payload));
|
||||||
case MGPWireOp::QueryEnd:
|
case MGPWireOp::QueryEnd:
|
||||||
case MGPWireOp::QueryAvailable:
|
return m_verbs && m_verbs->OnQueryEnd(*static_cast<const MGPQueryDesc*>(payload));
|
||||||
case MGPWireOp::QueryResult:
|
|
||||||
case MGPWireOp::QueryDestroy:
|
case MGPWireOp::QueryDestroy:
|
||||||
case MGPWireOp::QueryTimestamp:
|
return m_verbs && m_verbs->OnQueryDestroy(*static_cast<const MGPHandleOnly*>(payload));
|
||||||
case MGPWireOp::QueryCounter:
|
case MGPWireOp::QueryCounter:
|
||||||
return false;
|
return m_verbs && m_verbs->OnQueryCounter(*static_cast<const MGPQueryDesc*>(payload));
|
||||||
|
case MGPWireOp::QueryAvailable: {
|
||||||
|
Uint32 result = 0;
|
||||||
|
const Bool ok = m_verbs && m_verbs->OnQueryAvailable(*static_cast<const MGPHandleOnly*>(payload), result);
|
||||||
|
PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined,
|
||||||
|
ok ? &result : nullptr, ok ? sizeof(result) : 0);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
case MGPWireOp::QueryResult: {
|
||||||
|
QueryResultReply result{};
|
||||||
|
const Bool ok = m_verbs && m_verbs->OnQueryResult(*static_cast<const MGPQueryResultRequest*>(payload), result);
|
||||||
|
PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined,
|
||||||
|
ok ? &result : nullptr, ok ? sizeof(result) : 0);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
case MGPWireOp::QueryTimestamp: {
|
||||||
|
Int64 result = 0;
|
||||||
|
const Bool ok = m_verbs && m_verbs->OnQueryTimestamp(*static_cast<const MGPTimestampRequest*>(payload), result);
|
||||||
|
PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined,
|
||||||
|
ok ? &result : nullptr, ok ? sizeof(result) : 0);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- CSOs ------------------------------------------------------------------------
|
// ---- CSOs ------------------------------------------------------------------------
|
||||||
case MGPWireOp::CreateRenderState: {
|
case MGPWireOp::CreateRenderState: {
|
||||||
@@ -2103,9 +2125,8 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
m_verbs->OnGenerateMipmap(*static_cast<const MGPMipPlan*>(payload));
|
m_verbs->OnGenerateMipmap(*static_cast<const MGPMipPlan*>(payload));
|
||||||
|
|
||||||
case MGPWireOp::GetTextureImage:
|
case MGPWireOp::GetTextureImage:
|
||||||
// Off the reduced path (BRIEF §4) and outside P5b's measured 25: its emit-table slot
|
return m_verbs != nullptr &&
|
||||||
// is Fatal{UnmigratedVerb} on the client (contract §7 class C, wave 3 / P9).
|
m_verbs->OnGetTextureImage(*static_cast<const MGPReadbackInfo*>(payload), seq, m_replies);
|
||||||
return false;
|
|
||||||
|
|
||||||
// ---- the five class-B verbs: no MGPipeApply* exists, so v1's sink or nothing -------
|
// ---- the five class-B verbs: no MGPipeApply* exists, so v1's sink or nothing -------
|
||||||
case MGPWireOp::Blit:
|
case MGPWireOp::Blit:
|
||||||
@@ -2205,6 +2226,8 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
case MGPWireOp::BindStreamOutput:
|
case MGPWireOp::BindStreamOutput:
|
||||||
return m_verbs != nullptr &&
|
return m_verbs != nullptr &&
|
||||||
m_verbs->OnBindStreamOutput(*static_cast<const MGPStreamOutputBind*>(payload));
|
m_verbs->OnBindStreamOutput(*static_cast<const MGPStreamOutputBind*>(payload));
|
||||||
|
case MGPWireOp::DeleteStreamOutput:
|
||||||
|
return m_verbs && m_verbs->OnDeleteStreamOutput(*static_cast<const MGPStreamOutputBind*>(payload));
|
||||||
|
|
||||||
case MGPWireOp::SetStorageBlockBinding: {
|
case MGPWireOp::SetStorageBlockBinding: {
|
||||||
// The block name is the ONE string on the wire (CONTRACT-P5B.md i1): a kHasBlob
|
// The block name is the ONE string on the wire (CONTRACT-P5B.md i1): a kHasBlob
|
||||||
|
|||||||
@@ -445,6 +445,13 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
// exclusion list), so inventing a consumer for them would be building a semantics nobody
|
// exclusion list), so inventing a consumer for them would be building a semantics nobody
|
||||||
// can test this phase. Their arms validate both tails - which is the part a later phase
|
// can test this phase. Their arms validate both tails - which is the part a later phase
|
||||||
// must not have to re-derive - and return false.
|
// must not have to re-derive - and return false.
|
||||||
|
struct QueryResultReply {
|
||||||
|
Uint64 Value = 0;
|
||||||
|
Uint32 Produced = 0;
|
||||||
|
Uint32 Reserved = 0;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(QueryResultReply) == 16);
|
||||||
|
|
||||||
class WireVerbSink {
|
class WireVerbSink {
|
||||||
public:
|
public:
|
||||||
virtual ~WireVerbSink() = default;
|
virtual ~WireVerbSink() = default;
|
||||||
@@ -453,6 +460,15 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
virtual Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) { return false; }
|
virtual Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) { return false; }
|
||||||
virtual Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) { return false; }
|
virtual Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) { return false; }
|
||||||
virtual Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) { return false; }
|
virtual Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) { return false; }
|
||||||
|
virtual Bool OnQueryCreate(const MG_Pipe::MGPQueryDesc&) { return false; }
|
||||||
|
virtual Bool OnQueryBegin(const MG_Pipe::MGPQueryDesc&) { return false; }
|
||||||
|
virtual Bool OnQueryEnd(const MG_Pipe::MGPQueryDesc&) { return false; }
|
||||||
|
virtual Bool OnQueryCounter(const MG_Pipe::MGPQueryDesc&) { return false; }
|
||||||
|
virtual Bool OnQueryAvailable(const MG_Pipe::MGPHandleOnly&, Uint32&) { return false; }
|
||||||
|
virtual Bool OnQueryResult(const MG_Pipe::MGPQueryResultRequest&, QueryResultReply&) { return false; }
|
||||||
|
virtual Bool OnQueryDestroy(const MG_Pipe::MGPHandleOnly&) { return false; }
|
||||||
|
virtual Bool OnQueryTimestamp(const MG_Pipe::MGPTimestampRequest&, Int64&) { return false; }
|
||||||
|
virtual Bool OnDeleteStreamOutput(const MG_Pipe::MGPStreamOutputBind&) { return false; }
|
||||||
virtual Bool OnClear(const MG_Pipe::MGPClear& clear) {
|
virtual Bool OnClear(const MG_Pipe::MGPClear& clear) {
|
||||||
(void)clear;
|
(void)clear;
|
||||||
return false;
|
return false;
|
||||||
@@ -474,6 +490,9 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
(void)replies;
|
(void)replies;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
virtual Bool OnGetTextureImage(const MG_Pipe::MGPReadbackInfo&, Uint64, ReplySink*) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// `ranges` is info.NumDraws entries. `userIndices` is null unless the record set
|
// `ranges` is info.NumDraws entries. `userIndices` is null unless the record set
|
||||||
// kDrawHasUserIndices; `indirect` is null unless it set kDrawIsIndirect (P5b d1,
|
// kDrawHasUserIndices; `indirect` is null unless it set kDrawIsIndirect (P5b d1,
|
||||||
// CONTRACT-P5B.md). The layout refuses a record that sets both, so at most one of the
|
// CONTRACT-P5B.md). The layout refuses a record that sets both, so at most one of the
|
||||||
|
|||||||
@@ -1455,6 +1455,13 @@ namespace MobileGL::MG_State {
|
|||||||
return it != m_transformFeedbackObjects.end() && it->second.everBound;
|
return it != m_transformFeedbackObjects.end() && it->second.everBound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
Uint64 GLContext::GetTransformFeedbackLifetimeId(Uint index) const {
|
||||||
|
const auto found = m_transformFeedbackObjects.find(index);
|
||||||
|
return found == m_transformFeedbackObjects.end() ? 0 : found->second.lifetimeId;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void GLContext::MarkTransformFeedbackObjectForDeletion(Uint index) {
|
void GLContext::MarkTransformFeedbackObjectForDeletion(Uint index) {
|
||||||
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return;
|
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return;
|
||||||
// Deleting the bound object reverts to the default one (GL 4.6 core 13.2.1);
|
// Deleting the bound object reverts to the default one (GL 4.6 core 13.2.1);
|
||||||
|
|||||||
@@ -531,6 +531,9 @@ namespace MobileGL {
|
|||||||
// Never returns 0 - the counter starts at 1 so a zero-initialised memo slot cannot
|
// Never returns 0 - the counter starts at 1 so a zero-initialised memo slot cannot
|
||||||
// be mistaken for a live object.
|
// be mistaken for a live object.
|
||||||
Uint64 GetBoundTransformFeedbackLifetimeId() const { return m_boundTransformFeedbackLifetimeId; }
|
Uint64 GetBoundTransformFeedbackLifetimeId() const { return m_boundTransformFeedbackLifetimeId; }
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
Uint64 GetTransformFeedbackLifetimeId(Uint index) const;
|
||||||
|
#endif
|
||||||
// Whether the object carrying this identity still has an OPEN capture span - one
|
// Whether the object carrying this identity still has an OPEN capture span - one
|
||||||
// that glBeginTransformFeedback started and glEndTransformFeedback has not closed,
|
// that glBeginTransformFeedback started and glEndTransformFeedback has not closed,
|
||||||
// paused or not. A backend that hands out a bounded set of per-object slots must
|
// paused or not. A backend that hands out a bounded set of per-object slots must
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ TEST(PipeCatalogue, GeneratedTablesHoldTheWholeCatalogue) {
|
|||||||
// 13 + the five P5b-appended verbs (MG_Remote/CONTRACT-P5B.md): bind_shader_image,
|
// 13 + the five P5b-appended verbs (MG_Remote/CONTRACT-P5B.md): bind_shader_image,
|
||||||
// patch_parameter, bind_stream_output, set_storage_block_binding,
|
// patch_parameter, bind_stream_output, set_storage_block_binding,
|
||||||
// copy_framebuffer_to_texture.
|
// copy_framebuffer_to_texture.
|
||||||
EXPECT_EQ(ClassCount<kCtxVerb>(), 18u);
|
EXPECT_EQ(ClassCount<kCtxVerb>(), 19u);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A row nobody has migrated is null - which is exactly what "this subsystem has not been
|
// A row nobody has migrated is null - which is exactly what "this subsystem has not been
|
||||||
|
|||||||
@@ -3011,6 +3011,131 @@ TEST(FenceWireRoundTrip, NullNativeFenceUsesOnlyTheExistingMonolithFallback) {
|
|||||||
sink.SetBackend(nullptr);
|
sink.SetBackend(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int nativeQueryToken = 0;
|
||||||
|
Bool queryGpuReady = false;
|
||||||
|
Bool queryGenerated = false;
|
||||||
|
Uint32 queryReads = 0, queryEnds = 0, queryDeletes = 0;
|
||||||
|
constexpr Uint64 queryNativeValue = 0xfedcba9876543210ull;
|
||||||
|
|
||||||
|
void InstallQueryProbe(FenceProbeBackend& backend) {
|
||||||
|
InstallFenceProbe(backend);
|
||||||
|
queryGpuReady = queryGenerated = false;
|
||||||
|
queryReads = queryEnds = queryDeletes = 0;
|
||||||
|
backend.Table.GL.BeginXfbPrimitivesQuery = +[](Bool generated) -> MG_Backend::BackendQueryHandle {
|
||||||
|
queryGenerated = generated;
|
||||||
|
return &nativeQueryToken;
|
||||||
|
};
|
||||||
|
backend.Table.GL.EndXfbPrimitivesQuery = +[](MG_Backend::BackendQueryHandle query) {
|
||||||
|
EXPECT_EQ(query, &nativeQueryToken);
|
||||||
|
++queryEnds;
|
||||||
|
};
|
||||||
|
backend.Table.GL.IsQueryResultAvailable = +[](MG_Backend::BackendQueryHandle query) -> Bool {
|
||||||
|
EXPECT_EQ(query, &nativeQueryToken);
|
||||||
|
return queryGpuReady;
|
||||||
|
};
|
||||||
|
backend.Table.GL.GetQueryResult64 = +[](MG_Backend::BackendQueryHandle query, Bool, Uint64* value) -> Bool {
|
||||||
|
EXPECT_EQ(query, &nativeQueryToken);
|
||||||
|
++queryReads;
|
||||||
|
*value = queryNativeValue;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
backend.Table.GL.DeleteBackendQuery = +[](MG_Backend::BackendQueryHandle query) {
|
||||||
|
EXPECT_EQ(query, &nativeQueryToken);
|
||||||
|
++queryDeletes;
|
||||||
|
};
|
||||||
|
backend.Table.GL.ClientWaitSync = +[](MG_Backend::BackendSyncHandle sync, GLbitfield flags,
|
||||||
|
GLuint64 timeout) -> GLenum {
|
||||||
|
EXPECT_EQ(sync, &nativeFenceToken);
|
||||||
|
fenceFlags = flags;
|
||||||
|
fenceTimeout = timeout;
|
||||||
|
return queryGpuReady ? GL_CONDITION_SATISFIED : GL_TIMEOUT_EXPIRED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(QueryWireRoundTrip, PrimitiveQueriesPreservePendingStateNativeResultsAndIdentity) {
|
||||||
|
Wire2 wire;
|
||||||
|
FenceProbeBackend backend;
|
||||||
|
InstallQueryProbe(backend);
|
||||||
|
Server::ServerVerbSink sink;
|
||||||
|
sink.SetBackend(&backend);
|
||||||
|
wire.Decoder().SetVerbSink(&sink);
|
||||||
|
auto send = [&](MGPWireOp op, const auto& payload) {
|
||||||
|
const auto seq = wire.Encoder().EncodeRecord(op, &payload, sizeof(payload));
|
||||||
|
EXPECT_NE(seq, kInvalidSeq);
|
||||||
|
bool applied = false;
|
||||||
|
EXPECT_TRUE(wire.PumpOne(&applied));
|
||||||
|
EXPECT_TRUE(applied);
|
||||||
|
return seq;
|
||||||
|
};
|
||||||
|
const MGPQueryDesc desc{{51, 0}, GL_PRIMITIVES_GENERATED, 0};
|
||||||
|
send(MGPWireOp::QueryCreate, desc);
|
||||||
|
send(MGPWireOp::QueryBegin, desc);
|
||||||
|
EXPECT_TRUE(queryGenerated);
|
||||||
|
send(MGPWireOp::QueryEnd, desc);
|
||||||
|
EXPECT_EQ(queryEnds, 1u);
|
||||||
|
MGPQueryResultRequest request{};
|
||||||
|
request.Query = desc.Query;
|
||||||
|
const auto pendingSeq = send(MGPWireOp::QueryResult, request);
|
||||||
|
ASSERT_EQ(wire.Answers().All.back().Bytes.size(), sizeof(QueryResultReply));
|
||||||
|
QueryResultReply reply{};
|
||||||
|
std::memcpy(&reply, wire.Answers().All.back().Bytes.data(), sizeof(reply));
|
||||||
|
EXPECT_EQ(wire.Answers().All.back().Seq, pendingSeq);
|
||||||
|
EXPECT_EQ(wire.Answers().All.back().Status, ReplySink::kStatusOk);
|
||||||
|
EXPECT_EQ(reply.Produced, 0u);
|
||||||
|
EXPECT_EQ(queryReads, 0u) << "NO_WAIT must not call a potentially blocking native result read";
|
||||||
|
EXPECT_EQ(fenceTimeout, 0u);
|
||||||
|
queryGpuReady = true;
|
||||||
|
request.Wait = 1;
|
||||||
|
send(MGPWireOp::QueryResult, request);
|
||||||
|
std::memcpy(&reply, wire.Answers().All.back().Bytes.data(), sizeof(reply));
|
||||||
|
EXPECT_EQ(reply.Produced, 1u);
|
||||||
|
EXPECT_EQ(reply.Value, queryNativeValue);
|
||||||
|
EXPECT_EQ(queryReads, 1u);
|
||||||
|
EXPECT_EQ(fenceTimeout, GL_TIMEOUT_IGNORED);
|
||||||
|
EXPECT_EQ(fenceFlags, GL_SYNC_FLUSH_COMMANDS_BIT);
|
||||||
|
const MGPHandleOnly handle{desc.Query, static_cast<Uint32>(MGPipeKind::Query), 0};
|
||||||
|
send(MGPWireOp::QueryAvailable, handle);
|
||||||
|
Uint32 available = 0;
|
||||||
|
ASSERT_EQ(wire.Answers().All.back().Bytes.size(), sizeof(available));
|
||||||
|
std::memcpy(&available, wire.Answers().All.back().Bytes.data(), sizeof(available));
|
||||||
|
EXPECT_EQ(available, 1u);
|
||||||
|
send(MGPWireOp::QueryDestroy, handle);
|
||||||
|
EXPECT_EQ(queryDeletes, 1u);
|
||||||
|
EXPECT_EQ(fenceDeletes, 1u);
|
||||||
|
const MGPQueryDesc replacement{{51, 1}, GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, 0};
|
||||||
|
send(MGPWireOp::QueryCreate, replacement);
|
||||||
|
send(MGPWireOp::QueryBegin, replacement);
|
||||||
|
EXPECT_FALSE(queryGenerated);
|
||||||
|
sink.SetBackend(nullptr);
|
||||||
|
EXPECT_EQ(queryEnds, 2u);
|
||||||
|
EXPECT_EQ(queryDeletes, 2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(QueryWireRoundTrip, FailedNativeCreationDoesNotBecomeAnAvailableZeroResult) {
|
||||||
|
FenceProbeBackend backend;
|
||||||
|
InstallQueryProbe(backend);
|
||||||
|
backend.Table.GL.BeginXfbPrimitivesQuery = +[](Bool) -> MG_Backend::BackendQueryHandle { return nullptr; };
|
||||||
|
Server::ServerVerbSink sink;
|
||||||
|
sink.SetBackend(&backend);
|
||||||
|
const MGPQueryDesc desc{{7, 0}, GL_PRIMITIVES_GENERATED, 0};
|
||||||
|
ASSERT_TRUE(sink.OnQueryCreate(desc));
|
||||||
|
ASSERT_TRUE(sink.OnQueryBegin(desc));
|
||||||
|
ASSERT_TRUE(sink.OnQueryEnd(desc));
|
||||||
|
Uint32 available = 1;
|
||||||
|
EXPECT_TRUE(sink.OnQueryAvailable({desc.Query, static_cast<Uint32>(MGPipeKind::Query), 0}, available));
|
||||||
|
EXPECT_EQ(available, 0u);
|
||||||
|
MGPQueryResultRequest request{};
|
||||||
|
request.Query = desc.Query;
|
||||||
|
request.Wait = 1;
|
||||||
|
QueryResultReply reply{};
|
||||||
|
EXPECT_TRUE(sink.OnQueryResult(request, reply));
|
||||||
|
EXPECT_EQ(reply.Produced, 0u);
|
||||||
|
EXPECT_EQ(queryReads, 0u);
|
||||||
|
sink.SetBackend(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
#if MGTEST_HAVE_FORK
|
#if MGTEST_HAVE_FORK
|
||||||
TEST(FenceWireRoundTrip, DestroyedAndRecycledWireHandlesNeverReachTheBackend) {
|
TEST(FenceWireRoundTrip, DestroyedAndRecycledWireHandlesNeverReachTheBackend) {
|
||||||
const auto r = RunInChild([] {
|
const auto r = RunInChild([] {
|
||||||
|
|||||||
@@ -181,8 +181,8 @@ namespace {
|
|||||||
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
|
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
|
||||||
// P5 baseline five + f1 eleven + i1 seven + t2 six emitted slots.
|
// P5 baseline five + f1 eleven + i1 seven + t2 six emitted slots.
|
||||||
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
|
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
|
||||||
EXPECT_EQ(ImplementedVerbCount(), 54u);
|
EXPECT_EQ(ImplementedVerbCount(), 68u);
|
||||||
EXPECT_EQ(UnmigratedSlotCount(), 15u);
|
EXPECT_EQ(UnmigratedSlotCount(), 1u);
|
||||||
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
|
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
|
||||||
kRemoteEmitSlotCount);
|
kRemoteEmitSlotCount);
|
||||||
}
|
}
|
||||||
@@ -270,8 +270,9 @@ TEST(RemoteEmitTable, DeleteTransformFeedbackHasNoRowAndStillAbortsByItsOwnName)
|
|||||||
// failed.
|
// failed.
|
||||||
const ChildResult r = RunInChild([] { RemoteEmitTable().GL.DeleteTransformFeedback(7); });
|
const ChildResult r = RunInChild([] { RemoteEmitTable().GL.DeleteTransformFeedback(7); });
|
||||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"DeleteTransformFeedback\"}"), std::string::npos)
|
EXPECT_NE(r.Log.find("Fatal{NoClientSession, \"DeleteTransformFeedback\"}"), std::string::npos)
|
||||||
<< r.Log;
|
<< r.Log;
|
||||||
|
EXPECT_EQ(r.Log.find("Fatal{UnmigratedVerb"), std::string::npos) << r.Log;
|
||||||
}
|
}
|
||||||
#endif // MGTEST_HAVE_FORK
|
#endif // MGTEST_HAVE_FORK
|
||||||
|
|
||||||
@@ -326,12 +327,9 @@ TEST(RemoteEmitTable, AnUnmigratedSlotAbortsAndNamesItself) {
|
|||||||
// GetTexImage is the wave-3 tail (CONTRACT-P5B.md §7): no P5b package flips it, so this
|
// GetTexImage is the wave-3 tail (CONTRACT-P5B.md §7): no P5b package flips it, so this
|
||||||
// case keeps its subject across the four P5b landings. (It was DrawElements until d1 made
|
// case keeps its subject across the four P5b landings. (It was DrawElements until d1 made
|
||||||
// that a class-B emitter.)
|
// that a class-B emitter.)
|
||||||
const ChildResult r = RunInChild([] {
|
const ChildResult r = RunInChild([] { RemoteEmitTable().SetSwapInterval(1); });
|
||||||
RemoteEmitTable().GL.GetTexImage(0x0DE1 /*GL_TEXTURE_2D*/, 0, 0x1908 /*GL_RGBA*/,
|
|
||||||
0x1401 /*GL_UNSIGNED_BYTE*/, nullptr);
|
|
||||||
});
|
|
||||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"GetTexImage\"}"), std::string::npos) << r.Log;
|
EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"SetSwapInterval\"}"), std::string::npos) << r.Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(RemoteEmitTable, EachUnmigratedSlotNamesItsOwnSlot) {
|
TEST(RemoteEmitTable, EachUnmigratedSlotNamesItsOwnSlot) {
|
||||||
@@ -383,7 +381,7 @@ TEST(RemoteEmitTable, TheSevenI1SlotsAreClassBAndAreNotTheFatalThunk) {
|
|||||||
// assignment would look like (class C is assigned FIRST in BuildRemoteEmitTable precisely so
|
// assignment would look like (class C is assigned FIRST in BuildRemoteEmitTable precisely so
|
||||||
// that the mistake is loud rather than null).
|
// that the mistake is loud rather than null).
|
||||||
const MG_Backend::GlobalBackendFunctionsTable& table = RemoteEmitTable();
|
const MG_Backend::GlobalBackendFunctionsTable& table = RemoteEmitTable();
|
||||||
const void* fatal = reinterpret_cast<const void*>(table.GL.GetTexImage); // wave-3 tail, class C
|
const void* fatal = reinterpret_cast<const void*>(table.SetSwapInterval);
|
||||||
ASSERT_NE(fatal, nullptr);
|
ASSERT_NE(fatal, nullptr);
|
||||||
const void* const i1[] = {
|
const void* const i1[] = {
|
||||||
reinterpret_cast<const void*>(table.GL.BindImageTexture),
|
reinterpret_cast<const void*>(table.GL.BindImageTexture),
|
||||||
|
|||||||
Reference in New Issue
Block a user